Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ All errors and warnings are `SecurityTxtSpecViolation` subclasses in `src/Violat

Tests use [Nette Tester](https://tester.nette.org/) (`.phpt` files). Each test file is a standalone PHP script ending with `(new FooTest())->run()`. The `tests/bootstrap.php` sets up the autoloader and provides a `needsInternet()` helper that skips network-dependent tests unless `TEST_CASE_RUNNER_INCLUDE_SKIPPED=1`.

When an assertion fails, Tester writes the full actual and expected values to `tests/*/output/FooTest.actual` and `tests/*/output/FooTest.expected` and prints a `diff` command to compare them — use that instead of reading the truncated terminal output. These files are regenerated on every run; do not edit them.

Inline heredoc expected strings in `.phpt` files contain **raw ANSI escape characters** (not `\033[` literals). Text editors and the Edit tool show them as plain `[1;32m` etc., but the bytes are real ESC (0x1b) sequences. Replacing them requires handling the actual bytes — use Python with `ESC = '\033'` and string concatenation rather than text substitution.

### Constraints

- `parse_url()` is banned — use `Uri\WhatWg\Url` (PHP 8.5 built-in, WhatWG URL standard). Enforced by PHPStan via `phpstan.neon`.
Expand Down
6 changes: 6 additions & 0 deletions src/Check/ConsolePrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ public function enableColors(): void
}


public function ok(string $message): void
{
$this->print($this->colorGreen('[OK]'), $message);
}
Comment thread
spaze marked this conversation as resolved.
Comment thread
spaze marked this conversation as resolved.


public function info(string $message): void
{
$this->print($this->colorDarkGray('[Info]'), $message);
Expand Down
12 changes: 6 additions & 6 deletions src/Check/SecurityTxtCheckHostCli.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function (string $url): void {
);
$this->checkHost->addOnFinalUrl(
function (string $url): void {
$this->consolePrinter->info('Selecting security.txt located at ' . $this->consolePrinter->colorBold($url) . ' for further tests');
$this->consolePrinter->info('Using ' . $this->consolePrinter->colorBold($url));
},
);
$this->checkHost->addOnRedirect(
Expand All @@ -72,7 +72,7 @@ function (int $daysAgo, DateTimeImmutable $expiryDate): void {
);
$this->checkHost->addOnExpires(
function (int $inDays, DateTimeImmutable $expiryDate): void {
$this->consolePrinter->info($this->consolePrinter->colorGreen("The file will expire in {$inDays} " . ($inDays === 1 ? 'day' : 'days')) . " ({$expiryDate->format(DATE_RFC3339)})");
$this->consolePrinter->ok("The file will expire in {$inDays} " . ($inDays === 1 ? 'day' : 'days') . " ({$expiryDate->format(DATE_RFC3339)})");
},
Comment thread
spaze marked this conversation as resolved.
);
$this->checkHost->addOnHost(
Expand All @@ -82,9 +82,8 @@ function (string $host): void {
);
$this->checkHost->addOnValidSignature(
function (string $keyFingerprint, DateTimeImmutable $signatureDate): void {
$this->consolePrinter->info(sprintf(
'%s, key %s, signed on %s',
$this->consolePrinter->colorGreen('Signature valid'),
$this->consolePrinter->ok(sprintf(
'Signature valid, key %s, signed on %s',
$keyFingerprint,
$signatureDate->format(DATE_RFC3339),
));
Expand Down Expand Up @@ -126,9 +125,10 @@ function (string $keyFingerprint, DateTimeImmutable $signatureDate): void {
$noIpv6,
);
if (!$checkResult->isValid()) {
$this->consolePrinter->error($this->consolePrinter->colorRed('Please update the file!'));
$this->consolePrinter->error($this->consolePrinter->colorRed('The file is invalid'));
$this->exit(CheckExitStatus::Error);
} else {
$this->consolePrinter->ok($this->consolePrinter->colorGreen('The file is valid'));
$this->exit(CheckExitStatus::Ok);
}
} catch (SecurityTxtFetcherException $e) {
Expand Down
49 changes: 33 additions & 16 deletions tests/Check/ConsolePrinterTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,20 @@ require __DIR__ . '/../bootstrap.php';
final class ConsolePrinterTest extends TestCase
{

private ConsolePrinter $printer;


public function __construct()
{
$this->printer = new ConsolePrinter();
}


public function testPrinter(): void
public function testPrinterColorsOn(): void
{
$this->printer->enableColors();
$printer = new ConsolePrinter();
$printer->enableColors();
ob_start();
$this->printer->info('Never');
$this->printer->error('gonna');
$this->printer->warning('give');
$this->printer->info($this->printer->colorGreen('you'));
$this->printer->info($this->printer->colorBold('up'));
$printer->ok('🕺');
$printer->info('Never');
$printer->error('gonna');
$printer->warning('give');
$printer->info($printer->colorGreen('you'));
$printer->info($printer->colorBold('up'));
$output = ob_get_clean();
$expected = <<< EOT
[OK] 🕺
[Info] Never
[Error] gonna
[Warning] give
Expand All @@ -43,6 +37,29 @@ final class ConsolePrinterTest extends TestCase
Assert::same($expected . "\n", $output);
}


public function testPrinterColorsOff(): void
{
$printer = new ConsolePrinter();
ob_start();
$printer->ok('🕺');
$printer->info('Never');
$printer->error('gonna');
$printer->warning('give');
$printer->info($printer->colorGreen('you'));
$printer->info($printer->colorBold('up'));
$output = ob_get_clean();
$expected = <<< EOT
[OK] 🕺
[Info] Never
[Error] gonna
[Warning] give
[Info] you
[Info] up
EOT;
Assert::same($expected . "\n", $output);
}

}

(new ConsolePrinterTest())->run();
17 changes: 9 additions & 8 deletions tests/Check/SecurityTxtCheckHostCliTest.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ final class SecurityTxtCheckHostCliTest extends TestCase
[Info] Loading security.txt from https://example.com/security.txt
[Info] Redirected from https://example.com/security.txt to https://nah.example/
[Info] Not found https://nah.example/
[Info] Selecting security.txt located at https://example.com/.well-known/security.txt for further tests
[Info] Using https://example.com/.well-known/security.txt
[Error] The file at https://example.com/.well-known/security.txt has a correct Content-Type of text/plain but the charset=utf-8 parameter is missing (How to fix: Add a charset=utf-8 parameter, e.g. text/plain; charset=utf-8)
[Error] on line 2: The file is considered stale and should not be used (How to fix: The Expires field should contain a date and time in the future formatted according to the Internet profile of ISO 8601 as defined in RFC 3339, e.g. {$this->getExpiresExample()})
[Warning] security.txt not found at the top-level path (How to fix: Redirect the top-level file to the one under the /.well-known/ path)
[Error] The file has expired 42 days ago ({$this->expires})
[Error] Please update the file!
[Error] The file is invalid
EOT;
Assert::same($expected . "\n", $output);
Assert::same(CheckExitStatus::Error->value, $this->exitStatus);
Expand All @@ -93,10 +93,10 @@ final class SecurityTxtCheckHostCliTest extends TestCase
[Info] Loading security.txt from https://example.com/.well-known/security.txt
[Info] Loading security.txt from https://example.com/security.txt
[Info] Not found https://example.com/security.txt
[Info] Selecting security.txt located at https://example.com/.well-known/security.txt for further tests
[Info] Using https://example.com/.well-known/security.txt
[Warning] on line 2: The file will be considered stale in 5 days (How to fix: Update the value of the Expires field, e.g. {$this->getExpiresExample()})
[Info] The file will expire in 5 days ({$expires})
[Error] Please update the file!
[OK] The file will expire in 5 days ({$expires})
[Error] The file is invalid
EOT;
Assert::same($expected . "\n", $output);
Assert::same(CheckExitStatus::Error->value, $this->exitStatus);
Expand Down Expand Up @@ -137,9 +137,10 @@ final class SecurityTxtCheckHostCliTest extends TestCase
[Info] Loading security.txt from https://example.com/.well-known/security.txt
[Info] Loading security.txt from https://example.com/security.txt
[Info] Not found https://example.com/security.txt
[Info] Selecting security.txt located at https://example.com/.well-known/security.txt for further tests
[Info] The file will expire in 5 days ({$expires})
[Info] Signature valid, key AF6E1775E311FF78E911E7DC7F879001A9C8F50A, signed on 2025-07-22T02:10:07+00:00
[Info] Using https://example.com/.well-known/security.txt
[OK] The file will expire in 5 days ({$expires})
[OK] Signature valid, key AF6E1775E311FF78E911E7DC7F879001A9C8F50A, signed on 2025-07-22T02:10:07+00:00
[OK] The file is valid
EOT;
Assert::same($expected . "\n", $output);
Assert::same(CheckExitStatus::Ok->value, $this->exitStatus);
Expand Down