mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 11:21:09 +02:00
- Fix exists() to only catch 404/NotFound, rethrow real errors - Add downloadStream() for memory-efficient large file downloads - Add uploadMultipart() using @aws-sdk/lib-storage for large files - Add automatic pagination to list() via continuation tokens - Add CDN URL support (cdnUrl in BucketConfig, getCdnUrl() method) - Reduce factory boilerplate with generic createStorage() function - Add MinIO lifecycle rules for tmp/ prefixes (chat 90d, calendar 30d, picture 7d) - Add vitest setup with 56 tests covering client, factory, and utils Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
132 lines
3.3 KiB
TypeScript
132 lines
3.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
|
// Mock the client module — StorageClient must be a class
|
|
vi.mock('./client', () => ({
|
|
StorageClient: vi.fn(function (this: Record<string, unknown>) {
|
|
this.upload = vi.fn();
|
|
this.getBucketName = vi.fn();
|
|
}),
|
|
}));
|
|
|
|
import {
|
|
createStorage,
|
|
createStorageClient,
|
|
createPictureStorage,
|
|
createChatStorage,
|
|
} from './factory';
|
|
import { BUCKETS } from './types';
|
|
import { StorageClient } from './client';
|
|
|
|
describe('createStorage', () => {
|
|
beforeEach(() => {
|
|
process.env.NODE_ENV = 'development';
|
|
});
|
|
|
|
afterEach(() => {
|
|
delete process.env.PICTURE_STORAGE_PUBLIC_URL;
|
|
delete process.env.PICTURE_CDN_URL;
|
|
});
|
|
|
|
it('creates a client with the correct bucket name', () => {
|
|
createStorage('PICTURE');
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
expect.objectContaining({ name: BUCKETS.PICTURE })
|
|
);
|
|
});
|
|
|
|
it('resolves public URL from environment', () => {
|
|
process.env.PICTURE_STORAGE_PUBLIC_URL = 'https://cdn.example.com/picture';
|
|
|
|
createStorage('PICTURE');
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
expect.objectContaining({ publicUrl: 'https://cdn.example.com/picture' })
|
|
);
|
|
});
|
|
|
|
it('resolves CDN URL from environment', () => {
|
|
process.env.PICTURE_CDN_URL = 'https://cdn.fast.com/picture';
|
|
|
|
createStorage('PICTURE');
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
expect.objectContaining({ cdnUrl: 'https://cdn.fast.com/picture' })
|
|
);
|
|
});
|
|
|
|
it('allows explicit publicUrl override', () => {
|
|
process.env.PICTURE_STORAGE_PUBLIC_URL = 'https://from-env.com';
|
|
|
|
createStorage('PICTURE', 'https://explicit.com');
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
expect.objectContaining({ publicUrl: 'https://explicit.com' })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('convenience aliases', () => {
|
|
it('createPictureStorage creates PICTURE bucket', () => {
|
|
createPictureStorage();
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
expect.objectContaining({ name: BUCKETS.PICTURE })
|
|
);
|
|
});
|
|
|
|
it('createChatStorage creates CHAT bucket', () => {
|
|
createChatStorage();
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.any(Object),
|
|
expect.objectContaining({ name: BUCKETS.CHAT })
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('createStorageClient', () => {
|
|
afterEach(() => {
|
|
process.env.NODE_ENV = 'development';
|
|
delete process.env.S3_ENDPOINT;
|
|
delete process.env.S3_ACCESS_KEY;
|
|
delete process.env.S3_SECRET_KEY;
|
|
});
|
|
|
|
it('throws when endpoint is missing in production', () => {
|
|
process.env.NODE_ENV = 'production';
|
|
process.env.S3_ENDPOINT = '';
|
|
|
|
expect(() => createStorageClient(BUCKETS.CHAT)).toThrow('S3_ENDPOINT is required');
|
|
});
|
|
|
|
it('throws when credentials are missing in production', () => {
|
|
process.env.NODE_ENV = 'production';
|
|
process.env.S3_ENDPOINT = 'https://s3.example.com';
|
|
process.env.S3_ACCESS_KEY = '';
|
|
process.env.S3_SECRET_KEY = '';
|
|
|
|
expect(() => createStorageClient(BUCKETS.CHAT)).toThrow(
|
|
'S3_ACCESS_KEY and S3_SECRET_KEY are required'
|
|
);
|
|
});
|
|
|
|
it('uses MinIO defaults in development', () => {
|
|
process.env.NODE_ENV = 'development';
|
|
|
|
createStorageClient(BUCKETS.CHAT);
|
|
|
|
expect(StorageClient).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
endpoint: 'http://localhost:9000',
|
|
accessKeyId: 'minioadmin',
|
|
}),
|
|
expect.any(Object)
|
|
);
|
|
});
|
|
});
|