1e216d3468
gates / gates (push) Successful in 29s
An installed app the household never closed keeps what it had (decision 49). Red-proof RP-D2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
65 lines
3.3 KiB
Go
65 lines
3.3 KiB
Go
package stacks
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// R-773 — after a REMOVE + restore, an app's sign-up block (decision 47) was gone: the removed app had no app.yaml, so
|
|
// nothing carried the lock record, and the restore brought sign-up back open (measured 2026-10-01 on 9202, Karakeep:
|
|
// /signup 403 before, 200 after). Through the PRODUCTION restore write (PersistUnitRedeployConfig).
|
|
|
|
// The removed app comes back with the lock record AND its block — written before anything starts.
|
|
// COMPANION RED-PROOF: drop the restoreSignupLock call in PersistUnitRedeployConfig → both assertions fail.
|
|
func TestR773_ARemovedAppComesBackWithSignupClosed(t *testing.T) {
|
|
m := gateManager(t, signupYml)
|
|
dir := filepath.Join(m.cfg.Paths.StacksDir, "gapp")
|
|
if LoadAppConfig(dir) != nil {
|
|
t.Fatal("precondition: the removed app has no app.yaml")
|
|
}
|
|
must(t, m.PersistUnitRedeployConfig("gapp", map[string]string{"DOMAIN": "example.hu", "SUBDOMAIN": "gapp"}))
|
|
got := LoadAppConfig(dir)
|
|
if got == nil || got.SetupGate == nil || got.SetupGate.State != SetupGateOpen || got.SetupGate.OpenedBy != SetupGateByRestore {
|
|
t.Fatalf("the restore must record the sign-up lock (an OPEN gate record, by restore), got %+v", got)
|
|
}
|
|
b, err := os.ReadFile(m.signupBlockPath("gapp"))
|
|
if err != nil || !strings.Contains(string(b), "Host(`gapp.example.hu`)") || !strings.Contains(string(b), "PathPrefix(`/signup`)") {
|
|
t.Fatalf("the block must stand before the app starts: %v\n%s", err, b)
|
|
}
|
|
if blocked, _ := m.SignupBlocked("gapp"); !blocked {
|
|
t.Fatal("SignupBlocked says open after the restore")
|
|
}
|
|
// and the loop keeps it (the record says it is wanted)
|
|
must(t, os.Remove(m.signupBlockPath("gapp")))
|
|
m.SetupGateTick()
|
|
if _, err := os.Stat(m.signupBlockPath("gapp")); err != nil {
|
|
t.Fatal("the loop did not put the restore's block back")
|
|
}
|
|
}
|
|
|
|
// An app that was NOT removed keeps exactly what it had: a restore never closes sign-up the household left open on an
|
|
// app installed before decision 47 (decision 49 — the box never applies the lock by itself to an installed app).
|
|
func TestR773_AnInstalledAppWithoutALockGetsNone(t *testing.T) {
|
|
m := gateManager(t, signupYml)
|
|
dir := filepath.Join(m.cfg.Paths.StacksDir, "gapp")
|
|
must(t, SaveAppConfig(dir, &AppConfig{Deployed: true, Env: map[string]string{"DOMAIN": "example.hu", "SUBDOMAIN": "gapp"}}, m.encKey, nil))
|
|
must(t, m.PersistUnitRedeployConfig("gapp", map[string]string{"DOMAIN": "example.hu", "SUBDOMAIN": "gapp"}))
|
|
if got := LoadAppConfig(dir); got.SetupGate != nil {
|
|
t.Fatalf("an installed app without a lock must not get one from a restore, got %+v", got.SetupGate)
|
|
}
|
|
if _, err := os.Stat(m.signupBlockPath("gapp")); !os.IsNotExist(err) {
|
|
t.Fatal("no block may be written for it")
|
|
}
|
|
}
|
|
|
|
// A template with no sign-up lock gets no record from a restore.
|
|
func TestR773_NoLockInTheTemplateNoRecord(t *testing.T) {
|
|
m := gateManager(t, "display_name: Plain\ndeploy_fields:\n - env_var: DOMAIN\n type: domain\n - env_var: SUBDOMAIN\n type: subdomain\n default: gapp\n")
|
|
must(t, m.PersistUnitRedeployConfig("gapp", map[string]string{"DOMAIN": "example.hu", "SUBDOMAIN": "gapp"}))
|
|
if got := LoadAppConfig(filepath.Join(m.cfg.Paths.StacksDir, "gapp")); got.SetupGate != nil {
|
|
t.Fatalf("no lock in the template → no record, got %+v", got.SetupGate)
|
|
}
|
|
}
|