innhold/tests/Unit/PetitionFormTestCsv.php

367 lines
13 KiB
PHP
Raw Normal View History

<?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);
});