diff --git a/fleetd/src/test/java/dev/ltms/fleet/FleetdStartupReportTest.java b/fleetd/src/test/java/dev/ltms/fleet/FleetdStartupReportTest.java new file mode 100644 index 0000000..06248c3 --- /dev/null +++ b/fleetd/src/test/java/dev/ltms/fleet/FleetdStartupReportTest.java @@ -0,0 +1,79 @@ +package dev.ltms.fleet; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import dev.ltms.fleet.config.FleetConfig; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.slf4j.LoggerFactory; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Proves {@link Fleetd#main(String[])} calls every startup report before validation aborts startup. + * The invalid non-loopback bind makes {@link FleetConfig#validateAll()} throw before {@code main} + * can open the herdr socket or bind a port. The fixture also triggers every report, so removing any + * one call from {@code main} leaves its expected log line absent. + */ +class FleetdStartupReportTest { + + private static Level originalLevel; + + private static ListAppender attach() { + Logger logger = (Logger) LoggerFactory.getLogger(Fleetd.class); + originalLevel = logger.getLevel(); + logger.setLevel(Level.INFO); + ListAppender appender = new ListAppender<>(); + appender.start(); + logger.addAppender(appender); + return appender; + } + + private static void detach(ListAppender appender) { + Logger logger = (Logger) LoggerFactory.getLogger(Fleetd.class); + logger.detachAppender(appender); + logger.setLevel(originalLevel); + } + + private static boolean contains(ListAppender appender, String fragment) { + return appender.list.stream() + .map(ILoggingEvent::getFormattedMessage) + .anyMatch(message -> message.contains(fragment)); + } + + @Test + void mainReportsEveryStartupGapBeforeValidationAborts(@TempDir Path dir) throws Exception { + Path config = dir.resolve("fleetd.yaml"); + Files.writeString(config, """ + bind: + host: 0.0.0.0 + port: 8765 + profiles: + worker: + baseUrl: https://llm.ltms.dev/v1 + gitTokenEnv: GITEA_TOKEN + """); + + ListAppender appender = attach(); + try { + assertThrows(IllegalStateException.class, () -> Fleetd.main(new String[]{config.toString()})); + } finally { + detach(appender); + } + + assertTrue(contains(appender, "startup git host GITEA_HOST:"), + "Fleetd.main must report the git host shape"); + assertTrue(contains(appender, "member trust model: members run as the same OS user"), + "Fleetd.main must report the member trust model"); + assertTrue(contains(appender, "memberCredentials: absent or empty"), + "Fleetd.main must report an absent memberCredentials policy"); + assertTrue(contains(appender, "exhaustedPattern: profile(s) [worker] have no exhaustedPattern configured"), + "Fleetd.main must report profiles without exhaustedPattern"); + } +}