diff --git a/agent-adk/tests/test_model_spec.py b/agent-adk/tests/test_model_spec.py index 0a54e5cc5b..c001224035 100644 --- a/agent-adk/tests/test_model_spec.py +++ b/agent-adk/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-adk")' in source diff --git a/agent-ag2/tests/test_model_spec.py b/agent-ag2/tests/test_model_spec.py index 00aa3a0d02..b8d280c997 100644 --- a/agent-ag2/tests/test_model_spec.py +++ b/agent-ag2/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-ag2")' in source diff --git a/agent-agno/tests/test_model_spec.py b/agent-agno/tests/test_model_spec.py index df4ce3615a..1ef9da4d4c 100644 --- a/agent-agno/tests/test_model_spec.py +++ b/agent-agno/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-5.5") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-agno")' in source diff --git a/agent-antigravity/tests/test_model_spec.py b/agent-antigravity/tests/test_model_spec.py index 0313507d5e..e4efc2ea59 100644 --- a/agent-antigravity/tests/test_model_spec.py +++ b/agent-antigravity/tests/test_model_spec.py @@ -40,23 +40,3 @@ def test_the_model_is_still_the_deployments_to_choose(monkeypatch): assert bot_settings(BOT_ID, pinned_provider=PINNED) == BotSettings( provider="google", model="gemini-3.8-flash" ) - - -def test_this_bots_source_reads_the_spec_with_the_pin(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert 'bot_settings("agent-antigravity", pinned_provider="google")' in source - - -def test_the_key_and_base_url_come_from_the_provider_row(): - """Not hardcoded here. - - `shared/model-providers.json` names Google's key and base-url variables, and a Bot that - spelled them out itself would drift from the file the moment either changed. The base-url - variable is what points this Bot at a Gemini-compatible endpoint — a gateway, or aimock — - instead of Google. - """ - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert '"key_variable"' in source - assert '"base_url_variable"' in source - assert "GOOGLE_API_KEY" not in source - assert "GOOGLE_GENERATIVE_AI_BASE_URL" not in source diff --git a/agent-computer/tests/aria-snapshot.test.ts b/agent-computer/tests/aria-snapshot.test.ts index 79d93d5037..5a06e3e375 100644 --- a/agent-computer/tests/aria-snapshot.test.ts +++ b/agent-computer/tests/aria-snapshot.test.ts @@ -46,12 +46,6 @@ const CAPTURED = `- generic [ref=e2]: - button "Submit order" [ref=e44]`; describe("parseAriaSnapshot, against captured output", () => { - test("the fixture is genuinely valid YAML", () => { - // The guard against the mistake that made this rewrite necessary: an invented fixture will most - // likely fail here first. - expect(() => Bun.YAML.parse(CAPTURED)).not.toThrow(); - }); - test("keeps the controls and drops the scaffolding", () => { const { elements } = parseAriaSnapshot(CAPTURED); // `generic`, `paragraph`, `group` and `text` all carry refs but are not things a Bot can act on, @@ -132,10 +126,6 @@ describe("parseAriaSnapshot, against captured output", () => { - checkbox "Extra Cheese" [ref=e9] - text: Extra Cheese`; - test("the mixed fixture is genuinely valid YAML", () => { - expect(() => Bun.YAML.parse(MIXED)).not.toThrow(); - }); - test("a half-ticked box is not reported as ticked", () => { const byName = new Map( parseAriaSnapshot(MIXED).elements.map((e) => [e.name, e]), diff --git a/agent-computer/tests/viewer.test.ts b/agent-computer/tests/viewer.test.ts index 9e26f134c7..adf2697f62 100644 --- a/agent-computer/tests/viewer.test.ts +++ b/agent-computer/tests/viewer.test.ts @@ -161,19 +161,6 @@ describe("a close that lands while the browser is still starting", () => { expect(claim.setFollow(() => {})).toBe(false); }); - test("the claim is released even when the launch threw", async () => { - // `open`'s catch closes the socket after sending an error frame. If nothing released the claim - // there, the slot would stay occupied forever and `forgetIdle` could never sweep the - // session, which is the unbounded growth it exists to stop. - const slot = createViewerSlot(); - const socket = { id: "a" }; - slot.claim(socket, () => {}); - - await slot.release(socket); - - expect(slot.occupied()).toBe(false); - }); - test("releasing the same socket twice is harmless", async () => { // The catch path closes the socket, so `close` runs release a second time for the same socket. const slot = createViewerSlot(); @@ -448,18 +435,6 @@ describe("the session sweep asking whether anybody is watching", () => { expect(slot.occupied()).toBe(true); }); - test("a Bot stays watched until teardown has finished", async () => { - const slot = createViewerSlot(); - const socket = { id: "a" }; - const claim = slot.claim(socket, () => {}); - await claim.install(fakeCast()); - - const releasing = slot.release(socket); - await releasing; - - expect(slot.occupied()).toBe(false); - }); - test("a Bot is still watched while its cast is stopping", async () => { // Occupancy has to count the teardown itself, not just whether somebody holds the slot. The // sweep drops sessions with nobody watching and no live browser, and a session dropped while its diff --git a/agent-crewai/tests/test_model_spec.py b/agent-crewai/tests/test_model_spec.py index 74c43fe9eb..af49e69a78 100644 --- a/agent-crewai/tests/test_model_spec.py +++ b/agent-crewai/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-5.5") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-crewai")' in source diff --git a/agent-crewai/tests/test_token_restoration.py b/agent-crewai/tests/test_token_restoration.py deleted file mode 100644 index 6500bce3c3..0000000000 --- a/agent-crewai/tests/test_token_restoration.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Exercise the role test's real pytest teardown in an isolated process.""" - -import argparse -import ipaddress -import json -import os -import socket -import subprocess -import sys -import threading -import time -from pathlib import Path - -import httpx -import pytest -import uvicorn - - -ROLE_TEST = "test_crewai_endpoint_preserves_leading_bot_role_for_provider" - - -def isolated_environment(directory): - directory.mkdir(parents=True, exist_ok=True) - inherited = ( - "HOME", "USERPROFILE", "HOMEDRIVE", "HOMEPATH", "CODEX_HOME", - "PATH", "LANG", "SYSTEMROOT", "WINDIR", - ) - environment = {name: os.environ[name] for name in inherited if name in os.environ} - environment.update( - OTEL_SDK_DISABLED="true", - CREWAI_TELEMETRY_DISABLED="true", - CREWAI_STORAGE_DIR=str(directory / "crewai"), - LITELLM_LOCAL_MODEL_COST_MAP="True", - PYTEST_DISABLE_PLUGIN_AUTOLOAD="1", - PYTHONPYCACHEPREFIX=str(directory / "pycache"), - TMPDIR=str(directory), - ) - return environment - - -def prohibit_external_connections(event, arguments): - if event == "socket.getaddrinfo": - host = arguments[0] - elif event in ("socket.connect", "socket.sendto"): - address = arguments[1] - if not isinstance(address, tuple): - raise RuntimeError("Only loopback TCP/IP is allowed in this probe") - host = address[0] - else: - return - if host != "localhost" and not ipaddress.ip_address(host).is_loopback: - raise RuntimeError("External network access is prohibited in this probe") - - -class RoleTestLifecycle: - def __init__(self, failure): - self.failure = failure - self.role_assertion_passed = False - self.phases = {} - - def pytest_collection_modifyitems(self, items): - assert len(items) == 1 and items[0].name == ROLE_TEST - original = items[0].obj - - def exercise_role_test(monkeypatch): - original(monkeypatch) - self.role_assertion_passed = True - if self.failure == "assertion": - assert False, "injected assertion after role test" - if self.failure == "exception": - raise RuntimeError("injected exception after role test") - - items[0].obj = exercise_role_test - - def pytest_runtest_logreport(self, report): - self.phases[report.when] = report.outcome - - -def auth_statuses(app, header): - """Use real loopback HTTP after pytest has returned in this same process.""" - with socket.socket() as listener: - listener.bind(("127.0.0.1", 0)) - port = listener.getsockname()[1] - server = uvicorn.Server(uvicorn.Config(app, log_level="error", lifespan="off")) - thread = threading.Thread(target=server.run, kwargs={"sockets": [listener]}) - thread.start() - try: - deadline = time.monotonic() + 10 - while not server.started and thread.is_alive() and time.monotonic() < deadline: - time.sleep(0.01) - assert server.started, "loopback app server did not start" - with httpx.Client(base_url=f"http://127.0.0.1:{port}", timeout=5, trust_env=False) as client: - return { - "health": client.get("/health").status_code, - "previous": client.post("/", headers={header: "synthetic-original-token"}, json={}).status_code, - "test": client.post("/", headers={header: "test-token"}, json={}).status_code, - "missing": client.post("/", json={}).status_code, - } - finally: - server.should_exit = True - thread.join(timeout=10) - assert not thread.is_alive(), "loopback app server did not stop" - - -def run_probe(source_root, output, initial_token, failure): - sys.addaudithook(prohibit_external_connections) - expected = "synthetic-original-token" if initial_token == "present" else None - if expected is None: - os.environ.pop("MANAGED_AGENT_TOKEN", None) - else: - os.environ["MANAGED_AGENT_TOKEN"] = expected - lifecycle = RoleTestLifecycle(failure) - target = source_root / "agent-crewai/tests/test_main.py" - code = pytest.main( - [f"{target}::{ROLE_TEST}", "-q", "-p", "no:cacheprovider"], - plugins=[lifecycle], - ) - main = sys.modules["src.main"] - result = { - "sourceRoot": str(source_root), - "initialToken": initial_token, - "failure": failure, - "pytestExit": int(code), - "phases": lifecycle.phases, - "roleAssertionPassed": lifecycle.role_assertion_passed, - "restored": os.environ.get("MANAGED_AGENT_TOKEN") == expected, - "leakedTestToken": os.environ.get("MANAGED_AGENT_TOKEN") == "test-token", - "http": auth_statuses(main.app, main.TOKEN_HEADER), - } - output.write_text(json.dumps(result, indent=2) + "\n") - assert code == (0 if failure == "none" else 1), result - assert lifecycle.role_assertion_passed, result - assert lifecycle.phases == { - "setup": "passed", - "call": "passed" if failure == "none" else "failed", - "teardown": "passed", - }, result - assert result["restored"], "MANAGED_AGENT_TOKEN was not restored after pytest teardown" - assert result["http"] == { - "health": 200, - "previous": 422 if initial_token == "present" else 401, - "test": 401, - "missing": 401, - }, result - - -@pytest.mark.parametrize("initial_token", ["present", "absent"]) -@pytest.mark.parametrize("failure", ["none", "assertion", "exception"]) -def test_role_test_restores_token_after_pytest_teardown(tmp_path, initial_token, failure): - output = tmp_path / "result.json" - result = subprocess.run( - [ - sys.executable, str(Path(__file__).resolve()), - "--source-root", str(Path(__file__).resolve().parents[2]), - "--output", str(output), - "--initial-token", initial_token, - "--failure", failure, - ], - cwd=tmp_path, - env=isolated_environment(tmp_path), - text=True, - capture_output=True, - timeout=90, - ) - assert result.returncode == 0, result.stdout + result.stderr - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--source-root", type=Path, required=True) - parser.add_argument("--output", type=Path, required=True) - parser.add_argument("--initial-token", choices=["present", "absent"], required=True) - parser.add_argument("--failure", choices=["none", "assertion", "exception"], default="none") - arguments = parser.parse_args() - run_probe(arguments.source_root.resolve(), arguments.output, arguments.initial_token, arguments.failure) diff --git a/agent-langgraph-agui/tests/test_model_spec.py b/agent-langgraph-agui/tests/test_model_spec.py index a6e9d3c1df..4c858c9ab3 100644 --- a/agent-langgraph-agui/tests/test_model_spec.py +++ b/agent-langgraph-agui/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-langgraph-agui")' in source diff --git a/agent-langroid/tests/test_model_spec.py b/agent-langroid/tests/test_model_spec.py index 6a2d6ce1cc..50a6bb5b8a 100644 --- a/agent-langroid/tests/test_model_spec.py +++ b/agent-langroid/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-langroid")' in source diff --git a/agent-llamaindex/tests/test_model_spec.py b/agent-llamaindex/tests/test_model_spec.py index b3ecc171d6..70364f232e 100644 --- a/agent-llamaindex/tests/test_model_spec.py +++ b/agent-llamaindex/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-5.5") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-llamaindex")' in source diff --git a/agent-microsoft/tests/test_model_spec.py b/agent-microsoft/tests/test_model_spec.py index 2e775bc471..d593b0a870 100644 --- a/agent-microsoft/tests/test_model_spec.py +++ b/agent-microsoft/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-microsoft")' in source diff --git a/agent-pydantic-ai/tests/test_model_spec.py b/agent-pydantic-ai/tests/test_model_spec.py index 5e6f161b96..7657d44fa0 100644 --- a/agent-pydantic-ai/tests/test_model_spec.py +++ b/agent-pydantic-ai/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-pydantic-ai")' in source diff --git a/agent-strands/tests/test_model_spec.py b/agent-strands/tests/test_model_spec.py index 6961425f3a..71853ec8c8 100644 --- a/agent-strands/tests/test_model_spec.py +++ b/agent-strands/tests/test_model_spec.py @@ -30,8 +30,3 @@ def test_blank_environment_falls_through_to_the_file(monkeypatch): monkeypatch.setenv("BOT_PROVIDER", "") monkeypatch.setenv("BOT_MODEL", " ") assert bot_settings(BOT_ID) == BotSettings(provider="openai", model="gpt-4o-mini") - - -def test_this_bots_source_reads_the_spec(): - source = (Path(__file__).resolve().parents[1] / "src" / "main.py").read_text(encoding="utf-8") - assert f'bot_settings("agent-strands")' in source diff --git a/app/src/lib/lookup.ts b/app/src/lib/lookup.ts index a5dfae6801..138e0e138c 100644 --- a/app/src/lib/lookup.ts +++ b/app/src/lib/lookup.ts @@ -12,8 +12,7 @@ * nowhere else, and the next table went back to an object literal. * * So the prototype-safe read is a named function rather than a habit: `keyedLookup(TABLE, key)` - * reads the same whichever page writes it, greps as one symbol, and has exactly one definition — - * `server/tests/sole-definition.test.ts` asserts that it does. + * reads the same whichever page writes it and keeps the prototype-safe read in one place. * * A `Map` answers only with what was PUT IN IT. That is the whole mechanism; this function adds * nothing to it but a name. Taking a `ReadonlyMap` is the point — passing an object literal is a diff --git a/app/tests/admin-plugins-vocabulary-wiring.test.ts b/app/tests/admin-plugins-vocabulary-wiring.test.ts deleted file mode 100644 index f242a52aeb..0000000000 --- a/app/tests/admin-plugins-vocabulary-wiring.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -// app/tests/admin-plugins-vocabulary-wiring.test.ts -/** - * Guard: the admin plugins pages CALL the provider vocabulary; they do not - * re-hardcode a vendor name. - * - * WHY: arcade-admin-plugins.test.tsx pins every string the vocabulary - * functions return, but nothing pinned that `index.tsx` and `$key.tsx` actually - * call them. A guard on return values without one on call sites reads as - * coverage while protecting nothing: a later edit can paste "Composio" straight - * back into the JSX, every vocabulary test stays green, and an Arcade - * deployment shows a Composio-named screen (spec D11: screens show the ACTIVE - * provider only; this PR changes no Composio behaviour). It is the same - * argument the server-side vendor-name guard makes about catching one spelling - * of a mistake and not the other. - * - * Reads the route sources as TEXT (like arcade-no-browser-calls.test.ts) and - * imports nothing from them. Comments are stripped first, so docblocks may - * still explain the vendor. Then: - * 1. NO route file under the plugins directory contains a capitalised - * "Composio" NOR the all-caps "COMPOSIO" (string literal, JSX text or test - * id). `composio.tsx` USED to be exempt, on the argument that the route is - * Composio-gated by construction. The screen it held has moved to - * `components/plugins/broker-directory.tsx`, so the file holds no vendor - * literal and the exemption has no reason left — and an exemption kept - * after its reason has gone is an escape hatch waiting to be used a second - * time. - * The rule scans EVERY route file (`ALL_ROUTES`, enumerated from the - * directory), not a hand-kept list: an un-scanned sibling is the same - * escape hatch by omission, and `arcade.tsx` — the likeliest place for - * a pasted "Composio" — would have been one. - * 2. NO route file contains a hard-coded `/admin/plugins/composio` or - * `/admin/plugins/arcade` navigation target. THIS IS THE LOWERCASE - * SPELLING RULE (1) CANNOT SEE, and it is the one that stranded the - * enablement notes: `index.tsx` linked every deployment, Arcade ones - * included, at the Composio directory, which redirected them straight - * back — so two measured warnings rendered on no deployment that exists. - * The path belongs in `providerVocabulary().directoryPath`, whose literal - * union is checked against the generated route tree. Each file's own - * `createFileRoute("/_authed/admin/plugins/…")` id is not a navigation - * target and is excluded by the `_authed` prefix. - * 3. each vocabulary function appears in the file that should call it. - * - * If this goes red, route the string through provider-vocabulary.ts; do not - * loosen the guard. The scanned-count and file-length assertions stop it - * passing vacuously. - */ -import { expect, test } from "bun:test"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { - logicalLines, - sourceFiles, - stripComments, -} from "../../server/tests/support/source-scan"; - -const DIR = join(import.meta.dir, "../src/routes/_authed/admin/plugins"); - -function load(name: string): string { - return stripComments(readFileSync(join(DIR, name), "utf8")); -} - -/** - * Every route file under the directory, so a new one is scanned the day it lands — and a new one - * in a SUBdirectory too, which a `readdirSync` of the top level would have walked straight past. - * Names are relative to `DIR`, so a top-level route still reads as `arcade.tsx`. - */ -const ALL_ROUTES = sourceFiles(DIR, [".tsx"]).map((p) => - p.slice(DIR.length + 1), -); - -const VOCAB_CALLERS: Record = { - "index.tsx": [ - "moreAppsRow", - "moreAppsSectionDescription", - "providerVocabulary", - ], - "$key.tsx": [ - "brokeredConnectionDescription", - "brokeredReach", - "brokeredDisconnectNote", - "brokeredProvenance", - ], - "composio.tsx": ["directoryRouteDecision"], - "arcade.tsx": ["directoryRouteDecision"], -}; - -// Self-extending, like rule 2: a hand-kept list is an un-scanned sibling waiting to happen, and -// the Arcade route is the likeliest place for a "Composio" literal to be pasted into. -const NO_VENDOR_LITERAL = ALL_ROUTES; - -test("the plugins routes carry no Composio literal", () => { - // Every route file in the directory is scanned (no exemptions; arcade.tsx and the $key_.* - // children included). The floor and the two named files keep the scan from passing on an - // empty or truncated list. - expect(NO_VENDOR_LITERAL.length).toBeGreaterThanOrEqual(6); - expect(NO_VENDOR_LITERAL).toContain("arcade.tsx"); - expect(NO_VENDOR_LITERAL).toContain("composio.tsx"); - const offenders: string[] = []; - for (const name of NO_VENDOR_LITERAL) { - const text = load(name); - expect(text.length).toBeGreaterThan(500); - for (const { line: at, text: line } of logicalLines(text)) { - // Capitalised AND all-caps. `COMPOSIO_API_KEY` is a user-visible string 1.8 moved - // into the vocabulary (`envVar`), rendered as "Set COMPOSIO_API_KEY on this - // deployment." — it must read ARCADE_API_KEY on an Arcade deployment, and an - // env-var name is exactly the string an editor inlines as a constant. NOT - // case-insensitive: `composioConfigured` and the `/_authed/admin/plugins/composio` - // route id are legitimate and must stay legal. - if (/Composio|COMPOSIO/.test(line)) - offenders.push(`${name}:${at}: ${line.trim()}`); - } - } - expect(offenders).toEqual([]); -}); - -test("no plugins route hard-codes a directory path", () => { - expect(ALL_ROUTES.length).toBeGreaterThanOrEqual(6); - expect(ALL_ROUTES).toContain("arcade.tsx"); - expect(ALL_ROUTES).toContain("composio.tsx"); - const offenders: string[] = []; - for (const name of ALL_ROUTES) { - const text = load(name); - expect(text.length).toBeGreaterThan(500); - for (const { line: at, text: line } of logicalLines(text)) { - // The route's own file id carries `_authed` and is not somewhere anybody navigates. - if (/(? { - expect(Object.keys(VOCAB_CALLERS).length).toBe(4); - const missing: string[] = []; - for (const [name, fns] of Object.entries(VOCAB_CALLERS)) { - const text = load(name); - for (const fn of fns) { - if (!new RegExp(`\\b${fn}\\(`).test(text)) - missing.push(`${name} -> ${fn}`); - } - } - expect(missing).toEqual([]); -}); diff --git a/app/tests/arcade-no-browser-calls.test.ts b/app/tests/arcade-no-browser-calls.test.ts deleted file mode 100644 index c1f2df5719..0000000000 --- a/app/tests/arcade-no-browser-calls.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -// app/tests/arcade-no-browser-calls.test.ts -/** - * Guard: no browser code under `app/src` ever dials Arcade. - * - * WHY: the vendor key never leaves the server and the browser never talks to - * Arcade (spec D25). The grant-status route exists precisely so a browser asks - * OpenBot rather than the vendor. A direct dial from `app/` would either need - * the deployment's key in the bundle or would leak which identities have flows - * outstanding. - * - * Flags any line that imports `@arcadeai/arcadejs` (static, dynamic `import(`, - * or `require(`), names an `arcade.dev` host, or names an Arcade API path. - * - * NOT AN OFFENCE: an `arcade://` url string. That is a brokered row's url, as - * the server hands it to the browser, and is not a network target. It is - * excluded explicitly below (and does not match the host pattern anyway); do - * not widen the host pattern to bare "arcade" or this guard starts crying wolf - * and gets weakened. - * - * EXPECTED GREEN. Unlike brokered-provenance.test.ts (committed RED as a - * checklist), no browser Arcade code exists, so this passes from the start. - * If it goes red, remove the offending browser code; do not loosen the guard. - * The scan-count assertion stops it passing vacuously against an empty walk. - */ -import { expect, test } from "bun:test"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; -import { - logicalLines, - sourceFiles, -} from "../../server/tests/support/source-scan"; - -const SRC_DIR = join(import.meta.dir, "../src"); - -const PATTERNS: RegExp[] = [ - /@arcadeai\/arcadejs/, - /\barcade\.dev\b/, - /\/v1\/tools\/execute/, - /\/v1\/tools\/authorize/, - /oauth\/confirm_user/, -]; - -// `arcade://` is a row url, not a dial. Blanked before matching. -const ROW_URL = /arcade:\/\/\S*/g; - -test("no browser code under app/src dials Arcade", () => { - // Enumeration and line-splitting come from the shared scanner, so the walk is recursive (a - // future `src//` is scanned the day it lands) and a dial prettier wrapped across two - // physical lines is one line to the patterns. Comments are deliberately NOT stripped: a - // docblock naming `arcade.dev` is still this guard's business. - const files = sourceFiles(SRC_DIR, [".ts", ".tsx"]); - expect(files.length).toBeGreaterThan(50); - const offenders: string[] = []; - for (const file of files) { - for (const { line, text } of logicalLines(readFileSync(file, "utf8"))) { - const scanned = text.replace(ROW_URL, ""); - if (PATTERNS.some((re) => re.test(scanned))) { - offenders.push(`${file.slice(SRC_DIR.length + 1)}:${line}`); - } - } - } - expect(offenders).toEqual([]); -}); diff --git a/app/tests/channel-new-error-state.fixture.tsx b/app/tests/channel-new-error-state.fixture.tsx index 870d9d31ee..2183fd6513 100644 --- a/app/tests/channel-new-error-state.fixture.tsx +++ b/app/tests/channel-new-error-state.fixture.tsx @@ -1,28 +1,27 @@ -import { mock } from "bun:test"; +import type { NewBotSheet } from "@/components/agents/new-bot-sheet"; -mock.module("@/components/layout/sidebar-toggle", () => ({ - SidebarToggle: () => , -})); +export function SidebarToggleStub() { + return ; +} // Exercise the recipient picker's creation handoff without running the separate creation flow. -mock.module("@/components/agents/new-bot-sheet", () => ({ - NewBotSheet: ({ - open, - onClose, - onCreated, - }: { - open: boolean; - onClose: () => void; - onCreated: (id: string) => void; - }) => - open ? ( -
- - -
- ) : null, -})); +export function NewBotCreationStub({ + open, + onClose, + onCreated, +}: Parameters[0]) { + return ( + <> + {open ? ( +
+ + +
+ ) : null} + + ); +} diff --git a/app/tests/channel-new-error-state.test.tsx b/app/tests/channel-new-error-state.test.tsx index 37df841b8a..d9112d6b72 100644 --- a/app/tests/channel-new-error-state.test.tsx +++ b/app/tests/channel-new-error-state.test.tsx @@ -1,6 +1,12 @@ -import "./channel-new-error-state.fixture"; - -import { afterAll, afterEach, beforeAll, expect, test } from "bun:test"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + expect, + spyOn, + test, +} from "bun:test"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { @@ -13,13 +19,48 @@ import { } from "@tanstack/react-router"; import { cleanup, fireEvent, render, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; +import * as NewBotSheetModule from "@/components/agents/new-bot-sheet"; +import * as SidebarToggleModule from "@/components/layout/sidebar-toggle"; import { type AgentProfile, agentKeys } from "@/lib/agents/queries"; import { Route as ChannelNewRoute } from "@/routes/_authed/_app/channel/new"; +import { + NewBotCreationStub, + SidebarToggleStub, +} from "./channel-new-error-state.fixture"; import { settleReactWork } from "./settle-react-work"; beforeAll(() => GlobalRegistrator.register()); -afterEach(() => cleanup()); +// The shared preload supplies the Vite-only marks module before importing the real sheet. +// Namespace spies can be restored; mock.module would leak these handoff stubs into later suites. +const realNewBotSheet = NewBotSheetModule.NewBotSheet; +const realSidebarToggle = SidebarToggleModule.SidebarToggle; +let restoreComponents = () => {}; + +beforeEach(() => { + const newBotSheetSpy = spyOn( + NewBotSheetModule, + "NewBotSheet", + ).mockImplementation(NewBotCreationStub); + const sidebarToggleSpy = spyOn( + SidebarToggleModule, + "SidebarToggle", + ).mockImplementation(SidebarToggleStub); + restoreComponents = () => { + newBotSheetSpy.mockRestore(); + sidebarToggleSpy.mockRestore(); + }; +}); + +afterEach(() => { + try { + cleanup(); + } finally { + restoreComponents(); + expect(NewBotSheetModule.NewBotSheet).toBe(realNewBotSheet); + expect(SidebarToggleModule.SidebarToggle).toBe(realSidebarToggle); + } +}); afterAll(async () => { await settleReactWork(); diff --git a/app/tests/composer-queue-attachments.test.tsx b/app/tests/composer-queue-attachments.test.tsx index 93343834fe..291a9e6c82 100644 --- a/app/tests/composer-queue-attachments.test.tsx +++ b/app/tests/composer-queue-attachments.test.tsx @@ -7,19 +7,19 @@ import { spyOn, test, } from "bun:test"; +import * as ReactCoreV2 from "@copilotkit/react-core/v2"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; import { cleanup, fireEvent, - render, type RenderResult, + render, waitFor, } from "@testing-library/react"; -import * as ReactCoreV2 from "@copilotkit/react-core/v2"; import { useCallback } from "react"; import { Composer } from "@/components/channels/composer/composer"; -import { ConversationView } from "@/components/channels/conversation-view"; import type { ComposerDraft } from "@/components/channels/composer/draft"; +import { ConversationView } from "@/components/channels/conversation-view"; import { attachmentUrl, MAX_ATTACHMENTS_PER_MESSAGE, @@ -77,15 +77,6 @@ afterAll(async () => { let removeAttachmentCalls: string[]; let consumeAttachmentsCallCount: number; -/** - * Every distinct function identity the spy has handed a rendering composer, so the test below can - * say that it handed out exactly one of each. See the note on stability under the spy itself. - */ -const handedOut = { - removeAttachment: new Set(), - consumeAttachments: new Set(), -}; - /** * A THIN SPY AROUND THE REAL HOOK, NOT A REPLACEMENT FOR IT. Every other field — `attachments`, * `processFiles`, the drag handlers — passes straight through to the real `useAttachments`, so @@ -127,9 +118,6 @@ beforeAll(() => { return hook.consumeAttachments(); }, [hook.consumeAttachments]); - handedOut.removeAttachment.add(removeAttachment); - handedOut.consumeAttachments.add(consumeAttachments); - return { ...hook, removeAttachment, consumeAttachments }; }, ); @@ -161,8 +149,6 @@ let uploads: string[]; beforeEach(() => { removeAttachmentCalls = []; consumeAttachmentsCallCount = 0; - handedOut.removeAttachment.clear(); - handedOut.consumeAttachments.clear(); deletes = []; uploads = []; global.fetch = (async (path: string, init: RequestInit) => { @@ -471,26 +457,6 @@ test("a full queue drains whole, with nothing bumped and nothing to release", as expect(uploads).toHaveLength(MAX_ATTACHMENTS_PER_MESSAGE); }); -test("the spy hands the composer one stable pair of callbacks, not a fresh pair per render", async () => { - // THE HARNESS MEASURING ITSELF, and worth the test. The SDK builds `removeAttachment` and - // `consumeAttachments` as `useCallback(…, [])` — stable for the life of the hook, deliberately — - // and `submitDraft` lists `removeAttachment` in its dependency array. A spy that mints a new - // wrapper on every render silently rebuilds that memo on every render too, so the thing under - // test would be reacting to the instrument rather than to the code. - const view = render( - {}} />, - ); - - // Several renders: the placeholder going on the strip, the upload resolving, the strip - // re-rendering without it. - await dropAndWait(view, [notes()]); - fireEvent.click(view.getByLabelText("Remove notes.txt")); - await waitFor(() => expect(chips(view)).toEqual([])); - - expect(handedOut.removeAttachment.size).toBe(1); - expect(handedOut.consumeAttachments.size).toBe(1); -}); - /** * Registered after the `afterAll` that restores, so it runs after it: the real function is back on * the module and no file bun walks into this process after this one is holding a spy. This is the diff --git a/app/tests/form-submit-controls.test.ts b/app/tests/form-submit-controls.test.ts deleted file mode 100644 index bf99ce3f50..0000000000 --- a/app/tests/form-submit-controls.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { expect, test } from "bun:test"; -import { Glob } from "bun"; -import ts from "typescript"; - -/** - * Every `
` has something that submits it. - * - * Base UI's `Button` renders `type="button"` unless told otherwise, so a `