package application import ( "context" "errors" "fmt" "strings" "testing" "img_infinite_canvas/internal/domain/brandkit" "img_infinite_canvas/internal/domain/design" "img_infinite_canvas/internal/infrastructure/memory" ) const ( brandKitTestUserA = "11111111-1111-4111-8111-111111111111" brandKitTestUserB = "22222222-2222-4222-8222-222222222222" ) func TestBrandKitServiceEnforcesOwnershipAndSingleDefault(t *testing.T) { service := NewBrandKitService(memory.NewBrandKitStore()) ctxA := design.ContextWithUserID(context.Background(), brandKitTestUserA) ctxB := design.ContextWithUserID(context.Background(), brandKitTestUserB) first, err := service.Upsert(ctxA, "brand-kit-alpha", testBrandKitDocument("brand-kit-alpha", "Alpha"), true) if err != nil { t.Fatal(err) } if !first.IsDefault { t.Fatal("expected first kit to be default") } if _, err := service.Upsert(ctxA, "brand-kit-beta", testBrandKitDocument("brand-kit-beta", "Beta"), true); err != nil { t.Fatal(err) } kits, err := service.List(ctxA) if err != nil { t.Fatal(err) } defaults := 0 for _, kit := range kits { if kit.IsDefault { defaults++ if kit.ID != "brand-kit-beta" { t.Fatalf("unexpected default kit %q", kit.ID) } } } if defaults != 1 { t.Fatalf("expected one default, got %d", defaults) } if _, err := service.Get(ctxB, first.ID); !errors.Is(err, brandkit.ErrNotFound) { t.Fatalf("expected user isolation, got %v", err) } } func TestBrandKitServiceCompilesAuthoritativeContext(t *testing.T) { service := NewBrandKitService(memory.NewBrandKitStore()) ctx := design.ContextWithUserID(context.Background(), brandKitTestUserA) kit, err := service.Upsert(ctx, "brand-kit-alpha", testBrandKitDocument("brand-kit-alpha", "Alpha"), false) if err != nil { t.Fatal(err) } compiled, err := service.ResolveContext(ctx, kit.ID) if err != nil { t.Fatal(err) } for _, expected := range []string{"Binding creative system", "Alpha", "Signal Green", "#28C76F", "Display"} { if !strings.Contains(compiled, expected) { t.Fatalf("compiled context missing %q: %s", expected, compiled) } } if strings.Contains(compiled, "data:") { t.Fatal("compiled context must not include embedded asset data") } } func TestProjectBrandKitBindingUsesDefaultAndExplicitNone(t *testing.T) { store := memory.NewBrandKitStore() brandKits := NewBrandKitService(store) ctx := design.ContextWithUserID(context.Background(), brandKitTestUserA) if _, err := brandKits.Upsert(ctx, "brand-kit-alpha", testBrandKitDocument("brand-kit-alpha", "Alpha"), true); err != nil { t.Fatal(err) } repo := memory.NewProjectRepository() designs := NewDesignService(repo, nil, nil, nil, nil) designs.SetBrandKitService(brandKits) withDefault, err := designs.CreateProjectWithBrandKit(ctx, "Default", "", "", "", "", 0, false, nil) if err != nil { t.Fatal(err) } if withDefault.BrandKitID != "brand-kit-alpha" || !strings.Contains(withDefault.BrandContext, "Alpha") { t.Fatalf("default Brand Kit was not bound: %#v", withDefault) } none := "" withoutKit, err := designs.CreateProjectWithBrandKit(ctx, "None", "", "", "", "", 0, false, &none) if err != nil { t.Fatal(err) } if withoutKit.BrandKitID != "" || withoutKit.BrandContext != "" { t.Fatalf("explicit None should bypass the default: %#v", withoutKit) } bound, err := designs.UpdateProjectBrandKit(ctx, withoutKit.ID, "brand-kit-alpha") if err != nil { t.Fatal(err) } if bound.BrandKitID != "brand-kit-alpha" { t.Fatalf("expected explicit binding, got %q", bound.BrandKitID) } if err := designs.ClearBrandKitBindings(ctx, "brand-kit-alpha"); err != nil { t.Fatal(err) } cleared, err := repo.Get(ctx, bound.ID) if err != nil { t.Fatal(err) } if cleared.BrandKitID != "" { t.Fatalf("expected binding cleanup, got %q", cleared.BrandKitID) } } func TestProjectBrandKitBindingRejectsAnotherUsersKit(t *testing.T) { brandKits := NewBrandKitService(memory.NewBrandKitStore()) ctxA := design.ContextWithUserID(context.Background(), brandKitTestUserA) ctxB := design.ContextWithUserID(context.Background(), brandKitTestUserB) if _, err := brandKits.Upsert(ctxA, "brand-kit-alpha", testBrandKitDocument("brand-kit-alpha", "Alpha"), false); err != nil { t.Fatal(err) } repo := memory.NewProjectRepository() designs := NewDesignService(repo, nil, nil, nil, nil) designs.SetBrandKitService(brandKits) none := "" project, err := designs.CreateProjectAsyncWithBrandKit(ctxB, "Other user", "", "", "", "", 0, false, &none) if err != nil { t.Fatal(err) } if _, err := designs.UpdateProjectBrandKit(ctxB, project.ID, "brand-kit-alpha"); !errors.Is(err, brandkit.ErrNotFound) { t.Fatalf("expected ownership check to hide the Brand Kit, got %v", err) } stored, err := repo.Get(ctxB, project.ID) if err != nil { t.Fatal(err) } if stored.BrandKitID != "" { t.Fatalf("failed binding must not mutate the project, got %q", stored.BrandKitID) } } func testBrandKitDocument(id string, name string) string { return fmt.Sprintf(`{ "version": 1, "id": %q, "name": %q, "brandName": %q, "tagline": "Make the signal clear", "summary": "A focused product brand.", "audience": "Creative teams", "positioning": "Clear and precise", "personality": "Confident", "voice": {"traits": "Direct", "guidance": "Lead with outcomes", "sample": "Move with clarity"}, "logos": [{"id": "logo-primary", "name": "Primary", "variant": "primary", "usage": "Light backgrounds", "source": "upload", "dataUrl": "data:image/png;base64,AA=="}], "colorGroups": [{"id": "colors-primary", "name": "Primary", "colors": [{"id": "color-signal", "name": "Signal Green", "hex": "#28C76F", "usage": "Key actions"}]}], "typography": [{"id": "type-display", "label": "Display", "family": "Avenir Next", "weight": "700", "sizes": "48", "description": "Headlines"}], "referenceImages": [], "visual": {"keywords": "precise", "composition": "strict grid", "imagery": "real product", "dos": "use space", "donts": "avoid noise", "prompt": "Keep the product clear"}, "applyToNewProjects": false, "createdAt": "2026-01-01T00:00:00Z", "updatedAt": "2026-01-01T00:00:00Z" }`, id, name, name) }