Files
felhom-controller/controller/internal/web/sharing_connect_card_test.go
T
admin 37e12c82a7 v0.152.0 + felhom-samba 1.1.0 — mDNS for macOS, and the card stops offering a dead form
Capture on the box disproved the first theory: macOS DOES send a correct NBNS
query for <NAME><20> and nmbd DOES answer it correctly in 140us (flags 0x8580,
RCODE=0, right address) - macOS just never acts on it. NetBIOS there feeds
legacy browsing, not smb:// URL resolution, so the bare name can never work on
a Mac and nmbd was never the broken part.

felhom-samba 1.1.0 adds avahi + dbus, with avahi-daemon.conf and the _smb._tcp
service file templated from FELHOM_SERVER_NAME so a rename re-advertises. Both
daemons are non-fatal on failure - a discovery gap must not become an outage.

v0.151.0's card offered smb://<NAME> for Mac, which is exactly the dead form;
now smb://<NAME>.local. Windows keeps the flat \\<NAME>, which nmbd serves
correctly. Red-proofed both directions.

NOT claimed: Finder-sidebar discovery - published and answering on the wire,
but not observed working on the test Mac. Recorded OPEN.

TestRenderSambaCompose pinned the literal 1.0.0 tag, so an image bump read as a
renderer regression; now derives from SambaImage and asserts non-:latest.
2026-07-20 13:38:07 +02:00

182 lines
7.4 KiB
Go

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://</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.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://<NEV>.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</span>` cannot satisfy this by accident.
if strings.Contains(body, "smb://"+name+"</span>") {
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)
}
}
}