fix: use dependency injection for image-downloader fs mocks
The _fsForTest mutable object pattern fails on Linux Bun because test and source files get different module instances. Switch to passing fs operations as an optional parameter with defaults. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ac8e22e2f8
commit
cff360d00a
2 files changed
+40
-21
No files matched your search
@@ -2,11 +2,12 @@ import fs from "fs/promises";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import type { Octokits } from "../api/client";
|
import type { Octokits } from "../api/client";
|
||||||
|
|
||||||
// Mutable fs reference for cross-platform test compatibility.
|
export type FsOps = {
|
||||||
// On Linux, Bun gives test and source files different fs/promises objects,
|
mkdir: typeof fs.mkdir;
|
||||||
// so spyOn(fs, "mkdir") in a test file won't intercept calls here.
|
writeFile: typeof fs.writeFile;
|
||||||
// Tests override these methods directly instead.
|
};
|
||||||
export const _fsForTest = {
|
|
||||||
|
const defaultFsOps: FsOps = {
|
||||||
mkdir: fs.mkdir.bind(fs),
|
mkdir: fs.mkdir.bind(fs),
|
||||||
writeFile: fs.writeFile.bind(fs),
|
writeFile: fs.writeFile.bind(fs),
|
||||||
};
|
};
|
||||||
@@ -72,11 +73,12 @@ export async function downloadCommentImages(
|
|||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
comments: CommentWithImages[],
|
comments: CommentWithImages[],
|
||||||
|
fsOps: FsOps = defaultFsOps,
|
||||||
): Promise<Map<string, string>> {
|
): Promise<Map<string, string>> {
|
||||||
const urlToPathMap = new Map<string, string>();
|
const urlToPathMap = new Map<string, string>();
|
||||||
const downloadsDir = "/tmp/github-images";
|
const downloadsDir = "/tmp/github-images";
|
||||||
|
|
||||||
await _fsForTest.mkdir(downloadsDir, { recursive: true });
|
await fsOps.mkdir(downloadsDir, { recursive: true });
|
||||||
|
|
||||||
const { imageRegex, htmlImgRegex } = buildImageRegexes();
|
const { imageRegex, htmlImgRegex } = buildImageRegexes();
|
||||||
|
|
||||||
@@ -226,7 +228,7 @@ export async function downloadCommentImages(
|
|||||||
const arrayBuffer = await imageResponse.arrayBuffer();
|
const arrayBuffer = await imageResponse.arrayBuffer();
|
||||||
const buffer = Buffer.from(arrayBuffer);
|
const buffer = Buffer.from(arrayBuffer);
|
||||||
|
|
||||||
await _fsForTest.writeFile(localPath, buffer);
|
await fsOps.writeFile(localPath, buffer);
|
||||||
console.log(`✓ Saved: ${localPath}`);
|
console.log(`✓ Saved: ${localPath}`);
|
||||||
|
|
||||||
urlToPathMap.set(originalUrl, localPath);
|
urlToPathMap.set(originalUrl, localPath);
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import {
|
|||||||
} from "bun:test";
|
} from "bun:test";
|
||||||
import {
|
import {
|
||||||
downloadCommentImages,
|
downloadCommentImages,
|
||||||
_fsForTest,
|
|
||||||
type CommentWithImages,
|
type CommentWithImages,
|
||||||
|
type FsOps,
|
||||||
} from "../src/github/utils/image-downloader";
|
} from "../src/github/utils/image-downloader";
|
||||||
import type { Octokits } from "../src/github/api/client";
|
import type { Octokits } from "../src/github/api/client";
|
||||||
|
|
||||||
@@ -22,13 +22,13 @@ describe("downloadCommentImages", () => {
|
|||||||
let fetchSpy: any;
|
let fetchSpy: any;
|
||||||
let savedServerUrl: string | undefined;
|
let savedServerUrl: string | undefined;
|
||||||
|
|
||||||
// Store originals for restoration
|
// Mock functions for fs operations — injected via parameter, no module mutation needed
|
||||||
const origMkdir = _fsForTest.mkdir;
|
|
||||||
const origWriteFile = _fsForTest.writeFile;
|
|
||||||
|
|
||||||
// Mock functions for fs operations
|
|
||||||
const mockMkdir = jest.fn().mockResolvedValue(undefined);
|
const mockMkdir = jest.fn().mockResolvedValue(undefined);
|
||||||
const mockWriteFile = jest.fn().mockResolvedValue(undefined);
|
const mockWriteFile = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const mockFsOps: FsOps = {
|
||||||
|
mkdir: mockMkdir as any,
|
||||||
|
writeFile: mockWriteFile as any,
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Pin GITHUB_SERVER_URL so tests pass regardless of runner environment.
|
// Pin GITHUB_SERVER_URL so tests pass regardless of runner environment.
|
||||||
@@ -41,11 +41,8 @@ describe("downloadCommentImages", () => {
|
|||||||
consoleWarnSpy = spyOn(console, "warn").mockImplementation(() => {});
|
consoleWarnSpy = spyOn(console, "warn").mockImplementation(() => {});
|
||||||
consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
|
consoleErrorSpy = spyOn(console, "error").mockImplementation(() => {});
|
||||||
|
|
||||||
// Override _fsForTest methods directly — works cross-platform
|
|
||||||
mockMkdir.mockClear();
|
mockMkdir.mockClear();
|
||||||
mockWriteFile.mockClear();
|
mockWriteFile.mockClear();
|
||||||
_fsForTest.mkdir = mockMkdir as any;
|
|
||||||
_fsForTest.writeFile = mockWriteFile as any;
|
|
||||||
|
|
||||||
// Set fake system time for consistent filenames
|
// Set fake system time for consistent filenames
|
||||||
setSystemTime(new Date("2024-01-01T00:00:00.000Z")); // 1704067200000
|
setSystemTime(new Date("2024-01-01T00:00:00.000Z")); // 1704067200000
|
||||||
@@ -59,10 +56,6 @@ describe("downloadCommentImages", () => {
|
|||||||
delete process.env.GITHUB_SERVER_URL;
|
delete process.env.GITHUB_SERVER_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore original fs methods
|
|
||||||
_fsForTest.mkdir = origMkdir;
|
|
||||||
_fsForTest.writeFile = origWriteFile;
|
|
||||||
|
|
||||||
consoleLogSpy.mockRestore();
|
consoleLogSpy.mockRestore();
|
||||||
consoleWarnSpy.mockRestore();
|
consoleWarnSpy.mockRestore();
|
||||||
consoleErrorSpy.mockRestore();
|
consoleErrorSpy.mockRestore();
|
||||||
@@ -90,7 +83,13 @@ describe("downloadCommentImages", () => {
|
|||||||
const mockOctokit = createMockOctokit();
|
const mockOctokit = createMockOctokit();
|
||||||
const comments: CommentWithImages[] = [];
|
const comments: CommentWithImages[] = [];
|
||||||
|
|
||||||
await downloadCommentImages(mockOctokit, "owner", "repo", comments);
|
await downloadCommentImages(
|
||||||
|
mockOctokit,
|
||||||
|
"owner",
|
||||||
|
"repo",
|
||||||
|
comments,
|
||||||
|
mockFsOps,
|
||||||
|
);
|
||||||
|
|
||||||
expect(mockMkdir).toHaveBeenCalledWith("/tmp/github-images", {
|
expect(mockMkdir).toHaveBeenCalledWith("/tmp/github-images", {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
@@ -112,6 +111,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.size).toBe(0);
|
expect(result.size).toBe(0);
|
||||||
@@ -155,6 +155,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockOctokit.rest.issues.getComment).toHaveBeenCalledWith({
|
expect(mockOctokit.rest.issues.getComment).toHaveBeenCalledWith({
|
||||||
@@ -215,6 +216,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockOctokit.rest.pulls.getReviewComment).toHaveBeenCalledWith({
|
expect(mockOctokit.rest.pulls.getReviewComment).toHaveBeenCalledWith({
|
||||||
@@ -262,6 +264,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockOctokit.rest.pulls.getReview).toHaveBeenCalledWith({
|
expect(mockOctokit.rest.pulls.getReview).toHaveBeenCalledWith({
|
||||||
@@ -309,6 +312,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockOctokit.rest.issues.get).toHaveBeenCalledWith({
|
expect(mockOctokit.rest.issues.get).toHaveBeenCalledWith({
|
||||||
@@ -357,6 +361,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockOctokit.rest.pulls.get).toHaveBeenCalledWith({
|
expect(mockOctokit.rest.pulls.get).toHaveBeenCalledWith({
|
||||||
@@ -408,6 +413,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||||
@@ -459,6 +465,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(1); // Only downloaded once
|
expect(fetchSpy).toHaveBeenCalledTimes(1); // Only downloaded once
|
||||||
@@ -492,6 +499,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.size).toBe(0);
|
expect(result.size).toBe(0);
|
||||||
@@ -532,6 +540,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.size).toBe(0);
|
expect(result.size).toBe(0);
|
||||||
@@ -563,6 +572,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.size).toBe(0);
|
expect(result.size).toBe(0);
|
||||||
@@ -634,6 +644,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
expect(result.get(url)).toBe(
|
expect(result.get(url)).toBe(
|
||||||
`/tmp/github-images/image-${1704067200000 + callIndex}-0${ext}`,
|
`/tmp/github-images/image-${1704067200000 + callIndex}-0${ext}`,
|
||||||
@@ -678,6 +689,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||||
@@ -723,6 +735,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(mockOctokit.rest.issues.getComment).toHaveBeenCalledWith({
|
expect(mockOctokit.rest.issues.getComment).toHaveBeenCalledWith({
|
||||||
@@ -787,6 +800,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||||
@@ -837,6 +851,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||||
@@ -883,6 +898,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(1); // Only downloaded once
|
expect(fetchSpy).toHaveBeenCalledTimes(1); // Only downloaded once
|
||||||
@@ -927,6 +943,7 @@ describe("downloadCommentImages", () => {
|
|||||||
"owner",
|
"owner",
|
||||||
"repo",
|
"repo",
|
||||||
comments,
|
comments,
|
||||||
|
mockFsOps,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
Reference in new issue
Block a user