package web import ( "testing" ) // v0.147.0 slice 4b — the Megosztás bring-up progress card. // // The card's whole value is that it distinguishes states the old synchronous handler collapsed into // one silent form post. So the tests are about the DISTINCTIONS: // - "pulling" vs "starting" — decided by local image presence, and decided BEFORE the work starts // (afterwards the image is always present and the card could never truthfully say „letöltés"). // - reconcile-returned-nil is NOT the same as running: it also covers "deliberately deployed // nothing because there is no household password yet". // - compose up -d exits 0 on a crash-loop, so success must be PROBED, never inferred. // // SCOPE, STATED HONESTLY: these cover the single-flight slot, snapshot isolation and the phase // vocabulary. They do NOT drive runSambaEnsureJob end-to-end — that needs a real *stacks.Manager // (the Server field is the concrete type, not an interface), and introducing an interface purely for // this card was more churn than the slice warranted. The pulling-vs-starting decision and the // probed-liveness terminal state are therefore covered by the LIVE validation on the demo box, not // by a unit test. Recorded here rather than left as an implied gap; the ROADMAP's unified // async-job feedback item is where that seam belongs. func TestSambaEnsureSingleFlight(t *testing.T) { var st sambaEnsureState job := &sambaEnsureJob{Phase: sambaPhaseStarting} if !st.acquire(job) { t.Fatal("first acquire refused") } // A double-submit must not start a second `compose up -d` against the same stack dir. if st.acquire(&sambaEnsureJob{Phase: sambaPhaseStarting}) { t.Error("second acquire succeeded while a job was in flight") } st.release() if !st.acquire(&sambaEnsureJob{Phase: sambaPhaseStarting}) { t.Error("acquire refused after release") } } func TestSambaEnsureSnapshotIsACopy(t *testing.T) { var st sambaEnsureState job := &sambaEnsureJob{Phase: sambaPhasePulling} st.acquire(job) snap := st.snapshot() snap.Phase = "mutated-by-caller" if again := st.snapshot(); again.Phase != sambaPhasePulling { t.Errorf("a caller mutated the shared job through its snapshot: %q", again.Phase) } // And the live job must not leak into an already-taken snapshot either. snap2 := st.snapshot() job.Phase = sambaPhaseRunning st.set(job) if snap2.Phase != sambaPhasePulling { t.Errorf("an earlier snapshot changed under the caller: %q", snap2.Phase) } } func TestSambaEnsureStateStartsNil(t *testing.T) { var st sambaEnsureState // nil = never ran this process. The status handler maps that to `idle` and lets live container // state win, so a page loaded after a restart still tells the truth. if j := st.snapshot(); j != nil { t.Errorf("fresh state reported a job: %+v", j) } } // TestSambaPhaseConstantsAreDistinct guards the template contract: sharing.html maps these exact // strings, and a duplicate would silently render the wrong Hungarian sentence. func TestSambaPhaseConstantsAreDistinct(t *testing.T) { all := []string{ sambaPhasePulling, sambaPhaseStarting, sambaPhaseRunning, sambaPhaseNeedsPassword, sambaPhaseFailed, sambaPhaseIdle, } seen := map[string]bool{} for _, p := range all { if p == "" { t.Error("a phase constant is empty — the template would fall through to 'no card'") } if seen[p] { t.Errorf("duplicate phase constant %q", p) } seen[p] = true } }