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