c48f95fe06
appbackup/userdata.go: EnsureUserdataDir (MkdirAll + explicit setgid Chmod 2775 +
chown gid 1000), UserdataSkeleton, EnsureUserdataSkeleton; linux chown/StatGID +
non-linux stubs. stackEnv injects USERDATA_PATH=<HDD_PATH>/userdata. Skeleton
pre-created on register + FileBrowser sync; deploy belt (composeExecCustomEnv on
'up') pre-creates every ${USERDATA_PATH} bind source. FileBrowser mounts userdata
(was appdata) — uid 1000 can now write into 2775 setgid. #8: migrate merge walk +
copyFile preserve source setgid+group so the convention survives MigrateAll.
Non-hollow tests incl. Linux setgid assertions + migration-preserve companion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.9 KiB
Go
66 lines
2.9 KiB
Go
package appbackup
|
|
|
|
import "testing"
|
|
|
|
// TestDeriveStackName_KnownCrossRef asserts M19: deriveStackName cross-references the deployed-stack set
|
|
// so a stack whose slug ends in a DB-role token (e.g. `my-cache`) is NOT misattributed by pure
|
|
// suffix-stripping. The `my-cache` case fails on the pre-fix code (which stripped it to `my`).
|
|
func TestDeriveStackName_KnownCrossRef(t *testing.T) {
|
|
known := map[string]bool{"romm": true, "my-cache": true, "paperless-ngx": true}
|
|
|
|
cases := []struct {
|
|
container string
|
|
want string
|
|
note string
|
|
}{
|
|
{"romm-postgres", "romm", "role suffix of a real stack → strip"},
|
|
{"my-cache", "my-cache", "container name IS a real stack → do NOT strip to 'my'"}, // pre-fix: "my"
|
|
{"my-cache-postgres", "my-cache", "strip role, result is a known stack"}, // pre-fix: "my-cache" (ok) — but via prefix here
|
|
{"paperless-ngx-postgres", "paperless-ngx", "multi-hyphen stack, role suffix"},
|
|
{"romm_postgres", "romm", "underscore-separated compose name → longest known prefix"},
|
|
{"romm-1", "romm", "compose numeric suffix → longest known prefix"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := deriveStackName(c.container, known); got != c.want {
|
|
t.Errorf("deriveStackName(%q, known) = %q, want %q (%s)", c.container, got, c.want, c.note)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDeriveStackName_LegacyFallback asserts the nil/empty-known fast path preserves the old behaviour
|
|
// (pure suffix-strip), so callers without a stack list (e.g. appexport) don't regress.
|
|
func TestDeriveStackName_LegacyFallback(t *testing.T) {
|
|
cases := []struct {
|
|
container string
|
|
want string
|
|
}{
|
|
{"romm-postgres", "romm"},
|
|
{"paperless-ngx-postgres", "paperless-ngx"},
|
|
{"my-cache", "my"}, // legacy strips the role-token suffix
|
|
{"unknown-db", "unknown"}, // legacy strip
|
|
{"standalone", "standalone"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := deriveStackName(c.container, nil); got != c.want {
|
|
t.Errorf("deriveStackName(%q, nil) = %q, want %q (legacy)", c.container, got, c.want)
|
|
}
|
|
// empty (non-nil) map must behave identically to nil
|
|
if got := deriveStackName(c.container, map[string]bool{}); got != c.want {
|
|
t.Errorf("deriveStackName(%q, {}) = %q, want %q (legacy)", c.container, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDeriveStackName_UnknownContainerFallsBack asserts that when the container matches no known stack at
|
|
// all, the result falls back to the suffix-strip candidate (no spurious prefix match).
|
|
func TestDeriveStackName_UnknownContainerFallsBack(t *testing.T) {
|
|
known := map[string]bool{"romm": true}
|
|
if got := deriveStackName("grafana-db", known); got != "grafana" {
|
|
t.Fatalf("deriveStackName(grafana-db, {romm}) = %q, want grafana (fallback strip)", got)
|
|
}
|
|
// must NOT match "romm" as a prefix of an unrelated name
|
|
if got := deriveStackName("rommother-db", known); got != "rommother" {
|
|
t.Fatalf("deriveStackName(rommother-db) = %q, want rommother (no false prefix match)", got)
|
|
}
|
|
}
|