Closing gaps and cleaning house
This commit is contained in:
@@ -2606,6 +2606,132 @@ func TestResolveGeneratedFeatIdentityUsesLegacyAliasForNoSourceFamily(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGeneratedFeatIdentityUsesLegacyAliasForUnderscoreNoSourceFamily(t *testing.T) {
|
||||
ctx := &featGeneratedContext{
|
||||
lockData: map[string]int{
|
||||
"feat:greater_skill_focus_appraise": 1316,
|
||||
"feat:epic_skill_focus_appraise": 588,
|
||||
"feat:epic_skill_focus_animal_empathy": 587,
|
||||
},
|
||||
supplementalID: map[string]int{},
|
||||
tlkStateKeys: map[string]struct{}{},
|
||||
familySpecs: map[string]familyExpansionSpec{
|
||||
"greater_skill_focus": {
|
||||
FamilyKey: "greater_skill_focus",
|
||||
LegacyFamilyKeys: []string{"epic_skill_focus"},
|
||||
},
|
||||
},
|
||||
}
|
||||
spec := familyExpansionSpec{FamilyKey: "greater_skill_focus"}
|
||||
|
||||
got, rowID, hasID, err := ctx.resolveGeneratedFeatIdentity(spec, "appraise", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity: %v", err)
|
||||
}
|
||||
if got != "feat:greater_skill_focus_appraise" || !hasID || rowID != 588 {
|
||||
t.Fatalf("expected underscore greater skill focus to take legacy alias id 588, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
|
||||
got, rowID, hasID, err = ctx.resolveGeneratedFeatIdentity(spec, "animal_handling", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity renamed skill: %v", err)
|
||||
}
|
||||
if got != "feat:greater_skill_focus_animal_handling" || !hasID || rowID != 587 {
|
||||
t.Fatalf("expected renamed underscore greater skill focus to take legacy alias id 587, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGeneratedFeatIdentityTranslatesLegacyWeaponFamilySourceIDWithUnderscoreKey(t *testing.T) {
|
||||
ctx := &featGeneratedContext{
|
||||
lockData: map[string]int{
|
||||
"feat:epic_weapon_focus_club": 619,
|
||||
"feat:epic_weapon_specialization_club": 650,
|
||||
"feat:epic_overwhelming_critical_club": 900,
|
||||
},
|
||||
supplementalID: map[string]int{},
|
||||
tlkStateKeys: map[string]struct{}{},
|
||||
existingFeat: map[string]struct{}{
|
||||
"feat:epic_weapon_focus_club": {},
|
||||
"feat:epic_weapon_specialization_club": {},
|
||||
"feat:epic_overwhelming_critical_club": {},
|
||||
},
|
||||
familySpecs: map[string]familyExpansionSpec{
|
||||
"greater_weapon_focus": {
|
||||
FamilyKey: "greater_weapon_focus",
|
||||
LegacyFamilyKeys: []string{"epic_weapon_focus"},
|
||||
},
|
||||
"greater_weapon_specialization": {
|
||||
FamilyKey: "greater_weapon_specialization",
|
||||
LegacyFamilyKeys: []string{"epic_weapon_specialization"},
|
||||
},
|
||||
"overwhelming_critical": {
|
||||
FamilyKey: "overwhelming_critical",
|
||||
LegacyFamilyKeys: []string{"epic_overwhelming_critical"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
family string
|
||||
want string
|
||||
rowID int
|
||||
}{
|
||||
{family: "greater_weapon_focus", want: "feat:greater_weapon_focus_club", rowID: 619},
|
||||
{family: "greater_weapon_specialization", want: "feat:greater_weapon_specialization_club", rowID: 650},
|
||||
{family: "overwhelming_critical", want: "feat:overwhelming_critical_club", rowID: 900},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got, rowID, hasID, err := ctx.resolveGeneratedFeatIdentityBySource(tc.family, "club", fmt.Sprintf("%d", tc.rowID))
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentityBySource(%s): %v", tc.family, err)
|
||||
}
|
||||
if got != tc.want || !hasID || rowID != tc.rowID {
|
||||
t.Fatalf("expected %s at id %d, got key=%q id=%d hasID=%v", tc.want, tc.rowID, got, rowID, hasID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGeneratedFeatIdentityUsesConfiguredLegacyFamilyKeys(t *testing.T) {
|
||||
ctx := &featGeneratedContext{
|
||||
lockData: map[string]int{
|
||||
"feat:old_weapon_training_test_club": 4000,
|
||||
},
|
||||
supplementalID: map[string]int{},
|
||||
tlkStateKeys: map[string]struct{}{},
|
||||
familySpecs: map[string]familyExpansionSpec{
|
||||
"new_weapon_training": {
|
||||
FamilyKey: "new_weapon_training",
|
||||
LegacyFamilyKeys: []string{"old_weapon_training"},
|
||||
},
|
||||
},
|
||||
}
|
||||
spec := familyExpansionSpec{FamilyKey: "new_weapon_training"}
|
||||
|
||||
got, rowID, hasID, err := ctx.resolveGeneratedFeatIdentity(spec, "test_club", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity: %v", err)
|
||||
}
|
||||
if got != "feat:new_weapon_training_test_club" || !hasID || rowID != 4000 {
|
||||
t.Fatalf("expected configured legacy family to donate id 4000, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
|
||||
if !generatedAliasLockForKey(
|
||||
"feat:new_weapon_training_test_club",
|
||||
"feat:old_weapon_training_test_club",
|
||||
generatedFamilyAliases{"new_weapon_training": []string{"old_weapon_training"}},
|
||||
) {
|
||||
t.Fatal("expected configured legacy family lock to be movable")
|
||||
}
|
||||
|
||||
got, rowID, hasID, err = ctx.resolveGeneratedFeatIdentity(familyExpansionSpec{FamilyKey: "newweapontraining"}, "test_club", map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGeneratedFeatIdentity compact family key: %v", err)
|
||||
}
|
||||
if got != "feat:newweapontraining_test_club" || !hasID || rowID != 4000 {
|
||||
t.Fatalf("expected normalized configured legacy lookup to donate id 4000, got key=%q id=%d hasID=%v", got, rowID, hasID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFamilyExpansionMovesCanonicalLockToLegacyAliasID(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFeatGeneratedHarness(t, root, map[string]string{
|
||||
@@ -2616,6 +2742,7 @@ func TestBuildFamilyExpansionMovesCanonicalLockToLegacyAliasID(t *testing.T) {
|
||||
"name_prefix": "Greater Skill Focus",
|
||||
"label_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"constant_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"legacy_family_keys": ["epicskillfocus"],
|
||||
"child_ref_field": "REQSKILL",
|
||||
"child_source": {"dataset":"skills","predicate":"accessible"},
|
||||
"overrides": {"skills:concentration":{"ICON":"ife_greater_concentration"}}
|
||||
@@ -2650,6 +2777,150 @@ func TestBuildFamilyExpansionMovesCanonicalLockToLegacyAliasID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFamilyExpansionMovesUnderscoreCanonicalLockToUnderscoreLegacyAliasID(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFeatGeneratedHarness(t, root, map[string]string{
|
||||
"greater_skill_focus.json": `{
|
||||
"family": "greater_skill_focus",
|
||||
"family_key": "greater_skill_focus",
|
||||
"template": "masterfeats:greaterskillfocus",
|
||||
"name_prefix": "Greater Skill Focus",
|
||||
"label_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"constant_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"legacy_family_keys": ["epic_skill_focus"],
|
||||
"child_ref_field": "REQSKILL",
|
||||
"child_source": {"dataset":"skills","predicate":"accessible"},
|
||||
"overrides": {"skills:concentration":{"ICON":"ife_greater_concentration"}}
|
||||
}` + "\n",
|
||||
}, map[string]int{
|
||||
"feat:greater_skill_focus_concentration": 1317,
|
||||
"feat:epic_skill_focus_concentration": 589,
|
||||
})
|
||||
|
||||
result, err := BuildNative(testProject(root), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildNative: %v", err)
|
||||
}
|
||||
got, err := os.ReadFile(filepath.Join(result.Output2DADir, "feat.2da"))
|
||||
if err != nil {
|
||||
t.Fatalf("read feat.2da: %v", err)
|
||||
}
|
||||
text := string(got)
|
||||
if !strings.Contains(text, "589\tFEAT_GREATER_SKILL_FOCUS_CONCENTRATION") {
|
||||
t.Fatalf("expected underscore greater skill focus to move to legacy alias id 589, got:\n%s", text)
|
||||
}
|
||||
lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "feat", "lock.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("read feat lock: %v", err)
|
||||
}
|
||||
lockText := string(lockRaw)
|
||||
if !strings.Contains(lockText, `"feat:greater_skill_focus_concentration": 589`) {
|
||||
t.Fatalf("expected canonical underscore key to own legacy row id 589, got:\n%s", lockText)
|
||||
}
|
||||
if strings.Contains(lockText, "epic_skill_focus_concentration") {
|
||||
t.Fatalf("expected stale underscore epic skill focus lock to be pruned, got:\n%s", lockText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFamilyExpansionRegeneratesLegacyAliasIDWithoutFeatLock(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFeatGeneratedHarness(t, root, map[string]string{
|
||||
"greater_skill_focus.json": `{
|
||||
"family": "greater_skill_focus",
|
||||
"family_key": "greater_skill_focus",
|
||||
"template": "masterfeats:greaterskillfocus",
|
||||
"name_prefix": "Greater Skill Focus",
|
||||
"label_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"constant_prefix": "FEAT_GREATER_SKILL_FOCUS",
|
||||
"legacy_family_keys": ["epic_skill_focus"],
|
||||
"child_ref_field": "REQSKILL",
|
||||
"child_source": {"dataset":"skills","predicate":"accessible"},
|
||||
"overrides": {"skills:concentration":{"ICON":"ife_greater_concentration"}}
|
||||
}` + "\n",
|
||||
}, map[string]int{})
|
||||
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{
|
||||
"output": "feat.2da",
|
||||
"compare_reference": false,
|
||||
"columns": [
|
||||
"LABEL","FEAT","DESCRIPTION","ICON","MINATTACKBONUS","MINSTR","MINDEX","MININT","MINWIS","MINCON","MINCHA",
|
||||
"MINSPELLLVL","PREREQFEAT1","PREREQFEAT2","GAINMULTIPLE","EFFECTSSTACK","ALLCLASSESCANUSE","CATEGORY","MAXCR",
|
||||
"SPELLID","SUCCESSOR","CRValue","USESPERDAY","MASTERFEAT","TARGETSELF","OrReqFeat0","OrReqFeat1","OrReqFeat2",
|
||||
"OrReqFeat3","OrReqFeat4","REQSKILL","ReqSkillMinRanks","REQSKILL2","ReqSkillMinRanks2","Constant","TOOLSCATEGORIES",
|
||||
"HostileFeat","MinLevel","MinLevelClass","MaxLevel","MinFortSave","PreReqEpic","ReqAction"
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"id": 589,
|
||||
"key": "feat:epic_skill_focus_concentration",
|
||||
"LABEL": "FEAT_EPIC_SKILL_FOCUS_CONCENTRATION",
|
||||
"REQSKILL": 1,
|
||||
"MASTERFEAT": 5,
|
||||
"Constant": "FEAT_EPIC_SKILL_FOCUS_CONCENTRATION"
|
||||
}
|
||||
]
|
||||
}`+"\n")
|
||||
|
||||
result, err := BuildNative(testProject(root), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildNative: %v", err)
|
||||
}
|
||||
got, err := os.ReadFile(filepath.Join(result.Output2DADir, "feat.2da"))
|
||||
if err != nil {
|
||||
t.Fatalf("read feat.2da: %v", err)
|
||||
}
|
||||
text := string(got)
|
||||
if !strings.Contains(text, "589\tFEAT_GREATER_SKILL_FOCUS_CONCENTRATION") {
|
||||
t.Fatalf("expected empty lockfile rebuild to preserve legacy row id 589, got:\n%s", text)
|
||||
}
|
||||
lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "feat", "lock.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("read feat lock: %v", err)
|
||||
}
|
||||
lockText := string(lockRaw)
|
||||
if !strings.Contains(lockText, `"feat:greater_skill_focus_concentration": 589`) {
|
||||
t.Fatalf("expected regenerated lock to assign greater key to row 589, got:\n%s", lockText)
|
||||
}
|
||||
if strings.Contains(lockText, "epic_skill_focus_concentration") {
|
||||
t.Fatalf("expected regenerated lock to prune legacy epic key, got:\n%s", lockText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFamilyExpansionRejectsInvalidLegacyFamilyKeys(t *testing.T) {
|
||||
base := map[string]any{
|
||||
"family": "greater_skill_focus",
|
||||
"family_key": "greater_skill_focus",
|
||||
"template": "masterfeats:greaterskillfocus",
|
||||
"child_source": map[string]any{"dataset": "skills", "predicate": "accessible"},
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
keys []any
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "self alias",
|
||||
keys: []any{"greater_skill_focus"},
|
||||
errMsg: "legacy_family_keys must not include family_key",
|
||||
},
|
||||
{
|
||||
name: "normalized duplicate",
|
||||
keys: []any{"epicskillfocus", "epic_skill_focus"},
|
||||
errMsg: "legacy_family_keys contains duplicate-equivalent keys",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
obj := map[string]any{}
|
||||
for key, value := range base {
|
||||
obj[key] = value
|
||||
}
|
||||
obj["legacy_family_keys"] = tc.keys
|
||||
_, err := parseFamilyExpansionSpec(tc.name+".json", obj)
|
||||
if err == nil || !strings.Contains(err.Error(), tc.errMsg) {
|
||||
t.Fatalf("%s: expected %q error, got %v", tc.name, tc.errMsg, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPruneRetiredGeneratedFeatTLKEntries(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "feat", "generated"))
|
||||
|
||||
Reference in New Issue
Block a user