package templates import ( "context" "errors" "testing" "time" ) type memoryCatalog struct { items []Template err error } func (catalog *memoryCatalog) ListTemplates(_ context.Context, owner string) ([]Template, error) { if catalog.err != nil { return nil, catalog.err } out := []Template{} for _, item := range catalog.items { if item.OwnerID == owner { out = append(out, item) } } return out, nil } func (catalog *memoryCatalog) CreateTemplate(_ context.Context, item Template) (Template, error) { if catalog.err != nil { return Template{}, catalog.err } catalog.items = append(catalog.items, item) return item, nil } func (catalog *memoryCatalog) UpdateTemplate(_ context.Context, owner, id string, patch Patch, now time.Time) (Template, bool, error) { if catalog.err != nil { return Template{}, false, catalog.err } for index := range catalog.items { if catalog.items[index].OwnerID != owner || catalog.items[index].ID != id { continue } applyPatch(&catalog.items[index], patch) catalog.items[index].UpdatedAt = now return catalog.items[index], true, nil } return Template{}, false, nil } func (catalog *memoryCatalog) DeleteTemplate(_ context.Context, owner, id string) (Template, bool, error) { if catalog.err != nil { return Template{}, false, catalog.err } for index, item := range catalog.items { if item.OwnerID != owner || item.ID != id { continue } catalog.items = append(catalog.items[:index], catalog.items[index+1:]...) return item, true, nil } return Template{}, false, nil } func TestServiceNormalizesAndScopesTemplates(t *testing.T) { now := time.Date(2026, 8, 13, 12, 0, 0, 0, time.UTC) catalog := &memoryCatalog{} service := NewService(catalog, func() time.Time { return now }, func() string { return "tmpl_fixed" }) created, err := service.Create(context.Background(), "owner-a", CreateCommand{ Name: " Campaign ", Description: " ", Prompt: " make it vivid ", PreviewImageURL: "/uploads/preview.png", SortOrder: 2, Settings: Settings{Engine: "jimeng", Width: 2048, Height: 2048, Scale: 50}, }) if err != nil { t.Fatal(err) } if created.ID != "tmpl_fixed" || created.OwnerID != "owner-a" || created.Name != "Campaign" || created.Prompt != "make it vivid" { t.Fatalf("unexpected template: %#v", created) } if created.Description != "" || created.CreatedAt != now || created.UpdatedAt != now { t.Fatalf("unexpected normalized values: %#v", created) } if _, err := service.Create(context.Background(), "owner-a", CreateCommand{Name: "x", Prompt: "p", PreviewImageURL: "ftp://bad"}); !errors.Is(err, ErrInvalidTemplate) { t.Fatalf("expected invalid preview URL, got %v", err) } items, err := service.List(context.Background(), "owner-b") if err != nil || len(items) != 0 { t.Fatalf("scope leak: %#v %v", items, err) } } func TestServiceUpdateDeleteAndNotFound(t *testing.T) { now := time.Now().UTC() catalog := &memoryCatalog{items: []Template{{ID: "t1", OwnerID: "owner", Name: "old", Prompt: "p", Settings: Settings{}, CreatedAt: now, UpdatedAt: now}}} service := NewService(catalog, func() time.Time { return now.Add(time.Hour) }, nil) name := " new " updated, err := service.Update(context.Background(), "owner", "t1", UpdateCommand{Name: &name}) if err != nil || updated.Name != "new" { t.Fatalf("update: %#v %v", updated, err) } if _, err := service.Update(context.Background(), "other", "t1", UpdateCommand{Name: &name}); !errors.Is(err, ErrNotFound) { t.Fatalf("expected scoped not found, got %v", err) } deleted, err := service.Delete(context.Background(), "owner", "t1") if err != nil || deleted.ID != "t1" { t.Fatalf("delete: %#v %v", deleted, err) } if _, err := service.Delete(context.Background(), "owner", "t1"); !errors.Is(err, ErrNotFound) { t.Fatalf("expected not found, got %v", err) } } func TestServicePropagatesInfrastructureFailures(t *testing.T) { want := errors.New("database unavailable") service := NewService(&memoryCatalog{err: want}, nil, nil) if _, err := service.List(context.Background(), "owner"); !errors.Is(err, want) { t.Fatalf("expected wrapped cause, got %v", err) } }