| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- /**
- * 治根 #oss-3tb-orphan: 稳定路径单元测试
- *
- * 核心设计原则: **1 个内容 = 1 个文件**。同一内容多次调用, OSS 上始终只覆盖同一对象,
- * 垃圾不可能暴涨 (max 1 文件 1 次写)。
- *
- * 测试策略: mock ossService.uploadFile, 验证 storageService 传给它的 objectKey 是稳定的
- * (同一 contentKey 多次 → 同一 key; 不同 contentKey → 不同 key)
- *
- * 注意: storageService 是单例, constructor 在 import 时读 STORAGE_TYPE.
- * 每次测试用 vi.resetModules + 动态 import 强制重新读 env.
- */
- import { describe, it, expect, vi, beforeEach } from 'vitest';
- const mocks = vi.hoisted(() => ({
- uploadFile: vi.fn(),
- }));
- // mock ossService 必须在 import storageService 之前生效
- vi.mock('@/services/oss.service', () => ({
- ossService: { uploadFile: mocks.uploadFile },
- }));
- async function freshStorageService() {
- vi.resetModules();
- vi.stubEnv('STORAGE_TYPE', 'oss');
- // 重新 import, 会让 storageService 的 constructor 重读 STORAGE_TYPE
- const mod = await import('@/services/storage.service');
- return mod.storageService;
- }
- beforeEach(() => {
- mocks.uploadFile.mockReset();
- mocks.uploadFile.mockImplementation(async (_localPath: string, objectKey: string) =>
- `https://bucket.oss.aliyuncs.com/${objectKey}`,
- );
- });
- describe('storageService.uploadAudioByContentHash - 稳定 key 治根', () => {
- it('同一 contentKey 多次调用, 传给 OSS 的 objectKey 完全相同', async () => {
- const storageService = await freshStorageService();
- const url1 = await storageService.uploadAudioByContentHash('/tmp/a.mp3', 'preview', 'voiceA|1.0|0|50');
- const url2 = await storageService.uploadAudioByContentHash('/tmp/b.mp3', 'preview', 'voiceA|1.0|0|50');
- expect(mocks.uploadFile).toHaveBeenCalledTimes(2);
- const key1 = mocks.uploadFile.mock.calls[0][1] as string;
- const key2 = mocks.uploadFile.mock.calls[1][1] as string;
- expect(key1).toBe(key2); // ⭐ 核心: 同一 contentKey 永远产生同一 OSS key
- expect(url1).toBe(url2);
- });
- it('不同 contentKey 产生不同 OSS key', async () => {
- const storageService = await freshStorageService();
- await storageService.uploadAudioByContentHash('/tmp/a.mp3', 'preview', 'voiceA');
- await storageService.uploadAudioByContentHash('/tmp/b.mp3', 'preview', 'voiceB');
- const keyA = mocks.uploadFile.mock.calls[0][1] as string;
- const keyB = mocks.uploadFile.mock.calls[1][1] as string;
- expect(keyA).not.toBe(keyB);
- });
- it('不同 kind 隔离 OSS 目录 (preview / sync / segment 不能串)', async () => {
- const storageService = await freshStorageService();
- await storageService.uploadAudioByContentHash('/tmp/a.mp3', 'preview', 'sameKey');
- await storageService.uploadAudioByContentHash('/tmp/b.mp3', 'sync', 'sameKey');
- const key1 = mocks.uploadFile.mock.calls[0][1] as string;
- const key2 = mocks.uploadFile.mock.calls[1][1] as string;
- expect(key1).toMatch(/^audio\/preview\//);
- expect(key2).toMatch(/^audio\/sync\//);
- });
- it('contentKey 长度不影响 OSS key 长度 (sha256 截断到 32 字符)', async () => {
- const storageService = await freshStorageService();
- await storageService.uploadAudioByContentHash('/tmp/a.mp3', 'preview', 'x');
- await storageService.uploadAudioByContentHash('/tmp/b.mp3', 'preview', 'x'.repeat(10000));
- const key1 = mocks.uploadFile.mock.calls[0][1] as string;
- const key2 = mocks.uploadFile.mock.calls[1][1] as string;
- expect(key1).toMatch(/^audio\/preview\/[0-9a-f]{32}\.mp3$/);
- expect(key2).toMatch(/^audio\/preview\/[0-9a-f]{32}\.mp3$/);
- });
- });
- describe('storageService.uploadChapterAudio - 稳定 key 治根', () => {
- it('同一 chapterId 多次产生完全相同 key', async () => {
- const storageService = await freshStorageService();
- const url1 = await storageService.uploadChapterAudio('/tmp/a.mp3', 42);
- const url2 = await storageService.uploadChapterAudio('/tmp/b.mp3', 42);
- const key1 = mocks.uploadFile.mock.calls[0][1] as string;
- const key2 = mocks.uploadFile.mock.calls[1][1] as string;
- expect(key1).toBe('audio/chapter/42/full.mp3');
- expect(key2).toBe('audio/chapter/42/full.mp3');
- expect(url1).toBe(url2);
- });
- it('不同 chapterId 产生不同 key', async () => {
- const storageService = await freshStorageService();
- await storageService.uploadChapterAudio('/tmp/a.mp3', 42);
- await storageService.uploadChapterAudio('/tmp/b.mp3', 43);
- const key1 = mocks.uploadFile.mock.calls[0][1] as string;
- const key2 = mocks.uploadFile.mock.calls[1][1] as string;
- expect(key1).toBe('audio/chapter/42/full.mp3');
- expect(key2).toBe('audio/chapter/43/full.mp3');
- });
- });
|