Skip to content

Commit 21ab236

Browse files
committed
fix(test): update test suite
Updates test suite to work with file limits. Update testing to cover more edges cases. Testfiles were renamed to match the file they were testing
1 parent 13bcaac commit 21ab236

File tree

8 files changed

+154
-32
lines changed

8 files changed

+154
-32
lines changed

src/controllers/UploadController.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,20 +147,20 @@ export class UploadController extends Controller {
147147
@FormField() jsonData?: string,
148148
): Promise<UploadResponse> {
149149
try {
150+
if (!files?.length) {
151+
throw new NoFilesUploadedError();
152+
}
153+
150154
const storage = await StorageService.init();
151155

152156
if (jsonData) {
153157
console.debug("Got JSON data for future use");
154158
}
155159

156-
const blobs = files?.map(
160+
const blobs = files.map(
157161
(file) => new Blob([file.buffer], { type: file.mimetype }),
158162
);
159163

160-
if (!files || !blobs) {
161-
throw new NoFilesUploadedError();
162-
}
163-
164164
const uploadResults = await Promise.allSettled(
165165
files.map(async (file, index) => {
166166
try {

src/middleware/upload.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export const validateFile = async (
2525
req: Request,
2626
file: Express.Multer.File,
2727
): Promise<void> => {
28+
// Check if buffer exists
29+
if (!file.buffer) {
30+
throw new Error(FileValidationError.INVALID_TYPE);
31+
}
32+
2833
// 1. Check file size
2934
if (file.size > 11 * 1024 * 1024) {
3035
throw new Error(FileValidationError.SIZE_EXCEEDED);
@@ -47,7 +52,7 @@ export const validateFile = async (
4752
// Configure multer with validation
4853
export const upload = multer({
4954
limits: {
50-
fileSize: 10 * 1024 * 1024, // 10MB
55+
fileSize: 11 * 1024 * 1024, // 11MB
5156
files: 5,
5257
},
5358
storage: multer.memoryStorage(),
Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { describe, test, vi } from "vitest";
21
import { expect } from "chai";
2+
import { describe, test, vi } from "vitest";
33
import { mock } from "vitest-mock-extended";
44
import { UploadController } from "../../../src/controllers/UploadController.js";
5+
import { SingleUploadFailedError } from "../../../src/lib/uploads/errors.js";
56
import { StorageService } from "../../../src/services/StorageService.js";
67
import { createMockFile, mockTextFile } from "../../test-utils/mockFile.js";
78

@@ -150,4 +151,49 @@ describe("File upload at v1/upload", async () => {
150151
{ cid: "TEST_CID_2", fileName: "page.html" },
151152
]);
152153
});
154+
155+
test("Handles empty file array", async () => {
156+
mocks.init.mockResolvedValue(mockStorage);
157+
158+
const response = await controller.upload([]);
159+
160+
expect(response.success).to.be.false;
161+
expect(response.uploadStatus).to.equal("none");
162+
expect(response.message).to.equal("No files uploaded");
163+
});
164+
165+
test("Handles file size validation", async () => {
166+
mocks.init.mockResolvedValue(mockStorage);
167+
mockStorage.uploadFile.mockRejectedValue(
168+
new SingleUploadFailedError("large.txt", "Failed to upload large.txt"),
169+
);
170+
171+
const largeFile = createMockFile(
172+
Buffer.alloc(12 * 1024 * 1024),
173+
"large.txt",
174+
"text/plain",
175+
);
176+
177+
const response = await controller.upload([largeFile]);
178+
179+
expect(response.success).to.be.false;
180+
expect(response.uploadStatus).to.equal("none");
181+
expect(response.data?.failed[0]).to.deep.equal({
182+
fileName: "large.txt",
183+
error: "Failed to upload large.txt",
184+
});
185+
});
186+
187+
test("Processes optional JSON metadata", async () => {
188+
mocks.init.mockResolvedValue(mockStorage);
189+
mockStorage.uploadFile.mockResolvedValue({ cid: "TEST_CID" });
190+
191+
const response = await controller.upload(
192+
[mockTextFile],
193+
JSON.stringify({ key: "value" }),
194+
);
195+
196+
expect(response.success).to.be.true;
197+
expect(response.data?.results[0].cid).to.equal("TEST_CID");
198+
});
153199
});

test/middleware/upload.test.ts

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { describe, test, expect, vi } from "vitest";
2+
import { Request } from "express";
3+
import {
4+
FileExtension,
5+
fileTypeFromBuffer,
6+
type FileTypeResult,
7+
type MimeType,
8+
} from "file-type";
29
import { createMockFile } from "../test-utils/mockFile";
310
import { FileValidationError, validateFile } from "../../src/middleware/upload";
4-
import { fileTypeFromBuffer, type FileTypeResult } from "file-type";
5-
import { Request } from "express";
611
import { mock } from "vitest-mock-extended";
712

813
// Mock file-type
@@ -16,15 +21,15 @@ describe("Upload Middleware", () => {
1621
test("accepts valid file", async () => {
1722
const mockFile = createMockFile("test content", "test.txt", "text/plain");
1823
vi.mocked(fileTypeFromBuffer).mockResolvedValue({
19-
mime: "text/plain" as const,
20-
ext: "txt",
24+
mime: "text/plain" as MimeType,
25+
ext: "txt" as FileExtension,
2126
} satisfies FileTypeResult);
2227

2328
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
2429
});
2530

2631
test("rejects oversized file", async () => {
27-
const largeContent = "x".repeat(11 * 1024 * 1024); // 11MB
32+
const largeContent = "x".repeat(12 * 1024 * 1024); // 12MB
2833
const mockFile = createMockFile(largeContent, "large.txt", "text/plain");
2934

3035
await expect(validateFile(mockReq, mockFile)).rejects.toThrow(
@@ -47,8 +52,8 @@ describe("Upload Middleware", () => {
4752
test("rejects file with mismatched content type", async () => {
4853
const mockFile = createMockFile("<html></html>", "fake.txt", "text/plain");
4954
vi.mocked(fileTypeFromBuffer).mockResolvedValue({
50-
mime: "text/html" as const,
51-
ext: "html",
55+
mime: "text/html" as MimeType,
56+
ext: "html" as FileExtension,
5257
} satisfies FileTypeResult);
5358

5459
await expect(validateFile(mockReq, mockFile)).rejects.toThrow(
@@ -58,9 +63,9 @@ describe("Upload Middleware", () => {
5863

5964
test("handles different allowed file types", async () => {
6065
const testCases = [
61-
{ content: "test", name: "test.txt", type: "text/plain" as const },
62-
{ content: "{}", name: "test.json", type: "application/json" as const },
63-
{ content: "PDF", name: "test.pdf", type: "application/pdf" as const },
66+
{ content: "test", name: "test.txt", type: "text/plain" as MimeType },
67+
{ content: "{}", name: "test.json", type: "application/json" },
68+
{ content: "PDF", name: "test.pdf", type: "application/pdf" as MimeType },
6469
];
6570

6671
for (const testCase of testCases) {
@@ -69,18 +74,22 @@ describe("Upload Middleware", () => {
6974
testCase.name,
7075
testCase.type,
7176
);
72-
vi.mocked(fileTypeFromBuffer).mockResolvedValue({
73-
mime: testCase.type,
74-
ext: testCase.name.split(".")[1],
75-
} satisfies FileTypeResult);
77+
vi.mocked(fileTypeFromBuffer).mockResolvedValue(
78+
testCase.type === "application/json"
79+
? undefined // JSON files typically return undefined
80+
: ({
81+
mime: testCase.type as MimeType,
82+
ext: testCase.name.split(".")[1] as FileExtension,
83+
} satisfies FileTypeResult),
84+
);
7685

7786
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
7887
}
7988
});
8089

8190
test("handles null file type detection", async () => {
8291
const mockFile = createMockFile("test", "test.txt", "text/plain");
83-
vi.mocked(fileTypeFromBuffer).mockResolvedValue(null);
92+
vi.mocked(fileTypeFromBuffer).mockResolvedValue(undefined);
8493

8594
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
8695
});
@@ -93,4 +102,66 @@ describe("Upload Middleware", () => {
93102

94103
await expect(validateFile(mockReq, mockFile)).rejects.toThrow();
95104
});
105+
106+
test("accepts valid image files", async () => {
107+
const mockFile = createMockFile(
108+
"fake-image-data",
109+
"photo.jpg",
110+
"image/jpeg",
111+
);
112+
vi.mocked(fileTypeFromBuffer).mockResolvedValue({
113+
mime: "image/jpeg" as MimeType,
114+
ext: "jpg",
115+
} satisfies FileTypeResult);
116+
117+
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
118+
});
119+
120+
test("accepts valid JSON file", async () => {
121+
const mockFile = createMockFile(
122+
JSON.stringify({ test: "data" }),
123+
"data.json",
124+
"application/json",
125+
);
126+
// For JSON files, fileTypeFromBuffer typically returns undefined
127+
vi.mocked(fileTypeFromBuffer).mockResolvedValue(undefined);
128+
129+
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
130+
});
131+
132+
test("accepts file at exact size limit", async () => {
133+
const content = Buffer.alloc(11 * 1024 * 1024); // Exactly 11MB
134+
const mockFile = createMockFile(content, "at-limit.txt", "text/plain");
135+
vi.mocked(fileTypeFromBuffer).mockResolvedValue({
136+
mime: "text/plain" as MimeType,
137+
ext: "txt" as FileExtension,
138+
} satisfies FileTypeResult);
139+
140+
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
141+
});
142+
143+
test("accepts empty file with valid type", async () => {
144+
const mockFile = createMockFile("", "empty.txt", "text/plain");
145+
vi.mocked(fileTypeFromBuffer).mockResolvedValue(undefined);
146+
147+
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
148+
});
149+
150+
test("handles file without extension", async () => {
151+
const mockFile = createMockFile("content", "readme", "text/plain");
152+
vi.mocked(fileTypeFromBuffer).mockResolvedValue({
153+
mime: "text/plain" as MimeType,
154+
ext: "txt" as FileExtension,
155+
} satisfies FileTypeResult);
156+
157+
await expect(validateFile(mockReq, mockFile)).resolves.not.toThrow();
158+
});
159+
160+
test("rejects file with missing buffer", async () => {
161+
const mockFile = createMockFile("test", "test.txt", "text/plain");
162+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
163+
mockFile.buffer = undefined as any;
164+
165+
await expect(validateFile(mockReq, mockFile)).rejects.toThrow();
166+
});
96167
});

test/test-utils/mockFile.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ import { Buffer } from "buffer";
22
import { Readable } from "node:stream";
33

44
export const createMockFile = (
5-
content: string = "test content",
6-
filename: string = "test.txt",
7-
mimetype: string = "text/plain",
5+
content: string | Buffer,
6+
filename: string,
7+
mimetype = "text/plain",
88
): Express.Multer.File => ({
9-
buffer: Buffer.from(content),
10-
mimetype,
9+
buffer: Buffer.isBuffer(content) ? content : Buffer.from(content),
1110
originalname: filename,
11+
mimetype,
12+
size: Buffer.isBuffer(content) ? content.length : Buffer.byteLength(content),
1213
fieldname: "files",
1314
encoding: "7bit",
14-
size: Buffer.from(content).length,
1515
stream: null as unknown as Readable,
1616
destination: "",
1717
filename: filename,

vitest.config.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ export default defineConfig({
1111
// If you want a coverage reports even if your tests are failing, include the reportOnFailure option
1212
reportOnFailure: true,
1313
thresholds: {
14-
lines: 17,
15-
branches: 60,
16-
functions: 54,
17-
statements: 17,
14+
lines: 20,
15+
branches: 64,
16+
functions: 60,
17+
statements: 20,
1818
},
1919
include: ["src/**/*.ts"],
2020
exclude: [

0 commit comments

Comments
 (0)