import { expect, it } from "vitest"; import { ApktoolResourceAnalysisService } from "./ApktoolResourceAnalysisService.js"; import type { ApktoolResourceAnalysisPort } from "./ApktoolResourceAnalysisPort.js"; import { createAnalysisExecution } from "../AnalysisProvider.js"; import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js"; import { parseEvidence } from "../../domain/evidence.js"; import { ok } from "../../domain/result.js"; const identity = { id: "apktool", name: "Apktool", version: null }; const execution = createAnalysisExecution( { client: { command: "/usr/bin/apktool", command_source: "path", apktool_version: "2.7.0-dirty", }, }, identity, ); const refusedPort: ApktoolResourceAnalysisPort = { async inspectAvailability() { throw new Error("must not probe"); }, async close() {}, async execute() { throw new Error("must not execute"); }, }; it.each([ [{ path: "/targets/app.apk", include_strings: "yes" }], [{ path: "/targets/app.apk", locale: "not a locale" }], [{ path: "/targets/app.apk", extra: true }], ])("rejects invalid decode selection before any effect: %j", async (input) => { const service = new ApktoolResourceAnalysisService(refusedPort); expect( await service.execute("decode_android_resources", input), ).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } }); }); it("composes observed Evidence with the caller parameters inline", async () => { const port: ApktoolResourceAnalysisPort = { async inspectAvailability() { return { status: "available", code: null, reason: null, diagnostics: {} }; }, async close() {}, async execute(request) { expect(request).toEqual({ operation: "decode_android_resources", input: { path: "/targets/app.apk", include_strings: true }, }); return ok(execution); }, }; const result = await new ApktoolResourceAnalysisService(port).execute( "decode_android_resources", { path: "/targets/app.apk", include_strings: true }, ); if (!result.ok) throw new Error(result.error.message); const evidence = parseEvidence(result.value); expect(evidence.provider).toEqual(identity); expect(evidence.operation).toBe("decode_android_resources"); expect(evidence.parameters).toEqual({ path: "/targets/app.apk", include_strings: true, }); expect(evidence.confidence).toBe("observed"); }); it("preserves cancellation before execution", async () => { const controller = new AbortController(); controller.abort(); const service = new ApktoolResourceAnalysisService(refusedPort); const result = await service.execute( "decode_android_resources", { path: "/targets/app.apk", include_strings: true }, { signal: controller.signal }, ); expect(result.ok).toBe(false); if (result.ok) return; expect(result.error).toBeInstanceOf(AnalysisCancelledError); });