|
import { expect, test, describe, vi, beforeEach } from "vitest"; |
|
import type { RepoDesignation, RepoId } from "../types/public"; |
|
import { dirname, join } from "node:path"; |
|
import { lstat, mkdir, stat, symlink, rename } from "node:fs/promises"; |
|
import { pathsInfo } from "./paths-info"; |
|
import { createWriteStream, type Stats } from "node:fs"; |
|
import { getHFHubCachePath, getRepoFolderName } from "./cache-management"; |
|
import { toRepoId } from "../utils/toRepoId"; |
|
import { downloadFileToCacheDir } from "./download-file-to-cache-dir"; |
|
import { createSymlink } from "../utils/symlink"; |
|
|
|
vi.mock("node:fs/promises", () => ({ |
|
rename: vi.fn(), |
|
symlink: vi.fn(), |
|
lstat: vi.fn(), |
|
mkdir: vi.fn(), |
|
stat: vi.fn(), |
|
})); |
|
|
|
vi.mock("node:fs", () => ({ |
|
createWriteStream: vi.fn(), |
|
})); |
|
|
|
vi.mock("./paths-info", () => ({ |
|
pathsInfo: vi.fn(), |
|
})); |
|
|
|
vi.mock("../utils/symlink", () => ({ |
|
createSymlink: vi.fn(), |
|
})); |
|
|
|
const DUMMY_REPO: RepoId = { |
|
name: "hello-world", |
|
type: "model", |
|
}; |
|
|
|
const DUMMY_ETAG = "dummy-etag"; |
|
|
|
|
|
function _getBlobFile(params: { |
|
repo: RepoDesignation; |
|
etag: string; |
|
cacheDir?: string; // default to {@link getHFHubCache} |
|
}) { |
|
return join(params.cacheDir ?? getHFHubCachePath(), getRepoFolderName(toRepoId(params.repo)), "blobs", params.etag); |
|
} |
|
|
|
|
|
function _getSnapshotFile(params: { |
|
repo: RepoDesignation; |
|
path: string; |
|
revision: string; |
|
cacheDir?: string; // default to {@link getHFHubCache} |
|
}) { |
|
return join( |
|
params.cacheDir ?? getHFHubCachePath(), |
|
getRepoFolderName(toRepoId(params.repo)), |
|
"snapshots", |
|
params.revision, |
|
params.path |
|
); |
|
} |
|
|
|
describe("downloadFileToCacheDir", () => { |
|
const fetchMock: typeof fetch = vi.fn(); |
|
beforeEach(() => { |
|
vi.resetAllMocks(); |
|
|
|
vi.mocked(fetchMock).mockResolvedValue( |
|
new Response("dummy-body", { |
|
status: 200, |
|
headers: { |
|
etag: DUMMY_ETAG, |
|
"Content-Range": "bytes 0-54/55", |
|
}, |
|
}) |
|
); |
|
|
|
|
|
vi.mocked(stat).mockRejectedValue(new Error("Do not exists")); |
|
vi.mocked(lstat).mockRejectedValue(new Error("Do not exists")); |
|
}); |
|
|
|
test("should throw an error if fileDownloadInfo return nothing", async () => { |
|
await expect(async () => { |
|
await downloadFileToCacheDir({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
fetch: fetchMock, |
|
}); |
|
}).rejects.toThrowError("cannot get path info for /README.md"); |
|
|
|
expect(pathsInfo).toHaveBeenCalledWith( |
|
expect.objectContaining({ |
|
repo: DUMMY_REPO, |
|
paths: ["/README.md"], |
|
fetch: fetchMock, |
|
}) |
|
); |
|
}); |
|
|
|
test("existing symlinked and blob should not re-download it", async () => { |
|
|
|
const expectPointer = _getSnapshotFile({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
revision: "dd4bc8b21efa05ec961e3efc4ee5e3832a3679c7", |
|
}); |
|
|
|
vi.mocked(stat).mockResolvedValue({} as Stats); |
|
|
|
const output = await downloadFileToCacheDir({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
fetch: fetchMock, |
|
revision: "dd4bc8b21efa05ec961e3efc4ee5e3832a3679c7", |
|
}); |
|
|
|
expect(stat).toHaveBeenCalledOnce(); |
|
|
|
const starArg = vi.mocked(stat).mock.calls[0][0]; |
|
|
|
expect(starArg).toBe(expectPointer); |
|
expect(fetchMock).not.toHaveBeenCalledWith(); |
|
|
|
expect(output).toBe(expectPointer); |
|
}); |
|
|
|
test("existing symlinked and blob with default revision should not re-download it", async () => { |
|
|
|
const expectPointer = _getSnapshotFile({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
revision: "main", |
|
}); |
|
|
|
vi.mocked(stat).mockResolvedValue({} as Stats); |
|
vi.mocked(lstat).mockResolvedValue({} as Stats); |
|
vi.mocked(pathsInfo).mockResolvedValue([ |
|
{ |
|
oid: DUMMY_ETAG, |
|
size: 55, |
|
path: "README.md", |
|
type: "file", |
|
lastCommit: { |
|
date: new Date(), |
|
id: "main", |
|
title: "Commit msg", |
|
}, |
|
}, |
|
]); |
|
|
|
const output = await downloadFileToCacheDir({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
fetch: fetchMock, |
|
}); |
|
|
|
expect(stat).toHaveBeenCalledOnce(); |
|
expect(symlink).not.toHaveBeenCalledOnce(); |
|
|
|
const starArg = vi.mocked(stat).mock.calls[0][0]; |
|
|
|
expect(starArg).toBe(expectPointer); |
|
expect(fetchMock).not.toHaveBeenCalledWith(); |
|
|
|
expect(output).toBe(expectPointer); |
|
}); |
|
|
|
test("existing blob should only create the symlink", async () => { |
|
|
|
const expectPointer = _getSnapshotFile({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
revision: "dummy-commit-hash", |
|
}); |
|
|
|
const expectedBlob = _getBlobFile({ |
|
repo: DUMMY_REPO, |
|
etag: DUMMY_ETAG, |
|
}); |
|
|
|
|
|
vi.mocked(lstat).mockResolvedValue({} as Stats); |
|
|
|
vi.mocked(pathsInfo).mockResolvedValue([ |
|
{ |
|
oid: DUMMY_ETAG, |
|
size: 55, |
|
path: "README.md", |
|
type: "file", |
|
lastCommit: { |
|
date: new Date(), |
|
id: "dummy-commit-hash", |
|
title: "Commit msg", |
|
}, |
|
}, |
|
]); |
|
|
|
const output = await downloadFileToCacheDir({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
fetch: fetchMock, |
|
}); |
|
|
|
|
|
expect(lstat).toHaveBeenCalled(); |
|
expect(vi.mocked(lstat).mock.calls[0][0]).toBe(expectedBlob); |
|
|
|
|
|
expect(createSymlink).toHaveBeenCalledOnce(); |
|
|
|
expect(fetchMock).not.toHaveBeenCalled(); |
|
|
|
expect(output).toBe(expectPointer); |
|
}); |
|
|
|
test("expect resolve value to be the pointer path of downloaded file", async () => { |
|
|
|
const expectPointer = _getSnapshotFile({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
revision: "dummy-commit-hash", |
|
}); |
|
|
|
const expectedBlob = _getBlobFile({ |
|
repo: DUMMY_REPO, |
|
etag: DUMMY_ETAG, |
|
}); |
|
|
|
vi.mocked(pathsInfo).mockResolvedValue([ |
|
{ |
|
oid: DUMMY_ETAG, |
|
size: 55, |
|
path: "README.md", |
|
type: "file", |
|
lastCommit: { |
|
date: new Date(), |
|
id: "dummy-commit-hash", |
|
title: "Commit msg", |
|
}, |
|
}, |
|
]); |
|
|
|
|
|
vi.mocked(createWriteStream).mockReturnValue(async function* () {} as any); |
|
|
|
const output = await downloadFileToCacheDir({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
fetch: fetchMock, |
|
}); |
|
|
|
|
|
expect(vi.mocked(mkdir).mock.calls[0][0]).toBe(dirname(expectedBlob)); |
|
expect(vi.mocked(mkdir).mock.calls[1][0]).toBe(dirname(expectPointer)); |
|
|
|
expect(output).toBe(expectPointer); |
|
}); |
|
|
|
test("should write fetch response to blob", async () => { |
|
|
|
const expectPointer = _getSnapshotFile({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
revision: "dummy-commit-hash", |
|
}); |
|
|
|
const expectedBlob = _getBlobFile({ |
|
repo: DUMMY_REPO, |
|
etag: DUMMY_ETAG, |
|
}); |
|
|
|
|
|
vi.mocked(pathsInfo).mockResolvedValue([ |
|
{ |
|
oid: DUMMY_ETAG, |
|
size: 55, |
|
path: "README.md", |
|
type: "file", |
|
lastCommit: { |
|
date: new Date(), |
|
id: "dummy-commit-hash", |
|
title: "Commit msg", |
|
}, |
|
}, |
|
]); |
|
|
|
|
|
vi.mocked(createWriteStream).mockReturnValue(async function* () {} as any); |
|
|
|
await downloadFileToCacheDir({ |
|
repo: DUMMY_REPO, |
|
path: "/README.md", |
|
fetch: fetchMock, |
|
}); |
|
|
|
const incomplete = `${expectedBlob}.incomplete`; |
|
|
|
expect(createWriteStream).toHaveBeenCalledWith(incomplete); |
|
|
|
expect(rename).toHaveBeenCalledWith(incomplete, expectedBlob); |
|
|
|
expect(createSymlink).toHaveBeenCalledWith({ sourcePath: expectedBlob, finalPath: expectPointer }); |
|
}); |
|
}); |
|
|