Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
package support
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// TestSubmitCSATOwnershipAndOnce covers owner-only rating, eligibility, and one-rating rule.
|
||||
func TestSubmitCSATOwnershipAndOnce(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_csat_ratings'
|
||||
)`).Scan(&hasTable); err != nil {
|
||||
t.Fatalf("schema probe: %v", err)
|
||||
}
|
||||
if !hasTable {
|
||||
t.Skip("support_csat_ratings missing — run goose up for 029_support_desk")
|
||||
}
|
||||
|
||||
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, "CSAT 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("csat-owner-%s@example.test", prefix), "Owner"},
|
||||
{otherID, fmt.Sprintf("csat-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: %v", 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_csat_ratings WHERE company_id = $1`, companyID)
|
||||
_, _ = 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: "CSAT probe",
|
||||
Category: "other",
|
||||
Priority: "normal",
|
||||
Body: "Need help rating",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("create: %v", err)
|
||||
}
|
||||
|
||||
_, err = svc.SubmitCSAT(ctx, companyID, ownerID, ticket.ID, CSATInput{Score: 5, Comment: "secret PII should not log"})
|
||||
if !errors.Is(err, ErrCSATNotEligible) {
|
||||
t.Fatalf("open ticket rate err=%v want ErrCSATNotEligible", err)
|
||||
}
|
||||
|
||||
_, err = pg.Exec(ctx, `
|
||||
UPDATE support_tickets SET status = 'resolved', resolved_at = now(), updated_at = now()
|
||||
WHERE id = $1`, ticket.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve: %v", err)
|
||||
}
|
||||
|
||||
got, err := svc.GetForUser(ctx, companyID, ownerID, ticket.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
if !got.CSATEligible || got.CSAT != nil {
|
||||
t.Fatalf("eligible=%v csat=%v", got.CSATEligible, got.CSAT)
|
||||
}
|
||||
|
||||
_, err = svc.SubmitCSAT(ctx, companyID, otherID, ticket.ID, CSATInput{Score: 1})
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
t.Fatalf("other rate err=%v want ErrNotFound", err)
|
||||
}
|
||||
|
||||
rating, err := svc.SubmitCSAT(ctx, companyID, ownerID, ticket.ID, CSATInput{Score: 4, Comment: "good"})
|
||||
if err != nil {
|
||||
t.Fatalf("owner rate: %v", err)
|
||||
}
|
||||
if rating.Score != 4 || rating.Comment != "good" {
|
||||
t.Fatalf("rating=%+v", rating)
|
||||
}
|
||||
|
||||
_, err = svc.SubmitCSAT(ctx, companyID, ownerID, ticket.ID, CSATInput{Score: 5})
|
||||
if !errors.Is(err, ErrAlreadyRated) {
|
||||
t.Fatalf("second rate err=%v want ErrAlreadyRated", err)
|
||||
}
|
||||
|
||||
got, err = svc.GetForUser(ctx, companyID, ownerID, ticket.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get after: %v", err)
|
||||
}
|
||||
if got.CSATEligible || got.CSAT == nil || got.CSAT.Score != 4 {
|
||||
t.Fatalf("after rate eligible=%v csat=%v", got.CSATEligible, got.CSAT)
|
||||
}
|
||||
|
||||
agg, err := svc.AggregateCSAT(ctx, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("aggregate: %v", err)
|
||||
}
|
||||
if agg.Total < 1 || agg.Distribution["4"] < 1 {
|
||||
t.Fatalf("aggregate=%+v", agg)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user