package main import ( "reflect" "sort" "testing" ) func TestExtractMentions(t *testing.T) { cases := []struct { in string want []string }{ {"hola @alice", []string{"alice"}}, {"@Bob y @bob mismo", []string{"bob"}}, {"sin menciones", nil}, {"email@foo.com no cuenta como @real_user", []string{"foo.com", "real_user"}}, {"@a-b-c y @d.e", []string{"a-b-c", "d.e"}}, } for _, c := range cases { got := extractMentions(c.in) sort.Strings(got) sort.Strings(c.want) if !reflect.DeepEqual(got, c.want) { t.Errorf("extractMentions(%q) = %v, want %v", c.in, got, c.want) } } } func mkUser(t *testing.T, db *DB, username string) string { t.Helper() u, err := db.CreateUser(username, "passw", username) if err != nil { t.Fatalf("CreateUser %q: %v", username, err) } return u.ID } func mkCard(t *testing.T, db *DB, columnID, requester, title, assigneeID string) string { t.Helper() c, err := db.CreateCard(columnID, requester, title, "", "") if err != nil { t.Fatalf("CreateCard: %v", err) } if assigneeID != "" { if err := db.UpdateCardWithActor(c.ID, CardPatch{AssigneeID: &assigneeID, HasAssignee: true}, ""); err != nil { t.Fatalf("assign: %v", err) } } return c.ID } func TestCreateCardMessageAndNotify_AssigneeAndPreviousAuthors(t *testing.T) { db := setupTestDB(t) alice := mkUser(t, db, "alice") bob := mkUser(t, db, "bob") carol := mkUser(t, db, "carol") col, err := db.CreateColumn("Todo") if err != nil { t.Fatalf("CreateColumn: %v", err) } card := mkCard(t, db, col.ID, "x", "card", bob) // 1) alice writes; bob is assignee → bob gets assigned_chat. _, notifs, _, err := db.CreateCardMessageAndNotify(card, alice, "hola", nil) if err != nil { t.Fatalf("create msg: %v", err) } if len(notifs) != 1 || notifs[0].UserID != bob || notifs[0].Kind != NotifKindAssignedChat { t.Fatalf("expected single assigned_chat for bob, got %+v", notifs) } // 2) carol replies (carol is neither assignee nor previous author). // alice (previous author) gets reply; bob (assignee) gets assigned_chat. _, notifs, _, err = db.CreateCardMessageAndNotify(card, carol, "hola alice", nil) if err != nil { t.Fatalf("create msg: %v", err) } gotKinds := map[string]string{} for _, n := range notifs { gotKinds[n.UserID] = n.Kind } wantKinds := map[string]string{alice: NotifKindReply, bob: NotifKindAssignedChat} if !reflect.DeepEqual(gotKinds, wantKinds) { t.Fatalf("kinds = %+v, want %+v", gotKinds, wantKinds) } } func TestCreateCardMessageAndNotify_MentionsBeatOtherKinds(t *testing.T) { db := setupTestDB(t) alice := mkUser(t, db, "alice") bob := mkUser(t, db, "bob") col, _ := db.CreateColumn("Todo") card := mkCard(t, db, col.ID, "x", "card", bob) // bob is assignee // alice mentions bob explicitly → kind must be 'mention', not 'assigned_chat'. _, notifs, mentions, err := db.CreateCardMessageAndNotify(card, alice, "oye @bob mira esto", nil) if err != nil { t.Fatalf("create: %v", err) } if len(mentions) != 1 || mentions[0].UserID != bob { t.Fatalf("mentions = %+v, want [bob]", mentions) } if len(notifs) != 1 || notifs[0].UserID != bob || notifs[0].Kind != NotifKindMention { t.Fatalf("notifs = %+v, want single mention for bob", notifs) } } func TestCreateCardMessageAndNotify_UnknownMentionsIgnored(t *testing.T) { db := setupTestDB(t) alice := mkUser(t, db, "alice") col, _ := db.CreateColumn("Todo") card := mkCard(t, db, col.ID, "x", "card", "") _, notifs, mentions, err := db.CreateCardMessageAndNotify(card, alice, "hola @noexiste", nil) if err != nil { t.Fatalf("create: %v", err) } if len(mentions) != 0 || len(notifs) != 0 { t.Fatalf("got mentions=%v notifs=%v, want empty", mentions, notifs) } } func TestCreateCardMessageAndNotify_AuthorNeverSelfNotified(t *testing.T) { db := setupTestDB(t) alice := mkUser(t, db, "alice") col, _ := db.CreateColumn("Todo") card := mkCard(t, db, col.ID, "x", "card", alice) // alice is assignee // alice mentions herself + is assignee → no notification. _, notifs, _, err := db.CreateCardMessageAndNotify(card, alice, "monologo @alice", nil) if err != nil { t.Fatalf("create: %v", err) } if len(notifs) != 0 { t.Fatalf("notifs = %+v, want empty (self)", notifs) } } func TestListAndMarkRead(t *testing.T) { db := setupTestDB(t) alice := mkUser(t, db, "alice") bob := mkUser(t, db, "bob") col, _ := db.CreateColumn("Todo") card := mkCard(t, db, col.ID, "x", "card", bob) _, _, _, _ = db.CreateCardMessageAndNotify(card, alice, "1", nil) _, _, _, _ = db.CreateCardMessageAndNotify(card, alice, "2", nil) got, err := db.ListNotifications(bob, true, 0) if err != nil { t.Fatalf("list: %v", err) } if len(got) != 2 { t.Fatalf("len = %d, want 2", len(got)) } if n, _ := db.CountUnreadNotifications(bob); n != 2 { t.Fatalf("unread count = %d, want 2", n) } if err := db.MarkNotificationRead(bob, got[0].ID); err != nil { t.Fatalf("mark read: %v", err) } if n, _ := db.CountUnreadNotifications(bob); n != 1 { t.Fatalf("unread count after mark = %d, want 1", n) } // idempotent if err := db.MarkNotificationRead(bob, got[0].ID); err != nil { t.Fatalf("mark read 2nd time: %v", err) } if n, _ := db.MarkAllNotificationsRead(bob); n != 1 { t.Fatalf("mark all = %d, want 1", n) } if n, _ := db.CountUnreadNotifications(bob); n != 0 { t.Fatalf("unread count after mark-all = %d, want 0", n) } }