Files
felhom-agent/internal/localapi/netverify_logseq_test.go
T
admin cb692f8788 v0.83.0: observability pass — always-DEBUG capture ring + GET /debug/logs + heartbeat log-pull + gap-fill sweep
Capture layer: applog.New returns (logger, Ring) — slog fan-out, stderr at the
configured level, ~1000-entry ring fixed at LevelDebug (remote diagnostics
without a config flip). GET /debug/logs (token-authed, ?raw=1) + request-level
DEBUG middleware. Heartbeat log-pull mirrors the report logtail pattern:
envelope log_tail_requested -> next heartbeat carries log_tail (128KB cap,
consume-once, failed-push retry proven). Gap-fill sweep over netverify/
netstorage/netmount/signedjobs/selfupdate/disks/controller-swap/desired/loop.
Red-proofs: ring-at-emit-level FAILs capture test; drain removed FAILs
consume-once; dropped phase line FAILs the S7 log-sequence smoke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
2026-07-11 16:24:07 +02:00

68 lines
2.4 KiB
Go

package localapi
import (
"bytes"
"net/http"
"strings"
"testing"
applog "gitea.dooplex.hu/admin/felhom-agent/internal/log"
)
// S7 sweep smoke (agent half): a full fake NAS add at emit level INFO must leave the
// EXPECTED LOG SEQUENCE in the debug ring — this is the test that encodes "an operator
// can reconstruct the NAS flow from the debug view". Companion red-proof: remove any
// one of the asserted phase lines (e.g. the /proc/mounts verdict Debug) → its marker
// is absent → FAIL naming the missing phase.
func TestNetAdd_LogSequenceReconstructsFlow(t *testing.T) {
logger, ring := applog.NewWithWriter(&bytes.Buffer{}, "info", 200)
n := &fakeNetOps{}
srv := newVerifyServer(t, n, t.TempDir(), verifySeams{
mounted: func(string) bool { return true }, // §8: in /proc/mounts ⇒ verified
})
srv.logger = logger // ring-backed capture layer under the whole flow
body := `{"name":"vids","protocol":"smb","server":"nas","export":"vids","mapped_uid":1000,"mapped_gid":1000,"username":"u","password":"p"}`
if w := do(t, srv.Handler(), "POST", "/netstorage/add", "A", body); w.Code != http.StatusOK {
t.Fatalf("add: got %d (%s)", w.Code, w.Body.String())
}
final := pollVerify(t, srv.Handler())
if final["phase"] != netVerifyPhaseDone {
t.Fatalf("phase = %v, want done", final["phase"])
}
lines := ring.Lines(0)
joined := strings.Join(lines, "\n")
// The phase markers, in order (each must exist; each must come after the previous).
sequence := []string{
"NAS endpoint pre-probe passed",
"SMB credentials staged",
"network mount installed",
"netverify: job started",
"netverify: /proc/mounts verdict",
"netverify: mount verified",
}
pos := -1
for _, marker := range sequence {
idx := indexOfLine(lines, marker, pos+1)
if idx < 0 {
t.Fatalf("phase line %q missing (or out of order) — flow not reconstructable.\nring:\n%s", marker, joined)
}
pos = idx
}
// The secret never appears in any line (creds are path-only).
if strings.Contains(joined, "password") || strings.Contains(joined, `"p"`) {
t.Errorf("a credential-looking token leaked into the log ring:\n%s", joined)
}
}
// indexOfLine finds the first line at or after `from` containing marker; -1 if none.
func indexOfLine(lines []string, marker string, from int) int {
for i := from; i < len(lines); i++ {
if strings.Contains(lines[i], marker) {
return i
}
}
return -1
}