2524340759
- app.md - backend/dist/assets/index-D_Kep7Fb.js - backend/dist/index.html - backend/handlers.go - backend/main.go - frontend/src/App.tsx - frontend/src/api.ts - frontend/src/components/CardChatPanel.tsx - frontend/src/components/LoginPage.tsx - frontend/src/types.ts - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
180 lines
5.3 KiB
Go
180 lines
5.3 KiB
Go
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)
|
|
}
|
|
}
|