Files
elizaos--eliza/packages/app/scripts/lib/android-assistant-verify-lib.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

367 lines
13 KiB
JavaScript

/**
* Deterministic vitest coverage for the assistant-role/IME/assist-key
* verification parsers (#13581). Fixtures are real-shaped `adb`/`dumpsys`/`cmd`/
* `logcat` output slices, not mocks of the parser — the parsers are pure
* string→decision functions, so this suite proves the string handling that
* regresses silently off-device (a garbled scrape reading as "absent"), covering
* the present, absent, short-vs-full component form, and empty/garbage cases.
*
* Runs in the normal `packages/app` vitest lane (which collects
* `scripts/**\/*.test.mjs`) so a parser regression fails CI — the earlier
* `node:test` form was loaded by vitest but collected as ZERO tests, an
* unenforced green-by-skip.
*
* Run: `bun run --cwd packages/app test -- scripts/lib/android-assistant-verify-lib.test.mjs`
*/
import assert from "node:assert/strict";
// #13581 enforcement fix: run under vitest (the lane that collects
// scripts/**/*.test.mjs) instead of node:test, which vitest loads but collects
// as ZERO tests — a green-by-skip that left this regression guard unenforced.
// node:assert still throws on failure, so every assertion below fails the
// vitest test the same way; only the test-registration source changes.
import { test } from "vitest";
import {
APP_PACKAGE,
ASSISTANT_IME_COMPONENT,
ASSISTANT_VIS_COMPONENT,
assertDeepLinkLanded,
classifyImeAsrOutcome,
DEEP_LINK_SOURCES,
detectSurfaceInvocation,
LOG_TAGS,
normalizeComponent,
parseAssistantSurfaces,
parseDefaultInputMethod,
parseEnabledImes,
parseRoleHolders,
parseServiceRegistration,
parseVoiceInteractionService,
ROLE_ASSISTANT,
summarizeLaneVerdict,
} from "./android-assistant-verify-lib.mjs";
test("normalizeComponent canonicalizes short and full forms equally", () => {
const full = "ai.elizaos.app/ai.elizaos.app.ElizaVoiceInteractionService";
assert.equal(
normalizeComponent("ai.elizaos.app/.ElizaVoiceInteractionService"),
full,
);
assert.equal(normalizeComponent(full), full);
assert.throws(() => normalizeComponent("not a component"));
assert.throws(() => normalizeComponent(""));
assert.throws(() => normalizeComponent(undefined));
});
test("parseServiceRegistration finds the VIS in a real dumpsys package slice (short form)", () => {
const dump = `
Service Resolver Table:
Non-Data Actions:
android.service.voice.VoiceInteractionService:
4f2a1c ai.elizaos.app/.ElizaVoiceInteractionService filter 88b
Action: "android.service.voice.VoiceInteractionService"
`;
const result = parseServiceRegistration(
dump,
ASSISTANT_VIS_COMPONENT,
"android.service.voice.VoiceInteractionService",
);
assert.equal(result.registered, true);
assert.equal(result.actionSeen, true);
});
test("parseServiceRegistration reports absence when the component is missing", () => {
const dump = `Service Resolver Table:\n android.view.InputMethod:\n ce01 com.other.keyboard/.SomeIme filter aa\n`;
const result = parseServiceRegistration(
dump,
ASSISTANT_IME_COMPONENT,
"android.view.InputMethod",
);
assert.equal(result.registered, false);
});
test("parseAssistantSurfaces detects every declared surface, and reports the missing one", () => {
const full = `
ai.elizaos.app/.ElizaVoiceInteractionService
ai.elizaos.app/.ElizaVoiceInteractionSessionService
ai.elizaos.app/.ElizaRecognitionService
ai.elizaos.app/.ElizaVoiceInputMethodService
ai.elizaos.app/.ElizaAssistActivity
`;
const ok = parseAssistantSurfaces(full);
assert.equal(ok.allPresent, true);
assert.deepEqual(ok.missing, []);
const missingIme = full.replace(
"ai.elizaos.app/.ElizaVoiceInputMethodService\n",
"",
);
const partial = parseAssistantSurfaces(missingIme);
assert.equal(partial.allPresent, false);
assert.deepEqual(partial.missing, ["inputMethodService"]);
assert.equal(partial.present.voiceInteractionService, true);
});
test("parseAssistantSurfaces treats a renamed VIS as ABSENT (regression canary)", () => {
// A renamed component must not prefix-match the expected id — this is exactly
// the regression the lane exists to catch (rename the VIS → it stops
// registering under its expected component id).
const renamed = `
ai.elizaos.app/.ElizaVoiceInteractionServiceRENAMED
ai.elizaos.app/.ElizaVoiceInteractionSessionService
ai.elizaos.app/.ElizaRecognitionService
ai.elizaos.app/.ElizaVoiceInputMethodService
ai.elizaos.app/.ElizaAssistActivity
`;
const parsed = parseAssistantSurfaces(renamed);
assert.equal(parsed.allPresent, false);
assert.deepEqual(parsed.missing, ["voiceInteractionService"]);
// The session service must still match despite sharing a prefix with the VIS.
assert.equal(parsed.present.voiceInteractionSessionService, true);
});
test("parseRoleHolders recognizes the held ROLE_ASSISTANT and rejects noise lines", () => {
const held = parseRoleHolders("ai.elizaos.app\n");
assert.equal(held.heldByExpected, true);
assert.deepEqual(held.holders, [APP_PACKAGE]);
const none = parseRoleHolders("");
assert.equal(none.heldByExpected, false);
const other = parseRoleHolders("com.google.android.googlequicksearchbox\n");
assert.equal(other.heldByExpected, false);
const errorLine = parseRoleHolders(
"Exception occurred while executing 'holders'",
);
assert.deepEqual(errorLine.holders, []);
assert.equal(errorLine.heldByExpected, false);
assert.equal(ROLE_ASSISTANT, "android.app.role.ASSISTANT");
});
test("parseVoiceInteractionService reads dumpsys ComponentInfo and flattened settings forms", () => {
const dumpsys =
" mCurInteractor=ComponentInfo{ai.elizaos.app/ai.elizaos.app.ElizaVoiceInteractionService}";
const fromDump = parseVoiceInteractionService(dumpsys);
assert.equal(fromDump.isEliza, true);
const settings =
"ai.elizaos.app/ai.elizaos.app.ElizaVoiceInteractionService\n";
assert.equal(parseVoiceInteractionService(settings).isEliza, true);
const other =
" mCurInteractor=ComponentInfo{com.google.android.googlequicksearchbox/com.google.VoiceInteractionService}";
assert.equal(parseVoiceInteractionService(other).isEliza, false);
assert.equal(parseVoiceInteractionService("").selected, null);
});
test("parseDefaultInputMethod distinguishes selected Eliza IME from another keyboard and null", () => {
const eliza = parseDefaultInputMethod(
"ai.elizaos.app/.ElizaVoiceInputMethodService\n",
);
assert.equal(eliza.isEliza, true);
const other = parseDefaultInputMethod(
"com.google.android.inputmethod.latin/.LatinIME",
);
assert.equal(other.isEliza, false);
assert.equal(
other.selected,
"com.google.android.inputmethod.latin/.LatinIME",
);
assert.equal(parseDefaultInputMethod("null").selected, null);
assert.equal(parseDefaultInputMethod("").selected, null);
});
test("parseEnabledImes finds the Eliza IME in an `ime list -s` set", () => {
const list = [
"com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME",
"ai.elizaos.app/.ElizaVoiceInputMethodService",
].join("\n");
const parsed = parseEnabledImes(list);
assert.equal(parsed.elizaEnabled, true);
assert.equal(parsed.enabled.length, 2);
const without = parseEnabledImes(
"com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME",
);
assert.equal(without.elizaEnabled, false);
});
test("detectSurfaceInvocation catches the IME class tag, bracket marker, and deep-link source", () => {
const byTag = detectSurfaceInvocation(
"07-04 12:00:01.234 4210 4210 I ElizaVoiceIme: [ElizaVoiceInputMethodService] opening Eliza",
{
tag: LOG_TAGS.ime,
bracket: "ElizaVoiceInputMethodService",
source: DEEP_LINK_SOURCES.ime,
},
);
assert.equal(byTag.tagHit, true);
assert.equal(byTag.bracketHit, true);
assert.equal(byTag.detected, true);
const bySource = detectSurfaceInvocation(
"ActivityTaskManager: START u0 {act=android.intent.action.VIEW dat=elizaos://voice?source=android-ime...}",
{ source: DEEP_LINK_SOURCES.ime },
);
assert.equal(bySource.sourceHit, true);
assert.equal(bySource.detected, true);
const miss = detectSurfaceInvocation("nothing relevant here", {
tag: LOG_TAGS.vis,
source: DEEP_LINK_SOURCES.assistantSession,
});
assert.equal(miss.detected, false);
});
test("assertDeepLinkLanded requires BOTH resumed MainActivity and the expected source tag", () => {
const activityDump =
" ResumedActivity: ActivityRecord{a1b2 u0 ai.elizaos.app/.MainActivity t42}";
const logcat =
"ActivityTaskManager: START u0 {dat=elizaos://voice?source=android-assistant-session&voice=1 cmp=ai.elizaos.app/.MainActivity}";
const landed = assertDeepLinkLanded(
activityDump,
logcat,
DEEP_LINK_SOURCES.assistantSession,
);
assert.equal(landed.landed, true);
assert.equal(landed.mainActivityResumed, true);
assert.equal(landed.sourceSeen, true);
// Foreground MainActivity but no matching source tag → NOT landed (guards
// against a coincidental unrelated foreground pass).
const wrongSource = assertDeepLinkLanded(
activityDump,
"unrelated log",
DEEP_LINK_SOURCES.ime,
);
assert.equal(wrongSource.mainActivityResumed, true);
assert.equal(wrongSource.landed, false);
// Source present but MainActivity not resumed → NOT landed.
const notResumed = assertDeepLinkLanded(
"ResumedActivity: com.other/.Home",
logcat,
DEEP_LINK_SOURCES.assistantSession,
);
assert.equal(notResumed.landed, false);
});
test("classifyImeAsrOutcome distinguishes committed / engineOff / modelNotReady / error", () => {
assert.equal(
classifyImeAsrOutcome(
"ElizaVoiceIme: [ElizaVoiceInputMethodService] transcript committed (14 chars)",
),
"committed",
);
assert.equal(
classifyImeAsrOutcome(
"ElizaVoiceIme: [ElizaVoiceInputMethodService] ASR loopback unreachable: Connection refused",
),
"engineOff",
);
assert.equal(
classifyImeAsrOutcome(
"ElizaVoiceIme: [ElizaVoiceInputMethodService] ASR responded 503",
),
"modelNotReady",
);
assert.equal(
classifyImeAsrOutcome(
"ElizaVoiceIme: [ElizaVoiceInputMethodService] transcription error",
),
"error",
);
assert.equal(classifyImeAsrOutcome("nothing"), "unknown");
});
test("summarizeLaneVerdict passes only when every required surface checks out", () => {
const green = {
surfacesRegistered: true,
roleHeld: true,
imeSelected: true,
voiceinteractionLanded: true,
assistKeyLanded: true,
imeLanded: true,
asrOutcome: "committed",
};
assert.equal(summarizeLaneVerdict(green, true).pass, true);
// Engine off is acceptable when the agent is NOT required...
assert.equal(
summarizeLaneVerdict({ ...green, asrOutcome: "engineOff" }, false).pass,
true,
);
// ...but a hard failure when the agent IS required (never green-by-skip).
const requiredButOff = summarizeLaneVerdict(
{ ...green, asrOutcome: "engineOff" },
true,
);
assert.equal(requiredButOff.pass, false);
assert.match(requiredButOff.failures.join(" "), /ENGINE_OFF/);
const roleMissing = summarizeLaneVerdict(
{ ...green, roleHeld: false },
false,
);
assert.equal(roleMissing.pass, false);
assert.match(roleMissing.failures.join(" "), /assistant role/);
const voiceinteractionMissing = summarizeLaneVerdict(
{ ...green, voiceinteractionLanded: false },
false,
);
assert.equal(voiceinteractionMissing.pass, false);
assert.match(voiceinteractionMissing.failures.join(" "), /voiceinteraction/);
const assistKeyMissing = summarizeLaneVerdict(
{ ...green, assistKeyLanded: false },
false,
);
assert.equal(assistKeyMissing.pass, false);
assert.match(assistKeyMissing.failures.join(" "), /KEYCODE_ASSIST/);
// An unknown ASR outcome is the emulator lane's designed state: the verify
// lane never raises the IME keyboard or captures audio, so no ASR line is
// logged and the round-trip cannot be classified. It is acceptable when the
// agent is NOT required...
assert.equal(
summarizeLaneVerdict({ ...green, asrOutcome: "unknown" }, false).pass,
true,
);
// ...but a hard failure when a full engine IS required (never green-by-skip).
const requiredButUnknown = summarizeLaneVerdict(
{ ...green, asrOutcome: "unknown" },
true,
);
assert.equal(requiredButUnknown.pass, false);
assert.match(requiredButUnknown.failures.join(" "), /unknown/);
// A transcription error always fails, engine required or not.
assert.equal(
summarizeLaneVerdict({ ...green, asrOutcome: "error" }, false).pass,
false,
);
const notRegistered = summarizeLaneVerdict(
{ ...green, surfacesRegistered: false },
false,
);
assert.equal(notRegistered.pass, false);
});
test("exported component ids match the native manifest components", () => {
assert.equal(
ASSISTANT_VIS_COMPONENT,
"ai.elizaos.app/.ElizaVoiceInteractionService",
);
assert.equal(
ASSISTANT_IME_COMPONENT,
"ai.elizaos.app/.ElizaVoiceInputMethodService",
);
});