11import { EventEmitter } from "node:events" ;
2- import { existsSync , mkdtempSync , rmSync } from "node:fs" ;
2+ import { existsSync , mkdtempSync , readFileSync , readdirSync , rmSync } from "node:fs" ;
33import { tmpdir } from "node:os" ;
44import { join } from "node:path" ;
55import { PassThrough } from "node:stream" ;
66import { get as httpsGet } from "node:https" ;
77import type { ClientRequest , IncomingMessage } from "node:http" ;
88import { afterEach , describe , expect , it , vi } from "vitest" ;
9- import { downloadFile } from "./download.js" ;
9+ import { downloadFile , REDIRECT_CODES , redirectTarget } from "./download.js" ;
1010
1111const { unlinkSyncMock } = vi . hoisted ( ( ) => ( {
1212 unlinkSyncMock : vi . fn ( ) ,
@@ -22,12 +22,61 @@ vi.mock("node:fs", async (importOriginal) => {
2222const mockGet = vi . mocked ( httpsGet ) ;
2323const tempDirs : string [ ] = [ ] ;
2424
25+ function httpsResponse (
26+ statusCode : number ,
27+ headers : Record < string , string > = { } ,
28+ body ?: string | ( ( url : string ) => string ) ,
29+ ) : typeof httpsGet {
30+ return ( ( _url : string , callback : ( response : IncomingMessage ) => void ) => {
31+ const response = new PassThrough ( ) as PassThrough & {
32+ statusCode : number ;
33+ headers : Record < string , string > ;
34+ } ;
35+ response . statusCode = statusCode ;
36+ response . headers = headers ;
37+ const request = new EventEmitter ( ) as ClientRequest ;
38+ request . setTimeout = vi . fn ( ) ;
39+ callback ( response as unknown as IncomingMessage ) ;
40+ if ( body !== undefined ) {
41+ queueMicrotask ( ( ) => response . end ( typeof body === "function" ? body ( _url ) : body ) ) ;
42+ }
43+ return request ;
44+ } ) as typeof httpsGet ;
45+ }
46+
2547afterEach ( ( ) => {
2648 vi . clearAllMocks ( ) ;
2749 for ( const dir of tempDirs . splice ( 0 ) ) rmSync ( dir , { recursive : true , force : true } ) ;
2850} ) ;
2951
3052describe ( "downloadFile" , ( ) => {
53+ it ( "keeps concurrent partial downloads separate" , async ( ) => {
54+ mockGet . mockImplementation ( httpsResponse ( 200 , { } , ( url ) => url ) ) ;
55+
56+ const dir = mkdtempSync ( join ( tmpdir ( ) , "hyperframes-download-" ) ) ;
57+ tempDirs . push ( dir ) ;
58+ const dest = join ( dir , "model.onnx" ) ;
59+ const first = "https://example.test/first" ;
60+ const second = "https://example.test/second" ;
61+
62+ await Promise . all ( [ downloadFile ( first , dest ) , downloadFile ( second , dest ) ] ) ;
63+ expect ( [ first , second ] ) . toContain ( readFileSync ( dest , "utf-8" ) ) ;
64+ } ) ;
65+
66+ it ( "rejects a response that exceeds its byte limit" , async ( ) => {
67+ mockGet . mockImplementation ( httpsResponse ( 200 , { } , "too large" ) ) ;
68+
69+ const dir = mkdtempSync ( join ( tmpdir ( ) , "hyperframes-download-" ) ) ;
70+ tempDirs . push ( dir ) ;
71+ const dest = join ( dir , "model.onnx" ) ;
72+
73+ await expect (
74+ downloadFile ( "https://example.test/model.onnx" , dest , { maxBytes : 3 } ) ,
75+ ) . rejects . toThrow ( "Download exceeded 3 bytes" ) ;
76+ expect ( existsSync ( dest ) ) . toBe ( false ) ;
77+ expect ( readdirSync ( dir ) ) . toEqual ( [ ] ) ;
78+ } ) ;
79+
3180 it ( "rejects an idle response and removes the partial file" , async ( ) => {
3281 const actualFs = await vi . importActual < typeof import ( "node:fs" ) > ( "node:fs" ) ;
3382 let responseClosed = false ;
@@ -87,3 +136,59 @@ describe("downloadFile", () => {
87136 expect ( existsSync ( dest ) ) . toBe ( false ) ;
88137 } ) ;
89138} ) ;
139+
140+ describe ( "redirect handling" , ( ) => {
141+ it ( "rejects a redirect that the HTTPS client cannot follow" , async ( ) => {
142+ mockGet
143+ . mockImplementationOnce (
144+ httpsResponse ( 307 , { location : "http://cdn.example.test/model.onnx" } ) ,
145+ )
146+ . mockImplementationOnce ( ( ) => {
147+ throw new TypeError ( 'Protocol "http:" not supported' ) ;
148+ } ) ;
149+
150+ const dir = mkdtempSync ( join ( tmpdir ( ) , "hyperframes-download-" ) ) ;
151+ tempDirs . push ( dir ) ;
152+ await expect (
153+ downloadFile ( "https://example.test/model.onnx" , join ( dir , "model.onnx" ) ) ,
154+ ) . rejects . toThrow ( 'Protocol "http:" not supported' ) ;
155+ } ) ;
156+
157+ it ( "follows every redirect a host may answer with" , ( ) => {
158+ // 307 is the one that broke: HuggingFace answers the tokenizer with it,
159+ // and the original set stopped at 302, so the download fell through to the
160+ // not-200 branch and hung.
161+ for ( const code of [ 301 , 302 , 303 , 307 , 308 ] ) {
162+ expect ( REDIRECT_CODES . has ( code ) ) . toBe ( true ) ;
163+ }
164+ } ) ;
165+
166+ it ( "does not treat a success or an error as a redirect" , ( ) => {
167+ for ( const code of [ 200 , 204 , 404 , 500 ] ) {
168+ expect ( REDIRECT_CODES . has ( code ) ) . toBe ( false ) ;
169+ }
170+ } ) ;
171+
172+ it ( "resolves a relative location against the url that sent it" , ( ) => {
173+ // The actual failure: handing this path back as a request target is not a
174+ // valid URL, so nothing was ever fetched.
175+ expect (
176+ redirectTarget (
177+ "/api/resolve-cache/models/x/tokenizer.json" ,
178+ "https://huggingface.co/x/resolve/main/tokenizer.json" ,
179+ ) ,
180+ ) . toBe ( "https://huggingface.co/api/resolve-cache/models/x/tokenizer.json" ) ;
181+ } ) ;
182+
183+ it ( "keeps an absolute location as it is" , ( ) => {
184+ expect ( redirectTarget ( "https://cdn.example.com/blob" , "https://huggingface.co/a/b" ) ) . toBe (
185+ "https://cdn.example.com/blob" ,
186+ ) ;
187+ } ) ;
188+
189+ it ( "resolves a sibling-relative location" , ( ) => {
190+ expect ( redirectTarget ( "other.json" , "https://example.com/a/b/file.json" ) ) . toBe (
191+ "https://example.com/a/b/other.json" ,
192+ ) ;
193+ } ) ;
194+ } ) ;
0 commit comments