package plugin import ( "encoding/json" "testing" "github.com/alexei/tinyforge/internal/store" ) // TestWorkloadFromStore_MapsEveryField pins the full field-for-field contract // of the consolidated converter. The chief risk of extracting the three former // per-package copies into one shared function is a silently dropped field — // especially the three secrets (json:"-", so serialization-based tests can // never catch a regression here) and the GroupID<-AppID rename. func TestWorkloadFromStore_MapsEveryField(t *testing.T) { faces := []PublicFace{ {Subdomain: "app", Domain: "example.com", TargetService: "web", TargetPort: 8080, AccessListID: 3, EnableSSL: true}, {Subdomain: "api", Domain: "example.com", TargetPort: 9090}, } facesJSON, err := json.Marshal(faces) if err != nil { t.Fatalf("marshal faces: %v", err) } src := store.Workload{ ID: "wl-1", Name: "my-workload", AppID: "grp-7", SourceKind: "dockerfile", SourceConfig: `{"repo":"x"}`, TriggerKind: "git", TriggerConfig: `{"branch":"main"}`, PublicFaces: string(facesJSON), ParentWorkloadID: "parent-2", NotificationURL: "https://hooks.example.com/notify", NotificationSecret: "notif-secret", WebhookSecret: "wh-secret", WebhookSigningSecret: "wh-signing-secret", WebhookRequireSignature: true, CreatedAt: "2026-01-01T00:00:00Z", UpdatedAt: "2026-01-02T00:00:00Z", } got := WorkloadFromStore(src) if got.ID != src.ID { t.Errorf("ID = %q, want %q", got.ID, src.ID) } if got.Name != src.Name { t.Errorf("Name = %q, want %q", got.Name, src.Name) } if got.GroupID != src.AppID { t.Errorf("GroupID = %q, want AppID %q", got.GroupID, src.AppID) } if got.ParentWorkloadID != src.ParentWorkloadID { t.Errorf("ParentWorkloadID = %q, want %q", got.ParentWorkloadID, src.ParentWorkloadID) } if got.SourceKind != src.SourceKind { t.Errorf("SourceKind = %q, want %q", got.SourceKind, src.SourceKind) } if string(got.SourceConfig) != src.SourceConfig { t.Errorf("SourceConfig = %q, want %q", string(got.SourceConfig), src.SourceConfig) } if got.TriggerKind != src.TriggerKind { t.Errorf("TriggerKind = %q, want %q", got.TriggerKind, src.TriggerKind) } if string(got.TriggerConfig) != src.TriggerConfig { t.Errorf("TriggerConfig = %q, want %q", string(got.TriggerConfig), src.TriggerConfig) } if got.NotificationURL != src.NotificationURL { t.Errorf("NotificationURL = %q, want %q", got.NotificationURL, src.NotificationURL) } if got.NotificationSecret != src.NotificationSecret { t.Errorf("NotificationSecret not carried through") } if got.WebhookSecret != src.WebhookSecret { t.Errorf("WebhookSecret not carried through") } if got.WebhookSigningSecret != src.WebhookSigningSecret { t.Errorf("WebhookSigningSecret not carried through") } if got.WebhookRequireSignature != src.WebhookRequireSignature { t.Errorf("WebhookRequireSignature = %v, want %v", got.WebhookRequireSignature, src.WebhookRequireSignature) } if got.CreatedAt != src.CreatedAt { t.Errorf("CreatedAt = %q, want %q", got.CreatedAt, src.CreatedAt) } if got.UpdatedAt != src.UpdatedAt { t.Errorf("UpdatedAt = %q, want %q", got.UpdatedAt, src.UpdatedAt) } if len(got.PublicFaces) != len(faces) { t.Fatalf("PublicFaces len = %d, want %d", len(got.PublicFaces), len(faces)) } if got.PublicFaces[0] != faces[0] || got.PublicFaces[1] != faces[1] { t.Errorf("PublicFaces = %+v, want %+v", got.PublicFaces, faces) } } // TestWorkloadFromStore_PublicFaces covers the PublicFaces decode branch, // including the malformed-JSON path that the consolidation newly unified onto // "log and treat as empty" for every caller (the old reconciler/webhook copies // silently swallowed the error). A decode failure must never fail the // conversion or panic — it yields nil faces. func TestWorkloadFromStore_PublicFaces(t *testing.T) { tests := []struct { name string raw string wantLen int wantNil bool }{ {name: "empty string yields nil", raw: "", wantLen: 0, wantNil: true}, {name: "empty array yields empty", raw: "[]", wantLen: 0, wantNil: false}, {name: "malformed json yields nil", raw: "{not-json", wantLen: 0, wantNil: true}, {name: "wrong-shape json yields nil", raw: `{"a":1}`, wantLen: 0, wantNil: true}, {name: "single valid face", raw: `[{"Subdomain":"a"}]`, wantLen: 1, wantNil: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := WorkloadFromStore(store.Workload{ID: "wl", PublicFaces: tt.raw}) if len(got.PublicFaces) != tt.wantLen { t.Errorf("PublicFaces len = %d, want %d", len(got.PublicFaces), tt.wantLen) } if tt.wantNil && got.PublicFaces != nil { t.Errorf("PublicFaces = %+v, want nil", got.PublicFaces) } }) } }