mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-23 02:45:21 -05:00
632 lines
20 KiB
TypeScript
632 lines
20 KiB
TypeScript
import { randomUUID } from "crypto";
|
|
import { beforeEach, describe, expect, test, vi } from "vitest";
|
|
import { StorageErrorCode } from "@formbricks/storage";
|
|
import { TAccessType } from "@formbricks/types/storage";
|
|
import {
|
|
deleteFile,
|
|
deleteFilesByWorkspaceId,
|
|
getFileStreamForDownload,
|
|
getSignedUrlForUpload,
|
|
} from "./service";
|
|
|
|
// Mock external dependencies
|
|
vi.mock("crypto", () => ({
|
|
randomUUID: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@formbricks/logger", () => ({
|
|
logger: {
|
|
error: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock("@formbricks/storage", () => ({
|
|
StorageErrorCode: {
|
|
Unknown: "unknown",
|
|
S3ClientError: "s3_client_error",
|
|
S3CredentialsError: "s3_credentials_error",
|
|
FileNotFoundError: "file_not_found_error",
|
|
InvalidInput: "invalid_input",
|
|
},
|
|
deleteFile: vi.fn(),
|
|
deleteFilesByPrefix: vi.fn(),
|
|
getFileStream: vi.fn(),
|
|
getSignedDownloadUrl: vi.fn(),
|
|
getSignedUploadUrl: vi.fn(),
|
|
}));
|
|
|
|
// Import mocked dependencies
|
|
const { logger } = await import("@formbricks/logger");
|
|
const storageModule = await import("@formbricks/storage");
|
|
const {
|
|
deleteFile: deleteFileFromS3,
|
|
deleteFilesByPrefix,
|
|
getSignedUploadUrl,
|
|
getFileStream,
|
|
} = storageModule;
|
|
type MockedSignedUploadReturn = Awaited<ReturnType<typeof getSignedUploadUrl>>;
|
|
type MockedFileStreamReturn = Awaited<ReturnType<typeof getFileStream>>;
|
|
type MockedDeleteFileReturn = Awaited<ReturnType<typeof deleteFile>>;
|
|
type MockedDeleteFilesByPrefixReturn = Awaited<ReturnType<typeof deleteFilesByPrefix>>;
|
|
|
|
const mockUUID = "test-uuid-123-456-789-10";
|
|
|
|
describe("storage service", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(randomUUID).mockReturnValue(mockUUID);
|
|
});
|
|
|
|
describe("getSignedUrlForUpload", () => {
|
|
test("should generate signed URL for upload with unique filename", async () => {
|
|
const mockSignedUrlResponse = {
|
|
ok: true,
|
|
data: {
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockSignedUrlResponse);
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"test-image.jpg",
|
|
"env-123",
|
|
"image/jpeg",
|
|
"public" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.data).toEqual({
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
fileUrl: `/storage/env-123/public/test-image--fid--${mockUUID}.jpg`,
|
|
});
|
|
}
|
|
|
|
expect(getSignedUploadUrl).toHaveBeenCalledWith(
|
|
`test-image--fid--${mockUUID}.jpg`,
|
|
"image/jpeg",
|
|
"env-123/public",
|
|
1024 * 1024 * 10 // 10MB default
|
|
);
|
|
});
|
|
|
|
test("should return relative URL for private files", async () => {
|
|
const mockSignedUrlResponse = {
|
|
ok: true,
|
|
data: {
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockSignedUrlResponse);
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"test-doc.pdf",
|
|
"env-123",
|
|
"application/pdf",
|
|
"private" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.data.fileUrl).toBe(`/storage/env-123/private/test-doc--fid--${mockUUID}.pdf`);
|
|
}
|
|
});
|
|
|
|
test("should generate scoped private upload URL when path segments are provided", async () => {
|
|
const mockSignedUrlResponse = {
|
|
ok: true,
|
|
data: {
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockSignedUrlResponse);
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"test-doc.pdf",
|
|
"ws-123",
|
|
"application/pdf",
|
|
"private" as TAccessType,
|
|
1024 * 1024 * 10,
|
|
["surveys", "survey-123", "questions", "question-123"]
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.data.fileUrl).toBe(
|
|
`/storage/ws-123/private/surveys/survey-123/questions/question-123/test-doc--fid--${mockUUID}.pdf`
|
|
);
|
|
}
|
|
|
|
expect(getSignedUploadUrl).toHaveBeenCalledWith(
|
|
`test-doc--fid--${mockUUID}.pdf`,
|
|
"application/pdf",
|
|
"ws-123/private/surveys/survey-123/questions/question-123",
|
|
1024 * 1024 * 10
|
|
);
|
|
});
|
|
|
|
test("should properly sanitize filenames with special characters like # in URL", async () => {
|
|
const mockSignedUrlResponse = {
|
|
ok: true,
|
|
data: {
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockSignedUrlResponse);
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"test#file.txt",
|
|
"env-123",
|
|
"text/plain",
|
|
"public" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
// The filename should be URL-encoded to prevent # from being treated as a URL fragment
|
|
expect(result.data.fileUrl).toBe(`/storage/env-123/public/testfile--fid--${mockUUID}.txt`);
|
|
}
|
|
|
|
expect(getSignedUploadUrl).toHaveBeenCalledWith(
|
|
`testfile--fid--${mockUUID}.txt`,
|
|
"text/plain",
|
|
"env-123/public",
|
|
1024 * 1024 * 10 // 10MB default
|
|
);
|
|
});
|
|
|
|
test("should handle files with multiple dots in filename", async () => {
|
|
const mockSignedUrlResponse = {
|
|
ok: true,
|
|
data: {
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockSignedUrlResponse);
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"my.backup.file.pdf",
|
|
"env-123",
|
|
"application/pdf",
|
|
"public" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(getSignedUploadUrl).toHaveBeenCalledWith(
|
|
`my.backup.file--fid--${mockUUID}.pdf`,
|
|
"application/pdf",
|
|
"env-123/public",
|
|
1024 * 1024 * 10
|
|
);
|
|
});
|
|
|
|
test("should use custom maxFileUploadSize when provided", async () => {
|
|
const mockSignedUrlResponse = {
|
|
ok: true,
|
|
data: {
|
|
signedUrl: "https://s3.example.com/upload",
|
|
presignedFields: { key: "value" },
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockSignedUrlResponse);
|
|
|
|
await getSignedUrlForUpload(
|
|
"large-file.pdf",
|
|
"env-123",
|
|
"application/pdf",
|
|
"public" as TAccessType,
|
|
1024 * 1024 * 50 // 50MB
|
|
);
|
|
|
|
expect(getSignedUploadUrl).toHaveBeenCalledWith(
|
|
`large-file--fid--${mockUUID}.pdf`,
|
|
"application/pdf",
|
|
"env-123/public",
|
|
1024 * 1024 * 50
|
|
);
|
|
});
|
|
|
|
test("should return error when getSignedUploadUrl fails", async () => {
|
|
const mockErrorResponse = {
|
|
ok: false,
|
|
error: {
|
|
code: StorageErrorCode.S3ClientError,
|
|
},
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockErrorResponse);
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"test-file.pdf",
|
|
"env-123",
|
|
"application/pdf",
|
|
"public" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.code).toBe(StorageErrorCode.S3ClientError);
|
|
}
|
|
});
|
|
|
|
test("should handle unexpected errors and return unknown error", async () => {
|
|
vi.mocked(getSignedUploadUrl).mockRejectedValue(new Error("Unexpected error"));
|
|
|
|
const result = await getSignedUrlForUpload(
|
|
"test-file.pdf",
|
|
"env-123",
|
|
"application/pdf",
|
|
"public" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.code).toBe(StorageErrorCode.Unknown);
|
|
}
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
{ error: expect.any(Error) },
|
|
"Error getting signed url for upload"
|
|
);
|
|
});
|
|
|
|
test("should return InvalidInput when sanitized filename is empty or invalid", async () => {
|
|
const mockErrorResponse = {
|
|
ok: false,
|
|
error: { code: StorageErrorCode.InvalidInput },
|
|
} as MockedSignedUploadReturn;
|
|
|
|
vi.mocked(getSignedUploadUrl).mockResolvedValue(mockErrorResponse);
|
|
|
|
const result = await getSignedUrlForUpload("----.png", "env-123", "image/png", "public" as TAccessType);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.code).toBe(StorageErrorCode.InvalidInput);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("deleteFile", () => {
|
|
test("should call deleteFileFromS3 with correct file key", async () => {
|
|
const mockSuccessResult = {
|
|
ok: true,
|
|
data: undefined,
|
|
} as MockedDeleteFileReturn;
|
|
|
|
vi.mocked(deleteFileFromS3).mockResolvedValue(mockSuccessResult);
|
|
|
|
const result = await deleteFile("env-123", "public" as TAccessType, "test-file.jpg");
|
|
|
|
expect(result).toEqual(mockSuccessResult);
|
|
expect(deleteFileFromS3).toHaveBeenCalledWith("env-123/public/test-file.jpg");
|
|
});
|
|
|
|
test("should handle private access type", async () => {
|
|
const mockSuccessResult = {
|
|
ok: true,
|
|
data: undefined,
|
|
} as MockedDeleteFileReturn;
|
|
|
|
vi.mocked(deleteFileFromS3).mockResolvedValue(mockSuccessResult);
|
|
|
|
const result = await deleteFile("env-456", "private" as TAccessType, "private-doc.pdf");
|
|
|
|
expect(result).toEqual(mockSuccessResult);
|
|
expect(deleteFileFromS3).toHaveBeenCalledWith("env-456/private/private-doc.pdf");
|
|
});
|
|
|
|
test("should handle when deleteFileFromS3 returns error", async () => {
|
|
const mockErrorResult = {
|
|
ok: false,
|
|
error: {
|
|
code: StorageErrorCode.Unknown,
|
|
},
|
|
} as MockedDeleteFileReturn;
|
|
|
|
vi.mocked(deleteFileFromS3).mockResolvedValue(mockErrorResult);
|
|
|
|
const result = await deleteFile("env-123", "public" as TAccessType, "test-file.jpg");
|
|
|
|
expect(result).toEqual(mockErrorResult);
|
|
expect(deleteFileFromS3).toHaveBeenCalledWith("env-123/public/test-file.jpg");
|
|
});
|
|
|
|
test("should fall back to fallbackId path when primary returns FileNotFoundError", async () => {
|
|
const mockNotFound = {
|
|
ok: false,
|
|
error: { code: StorageErrorCode.FileNotFoundError },
|
|
} as MockedDeleteFileReturn;
|
|
const mockSuccess = { ok: true, data: undefined } as MockedDeleteFileReturn;
|
|
|
|
vi.mocked(deleteFileFromS3).mockResolvedValueOnce(mockNotFound).mockResolvedValueOnce(mockSuccess);
|
|
|
|
const result = await deleteFile("ws-456", "public" as TAccessType, "file.jpg", "env-123");
|
|
|
|
expect(result).toEqual(mockSuccess);
|
|
expect(deleteFileFromS3).toHaveBeenCalledTimes(2);
|
|
expect(deleteFileFromS3).toHaveBeenNthCalledWith(1, "ws-456/public/file.jpg");
|
|
expect(deleteFileFromS3).toHaveBeenNthCalledWith(2, "env-123/public/file.jpg");
|
|
});
|
|
|
|
test("should not fall back when primary delete succeeds", async () => {
|
|
const mockSuccess = { ok: true, data: undefined } as MockedDeleteFileReturn;
|
|
|
|
vi.mocked(deleteFileFromS3).mockResolvedValue(mockSuccess);
|
|
|
|
const result = await deleteFile("ws-456", "public" as TAccessType, "file.jpg", "env-123");
|
|
|
|
expect(result).toEqual(mockSuccess);
|
|
expect(deleteFileFromS3).toHaveBeenCalledTimes(1);
|
|
expect(deleteFileFromS3).toHaveBeenCalledWith("ws-456/public/file.jpg");
|
|
});
|
|
|
|
test("should not fall back on non-FileNotFound errors", async () => {
|
|
const mockError = {
|
|
ok: false,
|
|
error: { code: StorageErrorCode.S3ClientError },
|
|
} as MockedDeleteFileReturn;
|
|
|
|
vi.mocked(deleteFileFromS3).mockResolvedValue(mockError);
|
|
|
|
const result = await deleteFile("ws-456", "public" as TAccessType, "file.jpg", "env-123");
|
|
|
|
expect(result).toEqual(mockError);
|
|
expect(deleteFileFromS3).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("deleteFilesByWorkspaceId", () => {
|
|
test("should delete files under workspaceId and all environmentId prefixes", async () => {
|
|
const mockSuccessResult = {
|
|
ok: true,
|
|
data: undefined,
|
|
} as MockedDeleteFilesByPrefixReturn;
|
|
|
|
vi.mocked(deleteFilesByPrefix).mockResolvedValue(mockSuccessResult);
|
|
|
|
const result = await deleteFilesByWorkspaceId("ws-456", ["env-123", "env-789"]);
|
|
|
|
expect(result).toEqual(mockSuccessResult);
|
|
expect(deleteFilesByPrefix).toHaveBeenCalledTimes(3);
|
|
expect(deleteFilesByPrefix).toHaveBeenCalledWith("ws-456");
|
|
expect(deleteFilesByPrefix).toHaveBeenCalledWith("env-123");
|
|
expect(deleteFilesByPrefix).toHaveBeenCalledWith("env-789");
|
|
});
|
|
|
|
test("should return error if any prefix deletion fails", async () => {
|
|
const mockSuccessResult = {
|
|
ok: true,
|
|
data: undefined,
|
|
} as MockedDeleteFilesByPrefixReturn;
|
|
|
|
const mockErrorResult = {
|
|
ok: false,
|
|
error: {
|
|
code: StorageErrorCode.Unknown,
|
|
},
|
|
} as MockedDeleteFilesByPrefixReturn;
|
|
|
|
vi.mocked(deleteFilesByPrefix)
|
|
.mockResolvedValueOnce(mockSuccessResult)
|
|
.mockResolvedValueOnce(mockErrorResult);
|
|
|
|
const result = await deleteFilesByWorkspaceId("ws-456", ["env-123"]);
|
|
|
|
expect(result!.ok).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("getFileStreamForDownload", () => {
|
|
test("should return file stream for public file", async () => {
|
|
const mockStream = new ReadableStream();
|
|
const mockStreamResult = {
|
|
ok: true,
|
|
data: {
|
|
body: mockStream,
|
|
contentType: "image/jpeg",
|
|
contentLength: 12345,
|
|
},
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockStreamResult);
|
|
|
|
const result = await getFileStreamForDownload("test-image.jpg", "env-123", "public" as TAccessType);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.data.body).toBe(mockStream);
|
|
expect(result.data.contentType).toBe("image/jpeg");
|
|
expect(result.data.contentLength).toBe(12345);
|
|
}
|
|
expect(getFileStream).toHaveBeenCalledWith("env-123/public/test-image.jpg");
|
|
});
|
|
|
|
test("should return file stream for private file", async () => {
|
|
const mockStream = new ReadableStream();
|
|
const mockStreamResult = {
|
|
ok: true,
|
|
data: {
|
|
body: mockStream,
|
|
contentType: "application/pdf",
|
|
contentLength: 54321,
|
|
},
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockStreamResult);
|
|
|
|
const result = await getFileStreamForDownload("document.pdf", "env-456", "private" as TAccessType);
|
|
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.data.contentType).toBe("application/pdf");
|
|
}
|
|
expect(getFileStream).toHaveBeenCalledWith("env-456/private/document.pdf");
|
|
});
|
|
|
|
test("should decode URL-encoded filename", async () => {
|
|
const mockStream = new ReadableStream();
|
|
const mockStreamResult = {
|
|
ok: true,
|
|
data: {
|
|
body: mockStream,
|
|
contentType: "image/png",
|
|
contentLength: 1000,
|
|
},
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockStreamResult);
|
|
|
|
// URL-encoded filename with spaces: "my file.png" -> "my%20file.png"
|
|
const result = await getFileStreamForDownload("my%20file.png", "env-123", "public" as TAccessType);
|
|
|
|
expect(result.ok).toBe(true);
|
|
// Should decode %20 to space before passing to getFileStream
|
|
expect(getFileStream).toHaveBeenCalledWith("env-123/public/my file.png");
|
|
});
|
|
|
|
test("should return error when getFileStream fails with FileNotFoundError and no fallback", async () => {
|
|
const mockErrorResult = {
|
|
ok: false,
|
|
error: {
|
|
code: StorageErrorCode.FileNotFoundError,
|
|
},
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockErrorResult);
|
|
|
|
const result = await getFileStreamForDownload("missing-file.jpg", "env-123", "public" as TAccessType);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.code).toBe(StorageErrorCode.FileNotFoundError);
|
|
}
|
|
expect(getFileStream).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("should fall back to fallbackId path when primary path returns FileNotFoundError", async () => {
|
|
const mockErrorResult = {
|
|
ok: false,
|
|
error: { code: StorageErrorCode.FileNotFoundError },
|
|
} as MockedFileStreamReturn;
|
|
|
|
const mockStream = new ReadableStream();
|
|
const mockStreamResult = {
|
|
ok: true,
|
|
data: { body: mockStream, contentType: "image/jpeg", contentLength: 5000 },
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValueOnce(mockErrorResult).mockResolvedValueOnce(mockStreamResult);
|
|
|
|
const result = await getFileStreamForDownload(
|
|
"legacy-file.jpg",
|
|
"ws-456",
|
|
"public" as TAccessType,
|
|
"env-123"
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(getFileStream).toHaveBeenCalledTimes(2);
|
|
expect(getFileStream).toHaveBeenNthCalledWith(1, "ws-456/public/legacy-file.jpg");
|
|
expect(getFileStream).toHaveBeenNthCalledWith(2, "env-123/public/legacy-file.jpg");
|
|
});
|
|
|
|
test("should not fall back when primary path succeeds", async () => {
|
|
const mockStream = new ReadableStream();
|
|
const mockStreamResult = {
|
|
ok: true,
|
|
data: { body: mockStream, contentType: "image/jpeg", contentLength: 5000 },
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockStreamResult);
|
|
|
|
const result = await getFileStreamForDownload("file.jpg", "ws-456", "public" as TAccessType, "env-123");
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(getFileStream).toHaveBeenCalledTimes(1);
|
|
expect(getFileStream).toHaveBeenCalledWith("ws-456/public/file.jpg");
|
|
});
|
|
|
|
test("should not fall back on non-FileNotFound errors", async () => {
|
|
const mockErrorResult = {
|
|
ok: false,
|
|
error: { code: StorageErrorCode.S3ClientError },
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockErrorResult);
|
|
|
|
const result = await getFileStreamForDownload("file.jpg", "ws-456", "public" as TAccessType, "env-123");
|
|
|
|
expect(result.ok).toBe(false);
|
|
expect(getFileStream).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test("should return error when getFileStream fails with S3ClientError", async () => {
|
|
const mockErrorResult = {
|
|
ok: false,
|
|
error: {
|
|
code: StorageErrorCode.S3ClientError,
|
|
},
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockErrorResult);
|
|
|
|
const result = await getFileStreamForDownload("some-file.jpg", "env-123", "public" as TAccessType);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.code).toBe(StorageErrorCode.S3ClientError);
|
|
}
|
|
});
|
|
|
|
test("should handle unexpected errors and return unknown error", async () => {
|
|
vi.mocked(getFileStream).mockRejectedValue(new Error("Unexpected S3 error"));
|
|
|
|
const result = await getFileStreamForDownload("test-file.jpg", "env-123", "public" as TAccessType);
|
|
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error.code).toBe(StorageErrorCode.Unknown);
|
|
}
|
|
expect(logger.error).toHaveBeenCalledWith(
|
|
{ error: expect.any(Error) },
|
|
"Error getting file stream for download"
|
|
);
|
|
});
|
|
|
|
test("should handle filename with fid pattern", async () => {
|
|
const mockStream = new ReadableStream();
|
|
const mockStreamResult = {
|
|
ok: true,
|
|
data: {
|
|
body: mockStream,
|
|
contentType: "image/jpeg",
|
|
contentLength: 5000,
|
|
},
|
|
} as MockedFileStreamReturn;
|
|
|
|
vi.mocked(getFileStream).mockResolvedValue(mockStreamResult);
|
|
|
|
const result = await getFileStreamForDownload(
|
|
"photo--fid--abc123-def456.jpg",
|
|
"env-123",
|
|
"public" as TAccessType
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(getFileStream).toHaveBeenCalledWith("env-123/public/photo--fid--abc123-def456.jpg");
|
|
});
|
|
});
|
|
});
|