faef8f8a91
Operator ruling 2026-10-12 (S27, 09 §3 decision 222). The operator approves the Hungarian text before release. A box whose first ingestion failed now serves one bilingual waiting page and retries every minute instead of the wizard. Own commit so the lead can hold it back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
|
)
|
|
|
|
// R-554 (`09` §3 decision 222): the setup wizard is deleted. A box whose first ingestion FAILED serves the
|
|
// waiting page — in both languages, on every path, with a passing health answer — and not a 404 or a
|
|
// wizard; it retries, and once the hub answers it returns the configured config and closes the page.
|
|
//
|
|
// RED-PROOF (REPORT): make WaitingHandler answer http.NotFound for "/" → "GET / = 404"; make the loop
|
|
// return the unchanged cfg on the first tick → "the box did not come up configured".
|
|
func TestWaitForConfig_ServesWaitingPageThenStarts(t *testing.T) {
|
|
dir := t.TempDir()
|
|
_, cfgPath := writeBootstrap(t, dir, goodBootstrapV2)
|
|
|
|
var calls atomic.Int32
|
|
pull := func(_, _, _ string) (string, error) {
|
|
if calls.Add(1) <= 5 {
|
|
return "", errors.New("hub answered 503") // permanent: no in-call retry, the loop retries
|
|
}
|
|
return hubYAML, nil
|
|
}
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
addr := "http://" + ln.Addr().String()
|
|
|
|
// The first ingestion failed: the caller holds an unconfigured config.
|
|
start := MaybeIngest(cfgPath, config.Default(), testLogger(), pull)
|
|
if !Unconfigured(start) {
|
|
t.Fatal("setup: the first ingestion should have failed")
|
|
}
|
|
|
|
done := make(chan *config.Config, 1)
|
|
go func() {
|
|
done <- WaitForConfig(context.Background(), ln, cfgPath, start, testLogger(), pull, 150*time.Millisecond, "0.308.0")
|
|
}()
|
|
|
|
get := func(path string) (int, string) {
|
|
t.Helper()
|
|
resp, err := http.Get(addr + path)
|
|
if err != nil {
|
|
t.Fatalf("GET %s: %v", path, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
b, _ := io.ReadAll(resp.Body)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
code, body := get("/")
|
|
if code != http.StatusOK {
|
|
t.Fatalf("GET / = %d — an unconfigured box must show the waiting page, not a 404", code)
|
|
}
|
|
for _, want := range []string{`lang="hu"`, "nem érte el a központot", `lang="en"`, "has not reached the central service yet", "support@felhom.eu"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the waiting page does not carry %q", want)
|
|
}
|
|
}
|
|
if strings.Contains(strings.ToLower(body), "varázsló") || strings.Contains(body, "8081") {
|
|
t.Error("the waiting page still points at the setup wizard")
|
|
}
|
|
if code, _ := get("/backups/restore"); code != http.StatusOK {
|
|
t.Errorf("GET /backups/restore = %d — every path shows the waiting page", code)
|
|
}
|
|
if code, body := get("/api/health"); code != http.StatusOK || !strings.Contains(body, `"waiting_for_hub":true`) {
|
|
t.Errorf("health while waiting: %d %s", code, body)
|
|
}
|
|
|
|
select {
|
|
case got := <-done:
|
|
if got == nil || Unconfigured(got) || got.Customer.ID != "cust-8200" {
|
|
t.Fatalf("the box did not come up configured: %+v", got)
|
|
}
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("WaitForConfig never returned although the hub answered")
|
|
}
|
|
if _, err := http.Get(addr + "/"); err == nil {
|
|
t.Error("the waiting page is still served after the box was configured")
|
|
}
|
|
}
|
|
|
|
// A stop signal while waiting returns nil (the process exits); it never pretends to be configured.
|
|
func TestWaitForConfig_StopReturnsNil(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if got := WaitForConfig(ctx, nil, "", config.Default(), testLogger(), nil, time.Hour, "x"); got != nil {
|
|
t.Fatalf("a stopped wait returned a config: %+v", got)
|
|
}
|
|
}
|