- Concurrent saves preserve both hosters and globalSettings (write queue) - Backup recovery when main config file is corrupted (.bak fallback) - encrypt() rejects empty/null/undefined password - decrypt() rejects empty/null password All 63 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
72 lines
2.7 KiB
JavaScript
72 lines
2.7 KiB
JavaScript
const { describe, it } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { encrypt, decrypt } = require('../lib/backup-crypto');
|
|
|
|
describe('backup-crypto', () => {
|
|
const sampleConfig = {
|
|
hosters: { 'doodstream.com': { enabled: true, apiKey: 'test-key-123' } },
|
|
hosterSettings: { 'doodstream.com': { retries: 3 } },
|
|
globalSettings: { alwaysOnTop: false },
|
|
history: [{ file: 'test.mkv', link: 'https://example.com/abc' }]
|
|
};
|
|
|
|
it('encrypt then decrypt round-trips with correct password', () => {
|
|
const buf = encrypt(sampleConfig, 'mySecret!');
|
|
const result = decrypt(buf, 'mySecret!');
|
|
assert.deepStrictEqual(result, sampleConfig);
|
|
});
|
|
|
|
it('decrypt with wrong password throws', () => {
|
|
const buf = encrypt(sampleConfig, 'correct');
|
|
assert.throws(() => decrypt(buf, 'wrong'), /Falsches Passwort/);
|
|
});
|
|
|
|
it('decrypt with corrupted data throws', () => {
|
|
const buf = encrypt(sampleConfig, 'pw');
|
|
buf[buf.length - 1] ^= 0xff; // flip last byte
|
|
assert.throws(() => decrypt(buf, 'pw'), /Falsches Passwort/);
|
|
});
|
|
|
|
it('decrypt with invalid magic throws', () => {
|
|
// Buffer must be long enough to pass the length check (>= 4+16+12+16+1 = 49)
|
|
const buf = Buffer.alloc(60, 0x41); // 60 bytes of 'A'
|
|
assert.throws(() => decrypt(buf, 'pw'), /Keine gültige/);
|
|
});
|
|
|
|
it('decrypt with too-short buffer throws', () => {
|
|
assert.throws(() => decrypt(Buffer.alloc(10), 'pw'), /Ungültiges Backup-Format/);
|
|
});
|
|
|
|
it('handles empty config gracefully', () => {
|
|
const empty = { hosters: {}, hosterSettings: {}, globalSettings: {}, history: [] };
|
|
const buf = encrypt(empty, 'pw');
|
|
assert.deepStrictEqual(decrypt(buf, 'pw'), empty);
|
|
});
|
|
|
|
it('handles unicode passwords', () => {
|
|
const buf = encrypt(sampleConfig, 'Pässwört🔑');
|
|
const result = decrypt(buf, 'Pässwört🔑');
|
|
assert.deepStrictEqual(result, sampleConfig);
|
|
});
|
|
|
|
it('encrypt rejects empty password', () => {
|
|
assert.throws(() => encrypt(sampleConfig, ''), /Passwort/);
|
|
assert.throws(() => encrypt(sampleConfig, null), /Passwort/);
|
|
assert.throws(() => encrypt(sampleConfig, undefined), /Passwort/);
|
|
});
|
|
|
|
it('decrypt rejects empty password', () => {
|
|
const buf = encrypt(sampleConfig, 'valid');
|
|
assert.throws(() => decrypt(buf, ''), /Passwort/);
|
|
assert.throws(() => decrypt(buf, null), /Passwort/);
|
|
});
|
|
|
|
it('each encryption produces different output (random salt/iv)', () => {
|
|
const a = encrypt(sampleConfig, 'same');
|
|
const b = encrypt(sampleConfig, 'same');
|
|
assert.ok(!a.equals(b), 'two encryptions should differ');
|
|
// but both decrypt to same result
|
|
assert.deepStrictEqual(decrypt(a, 'same'), decrypt(b, 'same'));
|
|
});
|
|
});
|