@@ -16,8 +16,8 @@ const mockNet = vi.hoisted(() => ({
1616 fetch : vi . fn ( ) ,
1717} ) ) ;
1818
19- const mockExecFileAsync = vi . hoisted ( ( ) =>
20- vi . fn < ( cmd : string , args : string [ ] ) => Promise < unknown > > ( async ( ) => { } ) ,
19+ const mockExtractZip = vi . hoisted ( ( ) =>
20+ vi . fn < ( zipPath : string , extractDir : string ) => Promise < void > > ( async ( ) => { } ) ,
2121) ;
2222
2323vi . mock ( "electron" , ( ) => ( {
@@ -35,14 +35,8 @@ vi.mock("node:fs/promises", async () => {
3535 return { ...fs . promises , default : fs . promises } ;
3636} ) ;
3737
38- vi . mock ( "node:child_process" , ( ) => ( {
39- execFile : vi . fn ( ) ,
40- default : { execFile : vi . fn ( ) } ,
41- } ) ) ;
42-
43- vi . mock ( "node:util" , ( ) => ( {
44- promisify : ( ) => mockExecFileAsync ,
45- default : { promisify : ( ) => mockExecFileAsync } ,
38+ vi . mock ( "../../lib/extract-zip.js" , ( ) => ( {
39+ extractZip : mockExtractZip ,
4640} ) ) ;
4741
4842vi . mock ( "node:os" , ( ) => ( {
@@ -82,21 +76,19 @@ function mockFetchResponse(ok: boolean, status = 200) {
8276 } ;
8377}
8478
85- /** Simulate unzip by creating skill files in the extracted dir */
86- function simulateUnzip ( ) {
87- mockExecFileAsync . mockImplementation ( async ( _cmd : string , args : string [ ] ) => {
88- const dIdx = args . indexOf ( "-d" ) ;
89- if ( dIdx >= 0 ) {
90- const extractDir = args [ dIdx + 1 ] ;
79+ /** Simulate zip extraction by creating skill files in the extracted dir */
80+ function simulateExtractZip ( ) {
81+ mockExtractZip . mockImplementation (
82+ async ( _zipPath : string , extractDir : string ) => {
9183 vol . mkdirSync ( `${ extractDir } /skills/remote-skill` , {
9284 recursive : true ,
9385 } ) ;
9486 vol . writeFileSync (
9587 `${ extractDir } /skills/remote-skill/SKILL.md` ,
9688 "# Remote" ,
9789 ) ;
98- }
99- } ) ;
90+ } ,
91+ ) ;
10092}
10193
10294/** Create the bundled plugin directory in memfs */
@@ -116,7 +108,7 @@ describe("PosthogPluginService", () => {
116108
117109 mockApp . isPackaged = false ;
118110 mockNet . fetch . mockResolvedValue ( mockFetchResponse ( true ) ) ;
119- mockExecFileAsync . mockResolvedValue ( { } ) ;
111+ mockExtractZip . mockResolvedValue ( undefined ) ;
120112
121113 service = new PosthogPluginService ( ) ;
122114 } ) ;
@@ -204,7 +196,7 @@ describe("PosthogPluginService", () => {
204196 describe ( "updateSkills" , ( ) => {
205197 it ( "downloads, extracts, and installs skills" , async ( ) => {
206198 setupBundledPlugin ( ) ;
207- simulateUnzip ( ) ;
199+ simulateExtractZip ( ) ;
208200
209201 await service . updateSkills ( ) ;
210202
@@ -215,10 +207,7 @@ describe("PosthogPluginService", () => {
215207 expect ( mockNet . fetch ) . toHaveBeenCalledWith (
216208 "https://example.com/skills.zip" ,
217209 ) ;
218- expect ( mockExecFileAsync ) . toHaveBeenCalledWith (
219- "unzip" ,
220- expect . arrayContaining ( [ "-o" ] ) ,
221- ) ;
210+ expect ( mockExtractZip ) . toHaveBeenCalled ( ) ;
222211 } ) ;
223212
224213 it ( "performs atomic swap of skills directory" , async ( ) => {
@@ -227,7 +216,7 @@ describe("PosthogPluginService", () => {
227216 vol . mkdirSync ( `${ RUNTIME_SKILLS_DIR } /old-skill` , { recursive : true } ) ;
228217 vol . writeFileSync ( `${ RUNTIME_SKILLS_DIR } /old-skill/SKILL.md` , "# Old" ) ;
229218
230- simulateUnzip ( ) ;
219+ simulateExtractZip ( ) ;
231220 await service . updateSkills ( ) ;
232221
233222 // New skill should be present, old skill should be gone
@@ -245,7 +234,7 @@ describe("PosthogPluginService", () => {
245234 vol . mkdirSync ( RUNTIME_PLUGIN_DIR , { recursive : true } ) ;
246235 vol . writeFileSync ( `${ RUNTIME_PLUGIN_DIR } /plugin.json` , "{}" ) ;
247236
248- simulateUnzip ( ) ;
237+ simulateExtractZip ( ) ;
249238 await service . updateSkills ( ) ;
250239
251240 expect (
@@ -254,7 +243,7 @@ describe("PosthogPluginService", () => {
254243 } ) ;
255244
256245 it ( "emits 'updated' event on success" , async ( ) => {
257- simulateUnzip ( ) ;
246+ simulateExtractZip ( ) ;
258247 const handler = vi . fn ( ) ;
259248 service . on ( "skillsUpdated" , handler ) ;
260249
@@ -264,7 +253,7 @@ describe("PosthogPluginService", () => {
264253 } ) ;
265254
266255 it ( "throttles: skips if called within 30 minutes" , async ( ) => {
267- simulateUnzip ( ) ;
256+ simulateExtractZip ( ) ;
268257 await service . updateSkills ( ) ;
269258 mockNet . fetch . mockClear ( ) ;
270259
@@ -274,7 +263,7 @@ describe("PosthogPluginService", () => {
274263 } ) ;
275264
276265 it ( "allows update after throttle period expires" , async ( ) => {
277- simulateUnzip ( ) ;
266+ simulateExtractZip ( ) ;
278267 await service . updateSkills ( ) ;
279268 mockNet . fetch . mockClear ( ) ;
280269
@@ -319,15 +308,11 @@ describe("PosthogPluginService", () => {
319308 } ) ;
320309
321310 it ( "handles missing skills dir in archive" , async ( ) => {
322- // Unzip creates no skills directory
323- mockExecFileAsync . mockImplementation (
324- async ( _cmd : string , args : string [ ] ) => {
325- const dIdx = args . indexOf ( "-d" ) ;
326- if ( dIdx >= 0 ) {
327- const extractDir = args [ dIdx + 1 ] ;
328- vol . mkdirSync ( `${ extractDir } /random-dir` , { recursive : true } ) ;
329- vol . writeFileSync ( `${ extractDir } /random-dir/README.md` , "nope" ) ;
330- }
311+ // Extraction creates no skills directory
312+ mockExtractZip . mockImplementation (
313+ async ( _zipPath : string , extractDir : string ) => {
314+ vol . mkdirSync ( `${ extractDir } /random-dir` , { recursive : true } ) ;
315+ vol . writeFileSync ( `${ extractDir } /random-dir/README.md` , "nope" ) ;
331316 } ,
332317 ) ;
333318
@@ -339,7 +324,7 @@ describe("PosthogPluginService", () => {
339324 } ) ;
340325
341326 it ( "cleans up temp dir even on error" , async ( ) => {
342- mockExecFileAsync . mockRejectedValue ( new Error ( "unzip failed" ) ) ;
327+ mockExtractZip . mockRejectedValue ( new Error ( "extraction failed" ) ) ;
343328
344329 await service . updateSkills ( ) ;
345330
0 commit comments