11import { strict as assert } from "node:assert" ;
2- import { chmodSync , mkdtempSync , readFileSync , rmSync , writeFileSync } from "node:fs" ;
2+ import {
3+ chmodSync ,
4+ mkdtempSync ,
5+ readFileSync ,
6+ rmSync ,
7+ statSync ,
8+ symlinkSync ,
9+ readdirSync ,
10+ writeFileSync ,
11+ } from "node:fs" ;
312import http from "node:http" ;
413import { tmpdir } from "node:os" ;
5- import { join } from "node:path" ;
14+ import { basename , dirname , join } from "node:path" ;
615import { test } from "node:test" ;
716import { HEYGEN_NOT_AUTHENTICATED_MESSAGE } from "./heygen-cli.mjs" ;
817import { AVATAR_VIDEO_SIGNIN_MESSAGE } from "./heygen-video-provider.mjs" ;
918
19+ function cleanupDownload ( localPath ) {
20+ if (
21+ basename ( localPath ) === "video.mp4" &&
22+ basename ( dirname ( localPath ) ) . startsWith ( "media-use-heygen-video-" )
23+ ) {
24+ rmSync ( dirname ( localPath ) , { recursive : true , force : true } ) ;
25+ } else {
26+ rmSync ( localPath , { force : true } ) ;
27+ }
28+ }
29+
1030const VIDEO_FIXTURE = Buffer . from ( "tiny heygen video fixture" ) ;
1131let importCount = 0 ;
1232
@@ -148,7 +168,10 @@ test("downloads a generated avatar video and returns the generated MP4 result",
148168 } ) ;
149169 assert . ok ( result ) ;
150170 assert . equal ( join ( tmpdir ( ) , result . localPath . slice ( tmpdir ( ) . length + 1 ) ) , result . localPath ) ;
151- assert . match ( result . localPath , / m e d i a - u s e - h e y g e n - v i d e o - \d + - \d + \. m p 4 $ / ) ;
171+ assert . match ( result . localPath , / m e d i a - u s e - h e y g e n - v i d e o - [ ^ / \\ ] + [ / \\ ] v i d e o \. m p 4 $ / ) ;
172+ if ( process . platform !== "win32" ) {
173+ assert . equal ( statSync ( dirname ( result . localPath ) ) . mode & 0o777 , 0o700 ) ;
174+ }
152175 assert . deepEqual ( result , {
153176 localPath : result . localPath ,
154177 ext : ".mp4" ,
@@ -163,7 +186,7 @@ test("downloads a generated avatar video and returns the generated MP4 result",
163186 } ,
164187 ) ;
165188 } finally {
166- if ( localPath ) rmSync ( localPath , { force : true } ) ;
189+ if ( localPath ) cleanupDownload ( localPath ) ;
167190 await closeServer ( server ) ;
168191 }
169192} ) ;
@@ -190,7 +213,7 @@ test("tags video creation but not avatar or voice discovery", async () => {
190213 } ,
191214 ) ;
192215 } finally {
193- if ( localPath ) rmSync ( localPath , { force : true } ) ;
216+ if ( localPath ) cleanupDownload ( localPath ) ;
194217 await closeServer ( server ) ;
195218 }
196219} ) ;
@@ -221,7 +244,7 @@ test("uses explicit avatar and voice overrides without discovery", async () => {
221244 } ,
222245 ) ;
223246 } finally {
224- if ( localPath ) rmSync ( localPath , { force : true } ) ;
247+ if ( localPath ) cleanupDownload ( localPath ) ;
225248 await closeServer ( server ) ;
226249 }
227250} ) ;
@@ -246,7 +269,7 @@ test("caches discovered avatar and voice IDs for the process", async () => {
246269 } ,
247270 ) ;
248271 } finally {
249- for ( const localPath of localPaths ) rmSync ( localPath , { force : true } ) ;
272+ for ( const localPath of localPaths ) cleanupDownload ( localPath ) ;
250273 await closeServer ( server ) ;
251274 }
252275} ) ;
@@ -342,3 +365,61 @@ test("download failure after a successful create returns null and logs a diagnos
342365 await closeServer ( server ) ;
343366 }
344367} ) ;
368+
369+ test ( "uses private unique downloads even when time is fixed and the old name is planted" , async ( t ) => {
370+ const root = mkdtempSync ( join ( tmpdir ( ) , "hf-video-temp-test-" ) ) ;
371+ const previousTmpdir = process . env . TMPDIR ;
372+ process . env . TMPDIR = root ;
373+ t . mock . method ( Date , "now" , ( ) => 123456 ) ;
374+ t . mock . method ( globalThis , "fetch" , async ( ) => ( {
375+ ok : true ,
376+ headers : { get : ( ) => "4" } ,
377+ body : [ Buffer . from ( "new!" ) ] ,
378+ } ) ) ;
379+ try {
380+ const victim = join ( root , "victim" ) ;
381+ writeFileSync ( victim , "unchanged" ) ;
382+ symlinkSync ( victim , join ( root , `media-use-heygen-video-${ process . pid } -123456.mp4` ) ) ;
383+ await withFakeHeygen (
384+ { response : JSON . stringify ( { data : { video_url : "https://example.invalid/video.mp4" } } ) } ,
385+ async ( ) => {
386+ const generate = await freshGenerate ( ) ;
387+ const first = await generate ( "First" , { avatarId : "a" , voiceId : "v" } ) ;
388+ const second = await generate ( "Second" , { avatarId : "a" , voiceId : "v" } ) ;
389+ assert . ok ( first && second ) ;
390+ assert . notEqual ( first . localPath , second . localPath ) ;
391+ assert . equal ( readFileSync ( first . localPath , "utf8" ) , "new!" ) ;
392+ assert . equal ( readFileSync ( second . localPath , "utf8" ) , "new!" ) ;
393+ assert . equal ( readFileSync ( victim , "utf8" ) , "unchanged" ) ;
394+ } ,
395+ ) ;
396+ } finally {
397+ if ( previousTmpdir === undefined ) delete process . env . TMPDIR ;
398+ else process . env . TMPDIR = previousTmpdir ;
399+ rmSync ( root , { recursive : true , force : true } ) ;
400+ }
401+ } ) ;
402+
403+ test ( "removes private download staging on failure while returning null" , async ( t ) => {
404+ const root = mkdtempSync ( join ( tmpdir ( ) , "hf-video-temp-fail-" ) ) ;
405+ const previousTmpdir = process . env . TMPDIR ;
406+ process . env . TMPDIR = root ;
407+ t . mock . method ( globalThis , "fetch" , async ( ) => {
408+ throw new Error ( "download failed" ) ;
409+ } ) ;
410+ t . mock . method ( console , "error" , ( ) => { } ) ;
411+ try {
412+ await withFakeHeygen (
413+ { response : JSON . stringify ( { data : { video_url : "https://example.invalid/video.mp4" } } ) } ,
414+ async ( ) => {
415+ const generate = await freshGenerate ( ) ;
416+ assert . equal ( await generate ( "Failure" , { avatarId : "a" , voiceId : "v" } ) , null ) ;
417+ } ,
418+ ) ;
419+ assert . deepEqual ( readdirSync ( root ) , [ ] ) ;
420+ } finally {
421+ if ( previousTmpdir === undefined ) delete process . env . TMPDIR ;
422+ else process . env . TMPDIR = previousTmpdir ;
423+ rmSync ( root , { recursive : true , force : true } ) ;
424+ }
425+ } ) ;
0 commit comments