package dev.ltms.bridged.herdr; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import static org.junit.jupiter.api.Assertions.*; /** * CB-531. A lead is never spawned, so the daemon has to find it: these assert that an * operator-labelled tab is what makes a pane a lead, and — just as importantly — what does not. */ class LeadTabScannerTest { private static final ObjectMapper MAPPER = new ObjectMapper(); private static final long TTL = TimeUnit.SECONDS.toNanos(10); /** * A herdr whose workspace/tab/pane topology is declared per test. Counts calls so the caching * contract can be asserted, and can be made to fail on demand. */ private static final class TopologyHerdr implements HerdrClient { /** workspace_id → label. */ final Map workspaces = new LinkedHashMap<>(); /** tab_id → [workspace_id, label]. */ final Map tabs = new LinkedHashMap<>(); /** pane_id → [tab_id, terminal_id]. */ final Map panes = new LinkedHashMap<>(); int calls; boolean failing; TopologyHerdr workspace(String id, String label) { workspaces.put(id, label); return this; } TopologyHerdr tab(String tabId, String workspaceId, String label) { tabs.put(tabId, new String[]{workspaceId, label}); return this; } TopologyHerdr pane(String paneId, String tabId, String terminalId) { panes.put(paneId, new String[]{tabId, terminalId}); return this; } @Override public JsonNode call(String method, Object params) { calls++; if (failing) { throw new HerdrException("socket closed"); } List items = new ArrayList<>(); switch (method) { case "workspace.list" -> { workspaces.forEach((id, label) -> items.add( "{\"workspace_id\":\"%s\",\"label\":\"%s\"}".formatted(id, label))); return read("{\"workspaces\":[%s]}".formatted(String.join(",", items))); } case "tab.list" -> { String ws = String.valueOf(((Map) params).get("workspace_id")); tabs.forEach((id, t) -> { if (ws.equals(t[0])) { items.add(("{\"tab_id\":\"%s\",\"workspace_id\":\"%s\",\"label\":%s," + "\"pane_count\":1}").formatted(id, t[0], t[1] == null ? "null" : "\"" + t[1] + "\"")); } }); return read("{\"tabs\":[%s]}".formatted(String.join(",", items))); } case "pane.list" -> { panes.forEach((id, p) -> items.add( "{\"pane_id\":\"%s\",\"tab_id\":\"%s\",\"terminal_id\":\"%s\"}" .formatted(id, p[0], p[1]))); return read("{\"panes\":[%s]}".formatted(String.join(",", items))); } default -> throw new AssertionError("unexpected herdr call: " + method); } } private static JsonNode read(String json) { try { return MAPPER.readTree(json); } catch (Exception e) { throw new AssertionError(e); } } @Override public void close() { } } /** * The usual shape: one user space with lead tabs, one worker space bridged owns. * *

Not closed: {@code close()} is a no-op on this fake, and every test needs the handle after * the scanner is built (to mutate the topology or read {@code calls}). */ @SuppressWarnings("resource") private TopologyHerdr twoLeads() { return new TopologyHerdr() .workspace("w1", "main") .workspace("w9", "bridged-workers") .tab("w1:t1", "w1", "lead: opus-5.0") .tab("w1:t2", "w1", "lead: gpt-sol-5.6") .tab("w1:t3", "w1", "notes") .tab("w9:t1", "w9", "worker: gx10 #1") .pane("w1:p1", "w1:t1", "term_opus") .pane("w1:p2", "w1:t2", "term_gpt") .pane("w1:p3", "w1:t3", "term_notes") .pane("w9:p1", "w9:t1", "term_worker"); } private LeadTabScanner scanner(TopologyHerdr herdr, Map configured, AtomicLong clock) { return new LeadTabScanner(herdr, "lead:", Set.of("bridged-workers"), configured, TTL, clock::get); } @Test void everyLabelledTabBecomesALeadNamedByItsLabel() { Map leads = scanner(twoLeads(), Map.of(), new AtomicLong()).get(); assertEquals(Map.of("term_opus", "opus-5.0", "term_gpt", "gpt-sol-5.6"), leads, "two leads discovered from labels alone — no terminal_id was ever configured"); } @Test void anUnlabelledTabContributesNothing() { assertFalse(scanner(twoLeads(), Map.of(), new AtomicLong()).get().containsKey("term_notes")); } /** * The guard that matters: bridged labels its own worker tabs, so if a worker space were scanned * a naming accident would promote the fleet. The exclusion is by workspace, not by hoping the * worker template never collides. */ @Test void aTabInAWorkerSpaceIsNeverALeadEvenWhenItsLabelMatches() { TopologyHerdr herdr = twoLeads().tab("w9:t2", "w9", "lead: impostor") .pane("w9:p2", "w9:t2", "term_impostor"); assertFalse(scanner(herdr, Map.of(), new AtomicLong()).get().containsKey("term_impostor")); } @Test void aBarePrefixNamesNobodyAndIsRejected() { TopologyHerdr herdr = new TopologyHerdr().workspace("w1", "main") .tab("w1:t1", "w1", "lead:").pane("w1:p1", "w1:t1", "term_a"); assertEquals(Map.of(), scanner(herdr, Map.of(), new AtomicLong()).get(), "a lead with no name would resolve as PRIMARY with nothing to attribute it to"); } @Test void thePrefixMatchesCaseInsensitivelyAndTheNameIsTrimmed() { TopologyHerdr herdr = new TopologyHerdr().workspace("w1", "main") .tab("w1:t1", "w1", " LEAD: opus-5.0 ").pane("w1:p1", "w1:t1", "term_a"); assertEquals(Map.of("term_a", "opus-5.0"), scanner(herdr, Map.of(), new AtomicLong()).get()); } @Test void everyPaneInALeadTabResolvesAsThatLead() { // A human may split their own lead tab. Both panes are theirs, so both are that lead — // nothing bridged placed can land here (see the worker-space test above). TopologyHerdr herdr = twoLeads().pane("w1:p1b", "w1:t1", "term_opus_split"); assertEquals("opus-5.0", scanner(herdr, Map.of(), new AtomicLong()).get().get("term_opus_split")); } @Test void anExplicitlyConfiguredLeadIsMergedInAndOutranksALabel() { Map configured = Map.of("term_opus", "pinned-name", "term_extra", "from-config"); Map leads = scanner(twoLeads(), configured, new AtomicLong()).get(); assertEquals("pinned-name", leads.get("term_opus"), "an explicit pin is the operator's last word"); assertEquals("from-config", leads.get("term_extra"), "a configured lead needs no tab at all"); assertEquals("gpt-sol-5.6", leads.get("term_gpt")); } // ── caching ───────────────────────────────────────────────────────────────────────────────── @Test void aSecondLookupWithinTheTtlDoesNotTouchHerdr() { TopologyHerdr herdr = twoLeads(); AtomicLong clock = new AtomicLong(); LeadTabScanner s = scanner(herdr, Map.of(), clock); s.get(); int afterFirst = herdr.calls; clock.addAndGet(TTL - 1); s.get(); assertEquals(afterFirst, herdr.calls, "resolve() runs on every request — an un-cached scan would put herdr on that path"); } @Test void aTabLabelledAfterStartupIsPickedUpOnceTheTtlExpires() { TopologyHerdr herdr = twoLeads(); AtomicLong clock = new AtomicLong(); LeadTabScanner s = scanner(herdr, Map.of(), clock); assertFalse(s.get().containsKey("term_notes")); herdr.tab("w1:t3", "w1", "lead: late-arrival"); // the operator renames their tab clock.addAndGet(TTL); assertEquals("late-arrival", s.get().get("term_notes"), "the whole point over `leaders:`: no config edit, no restart"); } @Test void aFailedScanKeepsTheLeadsAlreadyKnownRatherThanDemotingThem() { TopologyHerdr herdr = twoLeads(); AtomicLong clock = new AtomicLong(); LeadTabScanner s = scanner(herdr, Map.of(), clock); Map before = s.get(); herdr.failing = true; clock.addAndGet(TTL); assertEquals(before, s.get(), "a herdr hiccup must not silently demote a live lead to a worker mid-session"); } @Test void aFailedFirstScanStillHonoursTheConfiguredLeads() { TopologyHerdr herdr = twoLeads(); herdr.failing = true; Map leads = scanner(herdr, Map.of("term_x", "opus-5.0"), new AtomicLong()).get(); assertEquals(Map.of("term_x", "opus-5.0"), leads, "config-named leads must not depend on herdr answering at all"); } @Test void aDownHerdrIsRetriedOncePerTtlNotOncePerRequest() { TopologyHerdr herdr = twoLeads(); herdr.failing = true; AtomicLong clock = new AtomicLong(); LeadTabScanner s = scanner(herdr, Map.of(), clock); s.get(); int afterFirst = herdr.calls; s.get(); s.get(); assertEquals(afterFirst, herdr.calls, "the failure path must be rate-limited too"); } }