diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java index 1372c22..ba2b8c3 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/FleetdAssemblyFleetAppTest.java @@ -40,6 +40,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; * FleetAppTwoDaemonTest} already proves {@code FleetApp} itself merges/gates correctly given two * clients; the gap this pins is that the assembly actually passes it two. * + *

Both directions, not just one (fleetd #612 issue comment 17525): the deleted + * guard's positive assertion required the exact pair {@code "new FleetApp(herdr, memberHerdr, + * workers,"}, which does not survive EITHER daemon being dropped. An earlier version of this class + * only proved the member-dropped direction, which left {@code new FleetApp(memberHerdr, + * memberHerdr, ...)} — the symmetric bug, {@code /healthz} green while the LEAD daemon is down — + * an undetected regression. {@link #healthzGoesRedWhenTheLeadDaemonIsDownEvenThoughTheMemberIsUp} + * closes that. + * *

Unlike the {@code ConnectionIdentity} half of CB-185 ({@code * FleetdAssemblyConnectionIdentityTest}), {@code /healthz} needs no caller identity at all, so * this test can bind {@link FleetdRuntime#app()} to a REAL ephemeral port (exactly {@code @@ -206,4 +214,24 @@ class FleetdAssemblyFleetAppTest { assertEquals(200, get(port, "/healthz").statusCode()); } + + /** + * The symmetric pin (fleetd #612 issue comment 17525): the LEAD daemon is down; the MEMBER + * daemon is healthy. If the assembly built {@code FleetApp} with only the member client + * (dropping {@code herdr} — the mirror of the bug above, {@code new FleetApp(memberHerdr, + * memberHerdr, ...)}), the down LEAD is invisible and {@code /healthz} stays 200. Without this + * case the pair above is one-directional and does not cover the deleted guard's positive + * assertion (it required BOTH {@code herdr,} and {@code memberHerdr,} in that order). + */ + @Test + void healthzGoesRedWhenTheLeadDaemonIsDownEvenThoughTheMemberIsUp(@TempDir Path dir) throws Exception { + FakeHerdr lead = new FakeHerdr().healthy(false); + FakeHerdr member = new FakeHerdr(); + + int port = assembleAndBind(dir, lead, member); + + HttpResponse res = get(port, "/healthz"); + assertEquals(503, res.statusCode(), + "a down LEAD daemon must not be masked by a healthy member: " + res.body()); + } }