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 }