innhold/tests/Unit/PetitionMapTest.php
Ruben b8e6d2537d Add browser and unit test suite with Pest + Playwright
Add comprehensive test coverage for core site features (content,
navigation, language, FAQ, news, petition, newsletter) using Pest
browser tests and unit tests for custom plugins. Includes test
infrastructure (Containerfile.test, compose.test.yaml), test
documentation, and test files covering petition form logic, CSV
handling, translation, date formatting, rate limiting, and map data
building.
2026-03-17 16:43:39 +01:00

134 lines
5 KiB
PHP

<?php
// petition-map.php defines cache-path constants and functions.
// petitionMapBuildData() takes an explicit $csvPath so it can be tested
// with temp files; the cache constants are not touched by these tests.
//
// Context is already stubbed in PetitionFormTest.php (loaded first alphabetically),
// so we do not redeclare it here.
require_once CUSTOM_DIR . '/plugins/page/petition-map.php';
// CSV header row shared across tests
const PETITION_MAP_CSV_HEADER = ['timestamp', 'email', 'firstname', 'surname', 'region', 'display', 'status', 'token', 'token_created', 'ip_hash'];
/**
* Write rows to a temp file and return its path.
* The caller is responsible for unlinking it.
*/
function mapTestCsv(array $rows): string
{
$path = tempnam(sys_get_temp_dir(), 'map_test_');
$fp = fopen($path, 'w');
foreach ($rows as $row) {
fputcsv($fp, $row, ',', '"', '');
}
fclose($fp);
return $path;
}
// --- petitionMapBuildData: missing / empty file ---
it('returns empty data when CSV does not exist', function () {
$result = petitionMapBuildData('/tmp/nonexistent-petition-map-test.csv');
expect($result['total'])->toBe(0);
expect($result)->toHaveKey('generated');
});
it('returns zero total for a CSV with only pending signatures', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Ola', 'Hansen', 'oslo', 'semi', 'pending', 'tok1', time(), 'hash1'],
]);
expect(petitionMapBuildData($path)['total'])->toBe(0);
unlink($path);
});
it('returns zero total for a CSV with only the header', function () {
$path = mapTestCsv([PETITION_MAP_CSV_HEADER]);
expect(petitionMapBuildData($path)['total'])->toBe(0);
unlink($path);
});
// --- petitionMapBuildData: counting confirmed signatures ---
it('counts confirmed signatures and groups them by region', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
[time(), 'b@b.com', 'Ola', 'Nilsen', 'oslo', 'semi', 'confirmed', 't2', time(), 'h2'],
[time(), 'c@b.com', 'Per', 'Olsen', 'bergen', 'semi', 'confirmed', 't3', time(), 'h3'],
]);
$result = petitionMapBuildData($path);
expect($result['total'])->toBe(3);
expect($result['regions']['oslo']['count'])->toBe(2);
expect($result['regions']['bergen']['count'])->toBe(1);
unlink($path);
});
it('ignores non-confirmed rows (pending, deleted, etc.)', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
[time(), 'b@b.com', 'Ola', 'Nilsen', 'oslo', 'semi', 'pending', 't2', time(), 'h2'],
[time(), 'c@b.com', 'Per', 'Olsen', 'oslo', 'semi', 'deleted', 't3', time(), 'h3'],
]);
$result = petitionMapBuildData($path);
expect($result['total'])->toBe(1);
unlink($path);
});
it('skips confirmed entries that have no region', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Kari', 'Hansen', '', 'semi', 'confirmed', 't1', time(), 'h1'],
]);
expect(petitionMapBuildData($path)['total'])->toBe(0);
unlink($path);
});
// --- petitionMapBuildData: name handling ---
it('uses only the first word of the firstname', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Kari Marte', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
]);
$signer = petitionMapBuildData($path)['regions']['oslo']['signers'][0];
expect($signer['n'])->toBe('Kari');
unlink($path);
});
it('sets name to null and anonymous flag true for anonymous display', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'anonymous', 'confirmed', 't1', time(), 'h1'],
]);
$signer = petitionMapBuildData($path)['regions']['oslo']['signers'][0];
expect($signer['n'])->toBeNull();
expect($signer['a'])->toBeTrue();
unlink($path);
});
it('sets anonymous flag false for non-anonymous display', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'a@b.com', 'Lars', 'Berg', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
]);
$signer = petitionMapBuildData($path)['regions']['oslo']['signers'][0];
expect($signer['a'])->toBeFalse();
unlink($path);
});
it('does not include emails, surnames, tokens, or ip_hashes in the output', function () {
$path = mapTestCsv([
PETITION_MAP_CSV_HEADER,
[time(), 'secret@b.com', 'Lars', 'SecretSurname', 'oslo', 'semi', 'confirmed', 'secret-token', time(), 'secret-hash'],
]);
$result = petitionMapBuildData($path);
$json = json_encode($result);
expect($json)->not->toContain('secret@b.com');
expect($json)->not->toContain('SecretSurname');
expect($json)->not->toContain('secret-token');
expect($json)->not->toContain('secret-hash');
unlink($path);
});