/** * Integration tests for Drawing API - Image Persistence * * These tests specifically target the bug from GitHub issue #17: * "Images don't load fully when reopening the file" * * The root cause was that sanitizeDrawingData() was truncating all strings * in the files object to 10000 characters, which corrupted base64 image data URLs. */ import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest"; import { getTestPrisma, cleanupTestDb, initTestDb, setupTestDb, createTestDrawingPayload, createSampleFilesObject, generateLargeImageDataUrl, compareFilesObjects, } from "./testUtils"; import { sanitizeDrawingData, validateImportedDrawing, configureSecuritySettings, resetSecuritySettings, getSecurityConfig, } from "../security"; // Test directly against the security functions first (unit-level) describe("Security Sanitization - Image Data URLs", () => { // Reset security settings before each test beforeEach(() => { resetSecuritySettings(); }); describe("configurable size limits", () => { it("should use default 10MB limit", () => { const config = getSecurityConfig(); expect(config.maxDataUrlSize).toBe(10 * 1024 * 1024); }); it("should allow configuring the size limit", () => { configureSecuritySettings({ maxDataUrlSize: 5 * 1024 * 1024 }); const config = getSecurityConfig(); expect(config.maxDataUrlSize).toBe(5 * 1024 * 1024); }); it("should reject dataURL exceeding configured limit", () => { // Set a small limit for testing configureSecuritySettings({ maxDataUrlSize: 1000 }); // Create a dataURL larger than 1000 chars const largeDataUrl = "data:image/png;base64," + "A".repeat(2000); const files = { "file-1": { id: "file-1", mimeType: "image/png", dataURL: largeDataUrl, created: Date.now(), }, }; const result = sanitizeDrawingData({ elements: [], appState: { viewBackgroundColor: "#ffffff" }, files, }); const resultFiles = result.files as Record; // Should be cleared because it exceeds the configured limit expect(resultFiles["file-1"].dataURL).toBe(""); }); it("should allow dataURL under configured limit", () => { // Set limit to 5000 chars configureSecuritySettings({ maxDataUrlSize: 5000 }); // Create a dataURL smaller than 5000 chars const smallDataUrl = "data:image/png;base64," + "A".repeat(100); const files = { "file-1": { id: "file-1", mimeType: "image/png", dataURL: smallDataUrl, created: Date.now(), }, }; const result = sanitizeDrawingData({ elements: [], appState: { viewBackgroundColor: "#ffffff" }, files, }); const resultFiles = result.files as Record; expect(resultFiles["file-1"].dataURL).toBe(smallDataUrl); }); it("should reset to defaults", () => { configureSecuritySettings({ maxDataUrlSize: 100 }); expect(getSecurityConfig().maxDataUrlSize).toBe(100); resetSecuritySettings(); expect(getSecurityConfig().maxDataUrlSize).toBe(10 * 1024 * 1024); }); }); describe("sanitizeDrawingData - files handling", () => { it("should preserve small image data URLs unchanged", () => { const files = createSampleFilesObject(1, "small"); const originalDataUrl = Object.values(files)[0].dataURL; const result = sanitizeDrawingData({ elements: [], appState: { viewBackgroundColor: "#ffffff" }, files, }); const resultFiles = result.files as Record; const resultDataUrl = Object.values(resultFiles)[0]?.dataURL; expect(resultDataUrl).toBe(originalDataUrl); expect(resultDataUrl.length).toBe(originalDataUrl.length); }); it("should preserve large image data URLs (>10000 chars) - REGRESSION TEST for issue #17", () => { const files = createSampleFilesObject(1, "large"); const originalDataUrl = Object.values(files)[0].dataURL; // Verify this is actually a large data URL that would trigger the bug expect(originalDataUrl.length).toBeGreaterThan(10000); const result = sanitizeDrawingData({ elements: [], appState: { viewBackgroundColor: "#ffffff" }, files, }); const resultFiles = result.files as Record; const resultDataUrl = Object.values(resultFiles)[0]?.dataURL; // THIS IS THE KEY ASSERTION - the old code would truncate to ~10000 chars expect(resultDataUrl.length).toBe(originalDataUrl.length); expect(resultDataUrl).toBe(originalDataUrl); }); it("should handle multiple images with large data URLs", () => { const files = createSampleFilesObject(3, "large"); const result = sanitizeDrawingData({ elements: [], appState: { viewBackgroundColor: "#ffffff" }, files, }); const comparison = compareFilesObjects(files, result.files as Record); expect(comparison.isEqual).toBe(true); expect(comparison.differences).toHaveLength(0); }); it("should sanitize malicious script tags in dataURL", () => { const maliciousFiles = { "file-1": { id: "file-1", mimeType: "image/png", dataURL: "data:image/png;base64,AAAA", created: Date.now(), }, }; const result = sanitizeDrawingData({ elements: [], appState: { viewBackgroundColor: "#ffffff" }, files: maliciousFiles, }); const resultFiles = result.files as Record; // The dataURL should be cleared or sanitized when it contains script tags expect(resultFiles["file-1"].dataURL).not.toContain("", mimeType: "image/png