Fix autogen to fail-open on missing asset manifests
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user