Files
callstack__agent-device/packages/platform-android/src/adb-transfer.test.ts
Michał Pierzchała 8af2660332 refactor(android): extract the adb executor and IME cluster into packages/platform-android (#2041) (#2044)
* refactor(android): extract adb executor and IME cluster into packages/platform-android (#2041)

Implementation moves behind an injected adb host port (R13-clean); shared
vocabulary moves to contracts (android-touch-plan, android-helper-artifacts)
and kernel (keyed-lock); root keeps thin re-export shims plus the composition
wiring that binds the port. session.ts/session-observability.ts no longer
import platform transport types (opaque unknown, R62 pattern).

* refactor(android): trim shims to consumed surface, break helper-install cycle, add gate coverage

Fallow-clean: shim re-exports carry only names root still consumes;
helper-package-install imports the package subpath directly (no cycle) and
reuses the contracts decision types; transitional R13 table gets planted-red
tests; ADR-0019 records the transitional exception; eager-closure rows added
for the new entry surfaces.

* review: restore maxBuffer on spawn options, scope the transitional test allowance

Adversarial review round 1: AndroidAdbSpawnOptions keeps maxBuffer for spawn-
signature width parity with the pre-move ExecBackgroundOptions; the R13 test
allowance narrows from any test file to the cluster's own src/platforms/android
__tests__ directory, with planted-red coverage for a foreign test file; ADR
text records the scoped allowance.

* review: keep an unbound adb host port loud in listAndroidAdbSerialsQuick

* style: format policy test

* refactor(android): split the extracted adb and IME modules into focused owners

Review: the moved adb-executor.ts (652 lines) and ime-lifecycle.ts (487 lines)
carried three concerns each across the package boundary. The entry subpaths are
now thin surfaces over focused modules — adb: transport vocabulary, failure
classification+enrichment, provider normalization, provider scope/routing,
port-reverse ownership, pull/install transfers; IME: ownership state, on-device
settings record, activation transaction, restore+orphan recovery — every module
under 250 lines, each with colocated tests over an in-memory host-port stub and
a shared fake IME device. R31's fence invariant repoints to ime-activation.ts;
eager-closure pins move to the split shape.

* rebase: reconcile the transitional android table with the #2050 mechanics-facet model

The R13 declaration check now composes both enumerated subpath sets on the
exact-list model #2050 introduced; the policy test fixture declares android's
transitional subpaths the same way it declares apple's runner facet.
2026-08-26 16:40:32 +02:00

72 lines
2.3 KiB
TypeScript

import { expect, test } from 'vitest';
import { bindAndroidAdbHostStub } from './adb-host.fixtures.ts';
import { installAndroidAdbPackage, pullAndroidAdbFile } from './adb-transfer.ts';
import type { AndroidAdbExecutorResult, AndroidAdbProvider } from './adb-transport.ts';
const ok = (): AndroidAdbExecutorResult => ({ exitCode: 0, stdout: '', stderr: '' });
test('semantic provider methods win over the exec fallback and escape the override scope', async () => {
let escaped = 0;
bindAndroidAdbHostStub({
withoutAdbCommandExecutorOverride: async (fn) => {
escaped += 1;
return await fn();
},
});
const pulls: string[][] = [];
const installs: string[] = [];
const provider: AndroidAdbProvider = {
exec: async () => {
throw new Error('exec fallback must not run when semantic methods exist');
},
pull: async (remotePath, localPath) => {
pulls.push([remotePath, localPath]);
return ok();
},
install: async (apkPath) => {
installs.push(apkPath);
return ok();
},
};
await pullAndroidAdbFile('/device/a.apk', '/tmp/a.apk', { provider });
await installAndroidAdbPackage('/tmp/a.apk', { provider, replace: true });
expect(pulls).toEqual([['/device/a.apk', '/tmp/a.apk']]);
expect(installs).toEqual(['/tmp/a.apk']);
expect(escaped).toBe(2);
});
test('the exec fallback lowers semantic install options into adb flags', async () => {
bindAndroidAdbHostStub();
const calls: string[][] = [];
const provider: AndroidAdbProvider = {
exec: async (args) => {
calls.push(args);
return ok();
},
};
await installAndroidAdbPackage('/tmp/a.apk', {
provider,
replace: true,
allowTestPackages: true,
allowDowngrade: true,
grantPermissions: true,
});
await pullAndroidAdbFile('/device/a.apk', '/tmp/a.apk', { provider });
expect(calls).toEqual([
['install', '-r', '-t', '-d', '-g', '/tmp/a.apk'],
['pull', '/device/a.apk', '/tmp/a.apk'],
]);
});
test('transfers without any provider or scope fail closed', async () => {
bindAndroidAdbHostStub();
await expect(pullAndroidAdbFile('/device/a.apk', '/tmp/a.apk')).rejects.toThrow(
/requires an adb provider/,
);
await expect(installAndroidAdbPackage('/tmp/a.apk')).rejects.toThrow(/requires an adb provider/);
});