64 lines
2.9 KiB
TypeScript
64 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { inventoryGpoContainer, recognizeGpoContainer } from '../../server/gpo/gpo-container-inventory';
|
|
import { readVerifiedZipFiles } from '../../server/gpo/zip-inventory';
|
|
import {
|
|
createGpoFolderTransport,
|
|
type GpoFolderFileSource
|
|
} from '../../src/gpo/gpo-folder-transport';
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
function source(relativePath: string, value: string): GpoFolderFileSource {
|
|
const bytes = encoder.encode(value);
|
|
return { relativePath, sizeBytes: bytes.byteLength, readBytes: async () => bytes };
|
|
}
|
|
|
|
const entries = [
|
|
source('SyntheticBackup/{22222222-2222-4222-8222-222222222222}/Backup.xml',
|
|
'<?xml version="1.0"?><BackupInst><ID>{22222222-2222-4222-8222-222222222222}</ID><GPOGuid>{11111111-1111-4111-8111-111111111111}</GPOGuid><GPODomain>synthetic.example</GPODomain><BackupTime>2026-07-21T10:00:00Z</BackupTime></BackupInst>'),
|
|
source('SyntheticBackup/{22222222-2222-4222-8222-222222222222}/DomainSysvol/GPO/gpt.ini', '[General]')
|
|
];
|
|
|
|
describe('unzipped GPO folder transport', () => {
|
|
it('creates a deterministic byte-preserving ZIP envelope accepted by the hardened GPO inventory', async () => {
|
|
const first = await createGpoFolderTransport(entries);
|
|
const reordered = await createGpoFolderTransport([...entries].reverse());
|
|
|
|
expect(first).toMatchObject({
|
|
fileName: 'SyntheticBackup.gpo-backup.zip',
|
|
fileCount: 2,
|
|
rootName: 'SyntheticBackup'
|
|
});
|
|
expect(first.bytes).toEqual(reordered.bytes);
|
|
expect(inventoryGpoContainer(first.bytes).recognition.status).toBe('gpo-backup-candidate');
|
|
|
|
const recognition = recognizeGpoContainer(first.bytes);
|
|
expect(recognition.recognition.status).toBe('gpo-backup-candidate');
|
|
expect(Object.keys(recognition).sort()).toEqual(['recognition', 'version']);
|
|
expect('archive' in recognition).toBe(false);
|
|
expect('inventory' in recognition).toBe(false);
|
|
|
|
const selected = readVerifiedZipFiles(first.bytes, [
|
|
'SyntheticBackup/{22222222-2222-4222-8222-222222222222}/DomainSysvol/GPO/gpt.ini'
|
|
]);
|
|
expect(new TextDecoder().decode(selected[0].bytes)).toBe('[General]');
|
|
});
|
|
|
|
it('rejects traversal and Unicode/case path collisions before reading file content', async () => {
|
|
await expect(createGpoFolderTransport([source('Backup/../escape.xml', 'x')]))
|
|
.rejects.toMatchObject({ code: 'gpo-folder-path-rejected' });
|
|
await expect(createGpoFolderTransport([
|
|
source('Backup/DomainSysvol/GPO/gpt.ini', 'one'),
|
|
source('backup/domainsysvol/gpo/GPT.INI', 'two')
|
|
])).rejects.toMatchObject({ code: 'gpo-folder-path-collision' });
|
|
});
|
|
|
|
it('rejects a changed file size at the browser read boundary', async () => {
|
|
await expect(createGpoFolderTransport([{
|
|
relativePath: 'Backup/Backup.xml',
|
|
sizeBytes: 2,
|
|
readBytes: async () => encoder.encode('three')
|
|
}])).rejects.toMatchObject({ code: 'gpo-folder-file-size-mismatch' });
|
|
});
|
|
});
|