path = tempnam(sys_get_temp_dir(), 'gis-test-');
$zip = new ZipArchive;
$zip->open($this->path, ZipArchive::OVERWRITE);
$zip->addFromString('xl/workbook.xml', '');
$zip->addFromString('xl/_rels/workbook.xml.rels', '');
$zip->addFromString('xl/worksheets/sheet1.xml', 'Private synthetic label
Private synthetic label
1+12
');
$zip->close();
}
protected function tearDown(): void
{
unlink($this->path);
parent::tearDown();
}
/** @return array */
private function manifest(): array
{
return ['sha256' => hash_file('sha256', $this->path), 'owner' => 'Synthetic owner', 'version' => 'test-1',
'received_at' => '2026-09-28', 'classification' => 'internal', 'inspection_authorization' => 'Synthetic test only'];
}
public function test_audit_quarantines_unknown_crs_and_reports_duplicates_without_copying_source_values(): void
{
$report = (new CartographyWorkbookAudit)->inspect($this->path, $this->manifest());
$this->assertSame(3, $report['rows_inspected']);
$this->assertSame('quarantined', $report['status']);
$this->assertFalse($report['publication_allowed']);
$this->assertSame([], $report['transformations']);
$this->assertSame('duplicate', $report['sheets'][0]['rows'][1]['status']);
$this->assertStringNotContainsString('Private synthetic label', json_encode($report));
$this->assertStringContainsString('formula_not_evaluated', json_encode($report));
$this->assertStringContainsString('original_crs_unknown', json_encode($report));
}
public function test_checksum_mismatch_is_rejected(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('checksum');
(new CartographyWorkbookAudit)->inspect($this->path, [...$this->manifest(), 'sha256' => str_repeat('0', 64)]);
}
public function test_inspection_requires_authorization_evidence(): void
{
$this->expectException(RuntimeException::class);
(new CartographyWorkbookAudit)->inspect($this->path, [...$this->manifest(), 'inspection_authorization' => '']);
}
public function test_xml_entities_are_rejected(): void
{
$zip = new ZipArchive;
$zip->open($this->path);
$zip->addFromString('xl/workbook.xml', ']>&x;');
$zip->close();
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('unsafe');
(new CartographyWorkbookAudit)->inspect($this->path, $this->manifest());
}
}