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.local", // Mac form — .local is load-bearing, see the regression test below "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.local") { 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://") { 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.local") || !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+".local") { t.Errorf("effective default name %q not rendered", want) } } // REGRESSION (v0.152.0) — the Mac line must offer `smb://.local`, never the bare name. // // v0.151.0 shipped `smb://FELHOM`, which is the ONE form that provably cannot work on a Mac. // Captured live 2026-07-20: macOS broadcasts a correct NBNS query for FELHOM<20>, the box answers // in 140us with a textbook positive response (flags 0x8580, RCODE=0, the right address), and macOS // never opens a TCP connection — NetBIOS feeds legacy browsing there, not smb:// URL resolution. // Sixteen seconds later the same Mac connected via `smb://FELHOM.local` on the first try. So this // is not a choice between two working spellings: the bare form is a dead end the page was actively // sending customers down. func TestSharingConnectCard_MacLineIsDotLocalNotBareName(t *testing.T) { for _, name := range []string{"FELHOM", "OTTHON"} { s, _ := connectCardServer(t, name, "192.0.2.10") body := getPage(t, s, "/sharing").Body.String() if !strings.Contains(body, "smb://"+name+".local") { t.Errorf("%s: the Mac line must offer smb://%s.local", name, name) } // Anchored on the closing tag, so `smb://FELHOM.local` cannot satisfy this by accident. if strings.Contains(body, "smb://"+name+"") { t.Errorf("%s: the page still offers the bare smb://%s — macOS cannot resolve it", name, name) } // Windows is served by nmbd and is proven working: fixing the Mac must not touch it. if !strings.Contains(body, `\\`+name) { t.Errorf("%s: the Windows flat-name form went missing", name) } if strings.Contains(body, `\\`+name+".local") { t.Errorf("%s: the Windows line gained .local — that is the Mac's mechanism, not nmbd's", name) } } }