Files
GearBox/tests/services/storage.service.test.ts
Jean-Luc Makiola 574a12e6fa fix: OIDC auth flow, Vite proxy, and PostgreSQL query compat
- Add auth redirect in root layout for unauthenticated users
- Proxy OIDC routes (/login, /callback, /logout) through Vite dev server
- Strip Secure flag from OIDC cookies in dev mode (HTTP localhost)
- Disable retry on auth query to prevent stale cookie loops
- Fix SQLite .get()/.all()/.run() calls in category and global-item
  services for PostgreSQL compatibility
- Add userId scoping to category service functions
- Add OIDC error logging in auth middleware
- Apply linter auto-formatting across affected files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:25:31 +02:00

161 lines
4.8 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
// Mock the S3 client send method
const mockSend = mock(() => Promise.resolve({}));
const mockGetSignedUrl = mock(() =>
Promise.resolve("https://minio:9000/gearbox-images/test.jpg?signed=1"),
);
// Mock modules before importing the service
mock.module("@aws-sdk/client-s3", () => ({
S3Client: class MockS3Client {
send = mockSend;
},
PutObjectCommand: class MockPutObjectCommand {
input: Record<string, unknown>;
constructor(input: Record<string, unknown>) {
this.input = input;
}
},
DeleteObjectCommand: class MockDeleteObjectCommand {
input: Record<string, unknown>;
constructor(input: Record<string, unknown>) {
this.input = input;
}
},
GetObjectCommand: class MockGetObjectCommand {
input: Record<string, unknown>;
constructor(input: Record<string, unknown>) {
this.input = input;
}
},
}));
mock.module("@aws-sdk/s3-request-presigner", () => ({
getSignedUrl: mockGetSignedUrl,
}));
// Set env vars before importing the service
process.env.S3_ENDPOINT = "http://localhost:9000";
process.env.S3_ACCESS_KEY = "minioadmin";
process.env.S3_SECRET_KEY = "minioadmin";
process.env.S3_BUCKET = "gearbox-images";
process.env.S3_REGION = "us-east-1";
// Import after mocking
const { uploadImage, deleteImage, getImageUrl, withImageUrl, withImageUrls } =
await import("@/server/services/storage.service");
describe("storage.service", () => {
beforeEach(() => {
mockSend.mockClear();
mockGetSignedUrl.mockClear();
mockGetSignedUrl.mockResolvedValue(
"https://minio:9000/gearbox-images/test.jpg?signed=1",
);
});
describe("uploadImage", () => {
test("calls PutObjectCommand with correct params", async () => {
const buffer = Buffer.from("fake-image-data");
await uploadImage(buffer, "test-image.jpg", "image/jpeg");
expect(mockSend).toHaveBeenCalledTimes(1);
const command = mockSend.mock.calls[0][0] as {
input: Record<string, unknown>;
};
expect(command.input.Bucket).toBe("gearbox-images");
expect(command.input.Key).toBe("test-image.jpg");
expect(command.input.ContentType).toBe("image/jpeg");
expect(Buffer.isBuffer(command.input.Body)).toBe(true);
});
test("handles ArrayBuffer input", async () => {
const arrayBuffer = new ArrayBuffer(10);
await uploadImage(arrayBuffer, "test.png", "image/png");
expect(mockSend).toHaveBeenCalledTimes(1);
const command = mockSend.mock.calls[0][0] as {
input: Record<string, unknown>;
};
expect(Buffer.isBuffer(command.input.Body)).toBe(true);
});
});
describe("deleteImage", () => {
test("calls DeleteObjectCommand with correct params", async () => {
await deleteImage("test-image.jpg");
expect(mockSend).toHaveBeenCalledTimes(1);
const command = mockSend.mock.calls[0][0] as {
input: Record<string, unknown>;
};
expect(command.input.Bucket).toBe("gearbox-images");
expect(command.input.Key).toBe("test-image.jpg");
});
});
describe("getImageUrl", () => {
test("calls getSignedUrl and returns result", async () => {
const url = await getImageUrl("test-image.jpg");
expect(mockGetSignedUrl).toHaveBeenCalledTimes(1);
expect(url).toBe("https://minio:9000/gearbox-images/test.jpg?signed=1");
});
});
describe("withImageUrl", () => {
test("returns null imageUrl when imageFilename is null", async () => {
const record = { id: 1, name: "Test Item", imageFilename: null };
const result = await withImageUrl(record);
expect(result.imageUrl).toBeNull();
expect(result.id).toBe(1);
expect(result.name).toBe("Test Item");
expect(mockGetSignedUrl).not.toHaveBeenCalled();
});
test("returns presigned URL when imageFilename is present", async () => {
const record = {
id: 1,
name: "Test Item",
imageFilename: "photo.jpg",
};
const result = await withImageUrl(record);
expect(result.imageUrl).toBe(
"https://minio:9000/gearbox-images/test.jpg?signed=1",
);
expect(result.id).toBe(1);
expect(mockGetSignedUrl).toHaveBeenCalledTimes(1);
});
});
describe("withImageUrls", () => {
test("processes array of records correctly", async () => {
const records = [
{ id: 1, imageFilename: "a.jpg" },
{ id: 2, imageFilename: null },
{ id: 3, imageFilename: "b.png" },
];
const results = await withImageUrls(records);
expect(results).toHaveLength(3);
expect(results[0].imageUrl).toBe(
"https://minio:9000/gearbox-images/test.jpg?signed=1",
);
expect(results[1].imageUrl).toBeNull();
expect(results[2].imageUrl).toBe(
"https://minio:9000/gearbox-images/test.jpg?signed=1",
);
// Called twice: for records[0] and records[2]
expect(mockGetSignedUrl).toHaveBeenCalledTimes(2);
});
test("handles empty array", async () => {
const results = await withImageUrls([]);
expect(results).toHaveLength(0);
});
});
});