11import { type UpdateCheckResult , checkForUpdate , printUpdateNotification } from '../update-notifier.js' ;
2- import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
3-
4- const { mockReadFile, mockWriteFile, mockMkdir } = vi . hoisted ( ( ) => ( {
5- mockReadFile : vi . fn ( ) ,
6- mockWriteFile : vi . fn ( ) ,
7- mockMkdir : vi . fn ( ) ,
8- } ) ) ;
2+ import { rmSync } from 'fs' ;
3+ import { mkdir , readFile , writeFile } from 'fs/promises' ;
4+ import { join } from 'path' ;
5+ import { afterAll , afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
6+
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+ } ) ;
915
10- vi . mock ( 'fs/promises' , ( ) => ( {
11- readFile : mockReadFile ,
12- writeFile : mockWriteFile ,
13- mkdir : mockMkdir ,
16+ vi . mock ( '../../lib/schemas/io/global-config.js' , ( ) => ( {
17+ GLOBAL_CONFIG_DIR : tmpDir ,
1418} ) ) ;
1519
16- vi . mock ( '../constants.js' , ( ) => ( {
17- PACKAGE_VERSION : '1.0.0' ,
18- } ) ) ;
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+ } ) ;
1924
20- const { mockFetchLatestVersion, mockCompareVersions } = vi . hoisted ( ( ) => ( {
25+ const { mockFetchLatestVersion } = vi . hoisted ( ( ) => ( {
2126 mockFetchLatestVersion : vi . fn ( ) ,
22- mockCompareVersions : vi . fn ( ) ,
2327} ) ) ;
2428
25- vi . mock ( '../commands/update/action.js' , ( ) => ( {
26- fetchLatestVersion : mockFetchLatestVersion ,
27- compareVersions : mockCompareVersions ,
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 ;
35+ const CACHE_FILE = join ( tmpDir , 'update-check.json' ) ;
2936
3037describe ( 'checkForUpdate' , ( ) => {
3138 beforeEach ( ( ) => {
3239 vi . spyOn ( Date , 'now' ) . mockReturnValue ( 1708646400000 ) ;
33- mockWriteFile . mockResolvedValue ( undefined ) ;
34- mockMkdir . mockResolvedValue ( undefined ) ;
40+ try {
41+ rmSync ( CACHE_DIR , { recursive : true } ) ;
42+ } catch { }
3543 } ) ;
3644
3745 afterEach ( ( ) => {
3846 vi . restoreAllMocks ( ) ;
39- mockReadFile . mockReset ( ) ;
40- mockWriteFile . mockReset ( ) ;
41- mockMkdir . mockReset ( ) ;
4247 mockFetchLatestVersion . mockReset ( ) ;
43- mockCompareVersions . mockReset ( ) ;
48+ } ) ;
49+
50+ afterAll ( ( ) => {
51+ rmSync ( tmpDir , { recursive : true } ) ;
4452 } ) ;
4553
4654 it ( 'fetches from registry when no cache exists' , async ( ) => {
47- mockReadFile . mockRejectedValue ( new Error ( 'ENOENT' ) ) ;
4855 mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
49- mockCompareVersions . mockReturnValue ( 1 ) ;
5056
5157 const result = await checkForUpdate ( ) ;
5258
@@ -55,12 +61,8 @@ describe('checkForUpdate', () => {
5561 } ) ;
5662
5763 it ( 'uses cache when last check was less than 24 hours ago' , async ( ) => {
58- const cache = JSON . stringify ( {
59- lastCheck : 1708646400000 - 1000 , // 1 second ago
60- latestVersion : '2.0.0' ,
61- } ) ;
62- mockReadFile . mockResolvedValue ( cache ) ;
63- mockCompareVersions . mockReturnValue ( 1 ) ;
64+ await mkdir ( CACHE_DIR , { recursive : true } ) ;
65+ await writeFile ( CACHE_FILE , JSON . stringify ( { lastCheck : 1708646400000 - 1000 , latestVersion : '2.0.0' } ) , 'utf-8' ) ;
6466
6567 const result = await checkForUpdate ( ) ;
6668
@@ -69,13 +71,13 @@ describe('checkForUpdate', () => {
6971 } ) ;
7072
7173 it ( 'fetches from registry when cache is expired' , async ( ) => {
72- const cache = JSON . stringify ( {
73- lastCheck : 1708646400000 - 25 * 60 * 60 * 1000 , // 25 hours ago
74- latestVersion : '1.5.0' ,
75- } ) ;
76- mockReadFile . mockResolvedValue ( cache ) ;
74+ await mkdir ( CACHE_DIR , { recursive : true } ) ;
75+ await writeFile (
76+ CACHE_FILE ,
77+ JSON . stringify ( { lastCheck : 1708646400000 - 25 * 60 * 60 * 1000 , latestVersion : '1.5.0' } ) ,
78+ 'utf-8'
79+ ) ;
7780 mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
78- mockCompareVersions . mockReturnValue ( 1 ) ;
7981
8082 const result = await checkForUpdate ( ) ;
8183
@@ -84,42 +86,31 @@ describe('checkForUpdate', () => {
8486 } ) ;
8587
8688 it ( 'writes cache after fetching' , async ( ) => {
87- mockReadFile . mockRejectedValue ( new Error ( 'ENOENT' ) ) ;
8889 mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
89- mockCompareVersions . mockReturnValue ( 1 ) ;
9090
9191 await checkForUpdate ( ) ;
9292
93- expect ( mockMkdir ) . toHaveBeenCalled ( ) ;
94- expect ( mockWriteFile ) . toHaveBeenCalledWith (
95- expect . stringContaining ( 'update-check.json' ) ,
96- JSON . stringify ( { lastCheck : 1708646400000 , latestVersion : '2.0.0' } ) ,
97- 'utf-8'
98- ) ;
93+ const cached = JSON . parse ( await readFile ( CACHE_FILE , 'utf-8' ) ) ;
94+ expect ( cached ) . toEqual ( { lastCheck : 1708646400000 , latestVersion : '2.0.0' } ) ;
9995 } ) ;
10096
10197 it ( 'returns updateAvailable: false when versions match' , async ( ) => {
102- mockReadFile . mockRejectedValue ( new Error ( 'ENOENT' ) ) ;
10398 mockFetchLatestVersion . mockResolvedValue ( '1.0.0' ) ;
104- mockCompareVersions . mockReturnValue ( 0 ) ;
10599
106100 const result = await checkForUpdate ( ) ;
107101
108102 expect ( result ) . toEqual ( { updateAvailable : false , latestVersion : '1.0.0' } ) ;
109103 } ) ;
110104
111105 it ( 'returns updateAvailable: false when current is newer' , async ( ) => {
112- mockReadFile . mockRejectedValue ( new Error ( 'ENOENT' ) ) ;
113106 mockFetchLatestVersion . mockResolvedValue ( '0.9.0' ) ;
114- mockCompareVersions . mockReturnValue ( - 1 ) ;
115107
116108 const result = await checkForUpdate ( ) ;
117109
118110 expect ( result ) . toEqual ( { updateAvailable : false , latestVersion : '0.9.0' } ) ;
119111 } ) ;
120112
121113 it ( 'returns null on fetch error' , async ( ) => {
122- mockReadFile . mockRejectedValue ( new Error ( 'ENOENT' ) ) ;
123114 mockFetchLatestVersion . mockRejectedValue ( new Error ( 'network error' ) ) ;
124115
125116 const result = await checkForUpdate ( ) ;
@@ -128,7 +119,8 @@ describe('checkForUpdate', () => {
128119 } ) ;
129120
130121 it ( 'returns null on cache parse error and fetch error' , async ( ) => {
131- mockReadFile . mockResolvedValue ( 'invalid json' ) ;
122+ await mkdir ( CACHE_DIR , { recursive : true } ) ;
123+ await writeFile ( CACHE_FILE , 'invalid json' , 'utf-8' ) ;
132124 mockFetchLatestVersion . mockRejectedValue ( new Error ( 'network error' ) ) ;
133125
134126 const result = await checkForUpdate ( ) ;
@@ -137,14 +129,18 @@ describe('checkForUpdate', () => {
137129 } ) ;
138130
139131 it ( 'succeeds even when cache write fails' , async ( ) => {
140- mockReadFile . mockRejectedValue ( new Error ( 'ENOENT' ) ) ;
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 ) ;
136+
141137 mockFetchLatestVersion . mockResolvedValue ( '2.0.0' ) ;
142- mockCompareVersions . mockReturnValue ( 1 ) ;
143- mockWriteFile . mockRejectedValue ( new Error ( 'EACCES' ) ) ;
144138
145139 const result = await checkForUpdate ( ) ;
146140
147141 expect ( result ) . toEqual ( { updateAvailable : true , latestVersion : '2.0.0' } ) ;
142+
143+ await chmod ( CACHE_DIR , 0o755 ) ;
148144 } ) ;
149145} ) ;
150146
0 commit comments