package db import ( "context" "path/filepath" "testing" "time" ) func openTestDB(t *testing.T) (*TicketRepo, *ClaimMessageRepo) { t.Helper() path := filepath.Join(t.TempDir(), "test.db") sqldb, err := Open(path) if err != nil { t.Fatal(err) } t.Cleanup(func() { sqldb.Close() }) return NewTicketRepo(sqldb), NewClaimMessageRepo(sqldb) } func TestInsertAndGet(t *testing.T) { repo, _ := openTestDB(t) ctx := context.Background() ticket := &Ticket{ UserID: "user1", Panel: "support_panel", Type: "support", ChannelID: "chan1", OpenedAt: time.Now(), } if err := repo.Insert(ctx, ticket); err != nil { t.Fatal(err) } if ticket.ID == 0 { t.Fatal("expected non-zero ID") } if ticket.TicketNumber != 1 { t.Errorf("ticket_number = %d, want 1", ticket.TicketNumber) } got, err := repo.GetByChannelID(ctx, "chan1") if err != nil { t.Fatal(err) } if got.UserID != "user1" { t.Errorf("user_id = %q, want user1", got.UserID) } if got.Status != "open" { t.Errorf("status = %q, want open", got.Status) } } func TestTicketNumberPerType(t *testing.T) { repo, _ := openTestDB(t) ctx := context.Background() for i := 0; i < 3; i++ { tk := &Ticket{UserID: "u", Panel: "p", Type: "support", ChannelID: "c" + string(rune('a'+i)), OpenedAt: time.Now()} if err := repo.Insert(ctx, tk); err != nil { t.Fatal(err) } } // mod type starts its own counter at 1 tk2 := &Ticket{UserID: "u", Panel: "p", Type: "mod", ChannelID: "cx", OpenedAt: time.Now()} if err := repo.Insert(ctx, tk2); err != nil { t.Fatal(err) } if tk2.TicketNumber != 1 { t.Errorf("mod ticket_number = %d, want 1", tk2.TicketNumber) } } func TestHasOpenTicket(t *testing.T) { repo, _ := openTestDB(t) ctx := context.Background() tk := &Ticket{UserID: "u1", Panel: "p", Type: "support", ChannelID: "c1", OpenedAt: time.Now()} repo.Insert(ctx, tk) found, err := repo.HasOpenTicket(ctx, "u1", "support") if err != nil || found == nil { t.Fatal("expected open ticket") } repo.SetClosed(ctx, tk.ID, "staff1", "resolved", "", time.Now()) found, _ = repo.HasOpenTicket(ctx, "u1", "support") if found != nil { t.Fatal("expected no open ticket after close") } } func TestClaimMessages(t *testing.T) { _, claims := openTestDB(t) ctx := context.Background() err := claims.Upsert(ctx, 1, "msg1", time.Now()) if err != nil { t.Fatal(err) } cm, err := claims.Get(ctx, 1) if err != nil || cm == nil { t.Fatal("expected claim message") } if cm.MessageID != "msg1" { t.Errorf("message_id = %q, want msg1", cm.MessageID) } // Upsert updates claims.Upsert(ctx, 1, "msg2", time.Now()) cm, _ = claims.Get(ctx, 1) if cm.MessageID != "msg2" { t.Errorf("after update message_id = %q, want msg2", cm.MessageID) } claims.Delete(ctx, 1) cm, _ = claims.Get(ctx, 1) if cm != nil { t.Fatal("expected nil after delete") } } func TestMigrationIdempotent(t *testing.T) { path := filepath.Join(t.TempDir(), "test.db") db1, err := Open(path) if err != nil { t.Fatal(err) } db1.Close() // Open again — migrations must not fail or duplicate db2, err := Open(path) if err != nil { t.Fatalf("second open: %v", err) } db2.Close() }