package admin import ( "os" "path/filepath" "strings" "testing" ) // Regression: the raw file API must canonicalize repeated "/" and "data/" // prefixes, or "//players/..." reaches the real file while the classifiers // see a non-player path and skip the redaction / online refusal. func TestCanonicalDataRelPathStripsRepeatedPrefixes(t *testing.T) { cases := map[string]string{ "players/accounts/x.yaml": "players/accounts/x.yaml", "/players/accounts/x.yaml": "players/accounts/x.yaml", "//players/accounts/x.yaml": "players/accounts/x.yaml", "data/players/accounts/x.yaml": "players/accounts/x.yaml", "data/data/players/accounts/x.yaml": "players/accounts/x.yaml", "../etc/passwd": "etc/passwd", } for in, want := range cases { if got := canonicalDataRelPath(in); got != want { t.Errorf("canonicalDataRelPath(%q) = %q, want %q", in, got, want) } } } // Regression: a valid-but-non-mapping body used to pass through unchanged and // blank password_hash. It must now be refused, and a mapping body must keep // the on-disk hash. func TestRestoreAccountSecretRefusesNonMappingBody(t *testing.T) { dir := t.TempDir() full := filepath.Join(dir, "players", "accounts", "alice.yaml") if err := os.MkdirAll(filepath.Dir(full), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(full, []byte("name: alice\npassword_hash: secret\n"), 0600); err != nil { t.Fatal(err) } if _, err := restoreAccountSecret("players/accounts/alice.yaml", full, []byte("# comment only\n")); err == nil { t.Fatal("non-mapping body accepted; the password hash would be blanked") } out, err := restoreAccountSecret("players/accounts/alice.yaml", full, []byte("name: alice\npassword_hash: \"[redacted]\"\n")) if err != nil { t.Fatal(err) } if !strings.Contains(string(out), "secret") { t.Fatalf("password hash not preserved: %s", out) } } // Regression: a failed undo returned a nil change exactly like an empty // history, so the UI reported "Nothing to undo" for a real failure. func TestUndoFailureIsReportedDistinctly(t *testing.T) { dir := t.TempDir() us := NewUndoStack(dir) us.Push(ChangeDesc{ Description: "edit with a missing before-image", FilePath: filepath.Join(dir, "rooms", "1.yaml"), OldContent: nil, NewContent: []byte("id: 1\n"), }) if ch := us.Undo(); ch != nil { t.Fatal("undo with a missing before-image unexpectedly succeeded") } if err := us.LastError(); err == nil { t.Fatal("LastError = nil after a failed undo; the UI would report 'Nothing to undo'") } // A genuinely empty history is a clean nil change and nil error. us2 := NewUndoStack(t.TempDir()) if ch := us2.Undo(); ch != nil || us2.LastError() != nil { t.Fatal("empty history must be a clean nil change and nil error") } }