package topdata import ( "net/http" "net/http/httptest" "path/filepath" "testing" "git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project" ) // When an optional autogen consumer's released manifest cannot be fetched, the // build must fail open: it keeps the consumer's already-pinned lock entries // (so their IDs are not freed and reshuffled later) and generates no new rows. func TestApplyAutogenConsumersPreservesLockEntriesWhenManifestUnavailable(t *testing.T) { root := testProjectRoot(t) // 404 for every request → released manifest is unavailable (not empty). srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) })) t.Cleanup(srv.Close) t.Setenv("SOW_ASSETS_SERVER_URL", srv.URL) t.Setenv("SOW_ASSETS_REPO", "ShadowsOverWestgate/sow-assets") p := testProject(root) p.Config.Autogen.Consumers = []project.AutogenConsumerConfig{ { ID: "accessory_visualeffects", Producer: "accessory_visualeffects", Dataset: "visualeffects", Mode: "accessory_visualeffects", Optional: true, Root: "vfxs", Include: []string{"head_accessories/**/*.mdl"}, Derive: project.AutogenDeriveConfig{Kind: "model_stem", GroupFrom: "first_path_segment"}, Manifest: project.AutogenManifestConfig{ReleaseTag: "head-vfx-manifest-current", AssetName: "sow-accessory-vfx-manifest.json", CacheName: "sow-accessory-vfx-manifest.json"}, }, } // On-disk lock pins an autogen-owned accessory key plus an authored key. lockPath := filepath.Join(root, "visualeffects-lock.json") writeFile(t, lockPath, `{"visualeffects:existing":17,"visualeffects:head_accessories/hat/bandana":10101}`+"\n") // Simulate post-prune collected state: the accessory key has already been // dropped from in-memory LockData (as pruneLockDataToActiveRows would do). collected := []nativeCollectedDataset{ { Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase, LockPath: lockPath}, Columns: []string{"Label"}, Rows: []map[string]any{{"id": 17, "key": "visualeffects:existing"}}, LockData: map[string]int{"visualeffects:existing": 17}, }, } got, err := applyAutogenConsumers(p, collected, nil) if err != nil { t.Fatalf("expected fail-open build, got error: %v", err) } if len(got) != 1 { t.Fatalf("expected one dataset, got %d", len(got)) } // The pinned accessory id must survive with its exact value (no jumble). if id, ok := got[0].LockData["visualeffects:head_accessories/hat/bandana"]; !ok || id != 10101 { t.Fatalf("expected preserved accessory lock id 10101, got %v (present=%v) lock=%#v", id, ok, got[0].LockData) } // The authored key is untouched. if id, ok := got[0].LockData["visualeffects:existing"]; !ok || id != 17 { t.Fatalf("expected authored key retained at 17, got %#v", got[0].LockData) } // No row is generated for the unavailable accessory entry. for _, row := range got[0].Rows { if key, _ := row["key"].(string); key == "visualeffects:head_accessories/hat/bandana" { t.Fatalf("expected no generated row while manifest unavailable, got %#v", row) } } } // A key that is NOT owned by the consumer (an authored row legitimately removed) // must not be resurrected by the preservation path. func TestPreserveAutogenConsumerLockEntriesIgnoresUnownedKeys(t *testing.T) { root := testProjectRoot(t) lockPath := filepath.Join(root, "visualeffects-lock.json") writeFile(t, lockPath, `{"visualeffects:retired_authored":3,"visualeffects:head_accessories/hat/bandana":10101}`+"\n") consumer := project.AutogenConsumerConfig{ ID: "accessory_visualeffects", Dataset: "visualeffects", Mode: "accessory_visualeffects", } collected := []nativeCollectedDataset{ { Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase, LockPath: lockPath}, LockData: map[string]int{}, }, } got, err := preserveAutogenConsumerLockEntries(collected, consumer) if err != nil { t.Fatalf("preserveAutogenConsumerLockEntries failed: %v", err) } if _, ok := got[0].LockData["visualeffects:head_accessories/hat/bandana"]; !ok { t.Fatalf("expected owned accessory key preserved, got %#v", got[0].LockData) } if _, ok := got[0].LockData["visualeffects:retired_authored"]; ok { t.Fatalf("expected unowned authored key NOT resurrected, got %#v", got[0].LockData) } }