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
149
apps/mobile-rn/__tests__/data-portability-service.test.ts
Normal file
149
apps/mobile-rn/__tests__/data-portability-service.test.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import {
|
||||
exportAccountArchive,
|
||||
importAccountArchive,
|
||||
stableJsonStringify,
|
||||
type PortablePayload,
|
||||
} from '../src/features/data-portability';
|
||||
import { sha256Hex } from '../src/features/data-portability/canonical-json';
|
||||
|
||||
const OWNER = '4f642f87-2d12-4dcf-9aa1-f4427d4078a8';
|
||||
|
||||
function payload(): PortablePayload {
|
||||
return {
|
||||
format: 'd3ro-account-portability',
|
||||
schema_version: 1,
|
||||
exported_at: '2026-08-21T00:00:00.000Z',
|
||||
owner_id: OWNER,
|
||||
source: 'cloud',
|
||||
account: { profile: null, settings: null, subscription: null },
|
||||
datasets: {
|
||||
dictionary: [],
|
||||
history: [],
|
||||
meetings: [],
|
||||
transcripts: [],
|
||||
meeting_memos: [],
|
||||
meeting_documents: [],
|
||||
custom_instructions: [],
|
||||
},
|
||||
exclusions: [
|
||||
'raw_audio',
|
||||
'storage_objects',
|
||||
'payment_credentials',
|
||||
'push_tokens',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function client(
|
||||
rpcImpl: (
|
||||
name: string,
|
||||
args?: Record<string, unknown>,
|
||||
) => Promise<{
|
||||
data: unknown;
|
||||
error: unknown;
|
||||
}>,
|
||||
): any {
|
||||
return {
|
||||
auth: {
|
||||
getUser: jest.fn(async () => ({
|
||||
data: { user: { id: OWNER } },
|
||||
error: null,
|
||||
})),
|
||||
},
|
||||
rpc: jest.fn(rpcImpl),
|
||||
};
|
||||
}
|
||||
|
||||
describe('account portability service contracts', () => {
|
||||
it('verifies the server export checksum and row count', async () => {
|
||||
const canonical = stableJsonStringify(payload());
|
||||
const fake = client(async name => ({
|
||||
data:
|
||||
name === 'export_account_portability'
|
||||
? [
|
||||
{
|
||||
canonical_payload: canonical,
|
||||
checksum: sha256Hex(canonical),
|
||||
exported_at: payload().exported_at,
|
||||
row_count: 0,
|
||||
},
|
||||
]
|
||||
: null,
|
||||
error: null,
|
||||
}));
|
||||
const result = await exportAccountArchive(OWNER, fake);
|
||||
expect(result.rowCount).toBe(0);
|
||||
expect(result.archive.checksum).toBe(sha256Hex(canonical));
|
||||
expect(fake.rpc).toHaveBeenCalledWith('export_account_portability');
|
||||
});
|
||||
|
||||
it('does not call restore RPC when a portable file is corrupted', async () => {
|
||||
const fake = client(async () => ({ data: null, error: null }));
|
||||
const invalid = JSON.stringify({
|
||||
format: 'd3ro-account-portability',
|
||||
schema_version: 1,
|
||||
checksum_algorithm: 'sha256',
|
||||
checksum: '0'.repeat(64),
|
||||
canonical_payload: stableJsonStringify(payload()),
|
||||
});
|
||||
|
||||
await expect(
|
||||
importAccountArchive(
|
||||
OWNER,
|
||||
invalid,
|
||||
'backup.json',
|
||||
'application/json',
|
||||
fake,
|
||||
),
|
||||
).rejects.toMatchObject({ code: 'checksum' });
|
||||
expect(fake.rpc).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('sends only canonical payload and checksum to the atomic restore RPC', async () => {
|
||||
const canonical = stableJsonStringify(payload());
|
||||
const archive = JSON.stringify({
|
||||
format: 'd3ro-account-portability',
|
||||
schema_version: 1,
|
||||
checksum_algorithm: 'sha256',
|
||||
checksum: sha256Hex(canonical),
|
||||
canonical_payload: canonical,
|
||||
});
|
||||
const fake = client(async (name, args) => ({
|
||||
data:
|
||||
name === 'restore_account_portability'
|
||||
? {
|
||||
status: 'imported',
|
||||
checksum: args?.supplied_checksum,
|
||||
imported_rows: 0,
|
||||
skipped_rows: 0,
|
||||
imported_at: '2026-08-21T00:01:00.000Z',
|
||||
}
|
||||
: null,
|
||||
error: null,
|
||||
}));
|
||||
const result = await importAccountArchive(
|
||||
OWNER,
|
||||
archive,
|
||||
'backup.json',
|
||||
'application/json',
|
||||
fake,
|
||||
);
|
||||
expect(result.result.status).toBe('imported');
|
||||
expect(fake.rpc).toHaveBeenCalledWith('restore_account_portability', {
|
||||
canonical_payload: canonical,
|
||||
supplied_checksum: sha256Hex(canonical),
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses a mismatched active session before export', async () => {
|
||||
const fake = client(async () => ({ data: null, error: null }));
|
||||
fake.auth.getUser.mockResolvedValue({
|
||||
data: { user: { id: '1d30574e-5da7-4b22-bf4a-028fd0084962' } },
|
||||
error: null,
|
||||
});
|
||||
await expect(exportAccountArchive(OWNER, fake)).rejects.toMatchObject({
|
||||
code: 'auth',
|
||||
});
|
||||
expect(fake.rpc).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue