feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
83
apps/mobile-rn/__tests__/recording-durability.test.ts
Normal file
83
apps/mobile-rn/__tests__/recording-durability.test.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { SUPABASE_URL } from '@d3ro/core/supabase-config';
|
||||
import { resumableAudioUploadTestContract } from '../src/features/import/resumable-audio-upload';
|
||||
import {
|
||||
durableQueueTestContract,
|
||||
type DurableQueueItem,
|
||||
} from '../src/features/recording/durable-processing-queue';
|
||||
|
||||
const USER_ID = '11111111-1111-4111-8111-111111111111';
|
||||
|
||||
function queueItem(overrides: Partial<DurableQueueItem> = {}): DurableQueueItem {
|
||||
const path = `${durableQueueTestContract.queueDirectory}/queued-abc123-def456.wav`;
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
id: 'abc123-def456',
|
||||
userId: USER_ID,
|
||||
meetingId: null,
|
||||
path,
|
||||
uri: `file://${path}`,
|
||||
fileName: 'recording.wav',
|
||||
mimeType: 'audio/wav',
|
||||
sizeBytes: 1_024,
|
||||
durationMs: 1_000,
|
||||
source: 'recording',
|
||||
languageCode: 'ko',
|
||||
status: 'retry',
|
||||
phase: 'uploading',
|
||||
attempts: 2,
|
||||
uploadedBytes: 512,
|
||||
nextAttemptAtMs: 1_000,
|
||||
lastErrorCode: 'upload',
|
||||
lastErrorMessage: 'offline',
|
||||
createdAtMs: 1,
|
||||
updatedAtMs: 2,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('resumable audio upload contract', () => {
|
||||
test('uses Supabase TUS six-megabyte chunks and UTF-8 metadata', () => {
|
||||
expect(resumableAudioUploadTestContract.chunkBytes).toBe(6 * 1024 * 1024);
|
||||
expect(resumableAudioUploadTestContract.encodeUtf8Base64('audio')).toBe('YXVkaW8=');
|
||||
expect(resumableAudioUploadTestContract.encodeUtf8Base64('음성')).toBe(
|
||||
Buffer.from('음성', 'utf8').toString('base64'),
|
||||
);
|
||||
});
|
||||
|
||||
test('accepts only the Supabase resumable endpoint and rejects an injected host', () => {
|
||||
const origin = new URL(SUPABASE_URL).origin;
|
||||
expect(
|
||||
resumableAudioUploadTestContract.allowedUploadUrl(
|
||||
`${origin}/storage/v1/upload/resumable/session-1`,
|
||||
),
|
||||
).toContain('/storage/v1/upload/resumable/session-1');
|
||||
expect(() => resumableAudioUploadTestContract.allowedUploadUrl(
|
||||
'https://attacker.invalid/storage/v1/upload/resumable/session-1',
|
||||
)).toThrow('untrusted');
|
||||
expect(() => resumableAudioUploadTestContract.allowedUploadUrl(
|
||||
`${origin}/rest/v1/private`,
|
||||
)).toThrow('untrusted');
|
||||
});
|
||||
});
|
||||
|
||||
describe('durable processing queue validation', () => {
|
||||
test('keeps a complete owned checkpoint and rejects path traversal', () => {
|
||||
const valid = queueItem();
|
||||
expect(durableQueueTestContract.parseQueue(JSON.stringify([valid]))).toEqual([valid]);
|
||||
expect(durableQueueTestContract.ownedQueuePath(valid.path)).toBe(true);
|
||||
expect(durableQueueTestContract.ownedQueuePath(
|
||||
`${durableQueueTestContract.queueDirectory}/../account.json`,
|
||||
)).toBe(false);
|
||||
expect(durableQueueTestContract.parseQueue(JSON.stringify([
|
||||
queueItem({ path: '/sdcard/private.wav', uri: 'file:///sdcard/private.wav' }),
|
||||
]))).toEqual([]);
|
||||
});
|
||||
|
||||
test('rejects impossible upload offsets and caps exponential retry delay', () => {
|
||||
expect(durableQueueTestContract.parseQueue(JSON.stringify([
|
||||
queueItem({ uploadedBytes: 2_048 }),
|
||||
]))).toEqual([]);
|
||||
expect(durableQueueTestContract.retryDelayMs(1)).toBe(5_000);
|
||||
expect(durableQueueTestContract.retryDelayMs(99)).toBe(15 * 60_000);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue