f1b9b41214
gates / gates (push) Successful in 47s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package lanresolver
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// recRunner records every privileged command EnsureDnsmasq would run and succeeds — nothing reaches
|
|
// apt, systemctl or the root checker.
|
|
type recRunner struct {
|
|
mu sync.Mutex
|
|
calls []string
|
|
}
|
|
|
|
func (r *recRunner) Run(_ context.Context, name string, args ...string) ([]byte, []byte, error) {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.calls = append(r.calls, strings.Join(append([]string{name}, args...), " "))
|
|
return nil, nil, nil
|
|
}
|
|
|
|
func (r *recRunner) RunStdin(ctx context.Context, _ io.Reader, name string, args ...string) ([]byte, []byte, error) {
|
|
return r.Run(ctx, name, args...)
|
|
}
|
|
|
|
func (r *recRunner) installed() bool {
|
|
for _, c := range r.calls {
|
|
if strings.HasPrefix(c, "apt-get install") && strings.HasSuffix(c, " dnsmasq") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// fixtureRoot builds a fake host root holding exactly the given relative files and points the REAL
|
|
// probe at it for the test's duration.
|
|
func fixtureRoot(t *testing.T, files ...string) {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
for _, f := range files {
|
|
p := filepath.Join(root, f)
|
|
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(p, nil, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
prev := hostRoot
|
|
hostRoot = root
|
|
t.Cleanup(func() { hostRoot = prev })
|
|
}
|
|
|
|
func ensure(t *testing.T) *recRunner {
|
|
t.Helper()
|
|
r := &recRunner{}
|
|
m := NewManager(r, "192.0.2.10", []string{"1.1.1.1"}, slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
if err := m.EnsureDnsmasq(context.Background()); err != nil {
|
|
t.Fatalf("EnsureDnsmasq: %v", err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// R-317: a host with `dnsmasq-base` (the /usr/sbin/dnsmasq binary) but WITHOUT the `dnsmasq` package
|
|
// (the service unit) must get the package installed — else the following `systemctl enable --now
|
|
// dnsmasq` hits a unit that does not exist and LAN name resolution silently never comes up.
|
|
//
|
|
// RED-PROOF: probe "usr/sbin/dnsmasq" instead of the unit paths in dnsmasqUnitInstalled → this fails
|
|
// with "install was skipped".
|
|
func TestEnsureDnsmasq_BinaryWithoutUnitInstalls(t *testing.T) {
|
|
fixtureRoot(t, "usr/sbin/dnsmasq")
|
|
r := ensure(t)
|
|
if !r.installed() {
|
|
t.Fatalf("install was skipped on a dnsmasq-base-only host (binary present, unit absent) — "+
|
|
"the enable that follows targets a missing unit (R-317). calls: %q", r.calls)
|
|
}
|
|
}
|
|
|
|
func TestEnsureDnsmasq_UnitPresentSkipsInstall(t *testing.T) {
|
|
for _, unit := range []string{"usr/lib/systemd/system/dnsmasq.service", "lib/systemd/system/dnsmasq.service"} {
|
|
t.Run(unit, func(t *testing.T) {
|
|
fixtureRoot(t, "usr/sbin/dnsmasq", unit)
|
|
if r := ensure(t); r.installed() {
|
|
t.Fatalf("apt-get install ran although the dnsmasq unit is present at %s: %q", unit, r.calls)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnsureDnsmasq_NothingPresentInstalls(t *testing.T) {
|
|
fixtureRoot(t)
|
|
if r := ensure(t); !r.installed() {
|
|
t.Fatalf("install skipped on a host with no dnsmasq at all: %q", r.calls)
|
|
}
|
|
}
|
|
|
|
// Production wiring for the hostRoot seam: the shipped probe resolves against the real root and asks
|
|
// about the unit the `dnsmasq` package owns — never the dnsmasq-base binary.
|
|
func TestEnsureDnsmasq_ProductionProbeIsTheUnit(t *testing.T) {
|
|
if hostRoot != "/" {
|
|
t.Fatalf("hostRoot default = %q, want \"/\" — the production probe would look in the wrong tree", hostRoot)
|
|
}
|
|
var sawUsrLib bool
|
|
for _, p := range dnsmasqUnitPaths {
|
|
full := filepath.Join(hostRoot, p)
|
|
if strings.HasSuffix(full, "/sbin/dnsmasq") || strings.HasSuffix(full, "/bin/dnsmasq") {
|
|
t.Errorf("probe path %s is the dnsmasq-base binary, not the dnsmasq unit (R-317)", full)
|
|
}
|
|
if full == "/usr/lib/systemd/system/dnsmasq.service" {
|
|
sawUsrLib = true
|
|
}
|
|
}
|
|
if !sawUsrLib {
|
|
t.Errorf("probe paths %q miss /usr/lib/systemd/system/dnsmasq.service (dpkg -S: owned by dnsmasq)", dnsmasqUnitPaths)
|
|
}
|
|
}
|