Files

117 lines
3.6 KiB
Go
Raw Permalink Normal View History

package support
import (
"context"
"errors"
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
// TestTicketCRUDAuthOwnership gates list/get so users only see their own tickets.
// Skips when DATABASE_URL unset or support_tickets migration not applied.
func TestTicketCRUDAuthOwnership(t *testing.T) {
dsn := strings.TrimSpace(os.Getenv("DATABASE_URL"))
if dsn == "" {
t.Skip("DATABASE_URL not set")
}
ctx := context.Background()
pg, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("postgres: %v", err)
}
t.Cleanup(pg.Close)
var hasTable bool
if err := pg.QueryRow(ctx, `
SELECT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'support_tickets'
)`).Scan(&hasTable); err != nil {
t.Fatalf("schema probe: %v", err)
}
if !hasTable {
t.Skip("support_tickets table missing — run goose up for 025_support_center")
}
companyID := uuid.New()
ownerID := uuid.New()
otherID := uuid.New()
prefix := companyID.String()[:8]
_, err = pg.Exec(ctx, `INSERT INTO companies (id, name, language) VALUES ($1, $2, 'en')`,
companyID, "Support Auth Co "+prefix)
if err != nil {
t.Fatalf("seed company: %v", err)
}
for _, u := range []struct {
id uuid.UUID
email string
name string
}{
{ownerID, fmt.Sprintf("owner-%s@example.test", prefix), "Owner"},
{otherID, fmt.Sprintf("other-%s@example.test", prefix), "Other"},
} {
_, err = pg.Exec(ctx, `
INSERT INTO users (id, email, name, password_hash, must_set_password, is_platform_admin, is_active)
VALUES ($1, $2, $3, 'x', false, false, true)`, u.id, u.email, u.name)
if err != nil {
t.Fatalf("seed user %s: %v", u.email, err)
}
_, err = pg.Exec(ctx, `
INSERT INTO memberships (company_id, user_id, role, status)
VALUES ($1, $2, 'member', 'active')`, companyID, u.id)
if err != nil {
t.Fatalf("seed membership: %v", err)
}
}
t.Cleanup(func() {
cctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, _ = pg.Exec(cctx, `DELETE FROM support_notifications WHERE ticket_id IN (SELECT id FROM support_tickets WHERE company_id = $1)`, companyID)
_, _ = pg.Exec(cctx, `DELETE FROM support_messages WHERE company_id = $1`, companyID)
_, _ = pg.Exec(cctx, `DELETE FROM support_tickets WHERE company_id = $1`, companyID)
_, _ = pg.Exec(cctx, `DELETE FROM memberships WHERE company_id = $1`, companyID)
_, _ = pg.Exec(cctx, `DELETE FROM users WHERE id IN ($1, $2)`, ownerID, otherID)
_, _ = pg.Exec(cctx, `DELETE FROM companies WHERE id = $1`, companyID)
})
svc := NewService(pg)
ticket, err := svc.Create(ctx, companyID, ownerID, CreateInput{
Subject: "Auth ownership probe",
Category: "bug",
Priority: "normal",
Body: "Initial message from owner",
})
if err != nil {
t.Fatalf("create: %v", err)
}
if _, err := svc.GetForUser(ctx, companyID, ownerID, ticket.ID); err != nil {
t.Fatalf("owner get: %v", err)
}
if _, err := svc.GetForUser(ctx, companyID, otherID, ticket.ID); !errors.Is(err, ErrNotFound) {
t.Fatalf("other get err=%v want ErrNotFound", err)
}
ownerList, _, err := svc.ListForUser(ctx, companyID, ownerID, "", 20, 0)
if err != nil {
t.Fatalf("owner list: %v", err)
}
if len(ownerList) != 1 || ownerList[0].ID != ticket.ID {
t.Fatalf("owner list=%v want ticket %s", ownerList, ticket.ID)
}
otherList, total, err := svc.ListForUser(ctx, companyID, otherID, "", 20, 0)
if err != nil {
t.Fatalf("other list: %v", err)
}
if total != 0 || len(otherList) != 0 {
t.Fatalf("other must not see owner tickets: total=%d list=%v", total, otherList)
}
}