367 lines
13 KiB
PHP
367 lines
13 KiB
PHP
|
|
<?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);
|
||
|
|
});
|