feat: import work hours from SaaS and exchange signed time sheet
This commit is contained in:
1 parent
7cf39850e6
commit
1a7c272414
73 files changed
+10488
-72
No files matched your search
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Logging;
|
||||
|
||||
use App\Logging\RedactSaasCredentialsProcessor;
|
||||
use Monolog\Level;
|
||||
use Monolog\LogRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RedactSaasCredentialsProcessorTest extends TestCase
|
||||
{
|
||||
private const KEY = 'sk-live-0123456789abcdef';
|
||||
|
||||
/**
|
||||
* The line Symfony's http client writes for every request it makes.
|
||||
*/
|
||||
public function testItRedactsTheKeyOutOfARequestLine(): void
|
||||
{
|
||||
$record = $this->process(sprintf(
|
||||
'Request: "GET https://saas.example/api/v2/time/account/%s/theo%%40example.com/2026/2"',
|
||||
self::KEY,
|
||||
));
|
||||
|
||||
$this->assertStringNotContainsString(self::KEY, $record->message);
|
||||
$this->assertStringContainsString('/time/account/[redacted]', $record->message);
|
||||
$this->assertStringContainsString('theo%40example.com/2026/2', $record->message, 'Only the key goes.');
|
||||
}
|
||||
|
||||
/**
|
||||
* The one a silenced client would have missed: an HTTP error repeats the url inside the
|
||||
* exception message, which then travels into whichever channel catches it.
|
||||
*/
|
||||
public function testItRedactsTheKeyOutOfAnExceptionMessage(): void
|
||||
{
|
||||
$record = $this->process(
|
||||
'Work hours import failed',
|
||||
['error' => sprintf('HTTP 500 returned for "https://saas.example/api/v2/time/account/%s/[email protected]/2026/2".', self::KEY)],
|
||||
);
|
||||
|
||||
$this->assertStringNotContainsString(self::KEY, json_encode($record->context));
|
||||
$this->assertStringContainsString('[redacted]', $record->context['error']);
|
||||
}
|
||||
|
||||
public function testItRedactsTheRosterAndDepartmentEndpointsToo(): void
|
||||
{
|
||||
$record = $this->process(sprintf(
|
||||
'GET https://saas.example/api/v2/employees/%s and /api/v2/employee/departments/%s',
|
||||
self::KEY,
|
||||
self::KEY,
|
||||
));
|
||||
|
||||
$this->assertStringNotContainsString(self::KEY, $record->message);
|
||||
$this->assertStringContainsString('/employees/[redacted]', $record->message);
|
||||
$this->assertStringContainsString('/employee/departments/[redacted]', $record->message);
|
||||
}
|
||||
|
||||
/**
|
||||
* The roster hands out a key per employee. None is configured here, so only the pattern
|
||||
* can catch those - which is why the pattern pass exists alongside the exact one.
|
||||
*/
|
||||
public function testAnUnknownKeyInAKeyPositionIsStillRedacted(): void
|
||||
{
|
||||
$record = $this->process('Request: "GET https://saas.example/api/v2/time/account/some-other-key/[email protected]/2026/2"');
|
||||
|
||||
$this->assertStringNotContainsString('some-other-key', $record->message);
|
||||
}
|
||||
|
||||
public function testItReachesIntoNestedContext(): void
|
||||
{
|
||||
$record = $this->process('failed', [
|
||||
'request' => ['url' => 'https://saas.example/api/v2/employees/'.self::KEY],
|
||||
]);
|
||||
|
||||
$this->assertStringNotContainsString(self::KEY, json_encode($record->context));
|
||||
}
|
||||
|
||||
public function testItLeavesOrdinaryRecordsAlone(): void
|
||||
{
|
||||
$record = $this->process('Work hours imported', ['teamer' => 'Test, Theo', 'minutes' => 480]);
|
||||
|
||||
$this->assertSame('Work hours imported', $record->message);
|
||||
$this->assertSame(['teamer' => 'Test, Theo', 'minutes' => 480], $record->context);
|
||||
}
|
||||
|
||||
/**
|
||||
* An unconfigured key is an empty string, which must never be treated as a secret to
|
||||
* replace - that would rewrite every character of every message.
|
||||
*/
|
||||
public function testAnUnconfiguredKeyRedactsNothingByValue(): void
|
||||
{
|
||||
$processor = new RedactSaasCredentialsProcessor('');
|
||||
$record = $processor(new LogRecord(new \DateTimeImmutable(), 'app', Level::Info, 'plain message'));
|
||||
|
||||
$this->assertSame('plain message', $record->message);
|
||||
}
|
||||
|
||||
private function process(string $message, array $context = []): LogRecord
|
||||
{
|
||||
$processor = new RedactSaasCredentialsProcessor(self::KEY);
|
||||
|
||||
return $processor(new LogRecord(new \DateTimeImmutable(), 'app', Level::Info, $message, $context));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user