149467c795
gates / gates (push) Successful in 23s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
106 lines
4.8 KiB
Go
106 lines
4.8 KiB
Go
package web
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
|
)
|
|
|
|
// v0.281.0 — the household's "Done" press asks the app first (Part A), and the sign-up block's answer (decision 47).
|
|
|
|
func pressDone(s *Server) *httptest.ResponseRecorder {
|
|
r := httptest.NewRequest(http.MethodPost, "/apps/gapp/setup-gate/open", nil)
|
|
w := httptest.NewRecorder()
|
|
s.appSetupGateOpenHandler(w, r, "gapp")
|
|
return w
|
|
}
|
|
|
|
// The uptime-kuma shape of 2026-09-29: a press BEFORE the setup. With a probe it is refused while the app says
|
|
// "not done", and when the probe cannot be read; it opens once the app says "done".
|
|
// COMPANION RED-PROOF: remove the SetupGateProbe check in appSetupGateOpenHandler → the first press opens the app.
|
|
func TestSetupGateButton_RefusedWhileTheAppSaysNotDone(t *testing.T) {
|
|
s := gateHarness(t)
|
|
app := filepath.Join(s.cfg.Paths.StacksDir, "gapp")
|
|
if err := os.WriteFile(filepath.Join(app, ".felhom.yml"), []byte("display_name: Gated App\nslug: gapp\nsetup_gate: true\nsetup_done_probe:\n url: http://gapp:80/status\n field: isInit\n done: \"true\"\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.stackMgr.ScanStacks(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
answer, probeErr := `{"isInit":false}`, error(nil)
|
|
defer stacks.SetSetupGateProbeGetForTest(func(string) ([]byte, error) { return []byte(answer), probeErr })()
|
|
if w := pressDone(s); w.Code != http.StatusConflict || !strings.Contains(w.Body.String(), "még nincs kész") {
|
|
t.Fatalf("press before the setup: %d %s — want 409 and the sentence", w.Code, w.Body.String())
|
|
}
|
|
if _, closed, _ := s.stackMgr.SetupGateHost("gapp.example.hu"); !closed {
|
|
t.Fatal("the press before the setup opened the gate")
|
|
}
|
|
answer, probeErr = `{"isInit":true}`, errors.New("connection refused")
|
|
if w := pressDone(s); w.Code != http.StatusConflict {
|
|
t.Fatalf("an unreadable status: %d — want 409 (fail closed)", w.Code)
|
|
}
|
|
probeErr = nil
|
|
if w := pressDone(s); w.Code != http.StatusOK {
|
|
t.Fatalf("the app says done: %d %s", w.Code, w.Body.String())
|
|
}
|
|
if _, closed, _ := s.stackMgr.SetupGateHost("gapp.example.hu"); closed {
|
|
t.Fatal("still closed after a press the app agreed with")
|
|
}
|
|
}
|
|
|
|
// The sign-up address answers "closed": a page for a browser, 403 JSON for anything else.
|
|
func TestSignupClosed_TheAnswer(t *testing.T) {
|
|
s := gateHarness(t)
|
|
r := httptest.NewRequest(http.MethodGet, "https://gapp.example.hu"+signupClosedPath, nil)
|
|
r.Host = "gapp.example.hu"
|
|
r.Header.Set("Accept", "text/html")
|
|
w := httptest.NewRecorder()
|
|
s.ServeSignupClosed(w, r)
|
|
if w.Code != http.StatusForbidden || !strings.Contains(w.Body.String(), "nem lehet regisztr") {
|
|
t.Fatalf("browser: %d\n%s", w.Code, w.Body.String())
|
|
}
|
|
r = httptest.NewRequest(http.MethodPost, signupClosedPath, strings.NewReader(`{"username":"x"}`))
|
|
w = httptest.NewRecorder()
|
|
s.ServeSignupClosed(w, r)
|
|
if w.Code != http.StatusForbidden || !strings.Contains(w.Body.String(), "sign-up is closed") {
|
|
t.Fatalf("API: %d %s", w.Code, w.Body.String())
|
|
}
|
|
// No block on this app (it is still gated): the window press says so.
|
|
req := httptest.NewRequest(http.MethodPost, "/apps/gapp/signup-window", nil)
|
|
rec := httptest.NewRecorder()
|
|
s.appSignupWindowHandler(rec, req, "gapp")
|
|
if rec.Code != http.StatusConflict {
|
|
t.Fatalf("window on a gated app: %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
// The page: a probe app now has the press too; an app without one asks first, in the page (felhomConfirm); the
|
|
// sign-up card with the app's own "how to add a family member" and the window button.
|
|
func TestSetupGatePage_ConfirmAndSignupCard(t *testing.T) {
|
|
noProbe := renderAppInfoWith(t, map[string]interface{}{"SetupGateClosed": true})
|
|
if !strings.Contains(noProbe, "felhomConfirm(b, 'Csak akkor nyomd meg") {
|
|
t.Fatal("no-probe press without the in-page confirm")
|
|
}
|
|
probe := renderAppInfoWith(t, map[string]interface{}{"SetupGateClosed": true, "SetupGateHasProbe": true})
|
|
if !strings.Contains(probe, "/apps/gapp/setup-gate/open") || strings.Contains(probe, "felhomConfirm(b, 'Csak akkor") {
|
|
t.Fatal("probe app: want the press, without the confirm")
|
|
}
|
|
closed := renderAppInfoWith(t, map[string]interface{}{"SignupClosed": true, "AppInfo": stacks.AppInfo{Tagline: "t", AddPeople: "Beállítások → Felhasználók → Meghívás."}})
|
|
if !strings.Contains(closed, `id="signup-card"`) || !strings.Contains(closed, "Meghívás") || !strings.Contains(closed, "/apps/gapp/signup-window") {
|
|
t.Fatal("sign-up card: text, how-to or window button missing")
|
|
}
|
|
open := renderAppInfoWith(t, map[string]interface{}{"SignupOpenUntil": "14:05"})
|
|
if !strings.Contains(open, "14:05") || strings.Contains(open, "/apps/gapp/signup-window") {
|
|
t.Fatal("open window: want the end time, no button")
|
|
}
|
|
if none := renderAppInfoWith(t, nil); strings.Contains(none, `id="signup-card"`) {
|
|
t.Fatal("a sign-up card on an app with no block")
|
|
}
|
|
}
|