1+ import { ONE_DAY_MS , ONE_HOUR_MS , ONE_SECOND_MS } from '../../lib/time-constants.js' ;
2+ import * as action from '../commands/update/action.js' ;
3+ import * as constants from '../constants.js' ;
14import { type UpdateCheckResult , checkForUpdate , printUpdateNotification } from '../update-notifier.js' ;
2- import { rmSync } from 'fs' ;
5+ import { mkdirSync , mkdtempSync , rmSync , writeFileSync } from 'fs' ;
36import { mkdir , readFile , writeFile } from 'fs/promises' ;
7+ import { tmpdir } from 'os' ;
48import { join } from 'path' ;
59import { afterAll , afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
610
7- const tmpDir = vi . hoisted ( ( ) => {
8- /* eslint-disable @typescript-eslint/no-require-imports */
9- const fs = require ( 'fs' ) ;
10- const os = require ( 'os' ) ;
11- const path = require ( 'path' ) ;
12- /* eslint-enable @typescript-eslint/no-require-imports */
13- return fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'update-notifier-test-' ) ) ;
14- } ) ;
15-
16- vi . mock ( '../../lib/schemas/io/global-config.js' , ( ) => ( {
17- GLOBAL_CONFIG_DIR : tmpDir ,
18- } ) ) ;
19-
20- vi . mock ( '../constants.js' , async importOriginal => {
21- const actual = await importOriginal < typeof import ( '../constants.js' ) > ( ) ;
22- return { ...actual , PACKAGE_VERSION : '1.0.0' } ;
23- } ) ;
24-
25- const { mockFetchLatestVersion } = vi . hoisted ( ( ) => ( {
26- mockFetchLatestVersion : vi . fn ( ) ,
27- } ) ) ;
28-
29- vi . mock ( '../commands/update/action.js' , async importOriginal => {
30- const actual = await importOriginal < typeof import ( '../commands/update/action.js' ) > ( ) ;
31- return { ...actual , fetchLatestVersion : mockFetchLatestVersion } ;
32- } ) ;
33-
34- const CACHE_DIR = tmpDir ;
11+ const NOW = 1708646400000 ;
12+ const tmpDir = mkdtempSync ( join ( tmpdir ( ) , 'update-notifier-test-' ) ) ;
3513const CACHE_FILE = join ( tmpDir , 'update-check.json' ) ;
3614
3715describe ( 'checkForUpdate' , ( ) => {
16+ let originalConfigDir : string | undefined ;
17+
3818 beforeEach ( ( ) => {
39- vi . spyOn ( Date , 'now' ) . mockReturnValue ( 1708646400000 ) ;
40- try {
41- rmSync ( CACHE_DIR , { recursive : true } ) ;
42- } catch { }
19+ originalConfigDir = process . env . AGENTCORE_CONFIG_DIR ;
20+ process . env . AGENTCORE_CONFIG_DIR = tmpDir ;
21+ vi . spyOn ( Date , 'now' ) . mockReturnValue ( NOW ) ;
22+ vi . spyOn ( constants , 'PACKAGE_VERSION' , 'get' ) . mockReturnValue ( '1.0.0' ) ;
23+ rmSync ( tmpDir , { recursive : true , force : true } ) ;
4324 } ) ;
4425
4526 afterEach ( ( ) => {
4627 vi . restoreAllMocks ( ) ;
47- mockFetchLatestVersion . mockReset ( ) ;
28+ if ( originalConfigDir === undefined ) {
29+ delete process . env . AGENTCORE_CONFIG_DIR ;
30+ } else {
31+ process . env . AGENTCORE_CONFIG_DIR = originalConfigDir ;
32+ }
4833 } ) ;
4934
5035 afterAll ( ( ) => {
51- rmSync ( tmpDir , { recursive : true } ) ;
36+ rmSync ( tmpDir , { recursive : true , force : true } ) ;
5237 } ) ;
5338
5439 it ( 'fetches from registry when no cache exists' , async ( ) => {
55- mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
40+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockResolvedValue ( '2.0.0' ) ;
5641
5742 const result = await checkForUpdate ( ) ;
5843
5944 expect ( result ) . toEqual ( { updateAvailable : true , latestVersion : '2.0.0' } ) ;
60- expect ( mockFetchLatestVersion ) . toHaveBeenCalled ( ) ;
6145 } ) ;
6246
6347 it ( 'uses cache when last check was less than 24 hours ago' , async ( ) => {
64- await mkdir ( CACHE_DIR , { recursive : true } ) ;
65- await writeFile ( CACHE_FILE , JSON . stringify ( { lastCheck : 1708646400000 - 1000 , latestVersion : '2.0.0' } ) , 'utf-8' ) ;
48+ await mkdir ( tmpDir , { recursive : true } ) ;
49+ await writeFile ( CACHE_FILE , JSON . stringify ( { lastCheck : NOW - ONE_SECOND_MS , latestVersion : '2.0.0' } ) , 'utf-8' ) ;
6650
6751 const result = await checkForUpdate ( ) ;
6852
6953 expect ( result ) . toEqual ( { updateAvailable : true , latestVersion : '2.0.0' } ) ;
70- expect ( mockFetchLatestVersion ) . not . toHaveBeenCalled ( ) ;
7154 } ) ;
7255
7356 it ( 'fetches from registry when cache is expired' , async ( ) => {
74- await mkdir ( CACHE_DIR , { recursive : true } ) ;
57+ await mkdir ( tmpDir , { recursive : true } ) ;
7558 await writeFile (
7659 CACHE_FILE ,
77- JSON . stringify ( { lastCheck : 1708646400000 - 25 * 60 * 60 * 1000 , latestVersion : '1.5.0' } ) ,
60+ JSON . stringify ( { lastCheck : NOW - ONE_DAY_MS - ONE_HOUR_MS , latestVersion : '1.5.0' } ) ,
7861 'utf-8'
7962 ) ;
80- mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
63+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockResolvedValue ( '2.0.0' ) ;
8164
8265 const result = await checkForUpdate ( ) ;
8366
8467 expect ( result ) . toEqual ( { updateAvailable : true , latestVersion : '2.0.0' } ) ;
85- expect ( mockFetchLatestVersion ) . toHaveBeenCalled ( ) ;
8668 } ) ;
8769
8870 it ( 'writes cache after fetching' , async ( ) => {
89- mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
71+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockResolvedValue ( '2.0.0' ) ;
9072
9173 await checkForUpdate ( ) ;
9274
9375 const cached = JSON . parse ( await readFile ( CACHE_FILE , 'utf-8' ) ) ;
94- expect ( cached ) . toEqual ( { lastCheck : 1708646400000 , latestVersion : '2.0.0' } ) ;
76+ expect ( cached ) . toEqual ( { lastCheck : NOW , latestVersion : '2.0.0' } ) ;
9577 } ) ;
9678
9779 it ( 'returns updateAvailable: false when versions match' , async ( ) => {
98- mockFetchLatestVersion . mockResolvedValue ( '1.0.0' ) ;
80+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockResolvedValue ( '1.0.0' ) ;
9981
10082 const result = await checkForUpdate ( ) ;
10183
10284 expect ( result ) . toEqual ( { updateAvailable : false , latestVersion : '1.0.0' } ) ;
10385 } ) ;
10486
10587 it ( 'returns updateAvailable: false when current is newer' , async ( ) => {
106- mockFetchLatestVersion . mockResolvedValue ( '0.9.0' ) ;
88+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockResolvedValue ( '0.9.0' ) ;
10789
10890 const result = await checkForUpdate ( ) ;
10991
11092 expect ( result ) . toEqual ( { updateAvailable : false , latestVersion : '0.9.0' } ) ;
11193 } ) ;
11294
11395 it ( 'returns null on fetch error' , async ( ) => {
114- mockFetchLatestVersion . mockRejectedValue ( new Error ( 'network error' ) ) ;
96+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockRejectedValue ( new Error ( 'network error' ) ) ;
11597
11698 const result = await checkForUpdate ( ) ;
11799
118100 expect ( result ) . toBeNull ( ) ;
119101 } ) ;
120102
121103 it ( 'returns null on cache parse error and fetch error' , async ( ) => {
122- await mkdir ( CACHE_DIR , { recursive : true } ) ;
104+ await mkdir ( tmpDir , { recursive : true } ) ;
123105 await writeFile ( CACHE_FILE , 'invalid json' , 'utf-8' ) ;
124- mockFetchLatestVersion . mockRejectedValue ( new Error ( 'network error' ) ) ;
106+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockRejectedValue ( new Error ( 'network error' ) ) ;
125107
126108 const result = await checkForUpdate ( ) ;
127109
128110 expect ( result ) . toBeNull ( ) ;
129111 } ) ;
130112
131113 it ( 'succeeds even when cache write fails' , async ( ) => {
132- await mkdir ( CACHE_DIR , { recursive : true } ) ;
133- await writeFile ( CACHE_FILE , '' , 'utf-8' ) ;
134- const { chmod } = await import ( 'fs/promises' ) ;
135- await chmod ( CACHE_DIR , 0o444 ) ;
114+ // Point config dir at a regular file — mkdir/writeFile will fail because
115+ // a file can't be used as a directory. Works cross-platform and as root.
116+ mkdirSync ( tmpDir , { recursive : true } ) ;
117+ const blocker = join ( tmpDir , 'not-a-dir' ) ;
118+ writeFileSync ( blocker , '' ) ;
119+ process . env . AGENTCORE_CONFIG_DIR = blocker ;
136120
137- mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
121+ vi . spyOn ( action , 'fetchLatestVersion' ) . mockResolvedValue ( '2.0.0' ) ;
138122
139123 const result = await checkForUpdate ( ) ;
140124
141125 expect ( result ) . toEqual ( { updateAvailable : true , latestVersion : '2.0.0' } ) ;
142-
143- await chmod ( CACHE_DIR , 0o755 ) ;
144126 } ) ;
145127} ) ;
146128
@@ -153,7 +135,6 @@ describe('printUpdateNotification', () => {
153135
154136 const output = stderrSpy . mock . calls . map ( c => c [ 0 ] ) . join ( '' ) ;
155137 expect ( output ) . toContain ( 'Update available:' ) ;
156- expect ( output ) . toContain ( '1.0.0' ) ;
157138 expect ( output ) . toContain ( '2.0.0' ) ;
158139 expect ( output ) . toContain ( 'npm install -g @aws/agentcore@latest' ) ;
159140
0 commit comments