package web import ( "html" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" ) // v0.271.0 — the per-box switch for AUTOMATIC app updates (`09` §3 decision 12: ON by default) on the // settings page, in both languages, through the REAL router, handler and template; and the saved choice // read back from settings.json's accessor the leg reads. // // COMPANION RED-PROOF (REPORT.md): make GetAppUpdateUnattended return false for an absent key — the // first assertion fails at "a box that never saved the switch must show it ON". func TestAppUpdateSwitch_DefaultOnAndSaved(t *testing.T) { s := testPageServer(t) hu := html.UnescapeString(getPage(t, s, "/settings").Body.String()) if !strings.Contains(hu, `data-app-update-unattended="true"`) || !strings.Contains(hu, "checked data-app-update-unattended") { t.Fatal("a box that never saved the switch must show it ON (decision 12)") } if !strings.Contains(hu, "Alkalmazások automatikus frissítése") || !strings.Contains(hu, "kipróbált lépéssel") { t.Error("the Hungarian page must carry the card's title and its one sentence") } en := html.UnescapeString(getPage(t, s, "/settings?lang=en").Body.String()) if !strings.Contains(en, "Automatic app updates") || !strings.Contains(en, "only with a tested step") { t.Error("the English page must carry the card's title and its one sentence") } if strings.Contains(en, "kipróbált lépéssel") { t.Error("the Hungarian sentence must be GONE from the English page") } // OFF: an unchecked box posts no field rec := postForm(t, s, "/settings/app-update", url.Values{}) if rec.Code != 200 { t.Fatalf("POST off = %d", rec.Code) } if s.settings.GetAppUpdateUnattended() { t.Fatal("saving with the box unchecked must turn the switch OFF") } body := html.UnescapeString(rec.Body.String()) if !strings.Contains(body, "kikapcsolva") || !strings.Contains(body, `data-app-update-unattended="false"`) { t.Error("the page after saving OFF must say so and show the box unchecked") } // ON again postForm(t, s, "/settings/app-update", url.Values{"app_update_unattended": {"on"}}) if !s.settings.GetAppUpdateUnattended() { t.Fatal("saving with the box checked must turn the switch back ON") } } // TestAutoUpdateLine_PageSaysItOnce — a successful automatic step is told on the app page only, in the // request's language, and only while the app still runs what that step installed. // // COMPANION RED-PROOF (REPORT.md): drop the AutoUpdateLine block from appDetailHandler — this fails at // "the English page must carry the automatic-update line". func TestAutoUpdateLine_PageSaysItOnce(t *testing.T) { s, _ := credsHarness(t) sd := filepath.Join(s.cfg.Paths.StacksDir, "crafty") write := func(pin string) { app := "deployed: true\npinned_images:\n web: " + pin + "\nlast_auto_update:\n at: \"2026-09-25T02:21:00Z\"\n outcome: done\n to:\n web: crafty:2.0.0\n" if err := os.WriteFile(filepath.Join(sd, "app.yaml"), []byte(app), 0o644); err != nil { t.Fatal(err) } _ = s.stackMgr.ScanStacks() } s.loadTemplates() render := func(lang string) string { rr := httptest.NewRecorder() s.appDetailHandler(rr, httptest.NewRequest("GET", "/apps/crafty?lang="+lang, nil), "crafty") return html.UnescapeString(rr.Body.String()) } write("crafty:2.0.0") en, hu := render("en"), render("hu") const enLine, huLine = "Automatic update at", "Automatikus frissítés" if !strings.Contains(en, enLine) || !strings.Contains(en, "— done.") { t.Fatalf("the English page must carry the automatic-update line") } if !strings.Contains(hu, huLine+" ") || !strings.Contains(hu, "— sikeres.") || strings.Contains(hu, enLine) { t.Fatalf("the Hungarian page must carry its own line and not the English one") } if !strings.Contains(hu, `data-auto-update="done"`) { t.Error("the line must render in its own marked alert") } // a later manual update moved the pin: the line is history and goes write("crafty:2.1.0") if strings.Contains(render("en"), enLine) { t.Error("the line must go once the app no longer runs what the automatic step installed") } }