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.
This commit is contained in:
parent
0b61643ec5
commit
b8e6d2537d
20 changed files with 1331 additions and 33 deletions
366
tests/Unit/PetitionFormTestCsv.php
Normal file
366
tests/Unit/PetitionFormTestCsv.php
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
<?php
|
||||
|
||||
// PetitionFormTest.php (loads first) already required petition-form.php and
|
||||
// defined the Context stub. This file only adds CSV-function tests.
|
||||
|
||||
const PETITION_CSV_HEADER = [
|
||||
'timestamp', 'email', 'firstname', 'surname',
|
||||
'region', 'display', 'status', 'token', 'token_created', 'ip_hash',
|
||||
];
|
||||
|
||||
/**
|
||||
* Write rows to a temp CSV and return its path. Caller must unlink().
|
||||
*/
|
||||
function petitionTestCsv(array $rows): string
|
||||
{
|
||||
$path = tempnam(sys_get_temp_dir(), 'petition_form_test_');
|
||||
$fp = fopen($path, 'w');
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($fp, $row, ',', '"', '');
|
||||
}
|
||||
fclose($fp);
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a minimal signature data array for petitionAppendSignature.
|
||||
*/
|
||||
function sigData(string $email, string $status = 'pending', string $token = 'tok'): array
|
||||
{
|
||||
return [
|
||||
'timestamp' => time(),
|
||||
'email' => $email,
|
||||
'firstname' => 'Test',
|
||||
'surname' => 'User',
|
||||
'region' => 'oslo',
|
||||
'display' => 'semi',
|
||||
'status' => $status,
|
||||
'token' => $token,
|
||||
'token_created' => time(),
|
||||
'ip_hash' => 'hash',
|
||||
];
|
||||
}
|
||||
|
||||
// --- petitionEmailExists ---
|
||||
|
||||
it('returns false when the CSV does not exist', function () {
|
||||
expect(petitionEmailExists('/tmp/nonexistent-petition-form-test.csv', 'a@b.com'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('returns false when the email is not in the CSV', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'other@b.com', 'Ola', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionEmailExists($path, 'nothere@b.com'))->toBeFalse();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns true when the email exists', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'kari@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionEmailExists($path, 'kari@b.com'))->toBeTrue();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('matches the email case-insensitively', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'kari@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionEmailExists($path, 'KARI@B.COM'))->toBeTrue();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionGetConfirmedSignatures ---
|
||||
|
||||
it('returns an empty array when the CSV does not exist', function () {
|
||||
expect(petitionGetConfirmedSignatures('/tmp/nonexistent-petition-form-test2.csv'))->toBe([]);
|
||||
});
|
||||
|
||||
it('returns only confirmed signatures', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_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'],
|
||||
]);
|
||||
$sigs = petitionGetConfirmedSignatures($path);
|
||||
expect(count($sigs))->toBe(1);
|
||||
expect($sigs[0]['firstname'])->toBe('Kari');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('sorts confirmed signatures newest-first', function () {
|
||||
$older = time() - 100;
|
||||
$newer = time();
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[$older, 'a@b.com', 'Older', 'Hansen', 'oslo', 'semi', 'confirmed', 't1', $older, 'h1'],
|
||||
[$newer, 'b@b.com', 'Newer', 'Nilsen', 'oslo', 'semi', 'confirmed', 't2', $newer, 'h2'],
|
||||
]);
|
||||
$sigs = petitionGetConfirmedSignatures($path);
|
||||
expect($sigs[0]['firstname'])->toBe('Newer');
|
||||
expect($sigs[1]['firstname'])->toBe('Older');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('does not include email or token in the returned signature data', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'secret@b.com', 'Lars', 'Berg', 'oslo', 'semi', 'confirmed', 'secret-tok', time(), 'h1'],
|
||||
]);
|
||||
$sig = petitionGetConfirmedSignatures($path)[0];
|
||||
expect($sig)->not->toHaveKey('email');
|
||||
expect($sig)->not->toHaveKey('token');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionGetSignatureByToken ---
|
||||
|
||||
it('returns null when the CSV does not exist', function () {
|
||||
expect(petitionGetSignatureByToken('/tmp/nonexistent.csv', 'tok'))->toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for an unknown token', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'real-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionGetSignatureByToken($path, 'wrong-tok'))->toBeNull();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns email, firstname, and surname for a matching token', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'kari@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'my-tok', time(), 'h1'],
|
||||
]);
|
||||
$sig = petitionGetSignatureByToken($path, 'my-tok');
|
||||
expect($sig)->not->toBeNull();
|
||||
expect($sig['email'])->toBe('kari@b.com');
|
||||
expect($sig['firstname'])->toBe('Kari');
|
||||
expect($sig['surname'])->toBe('Hansen');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionGetPendingSignatureByEmail ---
|
||||
|
||||
it('returns null when the CSV does not exist for pending lookup', function () {
|
||||
expect(petitionGetPendingSignatureByEmail('/tmp/nonexistent.csv', 'a@b.com'))->toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when the email is not present', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'other@b.com', 'Ola', 'Hansen', 'oslo', 'semi', 'pending', 't1', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionGetPendingSignatureByEmail($path, 'missing@b.com'))->toBeNull();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns the signature when the email matches', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'kari@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'tok1', time(), 'h1'],
|
||||
]);
|
||||
$sig = petitionGetPendingSignatureByEmail($path, 'kari@b.com');
|
||||
expect($sig)->not->toBeNull();
|
||||
expect($sig['email'])->toBe('kari@b.com');
|
||||
expect($sig['status'])->toBe('pending');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('matches the email case-insensitively for pending lookup', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'kari@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'tok1', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionGetPendingSignatureByEmail($path, 'KARI@B.COM'))->not->toBeNull();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionAppendSignature ---
|
||||
|
||||
it('creates a new CSV with a header and appends the first signature', function () {
|
||||
$path = tempnam(sys_get_temp_dir(), 'petition_append_test_');
|
||||
unlink($path); // petitionAppendSignature creates it
|
||||
|
||||
$result = petitionAppendSignature($path, sigData('new@b.com'));
|
||||
expect($result)->toBeTrue();
|
||||
expect(file_exists($path))->toBeTrue();
|
||||
|
||||
$rows = array_filter(explode("\n", file_get_contents($path)));
|
||||
expect(count($rows))->toBe(2); // header + 1 row
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('appends a signature to an existing CSV', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'first@b.com', 'First', 'User', 'oslo', 'semi', 'pending', 't1', time(), 'h1'],
|
||||
]);
|
||||
petitionAppendSignature($path, sigData('second@b.com', 'pending', 't2'));
|
||||
|
||||
$fp = fopen($path, 'r');
|
||||
fgetcsv($fp, null, ',', '"', ''); // skip header
|
||||
$rows = [];
|
||||
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) $rows[] = $row;
|
||||
fclose($path);
|
||||
|
||||
expect(count($rows))->toBe(2);
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('rejects a duplicate email and returns false', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'existing@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 't1', time(), 'h1'],
|
||||
]);
|
||||
$result = petitionAppendSignature($path, sigData('existing@b.com', 'pending', 't2'));
|
||||
expect($result)->toBeFalse();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('rejects a duplicate email case-insensitively', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'kari@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 't1', time(), 'h1'],
|
||||
]);
|
||||
$result = petitionAppendSignature($path, sigData('KARI@B.COM', 'pending', 't2'));
|
||||
expect($result)->toBeFalse();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionConfirmSignature ---
|
||||
|
||||
it('returns error when the CSV does not exist', function () {
|
||||
expect(petitionConfirmSignature('/tmp/nonexistent.csv', 'tok'))->toBe('error');
|
||||
});
|
||||
|
||||
it('returns error for an unknown token', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'real-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionConfirmSignature($path, 'wrong-tok'))->toBe('error');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns success and sets status to confirmed for a valid pending token', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'valid-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionConfirmSignature($path, 'valid-tok'))->toBe('success');
|
||||
|
||||
// Verify the status was actually updated in the file
|
||||
$fp = fopen($path, 'r');
|
||||
fgetcsv($fp, null, ',', '"', ''); // header
|
||||
$row = fgetcsv($fp, null, ',', '"', '');
|
||||
fclose($fp);
|
||||
expect($row[6])->toBe('confirmed');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns already when the token is already confirmed', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 'valid-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionConfirmSignature($path, 'valid-tok'))->toBe('already');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns expired when the token is older than 30 days', function () {
|
||||
$oldTime = time() - (31 * 86400);
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[$oldTime, 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'old-tok', $oldTime, 'h1'],
|
||||
]);
|
||||
expect(petitionConfirmSignature($path, 'old-tok'))->toBe('expired');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionDeleteSignature ---
|
||||
|
||||
it('returns error when the CSV does not exist for delete', function () {
|
||||
expect(petitionDeleteSignature('/tmp/nonexistent.csv', 'tok'))->toBe('error');
|
||||
});
|
||||
|
||||
it('returns error when the token is not found for delete', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 'real-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionDeleteSignature($path, 'wrong-tok'))->toBe('error');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns success and removes the row for a matching token', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 'del-tok', time(), 'h1'],
|
||||
[time(), 'b@b.com', 'Other', 'Nilsen', 'oslo', 'semi', 'confirmed', 'keep-tok', time(), 'h2'],
|
||||
]);
|
||||
expect(petitionDeleteSignature($path, 'del-tok'))->toBe('success');
|
||||
|
||||
$fp = fopen($path, 'r');
|
||||
fgetcsv($fp, null, ',', '"', ''); // header
|
||||
$rows = [];
|
||||
while (($row = fgetcsv($fp, null, ',', '"', '')) !== false) $rows[] = $row;
|
||||
fclose($fp);
|
||||
|
||||
expect(count($rows))->toBe(1);
|
||||
expect($rows[0][7])->toBe('keep-tok');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
// --- petitionUpdateSignatureToken ---
|
||||
|
||||
it('returns false when the CSV does not exist for token update', function () {
|
||||
expect(petitionUpdateSignatureToken('/tmp/nonexistent.csv', 'a@b.com', 'new-tok'))->toBeFalse();
|
||||
});
|
||||
|
||||
it('returns false when the email has no pending entry', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 'old-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionUpdateSignatureToken($path, 'a@b.com', 'new-tok'))->toBeFalse();
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('returns true and updates the token for a pending entry', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'pending', 'old-tok', time(), 'h1'],
|
||||
]);
|
||||
expect(petitionUpdateSignatureToken($path, 'a@b.com', 'new-tok'))->toBeTrue();
|
||||
|
||||
$fp = fopen($path, 'r');
|
||||
fgetcsv($fp, null, ',', '"', ''); // header
|
||||
$row = fgetcsv($fp, null, ',', '"', '');
|
||||
fclose($fp);
|
||||
expect($row[7])->toBe('new-tok');
|
||||
unlink($path);
|
||||
});
|
||||
|
||||
it('only updates pending entries, not confirmed ones', function () {
|
||||
$path = petitionTestCsv([
|
||||
PETITION_CSV_HEADER,
|
||||
[time(), 'a@b.com', 'Kari', 'Hansen', 'oslo', 'semi', 'confirmed', 'conf-tok', time(), 'h1'],
|
||||
]);
|
||||
$result = petitionUpdateSignatureToken($path, 'a@b.com', 'new-tok');
|
||||
expect($result)->toBeFalse();
|
||||
|
||||
// Confirmed token should be unchanged
|
||||
$fp = fopen($path, 'r');
|
||||
fgetcsv($fp, null, ',', '"', '');
|
||||
$row = fgetcsv($fp, null, ',', '"', '');
|
||||
fclose($fp);
|
||||
expect($row[7])->toBe('conf-tok');
|
||||
unlink($path);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue