@@ -3,14 +3,16 @@ import crypto from 'node:crypto';
33import { promises as fs } from 'node:fs' ;
44import os from 'node:os' ;
55import path from 'node:path' ;
6- import { test } from 'vitest' ;
6+ import { beforeEach , test } from 'vitest' ;
77import {
88 captureAndroidSnapshotWithHelper ,
99 ensureAndroidSnapshotHelper ,
10+ forgetAndroidSnapshotHelperInstall ,
1011 parseAndroidSnapshotHelperManifest ,
1112 parseAndroidSnapshotHelperOutput ,
1213 parseAndroidSnapshotHelperXml ,
1314 prepareAndroidSnapshotHelperArtifactFromManifestUrl ,
15+ resetAndroidSnapshotHelperInstallCache ,
1416 verifyAndroidSnapshotHelperArtifact ,
1517 type AndroidAdbExecutor ,
1618 type AndroidSnapshotHelperManifest ,
@@ -31,6 +33,10 @@ const manifest: AndroidSnapshotHelperManifest = {
3133 installArgs : [ 'install' , '-r' , '-t' ] ,
3234} ;
3335
36+ beforeEach ( ( ) => {
37+ resetAndroidSnapshotHelperInstallCache ( ) ;
38+ } ) ;
39+
3440test ( 'parseAndroidSnapshotHelperOutput reconstructs XML chunks and metadata' , ( ) => {
3541 const xml = '<?xml version="1.0"?><hierarchy><node text="first second" /></hierarchy>' ;
3642 const output = helperOutput ( {
@@ -212,6 +218,132 @@ test('ensureAndroidSnapshotHelper installs when missing and skips current versio
212218 assert . equal ( skipped . reason , 'current' ) ;
213219} ) ;
214220
221+ test ( 'ensureAndroidSnapshotHelper caches successful install checks per device and helper version' , async ( ) => {
222+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'snapshot-helper-install-cache-' ) ) ;
223+ const apkPath = path . join ( tmpDir , 'helper.apk' ) ;
224+ await fs . writeFile ( apkPath , 'helper-apk' ) ;
225+ const localManifest = {
226+ ...manifest ,
227+ sha256 : sha256Text ( 'helper-apk' ) ,
228+ } ;
229+ const calls : string [ ] [ ] = [ ] ;
230+ const adb : AndroidAdbExecutor = async ( args ) => {
231+ calls . push ( args ) ;
232+ if ( args . includes ( '--show-versioncode' ) ) {
233+ return { exitCode : 1 , stdout : '' , stderr : 'not found' } ;
234+ }
235+ return { exitCode : 0 , stdout : '' , stderr : '' } ;
236+ } ;
237+ const artifact = { apkPath, manifest : localManifest } ;
238+
239+ const installed = await ensureAndroidSnapshotHelper ( {
240+ adb,
241+ artifact,
242+ deviceKey : 'android:emulator-5554' ,
243+ } ) ;
244+ const cached = await ensureAndroidSnapshotHelper ( {
245+ adb,
246+ artifact,
247+ deviceKey : 'android:emulator-5554' ,
248+ } ) ;
249+
250+ assert . equal ( installed . reason , 'missing' ) ;
251+ assert . equal ( cached . reason , 'current' ) ;
252+ assert . equal ( cached . installed , false ) ;
253+ assert . equal ( cached . installedVersionCode , localManifest . versionCode ) ;
254+ assert . deepEqual ( calls , [
255+ [
256+ 'shell' ,
257+ 'cmd' ,
258+ 'package' ,
259+ 'list' ,
260+ 'packages' ,
261+ '--show-versioncode' ,
262+ localManifest . packageName ,
263+ ] ,
264+ [ 'install' , '-r' , '-t' , apkPath ] ,
265+ ] ) ;
266+
267+ await ensureAndroidSnapshotHelper ( {
268+ adb,
269+ artifact,
270+ deviceKey : 'android:device-2' ,
271+ } ) ;
272+ assert . equal ( calls . length , 4 ) ;
273+
274+ forgetAndroidSnapshotHelperInstall ( {
275+ deviceKey : 'android:emulator-5554' ,
276+ packageName : localManifest . packageName ,
277+ versionCode : localManifest . versionCode ,
278+ } ) ;
279+ await ensureAndroidSnapshotHelper ( {
280+ adb,
281+ artifact,
282+ deviceKey : 'android:emulator-5554' ,
283+ } ) ;
284+ assert . equal ( calls . length , 6 ) ;
285+ } ) ;
286+
287+ test ( 'ensureAndroidSnapshotHelper always policy bypasses cached install result' , async ( ) => {
288+ const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'snapshot-helper-install-always-' ) ) ;
289+ const apkPath = path . join ( tmpDir , 'helper.apk' ) ;
290+ await fs . writeFile ( apkPath , 'helper-apk' ) ;
291+ const localManifest = {
292+ ...manifest ,
293+ sha256 : sha256Text ( 'helper-apk' ) ,
294+ } ;
295+ const calls : string [ ] [ ] = [ ] ;
296+ const adb : AndroidAdbExecutor = async ( args ) => {
297+ calls . push ( args ) ;
298+ if ( args . includes ( '--show-versioncode' ) ) {
299+ return {
300+ exitCode : 0 ,
301+ stdout : `package:${ localManifest . packageName } versionCode:${ localManifest . versionCode } ` ,
302+ stderr : '' ,
303+ } ;
304+ }
305+ return { exitCode : 0 , stdout : '' , stderr : '' } ;
306+ } ;
307+ const artifact = { apkPath, manifest : localManifest } ;
308+
309+ const cached = await ensureAndroidSnapshotHelper ( {
310+ adb,
311+ artifact,
312+ deviceKey : 'android:emulator-5554' ,
313+ } ) ;
314+ const forced = await ensureAndroidSnapshotHelper ( {
315+ adb,
316+ artifact,
317+ deviceKey : 'android:emulator-5554' ,
318+ installPolicy : 'always' ,
319+ } ) ;
320+
321+ assert . equal ( cached . reason , 'current' ) ;
322+ assert . equal ( forced . reason , 'forced' ) ;
323+ assert . equal ( forced . installed , true ) ;
324+ assert . deepEqual ( calls , [
325+ [
326+ 'shell' ,
327+ 'cmd' ,
328+ 'package' ,
329+ 'list' ,
330+ 'packages' ,
331+ '--show-versioncode' ,
332+ localManifest . packageName ,
333+ ] ,
334+ [
335+ 'shell' ,
336+ 'cmd' ,
337+ 'package' ,
338+ 'list' ,
339+ 'packages' ,
340+ '--show-versioncode' ,
341+ localManifest . packageName ,
342+ ] ,
343+ [ 'install' , '-r' , '-t' , apkPath ] ,
344+ ] ) ;
345+ } ) ;
346+
215347test ( 'verifyAndroidSnapshotHelperArtifact rejects checksum mismatch' , async ( ) => {
216348 const tmpDir = await fs . mkdtemp ( path . join ( os . tmpdir ( ) , 'snapshot-helper-sha-' ) ) ;
217349 const apkPath = path . join ( tmpDir , 'helper.apk' ) ;
0 commit comments