Assessor_2.0/tests/server/gpo-folder-transport.test.ts

81 lines
3.6 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' });
});
it('compresses safe folder content that exceeds the smaller transport limit', async () => {
const repeated = 'policy-setting='.repeat(256);
const transport = await createGpoFolderTransport([
source('SyntheticBackup/DomainSysvol/GPO/Registry.pol', repeated)
], {
maximumSourceBytes: 8 * 1024,
maximumTransportBytes: 1024
});
expect(transport.sourceBytes).toBeGreaterThan(1024);
expect(transport.bytes.byteLength).toBeLessThan(1024);
const restored = readVerifiedZipFiles(transport.bytes, [
'SyntheticBackup/DomainSysvol/GPO/Registry.pol'
]);
expect(new TextDecoder().decode(restored[0].bytes)).toBe(repeated);
});
});