v0.151.0 — the Megosztás page stops reloading, and says how to connect
S-1: /sharing/status coerced idle->running on the PHASE channel, so the first poll of every steady-state page load reported a terminal job that never ran and the client's repaint-reload fired ~1.2s apart, forever. The coercion's real duty (liveness must never be contradicted) belongs to the 'running' LEVEL field beside it, and is now pinned by its own regression test. S-4 core: a terminal 'running' is served exactly once, so a REAL bring-up cannot re-arm the reload on the page it just caused. failed/needs_password/in-flight are never consumed. Unified async-job feedback stays the ROADMAP item. S-2/S-5: new connect card with the Windows form, the Mac form and the direct smb://<IP>, read from the SAMBA container's netns (the controller is on a docker bridge and would answer 172.x). Derived per render, cached nowhere - the address is a DHCP lease. Underivable => the line is omitted. sharing.html's <script> block is byte-identical to v0.150.0. Red-proofed three ways. 23/23 packages green.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
)
|
||||
|
||||
// v0.151.0 — the „Csatlakozás a megosztáshoz" card (DIAG-sharing-2026-07-20.md, S-2 + S-5).
|
||||
//
|
||||
// The page used to show only the configured NetBIOS name. When a network fails to resolve it the
|
||||
// customer has no fallback and guesses an address; the guess that produced the diagnosis was the
|
||||
// Proxmox HOST's IP, which never ran smbd. So the card must name both ways in — and the direct
|
||||
// address must be re-derived on every render, because the guest holds it by DHCP.
|
||||
|
||||
// connectCardServer renders /sharing with sharing enabled, the given server name, and a counted
|
||||
// address seam. Returns the page body and a pointer to the derivation call count.
|
||||
func connectCardServer(t *testing.T, serverName, addr string) (*Server, *int) {
|
||||
t.Helper()
|
||||
s := testPageServer(t)
|
||||
if err := s.settings.SetSMBServerName(serverName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.settings.SetSMBEnabled(true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
calls := 0
|
||||
s.sambaAddrFn = func() string { calls++; return addr }
|
||||
return s, &calls
|
||||
}
|
||||
|
||||
// Scenario D — both name forms and the direct address are on the page, and the address came from a
|
||||
// derivation performed on THIS render.
|
||||
func TestSharingConnectCard_ShowsNameAndDirectAddress(t *testing.T) {
|
||||
s, calls := connectCardServer(t, "FELHOM", "192.0.2.10")
|
||||
|
||||
body := getPage(t, s, "/sharing").Body.String()
|
||||
for _, want := range []string{
|
||||
"Csatlakoz", // the card heading (ASCII-safe substring)
|
||||
`\\FELHOM`, // Windows form
|
||||
"smb://FELHOM", // Mac form
|
||||
"smb://192.0.2.10", // the fallback that did not exist before
|
||||
} {
|
||||
if !strings.Contains(body, want) {
|
||||
t.Errorf("page missing %q", want)
|
||||
}
|
||||
}
|
||||
if *calls == 0 {
|
||||
t.Error("address seam never called — the page cannot be showing a freshly derived address")
|
||||
}
|
||||
}
|
||||
|
||||
// S-5: derived per render, never memoized. A DHCP lease that is read once and reused is an address
|
||||
// that eventually sends customers to someone else's machine.
|
||||
func TestSharingConnectCard_AddressDerivedFreshEveryRender(t *testing.T) {
|
||||
s, calls := connectCardServer(t, "FELHOM", "192.0.2.10")
|
||||
|
||||
getPage(t, s, "/sharing")
|
||||
afterFirst := *calls
|
||||
if afterFirst != 1 {
|
||||
t.Fatalf("first render: %d derivations, want exactly 1", afterFirst)
|
||||
}
|
||||
getPage(t, s, "/sharing")
|
||||
if *calls != 2 {
|
||||
t.Errorf("second render: total derivations = %d, want 2 — the address is being cached", *calls)
|
||||
}
|
||||
|
||||
// And a CHANGED address must reach the page, which a cache would prevent.
|
||||
s.sambaAddrFn = func() string { *calls++; return "198.51.100.7" }
|
||||
body := getPage(t, s, "/sharing").Body.String()
|
||||
if !strings.Contains(body, "smb://198.51.100.7") {
|
||||
t.Error("the new address did not render — a stale value survived the re-derivation")
|
||||
}
|
||||
if strings.Contains(body, "192.0.2.10") {
|
||||
t.Error("the OLD address is still on the page")
|
||||
}
|
||||
}
|
||||
|
||||
// Fail-quiet: no derivable address omits the fallback lines but keeps the name lines. An
|
||||
// address-less page is strictly better than a page that fails, or one that prints a wrong address.
|
||||
func TestSharingConnectCard_NoAddressOmitsTheLine(t *testing.T) {
|
||||
s, _ := connectCardServer(t, "FELHOM", "")
|
||||
|
||||
body := getPage(t, s, "/sharing").Body.String()
|
||||
if !strings.Contains(body, "smb://FELHOM") {
|
||||
t.Error("name lines must remain when no address can be derived")
|
||||
}
|
||||
if strings.Contains(body, "smb://\n") || strings.Contains(body, "smb://<") || strings.Contains(body, "smb://</span>") {
|
||||
t.Error("an empty direct-address line rendered")
|
||||
}
|
||||
// The „változhat" caveat belongs to the address lines and must go with them.
|
||||
if strings.Contains(body, "zvetlen c") { // „közvetlen cím", ASCII-safe substring
|
||||
t.Error("the direct-address block rendered without an address")
|
||||
}
|
||||
}
|
||||
|
||||
// The name is CONFIGURABLE — nothing may hardcode FELHOM.
|
||||
func TestSharingConnectCard_UsesConfiguredName(t *testing.T) {
|
||||
s, _ := connectCardServer(t, "OTTHON", "192.0.2.10")
|
||||
|
||||
body := getPage(t, s, "/sharing").Body.String()
|
||||
if !strings.Contains(body, "smb://OTTHON") || !strings.Contains(body, `\\OTTHON`) {
|
||||
t.Error("the configured server name is not on the page")
|
||||
}
|
||||
if strings.Contains(body, "FELHOM") {
|
||||
t.Error("FELHOM rendered while the configured name is OTTHON — a hardcoded default leaked")
|
||||
}
|
||||
}
|
||||
|
||||
// The whole card is gated on the feature being on: a box with sharing off must not be told how to
|
||||
// connect to a service that is not running.
|
||||
func TestSharingConnectCard_AbsentWhenSharingDisabled(t *testing.T) {
|
||||
s := testPageServer(t)
|
||||
if err := s.settings.SetSMBServerName("FELHOM"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
calls := 0
|
||||
s.sambaAddrFn = func() string { calls++; return "192.0.2.10" }
|
||||
|
||||
body := getPage(t, s, "/sharing").Body.String()
|
||||
if strings.Contains(body, "Csatlakoz") {
|
||||
t.Error("the connect card rendered with sharing disabled")
|
||||
}
|
||||
if strings.Contains(body, "smb://192.0.2.10") {
|
||||
t.Error("a direct address rendered with sharing disabled")
|
||||
}
|
||||
if calls != 0 {
|
||||
t.Errorf("address derived %d times with sharing off — a needless docker exec per render", calls)
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against the settings layer being bypassed: the page data reads the EFFECTIVE name, so an
|
||||
// unset name still renders the product default rather than an empty `smb://`.
|
||||
func TestSharingConnectCard_UnsetNameFallsBackToEffective(t *testing.T) {
|
||||
s := testPageServer(t)
|
||||
if err := s.settings.SetSMBEnabled(true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.sambaAddrFn = func() string { return "192.0.2.10" }
|
||||
|
||||
def := settings.SMBSettings{}
|
||||
want := def.EffectiveServerName()
|
||||
if want == "" {
|
||||
t.Skip("no product default server name to assert")
|
||||
}
|
||||
if body := getPage(t, s, "/sharing").Body.String(); !strings.Contains(body, "smb://"+want) {
|
||||
t.Errorf("effective default name %q not rendered", want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user