Files
felhom-controller/controller/internal/infra/samba_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

111 lines
3.7 KiB
Go

package infra
import (
"strings"
"testing"
)
func sampleSambaData() SambaData {
return SambaData{
ServerName: "FELHOM",
UID: 1000,
Shares: []SambaShareRender{
{Name: "dokumentumok", Path: "/mnt/felhom-drives/scratch1/shares/dokumentumok", ReadOnly: false},
{Name: "filmek", Path: "/mnt/felhom-drives/media/filmek", ReadOnly: true},
},
}
}
// Exact golden for smb.conf (§10): the hardened global block + one section per share, in registry
// order, with the force-user block. A drift in any managed directive fails here.
func TestRenderSambaConfig_Golden(t *testing.T) {
const want = `# Samba (LAN network-sharing) — managed by felhom-controller (R-7).
# WARNING: auto-generated. Manual edits are overwritten on the next share change.
[global]
workgroup = WORKGROUP
server string = Felhom hálózati megosztás
netbios name = FELHOM
security = user
map to guest = never
server min protocol = SMB2
disable netbios = no
bind interfaces only = yes
interfaces = lo eth0
smb ports = 445
load printers = no
printing = bsd
printcap name = /dev/null
disable spoolss = yes
[dokumentumok]
path = /mnt/felhom-drives/scratch1/shares/dokumentumok
read only = no
valid users = felhom
force user = felhom
force group = felhom
create mask = 0644
directory mask = 0755
[filmek]
path = /mnt/felhom-drives/media/filmek
read only = yes
valid users = felhom
force user = felhom
force group = felhom
create mask = 0644
directory mask = 0755
`
got := RenderSambaConfig(sampleSambaData())
if got != want {
t.Errorf("smb.conf golden mismatch.\n--- got ---\n%s\n--- want ---\n%s", got, want)
}
}
func TestRenderSambaConfig_NoShares(t *testing.T) {
got := RenderSambaConfig(SambaData{ServerName: "OTTHON", UID: 1000})
if !strings.Contains(got, "netbios name = OTTHON") {
t.Error("server name not rendered")
}
if strings.Count(got, "[") != 1 { // only [global]
t.Errorf("no shares should mean only the [global] section:\n%s", got)
}
}
// Compose: host network + pinned image + config :ro + passdb volume, and — the Scenario B core — a
// read-only share gets a :ro bind while a writable one does not. Share binds are sorted (filmek<scratch1).
func TestRenderSambaCompose(t *testing.T) {
got := RenderSambaCompose(sampleSambaData())
for _, want := range []string{
"network_mode: host",
"image: " + SambaImage,
"- ./smb.conf:/etc/samba/smb.conf:ro",
"- samba-passdb:/var/lib/samba",
"FELHOM_SERVER_NAME=FELHOM",
"FELHOM_UID=1000",
} {
if !strings.Contains(got, want) {
t.Errorf("compose missing %q\n%s", want, got)
}
}
// The tag must be EXPLICIT — a floating :latest breaks reproducible pins and is the thing the
// constant exists to prevent. Asserted on the constant's shape, so an image bump is a one-line
// change in infra.go and never a test edit.
if strings.HasSuffix(SambaImage, ":latest") || !strings.Contains(SambaImage, ":") {
t.Errorf("SambaImage %q must carry an explicit non-latest tag", SambaImage)
}
// Read-only share → :ro bind (red-proof target: drop the :ro in the renderer and this fails).
roBind := "- /mnt/felhom-drives/media/filmek:/mnt/felhom-drives/media/filmek:ro"
if !strings.Contains(got, roBind) {
t.Errorf("read-only share must get a :ro bind, missing %q\n%s", roBind, got)
}
// Writable share → plain bind, NOT :ro.
rwBind := "- /mnt/felhom-drives/scratch1/shares/dokumentumok:/mnt/felhom-drives/scratch1/shares/dokumentumok\n"
if !strings.Contains(got, rwBind) {
t.Errorf("writable share must get a plain bind, missing %q\n%s", rwBind, got)
}
if strings.Contains(got, "/mnt/felhom-drives/scratch1/shares/dokumentumok:/mnt/felhom-drives/scratch1/shares/dokumentumok:ro") {
t.Error("writable share must NOT be :ro")
}
}