Files
elizaos--eliza/packages/app/scripts/lib/chat-failure-strings.test.mjs
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

191 lines
8.1 KiB
JavaScript

/**
* Parity guard for the mobile chat-reply failure vocabulary and the iOS
* XCUITest verifier that consumes it.
*
* The tests pin the historical smoke classifier behaviour, prove the checked-in
* Swift and runtime TypeScript artifacts are generated from the shared list, and
* guard the #13687 anti-false-green contract: the device verifier accepts only a
* marker echo while classifying failure-string, not-ready, and unrecognized
* replies distinctly.
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { describe, expect, it } from "vitest";
import {
ANDROID_FAILURE_FRAGMENTS,
ANDROID_FULL_TURN_FAILURE_RE,
buildFailureRegExp,
IOS_FAILURE_FRAGMENTS,
IOS_FULL_BUN_SMOKE_FAILURE_RE,
renderSwiftFailureStrings,
renderTypeScriptFailureStrings,
THINK_TAG_FAILURE_FRAGMENTS,
} from "./chat-failure-strings.mjs";
const here = path.dirname(fileURLToPath(import.meta.url));
const swiftArtifactPath = path.resolve(
here,
"../../../app-core/platforms/ios/App/AppUITests/ChatFailureStrings.generated.swift",
);
const bootCaptureUITestsPath = path.resolve(
here,
"../../../app-core/platforms/ios/App/AppUITests/BootCaptureUITests.swift",
);
const appCoreTsArtifactPath = path.resolve(
here,
"../../../app-core/src/platform/chat-failure-strings.generated.ts",
);
const iosRuntimeBridgePath = path.resolve(
here,
"../../../app-core/src/platform/ios-runtime-bridge.ts",
);
const appMainPath = path.resolve(here, "../../src/main.tsx");
// The exact hand-authored classifier sources that lived inline in
// mobile-local-chat-smoke.mjs before #13687. Android includes the old regex plus
// its old inline think-tag sidecar. Pinned here so a fragment reorder/edit that
// changes matching behaviour is caught, not silently accepted.
const HISTORICAL_IOS_SOURCE =
"something went wrong|backend is not running|local backend is not running|no local backend|no local model|no model registered|no provider|connect a provider|waiting for the model download|timed out|<think\\b|<\\/think>|\\/?\\bno_think\\b";
const HISTORICAL_ANDROID_SOURCE =
"something went wrong|no local gguf|no local model|no model registered|no provider|connect a provider|device_disconnected|device_timeout|timed out|chat generation failed|waiting for the model download|set chat routing|progress:\\s*0%|<think\\b|<\\/think>|\\/?\\bno_think\\b";
describe("chat-failure-strings single source of truth (#13687)", () => {
it("reproduces the historical iOS/Android failure classifiers byte-for-byte", () => {
expect(IOS_FULL_BUN_SMOKE_FAILURE_RE.source).toBe(HISTORICAL_IOS_SOURCE);
expect(IOS_FULL_BUN_SMOKE_FAILURE_RE.flags).toBe("i");
expect(ANDROID_FULL_TURN_FAILURE_RE.source).toBe(HISTORICAL_ANDROID_SOURCE);
expect(ANDROID_FULL_TURN_FAILURE_RE.flags).toBe("i");
});
it("derives each regex from its fragment list (join is the only transform)", () => {
expect(buildFailureRegExp(IOS_FAILURE_FRAGMENTS).source).toBe(
IOS_FULL_BUN_SMOKE_FAILURE_RE.source,
);
expect(buildFailureRegExp(ANDROID_FAILURE_FRAGMENTS).source).toBe(
ANDROID_FULL_TURN_FAILURE_RE.source,
);
});
it("shares the think-tag leakage fragments across surfaces", () => {
for (const fragment of THINK_TAG_FAILURE_FRAGMENTS) {
expect(IOS_FAILURE_FRAGMENTS).toContain(fragment);
expect(ANDROID_FAILURE_FRAGMENTS).toContain(fragment);
}
expect(THINK_TAG_FAILURE_FRAGMENTS.length).toBeGreaterThan(0);
});
it("rejects an empty fragment list (fail-closed builder)", () => {
expect(() => buildFailureRegExp([])).toThrow(/non-empty/);
expect(() => buildFailureRegExp(null)).toThrow(/non-empty/);
});
it("committed Swift artifact byte-matches the generator (no drift / stale regen)", () => {
const committed = fs.readFileSync(swiftArtifactPath, "utf8");
expect(committed).toBe(renderSwiftFailureStrings());
});
it("committed app-core TypeScript artifact byte-matches the generator", () => {
const committed = fs.readFileSync(appCoreTsArtifactPath, "utf8");
expect(committed).toBe(renderTypeScriptFailureStrings());
});
it("browser/runtime smoke checks consume the generated TypeScript artifact", () => {
const bridge = fs.readFileSync(iosRuntimeBridgePath, "utf8");
const appMain = fs.readFileSync(appMainPath, "utf8");
expect(bridge).toContain('from "./chat-failure-strings.generated"');
expect(appMain).toContain("IOS_FULL_BUN_SMOKE_FAILURE_RE");
expect(bridge).not.toContain("backend is not running|local backend");
expect(appMain).not.toContain("something went wrong|<think");
});
it("the XCUITest reply verifier consumes the shared vocabulary (not dead code)", () => {
// Guards against the artifact drifting back into an unreferenced file while
// BootCaptureUITests keeps its old "any new text is a reply" heuristic
// (the #13687 false-green). The Swift verifier must reference the generated
// enum so an error render is classified as a failure, not accepted.
const bootCapture = fs.readFileSync(bootCaptureUITestsPath, "utf8");
expect(bootCapture).toContain("ChatFailureStrings.ios");
});
it("the XCUITest verifier only accepts marker-echo replies and classifies every verdict", () => {
const bootCapture = fs.readFileSync(bootCaptureUITestsPath, "utf8");
expect(bootCapture).not.toContain("Say hello in exactly three words.");
expect(bootCapture).toContain('let replyMarker = "IOS_CHAT_OK"');
expect(bootCapture).toContain(
'"Start your reply with exactly \\(replyMarker), then say hello in one short sentence."',
);
expect(bootCapture).toContain("marker-hit");
expect(bootCapture).toContain("failure-string:");
expect(bootCapture).toContain("not-ready");
expect(bootCapture).toContain("reply-not-ready");
expect(bootCapture).toContain("unrecognized-text");
expect(bootCapture).toContain("reply-unrecognized-text");
const markerBranch = bootCapture.match(
/if let c = candidate,\s*c\.localizedCaseInsensitiveContains\(replyMarker\)\s*\{(?<body>[\s\S]*?)\n {12}\}/,
);
expect(markerBranch?.groups?.body).toContain("reply = c");
const unrecognizedBranch = bootCapture.match(
/if let c = candidate, !looksNotReady\(c\) \{(?<body>[\s\S]*?)\n {12}\}/,
);
expect(unrecognizedBranch?.groups?.body).toContain(
"unrecognizedObservation = c",
);
expect(unrecognizedBranch?.groups?.body).not.toContain("reply = c");
});
it("the Swift artifact enumerates the same fragments as the JS lists", () => {
const swift = renderSwiftFailureStrings();
for (const fragment of IOS_FAILURE_FRAGMENTS) {
expect(swift).toContain(JSON.stringify(fragment));
}
for (const fragment of ANDROID_FAILURE_FRAGMENTS) {
expect(swift).toContain(JSON.stringify(fragment));
}
});
describe("classifies error renders as failures, real replies as pass", () => {
// The exact heading the ErrorBoundary renders
// (packages/ui/src/components/ui/error-boundary.tsx) — the #13687 false-green.
it("iOS: error-boundary heading is a failure", () => {
expect(IOS_FULL_BUN_SMOKE_FAILURE_RE.test("Something went wrong")).toBe(
true,
);
});
it("iOS: backend-down + think-tag leak are failures", () => {
expect(
IOS_FULL_BUN_SMOKE_FAILURE_RE.test("Local backend is not running"),
).toBe(true);
expect(
IOS_FULL_BUN_SMOKE_FAILURE_RE.test("<think>chain of thought</think>"),
).toBe(true);
});
it("iOS: the genuine expected reply is NOT a failure", () => {
expect(IOS_FULL_BUN_SMOKE_FAILURE_RE.test("ios smoke model works")).toBe(
false,
);
});
it("Android: device disconnect / chat-generation-failed are failures", () => {
expect(ANDROID_FULL_TURN_FAILURE_RE.test("device_disconnected")).toBe(
true,
);
expect(ANDROID_FULL_TURN_FAILURE_RE.test("chat generation failed")).toBe(
true,
);
expect(ANDROID_FULL_TURN_FAILURE_RE.test("progress: 0%")).toBe(true);
});
it("Android: the genuine expected reply is NOT a failure", () => {
expect(
ANDROID_FULL_TURN_FAILURE_RE.test("android smoke model works"),
).toBe(false);
});
});
});