/** * Integration tests for security upload controls: * - Upload route has rate limit config (M13) * - Per-user storage quota enforcement (L3) * * Note: Rate limit enforcement requires @fastify/rate-limit plugin which * the test server does not register. These tests verify the routes accept * requests correctly and that quota logic is wired up. Full rate limit * enforcement is tested via the production server which registers the plugin. */ import { eq, sql } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { db, schema } from "../../../apps/api/src/db/index.js"; import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp, } from "../test-server.js"; let testApp: TestApp; let adminToken: string; beforeAll(async () => { testApp = await buildTestApp(); adminToken = await loginAsAdmin(testApp.app); }, 30_000); afterAll(async () => { await testApp.cleanup(); }, 10_000); /** * Create a minimal valid PNG buffer (1x1 pixel, transparent). */ function createMinimalPng(): Buffer { return Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", "base64", ); } describe("Upload endpoint accepts valid files (M13 rate limit configured)", () => { it("accepts authenticated uploads to /api/v1/files/upload", async () => { const png = createMinimalPng(); const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: png }, ]); const res = await testApp.app.inject({ method: "POST", url: "/api/v1/files/upload", headers: { "content-type": contentType, authorization: `Bearer ${adminToken}`, }, payload: body, }); expect(res.statusCode).toBe(201); const resBody = JSON.parse(res.body); expect(resBody.files).toBeDefined(); expect(resBody.files.length).toBe(1); }); it("accepts uploads to /api/v1/upload", async () => { const png = createMinimalPng(); const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test-upload.png", contentType: "image/png", content: png }, ]); const res = await testApp.app.inject({ method: "POST", url: "/api/v1/upload", headers: { "content-type": contentType, authorization: `Bearer ${adminToken}`, }, payload: body, }); expect(res.statusCode).toBe(200); const resBody = JSON.parse(res.body); expect(resBody.jobId).toBeDefined(); expect(resBody.files).toBeDefined(); }); }); describe("Per-user storage quota enforcement (L3)", () => { it("accepts uploads when within storage quota", async () => { const png = createMinimalPng(); const { body, contentType } = createMultipartPayload([ { name: "file", filename: "quota-ok.png", contentType: "image/png", content: png }, ]); const res = await testApp.app.inject({ method: "POST", url: "/api/v1/files/upload", headers: { "content-type": contentType, authorization: `Bearer ${adminToken}`, }, payload: body, }); expect(res.statusCode).toBe(201); }); it("tracks file sizes in the database for quota calculation", async () => { // Verify we can query total storage per user (the quota check mechanism) const [adminUser] = await db .select() .from(schema.users) .where(eq(schema.users.username, "admin")); expect(adminUser).toBeDefined(); const [result] = await db .select({ total: sql`coalesce(sum(${schema.userFiles.size}), 0)` }) .from(schema.userFiles) .where(eq(schema.userFiles.userId, adminUser?.id ?? "")); // Should have some bytes from the uploads in previous tests expect(result).toBeDefined(); // Postgres returns SUM as string (bigint); coerce for comparison expect(Number(result?.total)).toBeGreaterThanOrEqual(0); }); it("quota check query returns 0 for users with no files", async () => { const [result] = await db .select({ total: sql`coalesce(sum(${schema.userFiles.size}), 0)` }) .from(schema.userFiles) .where(eq(schema.userFiles.userId, "nonexistent-user-id")); expect(result).toBeDefined(); // Postgres returns SUM as string (bigint); coerce for comparison expect(Number(result?.total)).toBe(0); }); });