Lock identity for accessory VFX rows is now the model source path (dataset:group/subgroup/file.mdl) rather than the config-derived presentation key. Config-only edits (key_format, delimiter, case, strip_prefixes) no longer reshuffle row IDs. Adds: accessoryVisualeffectLockKey, hasKey helper, stale pruning loop (managed keys with no live entry are freed), and one-time cutover remap (old config-derived key in historical lock → new model-path key, with the old key pruned). autogenConsumerManagedLockKeyMatcher is now config-independent (dataset prefix + first path segment ∈ policy groups). Updates autogen_manifest_file_test.go and topdata_test.go lock-key assertions to match the new model-path scheme.
115 lines
4.5 KiB
Go
115 lines
4.5 KiB
Go
package topdata
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
|
)
|
|
|
|
func accConsumer() project.AutogenConsumerConfig {
|
|
return project.AutogenConsumerConfig{
|
|
ID: "accessory_visualeffects", Producer: "accessory_visualeffects",
|
|
Dataset: "visualeffects", Mode: "accessory_visualeffects", Optional: true,
|
|
AccessoryVisualeffects: project.AccessoryVisualeffectsConfig{
|
|
Groups: map[string]project.AccessoryVisualeffectGroupConfig{
|
|
"head_accessories": {ModelColumns: []string{"Imp_HeadCon_Node"}},
|
|
},
|
|
GroupTokenSource: "folder_name", CategoryFrom: "immediate_parent",
|
|
Delimiter: "/", Case: "preserve",
|
|
StripModelPrefixes: []string{"hfx_"},
|
|
KeyFormat: "{dataset}:{group}{delimiter}{category_segment}{stem}",
|
|
LabelFormat: "{group}{delimiter}{category_segment}{stem}",
|
|
ModelColumn: "Imp_HeadCon_Node",
|
|
},
|
|
}
|
|
}
|
|
|
|
func vfxEntry() autogenManifestEntry {
|
|
return autogenManifestEntry{
|
|
Source: "head_accessories/hat/hfx_bandana.mdl", Group: "head_accessories",
|
|
Subgroup: "hat", ModelStem: "hfx_bandana",
|
|
}
|
|
}
|
|
|
|
func vfxDataset(lock map[string]int) nativeCollectedDataset {
|
|
return nativeCollectedDataset{
|
|
Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase},
|
|
Columns: []string{"Label", "Imp_HeadCon_Node"},
|
|
Rows: nil,
|
|
LockData: lock,
|
|
}
|
|
}
|
|
|
|
func TestAccessoryLockKeyIsModelAnchored(t *testing.T) {
|
|
got := accessoryVisualeffectLockKey("visualeffects", vfxEntry())
|
|
want := "visualeffects:head_accessories/hat/hfx_bandana.mdl"
|
|
if got != want {
|
|
t.Fatalf("lock key = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
// A config-only change (here: a different key_format) must NOT move the ID,
|
|
// because identity is the model path, not the presentation key.
|
|
func TestConfigOnlyChangeKeepsLockID(t *testing.T) {
|
|
lockKey := "visualeffects:head_accessories/hat/hfx_bandana.mdl"
|
|
collected := []nativeCollectedDataset{vfxDataset(map[string]int{lockKey: 555})}
|
|
|
|
c := accConsumer()
|
|
c.AccessoryVisualeffects.KeyFormat = "{dataset}:{stem}" // config-only change
|
|
got, err := augmentWithAutogeneratedAccessoryVisualeffects(collected, []autogenManifestEntry{vfxEntry()}, c, nil)
|
|
if err != nil {
|
|
t.Fatalf("augment failed: %v", err)
|
|
}
|
|
if got[0].LockData[lockKey] != 555 {
|
|
t.Fatalf("expected ID preserved at 555, got %#v", got[0].LockData)
|
|
}
|
|
}
|
|
|
|
// A model re-export (same path) keeps its ID — content change != identity change.
|
|
func TestModelReExportKeepsLockID(t *testing.T) {
|
|
lockKey := "visualeffects:head_accessories/hat/hfx_bandana.mdl"
|
|
collected := []nativeCollectedDataset{vfxDataset(map[string]int{lockKey: 777})}
|
|
got, err := augmentWithAutogeneratedAccessoryVisualeffects(collected, []autogenManifestEntry{vfxEntry()}, accConsumer(), nil)
|
|
if err != nil {
|
|
t.Fatalf("augment failed: %v", err)
|
|
}
|
|
if got[0].LockData[lockKey] != 777 {
|
|
t.Fatalf("expected ID 777 preserved, got %#v", got[0].LockData)
|
|
}
|
|
}
|
|
|
|
// A removed model frees its old ID; the surviving model keeps its lock entry.
|
|
func TestModelRemovalPrunesStaleLockKey(t *testing.T) {
|
|
keep := "visualeffects:head_accessories/hat/hfx_bandana.mdl"
|
|
gone := "visualeffects:head_accessories/hat/hfx_removed.mdl"
|
|
collected := []nativeCollectedDataset{vfxDataset(map[string]int{keep: 1, gone: 2})}
|
|
got, err := augmentWithAutogeneratedAccessoryVisualeffects(collected, []autogenManifestEntry{vfxEntry()}, accConsumer(), nil)
|
|
if err != nil {
|
|
t.Fatalf("augment failed: %v", err)
|
|
}
|
|
if _, present := got[0].LockData[gone]; present {
|
|
t.Fatalf("expected removed model's lock key pruned, got %#v", got[0].LockData)
|
|
}
|
|
if got[0].LockData[keep] != 1 {
|
|
t.Fatalf("expected surviving model ID 1 kept, got %#v", got[0].LockData)
|
|
}
|
|
}
|
|
|
|
// Cutover: a lock that only has the OLD config-derived key adopts that ID under
|
|
// the new model-path key, with zero shift, and prunes the old key.
|
|
func TestCutoverRemapPreservesID(t *testing.T) {
|
|
oldKey := "visualeffects:head_accessories/hat/bandana" // {dataset}:{group}/{category}/{stem-without-hfx_}
|
|
newKey := "visualeffects:head_accessories/hat/hfx_bandana.mdl"
|
|
collected := []nativeCollectedDataset{vfxDataset(map[string]int{oldKey: 4242})}
|
|
got, err := augmentWithAutogeneratedAccessoryVisualeffects(collected, []autogenManifestEntry{vfxEntry()}, accConsumer(), nil)
|
|
if err != nil {
|
|
t.Fatalf("augment failed: %v", err)
|
|
}
|
|
if got[0].LockData[newKey] != 4242 {
|
|
t.Fatalf("expected cutover to carry ID 4242 to new key, got %#v", got[0].LockData)
|
|
}
|
|
if _, present := got[0].LockData[oldKey]; present {
|
|
t.Fatalf("expected old config key pruned after cutover, got %#v", got[0].LockData)
|
|
}
|
|
}
|