package stacks import ( "bytes" "errors" "log" "net/http/httptest" "strings" "testing" ) // R-1323: the checked FileBrowser container can be replaced between the check and the leave (the image moved at start). // „No such container" then means the OLD one is gone, not that every app still reaches FileBrowser. The line reads the // CURRENT container's networks and says what is true; a current container that IS on the app network keeps the alarm. // // RED-PROOF (2026-11-02): before the fix the first case logged „could not leave traefik-public — every app can still reach // it … No such container" (seen on 9202, 0.346.1-rc.1, 2026-10-27), and this fails on that line. func r1323Rig(t *testing.T, currentOnApps bool) (*Manager, *bytes.Buffer) { t.Helper() fb := &fakeFB{password: "admin"} srv := httptest.NewServer(fb.handler()) t.Cleanup(srv.Close) m, _, _ := fbManager(t, srv.URL) var buf bytes.Buffer m.logger = log.New(&buf, "", 0) runs := []string{"old111 2026-10-27T14:58:15Z"} m.fbStarted = func() string { r := runs[0] if len(runs) > 1 { runs = runs[1:] } return r } nets := map[string]bool{"old111@traefik-public": true, "new222@traefik-public": currentOnApps} m.fbNetOnFn = func(c, n string) bool { return nets[c+"@"+n] } m.fbNetConnectFn = func(c, n string) error { nets[c+"@"+n] = true; return nil } m.fbNetDisconnectFn = func(c, n string) error { if c == "old111" { runs = []string{"new222 2026-10-27T14:58:17Z"} // the recreate won the race return errors.New("Error response from daemon: No such container: old111") } return nil } m.fbNetEnsureFn = func(string) error { return nil } m.fbOfficeLinkedFn = func() (bool, bool) { return false, false } return m, &buf } func TestR1323_AReplacedContainerIsNotReportedAsExposed(t *testing.T) { m, buf := r1323Rig(t, false) m.fileBrowserGateLocked() got := buf.String() if strings.Contains(got, "every app can still reach it") { t.Fatalf("a false alarm for a container that is gone: %q", got) } if !strings.Contains(got, "new222") || !strings.Contains(got, "not on traefik-public") { t.Fatalf("the line must name the current container and the truth: %q", got) } } func TestR1323_ACurrentContainerOnTheAppNetworkKeepsTheAlarm(t *testing.T) { m, buf := r1323Rig(t, true) m.fileBrowserGateLocked() got := buf.String() if !strings.Contains(got, "every app can still reach it") || !strings.Contains(got, "new222") { t.Fatalf("the replacement IS on the app network: the alarm stays, naming it: %q", got) } } // Any other failure to leave keeps the alarm as before (control). func TestR1323_AnotherDisconnectFailureKeepsTheAlarm(t *testing.T) { m, buf := r1323Rig(t, false) m.fbNetDisconnectFn = func(c, n string) error { return errors.New("daemon busy") } m.fileBrowserGateLocked() if got := buf.String(); !strings.Contains(got, "every app can still reach it") || !strings.Contains(got, "daemon busy") { t.Fatalf("a real failure to leave must keep its alarm: %q", got) } }