2024-09-21 03:10:28 +00:00
|
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { readdirSync } from 'node:fs';
|
|
|
|
import { readFile } from 'node:fs/promises';
|
|
|
|
import { basename, join } from 'node:path';
|
|
|
|
import { Readable } from 'node:stream';
|
|
|
|
import { BackupLevel } from '@signalapp/libsignal-client/zkgroup';
|
|
|
|
import { InputStream } from '@signalapp/libsignal-client/dist/io';
|
|
|
|
import {
|
|
|
|
ComparableBackup,
|
|
|
|
Purpose,
|
|
|
|
} from '@signalapp/libsignal-client/dist/MessageBackup';
|
|
|
|
import { assert } from 'chai';
|
|
|
|
|
|
|
|
import { clearData } from './helpers';
|
2024-10-07 19:58:59 +00:00
|
|
|
import { loadAllAndReinitializeRedux } from '../../services/allLoaders';
|
2024-09-21 03:10:28 +00:00
|
|
|
import { backupsService, BackupType } from '../../services/backups';
|
2024-10-07 19:58:59 +00:00
|
|
|
import { initialize as initializeExpiringMessageService } from '../../services/expiringMessagesDeletion';
|
|
|
|
import { singleProtoJobQueue } from '../../jobs/singleProtoJobQueue';
|
2024-09-21 03:10:28 +00:00
|
|
|
import { DataWriter } from '../../sql/Client';
|
|
|
|
|
|
|
|
const { BACKUP_INTEGRATION_DIR } = process.env;
|
|
|
|
|
|
|
|
class MemoryStream extends InputStream {
|
|
|
|
private offset = 0;
|
|
|
|
|
|
|
|
constructor(private readonly buffer: Buffer) {
|
|
|
|
super();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override async read(amount: number): Promise<Buffer> {
|
|
|
|
const result = this.buffer.slice(this.offset, this.offset + amount);
|
|
|
|
this.offset += amount;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
public override async skip(amount: number): Promise<void> {
|
|
|
|
this.offset += amount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
describe('backup/integration', () => {
|
2024-10-07 19:58:59 +00:00
|
|
|
before(async () => {
|
|
|
|
await initializeExpiringMessageService(singleProtoJobQueue);
|
|
|
|
});
|
|
|
|
|
2024-09-21 03:10:28 +00:00
|
|
|
beforeEach(async () => {
|
|
|
|
await clearData();
|
2024-10-07 19:58:59 +00:00
|
|
|
await loadAllAndReinitializeRedux();
|
2024-09-21 03:10:28 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
await DataWriter.removeAll();
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!BACKUP_INTEGRATION_DIR) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const files = readdirSync(BACKUP_INTEGRATION_DIR)
|
|
|
|
.filter(file => file.endsWith('.binproto'))
|
|
|
|
.map(file => join(BACKUP_INTEGRATION_DIR, file));
|
|
|
|
|
2024-09-23 20:56:33 +00:00
|
|
|
if (files.length === 0) {
|
|
|
|
it('no backup tests', () => {
|
|
|
|
throw new Error('No backup integration tests');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-09-21 03:10:28 +00:00
|
|
|
for (const fullPath of files) {
|
|
|
|
it(basename(fullPath), async () => {
|
|
|
|
const expectedBuffer = await readFile(fullPath);
|
|
|
|
|
|
|
|
await backupsService.importBackup(
|
|
|
|
() => Readable.from([expectedBuffer]),
|
|
|
|
BackupType.TestOnlyPlaintext
|
|
|
|
);
|
|
|
|
|
|
|
|
const exported = await backupsService.exportBackupData(
|
|
|
|
BackupLevel.Media,
|
|
|
|
BackupType.TestOnlyPlaintext
|
|
|
|
);
|
|
|
|
|
|
|
|
const actualStream = new MemoryStream(Buffer.from(exported));
|
|
|
|
const expectedStream = new MemoryStream(expectedBuffer);
|
|
|
|
|
|
|
|
const actual = await ComparableBackup.fromUnencrypted(
|
|
|
|
Purpose.RemoteBackup,
|
|
|
|
actualStream,
|
|
|
|
BigInt(exported.byteLength)
|
|
|
|
);
|
|
|
|
const expected = await ComparableBackup.fromUnencrypted(
|
|
|
|
Purpose.RemoteBackup,
|
|
|
|
expectedStream,
|
|
|
|
BigInt(expectedBuffer.byteLength)
|
|
|
|
);
|
|
|
|
|
|
|
|
const actualString = actual.comparableString();
|
|
|
|
const expectedString = expected.comparableString();
|
|
|
|
|
|
|
|
if (expectedString.includes('ReleaseChannelDonationRequest')) {
|
|
|
|
// Skip the unsupported tests
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need "deep*" for fancy diffs
|
|
|
|
assert.deepStrictEqual(actualString, expectedString);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|