toBe('John Doe'); expect(petitionSanitizeCSV('john@example.com'))->toBe('john@example.com'); expect(petitionSanitizeCSV(''))->toBe(''); }); it('prefixes formula-starting characters to prevent CSV injection', function () { expect(petitionSanitizeCSV('=SUM(A1)'))->toBe("'=SUM(A1)"); expect(petitionSanitizeCSV('+evil'))->toBe("'+evil"); expect(petitionSanitizeCSV('-evil'))->toBe("'-evil"); expect(petitionSanitizeCSV('@evil'))->toBe("'@evil"); }); it('prefixes tab and newline characters', function () { expect(petitionSanitizeCSV("\t data"))->toBe("'\t data"); expect(petitionSanitizeCSV("\r data"))->toBe("'\r data"); expect(petitionSanitizeCSV("\n data"))->toBe("'\n data"); }); it('only checks the first character', function () { expect(petitionSanitizeCSV('safe=still safe'))->toBe('safe=still safe'); }); // --- petitionGetCsvPath --- it('builds the correct CSV path for a valid petition ID', function () { $path = petitionGetCsvPath('my-petition'); expect($path)->toEndWith('/data/petitions/my-petition.csv'); }); it('strips non-alphanumeric characters from petition ID', function () { $path = petitionGetCsvPath('my petition!'); expect($path)->toEndWith('/data/petitions/mypetition.csv'); }); it('throws for an empty petition ID', function () { petitionGetCsvPath(''); })->throws(Exception::class); it('strips path traversal characters rather than throwing', function () { // Dots and slashes are stripped by the regex, leaving a safe ID $path = petitionGetCsvPath('../evil'); expect($path)->toEndWith('/data/petitions/evil.csv'); });