fix: use dependency injection for image-downloader fs mocks

mock.module("fs/promises") on Linux Bun poisons the entire mock
tracking system — not just fs mocks, but ALL mock/spy assertions
(octokit mocks, spyOn(global, "fetch"), etc.) stop recording calls.

Replace with dependency injection: downloadCommentImages() now accepts
an optional fsDeps parameter. Tests pass mock mkdir/writeFile directly.
Production callers omit it and get real fs. No mock.module anywhere.

Fixes 18 test failures blocking CI on Forgejo Linux runners.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad MocanuandClaude Opus 4.6 committed 2026-02-26 10:45:08 +02:00
1 parent 6113a4ef2d
commit a6b3c94004
3 files changed
+54 -16

No files matched your search

+28 -14
View File
@@ -9,22 +9,10 @@ import {
setSystemTime,
} from "bun:test";
// Mock fs/promises at the module loader level. Bun hoists mock.module calls
// before imports, so the source module's `import fs from "fs/promises"` gets
// these mocks. This avoids cross-module jest.fn() tracking bugs on Linux Bun.
const mockMkdir = mock(() => Promise.resolve(undefined as any));
const mockWriteFile = mock(() => Promise.resolve(undefined as any));
mock.module("fs/promises", () => ({
default: {
mkdir: mockMkdir,
writeFile: mockWriteFile,
},
}));
import {
downloadCommentImages,
type CommentWithImages,
type FsDeps,
} from "../src/github/utils/image-downloader";
import type { Octokits } from "../src/github/api/client";
@@ -35,6 +23,14 @@ describe("downloadCommentImages", () => {
let fetchSpy: any;
let savedServerUrl: string | undefined;
// DI mocks for fs — avoids mock.module which poisons mock tracking on Linux Bun
const mockMkdir = mock(() => Promise.resolve(undefined as any));
const mockWriteFile = mock(() => Promise.resolve(undefined as any));
const fsDeps: FsDeps = {
mkdir: mockMkdir as any,
writeFile: mockWriteFile as any,
};
beforeEach(() => {
// Pin GITHUB_SERVER_URL so tests pass regardless of runner environment.
// The image-downloader reads process.env directly at call time.
@@ -88,7 +84,7 @@ describe("downloadCommentImages", () => {
const mockOctokit = createMockOctokit();
const comments: CommentWithImages[] = [];
await downloadCommentImages(mockOctokit, "owner", "repo", comments);
await downloadCommentImages(mockOctokit, "owner", "repo", comments, fsDeps);
expect(mockMkdir).toHaveBeenCalledWith("/tmp/github-images", {
recursive: true,
@@ -110,6 +106,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(result.size).toBe(0);
@@ -155,6 +152,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(mockOctokit.rest.issues.getComment).toHaveBeenCalledWith({
@@ -217,6 +215,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(mockOctokit.rest.pulls.getReviewComment).toHaveBeenCalledWith({
@@ -266,6 +265,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(mockOctokit.rest.pulls.getReview).toHaveBeenCalledWith({
@@ -315,6 +315,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(mockOctokit.rest.issues.get).toHaveBeenCalledWith({
@@ -365,6 +366,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(mockOctokit.rest.pulls.get).toHaveBeenCalledWith({
@@ -418,6 +420,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(2);
@@ -471,6 +474,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(1); // Only downloaded once
@@ -506,6 +510,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(result.size).toBe(0);
@@ -548,6 +553,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(result.size).toBe(0);
@@ -579,6 +585,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(result.size).toBe(0);
@@ -652,6 +659,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(result.get(url)).toBe(
`/tmp/github-images/image-${1704067200000 + callIndex}-0${ext}`,
@@ -698,6 +706,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(1);
@@ -745,6 +754,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(mockOctokit.rest.issues.getComment).toHaveBeenCalledWith({
@@ -811,6 +821,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(2);
@@ -863,6 +874,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(2);
@@ -911,6 +923,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(1); // Only downloaded once
@@ -957,6 +970,7 @@ describe("downloadCommentImages", () => {
"owner",
"repo",
comments,
fsDeps,
);
expect(fetchSpy).toHaveBeenCalledTimes(1);