kept second-drive copies (D25: R-1175, R-1181, R-1179); .fab import keeps the family gate (R-1191); stale restore temp (R-1188 c); the overcommit sentence made true (R-1192); escrow before the floor (R-1176); unreleased
gates / gates (push) Successful in 1m20s
gates / gates (push) Successful in 1m20s
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
package stacks
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// R-1191 (proven live on 9202, felhom.eu audits/dataloss-hunt-2026-10-20/C3-R1187e-fab-family-gate/): a `.fab` import of
|
||||
// a family-only app must come back WITH its door — the gate file before the first start, the record carrying it, and
|
||||
// the reconcile loop keeping it. The three catalog family apps' stanzas (Glance, MeTube: family_gate only; Grimmory:
|
||||
// with exceptions) are the fixtures. COMPANION RED-PROOF: make PrepareImportedApp skip prepareFamilyGate → each case
|
||||
// fails „no door after the import"; and the old bare record (SaveImportedAppConfig without carryLifeRecords) fails the
|
||||
// installed-app case.
|
||||
func TestR1191_AnImportedFamilyAppKeepsItsDoor(t *testing.T) {
|
||||
base := "deploy_fields:\n - env_var: DOMAIN\n type: domain\n - env_var: SUBDOMAIN\n type: subdomain\n default: gapp\n"
|
||||
for name, yml := range map[string]string{
|
||||
"glance": "display_name: Glance\nfamily_gate: true\n" + base,
|
||||
"metube": "display_name: MeTube\nfamily_gate: true\n" + base,
|
||||
"grimmory": "display_name: Grimmory\nfamily_gate: true\nfamily_gate_except: [\"/api/v1/opds\", \"/api/v2/opds\", \"/api/kobo\", \"/api/koreader\", \"/komga/api\"]\n" + base,
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
m := gateManager(t, yml)
|
||||
dir := filepath.Join(m.cfg.Paths.StacksDir, "gapp")
|
||||
env := map[string]string{"DOMAIN": "example.hu", "SUBDOMAIN": "gapp"}
|
||||
// a REMOVED app imported from its bundle (no record before)
|
||||
fresh, err := m.SaveImportedAppConfig(dir, env)
|
||||
must(t, err)
|
||||
if !fresh {
|
||||
t.Fatal("an app with no record must import as fresh")
|
||||
}
|
||||
must(t, m.PrepareImportedApp(dir, env, fresh))
|
||||
if _, err := os.Stat(m.familyGatePath("gapp")); err != nil {
|
||||
t.Fatal("no door after the import — the app would start open to anyone (R-1191)")
|
||||
}
|
||||
cfg := LoadAppConfig(dir)
|
||||
if cfg == nil || cfg.FamilyGate == nil || cfg.DesiredState != DesiredStateRunning {
|
||||
t.Fatalf("record after the import: %+v", cfg)
|
||||
}
|
||||
m.SetupGateTick() // the reconcile that removed the file of a gate-less record
|
||||
if _, err := os.Stat(m.familyGatePath("gapp")); err != nil {
|
||||
t.Fatal("the reconcile loop removed the imported app's door")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// An INSTALLED app re-imported over itself keeps the records it had (the gate's „since", an update's history) — the
|
||||
// import never replaces them with a bare record.
|
||||
func TestR1191_AnImportOverAnInstalledAppKeepsItsRecords(t *testing.T) {
|
||||
m := gateManager(t, familyYml)
|
||||
dir := filepath.Join(m.cfg.Paths.StacksDir, "gapp")
|
||||
env := map[string]string{"DOMAIN": "example.hu", "SUBDOMAIN": "gapp"}
|
||||
prior := &AppConfig{Deployed: true, Env: env, FamilyGate: &FamilyGateRecord{Since: "2026-10-01T00:00:00Z", Hosts: []string{"gapp.example.hu"}},
|
||||
PinnedImages: map[string]string{"gapp": "example/gapp:1.2.3"}}
|
||||
must(t, SaveAppConfig(dir, prior, m.encKey, nil))
|
||||
fresh, err := m.SaveImportedAppConfig(dir, env)
|
||||
must(t, err)
|
||||
if fresh {
|
||||
t.Fatal("an installed app must not import as fresh")
|
||||
}
|
||||
must(t, m.PrepareImportedApp(dir, env, fresh))
|
||||
cfg := LoadAppConfig(dir)
|
||||
if cfg == nil || cfg.FamilyGate == nil || cfg.FamilyGate.Since != "2026-10-01T00:00:00Z" || cfg.PinnedImages["gapp"] != "example/gapp:1.2.3" {
|
||||
t.Fatalf("the installed app's records were not kept: %+v", cfg)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user