5b8656974b
The command form ran only when the lock was SET, so a database switch closed by
`command` stayed closed through the household's 15-minute window. New twin
fields `open_command` + `open_success` (same service/user/args_env and the same
argv-safe expansion and success-marker rules as `command`/`success`).
- liftNativeLock (the window, via OpenSignupWindow → goNativeLock(false)): marks
the gate record native_lock "opening" BEFORE anything opens, lifts the env,
runs open_command; only full success records "lifted". A failed open closes
the switch again at once and records after_setup {ok: false, step: open}; the
app page shows its own line (app_info.signup_native_open_failed).
- The close: reconcileSignupBlocks (every 20 s and at controller start) runs
`command` for any non-"applied" state once no window runs. A failed close
after a window is logged ERROR ("may still be OPEN past the household's
window") and retried every nativeLockOpenRetry (2 min) instead of 30.
- After a successful app update, verifyAndConclude → markNativeLockForReapply
sets native_lock "" so the loop closes the switch again.
- A template with `command` but no `open_command` keeps today's window (env
only) and logs once per app that its own switch cannot be reopened.
Tests: internal/stacks/after_setup_r717_test.go (7), web render + parity case.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
41 lines
2.0 KiB
Go
41 lines
2.0 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// v0.282.0 (decision 49) — "close sign-up now": the card on an app installed before the rule, and the refusal on
|
|
// an app with nothing to close.
|
|
func TestCloseSignupPage_TheCardAndItsPress(t *testing.T) {
|
|
on := renderAppInfoWith(t, map[string]interface{}{"CloseSignupOffered": true})
|
|
if !strings.Contains(on, `id="close-signup-card"`) || !strings.Contains(on, "/apps/gapp/close-signup") || !strings.Contains(on, "Zárd le a regisztrációt most") {
|
|
t.Fatal("offered, but the card, its sentence or its press is missing")
|
|
}
|
|
if off := renderAppInfoWith(t, nil); strings.Contains(off, `id="close-signup-card"`) {
|
|
t.Fatal("the card shows on an app with nothing to close")
|
|
}
|
|
native := renderAppInfoWith(t, map[string]interface{}{"SignupClosed": true, "SignupNative": true, "SignupNativeFailed": true})
|
|
if !strings.Contains(native, "újraindul") || !strings.Contains(native, `id="signup-native-failed"`) {
|
|
t.Fatal("the window's restart line or the failed-switch line is missing")
|
|
}
|
|
// R-717: a failed OPEN says so in its own words — never "could not be closed".
|
|
openFailed := renderAppInfoWith(t, map[string]interface{}{"SignupClosed": true, "SignupNativeOpenFailed": true})
|
|
if !strings.Contains(openFailed, `id="signup-native-open-failed"`) || !strings.Contains(openFailed, "megnyitni") || strings.Contains(openFailed, `id="signup-native-failed"`) {
|
|
t.Fatal("the failed-open line is missing, or the failed-close line shows instead")
|
|
}
|
|
}
|
|
|
|
// COMPANION RED-PROOF: skip the CloseSignupNow error mapping → the gated app answers 500, not 409.
|
|
func TestCloseSignup_RefusedWhenNothingToClose(t *testing.T) {
|
|
s := gateHarness(t) // gapp: gated (closed), no signup_block
|
|
r := httptest.NewRequest(http.MethodPost, "/apps/gapp/close-signup", nil)
|
|
w := httptest.NewRecorder()
|
|
s.appCloseSignupHandler(w, r, "gapp")
|
|
if w.Code != http.StatusConflict {
|
|
t.Fatalf("close sign-up on an app with nothing to close: %d", w.Code)
|
|
}
|
|
}
|