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:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+213
View File
@@ -0,0 +1,213 @@
package catalog
import (
"context"
"os"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestValidateAttributeIDsOwned(t *testing.T) {
a := uuid.MustParse("11111111-1111-1111-1111-111111111111")
b := uuid.MustParse("22222222-2222-2222-2222-222222222222")
c := uuid.MustParse("33333333-3333-3333-3333-333333333333")
t.Run("empty request", func(t *testing.T) {
if err := validateAttributeIDsOwned(nil, nil); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
t.Run("all owned", func(t *testing.T) {
if err := validateAttributeIDsOwned([]uuid.UUID{a, b}, []uuid.UUID{b, a}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
t.Run("duplicate request ids still ok when owned", func(t *testing.T) {
// Ownership query returns distinct rows; duplicates are allowed through to INSERT
// (unique constraint rejects them later — same as the old per-row path).
if err := validateAttributeIDsOwned([]uuid.UUID{a, a}, []uuid.UUID{a}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
t.Run("missing id", func(t *testing.T) {
err := validateAttributeIDsOwned([]uuid.UUID{a, c}, []uuid.UUID{a})
if err == nil || err.Error() != "attribute not found" {
t.Fatalf("got %v, want attribute not found", err)
}
})
t.Run("cross-tenant treated as missing", func(t *testing.T) {
err := validateAttributeIDsOwned([]uuid.UUID{b}, nil)
if err == nil || err.Error() != "attribute not found" {
t.Fatalf("got %v, want attribute not found", err)
}
})
}
func asUUID(t *testing.T, v any) uuid.UUID {
t.Helper()
switch x := v.(type) {
case uuid.UUID:
return x
case string:
id, err := uuid.Parse(x)
if err != nil {
t.Fatalf("parse uuid %q: %v", x, err)
}
return id
case [16]byte:
return uuid.UUID(x)
default:
t.Fatalf("unexpected uuid type %T", v)
return uuid.Nil
}
}
func TestReplaceCategoryAttributesBatch(t *testing.T) {
dsn := strings.TrimSpace(os.Getenv("DATABASE_URL"))
if dsn == "" {
t.Skip("DATABASE_URL not set")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
pg, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatalf("postgres: %v", err)
}
defer pg.Close()
companyID := uuid.New()
_, err = pg.Exec(ctx, `INSERT INTO companies (id, name) VALUES ($1, $2)`,
companyID, "links-test-"+companyID.String()[:8])
if err != nil {
t.Fatalf("insert company: %v", err)
}
t.Cleanup(func() {
_, _ = pg.Exec(context.Background(), `DELETE FROM companies WHERE id = $1`, companyID)
})
svc := &Service{Pool: pg}
catUnique := "links-cat-" + companyID.String()[:8]
if _, err := svc.CreateCategory(ctx, companyID, "Links Test Cat", catUnique, nil, nil); err != nil {
t.Fatalf("CreateCategory: %v", err)
}
attrA, err := svc.CreateAttribute(ctx, companyID, "color", "Color", "string", nil, nil, nil)
if err != nil {
t.Fatalf("CreateAttribute A: %v", err)
}
attrB, err := svc.CreateAttribute(ctx, companyID, "size", "Size", "string", nil, nil, nil)
if err != nil {
t.Fatalf("CreateAttribute B: %v", err)
}
idA := asUUID(t, attrA["id"])
idB := asUUID(t, attrB["id"])
otherCompany := uuid.New()
_, err = pg.Exec(ctx, `INSERT INTO companies (id, name) VALUES ($1, $2)`,
otherCompany, "links-other-"+otherCompany.String()[:8])
if err != nil {
t.Fatalf("insert other company: %v", err)
}
t.Cleanup(func() {
_, _ = pg.Exec(context.Background(), `DELETE FROM companies WHERE id = $1`, otherCompany)
})
foreign, err := svc.CreateAttribute(ctx, otherCompany, "foreign", "Foreign", "string", nil, nil, nil)
if err != nil {
t.Fatalf("CreateAttribute foreign: %v", err)
}
foreignID := asUUID(t, foreign["id"])
t.Run("batch replace with required flags", func(t *testing.T) {
err := svc.ReplaceCategoryAttributes(ctx, companyID, catUnique, []uuid.UUID{idA, idB}, map[string]bool{
idA.String(): true,
})
if err != nil {
t.Fatalf("ReplaceCategoryAttributes: %v", err)
}
items, err := svc.ListCategoryAttributes(ctx, companyID, catUnique)
if err != nil {
t.Fatalf("ListCategoryAttributes: %v", err)
}
if len(items) != 2 {
t.Fatalf("want 2 links, got %d", len(items))
}
byAttr := map[uuid.UUID]bool{}
for _, item := range items {
aid := asUUID(t, item["attribute_id"])
req, _ := item["required"].(bool)
byAttr[aid] = req
}
if !byAttr[idA] {
t.Fatal("attribute A should be required")
}
if byAttr[idB] {
t.Fatal("attribute B should not be required")
}
})
t.Run("replace clears previous links", func(t *testing.T) {
err := svc.ReplaceCategoryAttributes(ctx, companyID, catUnique, []uuid.UUID{idB}, nil)
if err != nil {
t.Fatalf("ReplaceCategoryAttributes: %v", err)
}
items, err := svc.ListCategoryAttributes(ctx, companyID, catUnique)
if err != nil {
t.Fatalf("ListCategoryAttributes: %v", err)
}
if len(items) != 1 {
t.Fatalf("want 1 link, got %d", len(items))
}
if asUUID(t, items[0]["attribute_id"]) != idB {
t.Fatalf("want attribute B, got %v", items[0]["attribute_id"])
}
})
t.Run("empty list clears all", func(t *testing.T) {
err := svc.ReplaceCategoryAttributes(ctx, companyID, catUnique, nil, nil)
if err != nil {
t.Fatalf("ReplaceCategoryAttributes: %v", err)
}
items, err := svc.ListCategoryAttributes(ctx, companyID, catUnique)
if err != nil {
t.Fatalf("ListCategoryAttributes: %v", err)
}
if len(items) != 0 {
t.Fatalf("want 0 links, got %d", len(items))
}
})
t.Run("missing attribute", func(t *testing.T) {
err := svc.ReplaceCategoryAttributes(ctx, companyID, catUnique, []uuid.UUID{uuid.New()}, nil)
if err == nil || err.Error() != "attribute not found" {
t.Fatalf("got %v, want attribute not found", err)
}
})
t.Run("cross-tenant attribute", func(t *testing.T) {
err := svc.ReplaceCategoryAttributes(ctx, companyID, catUnique, []uuid.UUID{foreignID}, nil)
if err == nil || err.Error() != "attribute not found" {
t.Fatalf("got %v, want attribute not found", err)
}
items, err := svc.ListCategoryAttributes(ctx, companyID, catUnique)
if err != nil {
t.Fatalf("ListCategoryAttributes: %v", err)
}
if len(items) != 0 {
t.Fatalf("failed replace must leave links empty, got %d", len(items))
}
})
t.Run("category not found", func(t *testing.T) {
err := svc.ReplaceCategoryAttributes(ctx, companyID, "no-such-category", []uuid.UUID{idA}, nil)
if err == nil || err.Error() != "category not found" {
t.Fatalf("got %v, want category not found", err)
}
})
}