78 lines
3.6 KiB
PHP
78 lines
3.6 KiB
PHP
<?php
|
|||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
require_once __DIR__ . '/../app/Domain/Notification/NotificationRecord.php';
|
||
|
|
require_once __DIR__ . '/../app/Domain/Notification/NotificationQueue.php';
|
||
|
|
|
||
|
|
use App\Domain\Notification\NotificationQueue;
|
||
|
|
|
||
|
|
final class NotificationFakeStatement extends PDOStatement
|
||
|
|
{
|
||
|
|
private array $params = [];
|
||
|
|
public function __construct(private readonly NotificationFakePdo $pdo, private readonly string $sql) {}
|
||
|
|
public function execute(?array $params = null): bool
|
||
|
|
{
|
||
|
|
$this->params = $params ?? [];
|
||
|
|
if (str_starts_with($this->sql, 'SELECT')) {
|
||
|
|
$this->pdo->selectedEmail = (string)($this->params['email'] ?? '');
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (str_starts_with($this->sql, 'INSERT') && $this->pdo->failOnInsert === $this->params['user']) {
|
||
|
|
throw new RuntimeException('simulated partial failure');
|
||
|
|
}
|
||
|
|
if (str_starts_with($this->sql, 'UPDATE')) {
|
||
|
|
$this->pdo->updateParams = $this->params;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
public function fetchColumn(int $column = 0): mixed
|
||
|
|
{
|
||
|
|
return $this->pdo->users[$this->pdo->selectedEmail] ?? false;
|
||
|
|
}
|
||
|
|
public function rowCount(): int { return $this->pdo->updateRowCount; }
|
||
|
|
}
|
||
|
|
|
||
|
|
final class NotificationFakePdo extends PDO
|
||
|
|
{
|
||
|
|
public array $users = ['one@example.com' => 1, 'two@example.com' => 2];
|
||
|
|
public ?string $selectedEmail = null;
|
||
|
|
public mixed $failOnInsert = null;
|
||
|
|
public int $updateRowCount = 1;
|
||
|
|
public array $updateParams = [];
|
||
|
|
public bool $rolledBack = false;
|
||
|
|
public bool $inTxn = false;
|
||
|
|
public function __construct() {}
|
||
|
|
public function beginTransaction(): bool { $this->inTxn = true; return true; }
|
||
|
|
public function inTransaction(): bool { return $this->inTxn; }
|
||
|
|
public function rollBack(): bool { $this->rolledBack = true; $this->inTxn = false; return true; }
|
||
|
|
public function commit(): bool { $this->inTxn = false; return true; }
|
||
|
|
public function prepare(string $query, array $options = []): PDOStatement|false { return new NotificationFakeStatement($this, $query); }
|
||
|
|
public function lastInsertId(?string $name = null): string { return '1'; }
|
||
|
|
}
|
||
|
|
|
||
|
|
function notification_persistence_assert(bool $condition, string $message): void
|
||
|
|
{
|
||
|
|
if (!$condition) throw new RuntimeException($message);
|
||
|
|
}
|
||
|
|
|
||
|
|
$pdo = new NotificationFakePdo();
|
||
|
|
$pdo->failOnInsert = 2;
|
||
|
|
$queue = new NotificationQueue();
|
||
|
|
try {
|
||
|
|
$queue->enqueue($pdo, [
|
||
|
|
'type' => 'assignment_created', 'recipients' => ['one@example.com', 'two@example.com'],
|
||
|
|
'title' => 'Assigned', 'body' => 'Jobcard assigned', 'deduplication_key' => 'assignment:42',
|
||
|
|
]);
|
||
|
|
throw new RuntimeException('Expected the simulated second-recipient failure.');
|
||
|
|
} catch (RuntimeException $error) {
|
||
|
|
notification_persistence_assert($error->getMessage() === 'simulated partial failure', 'The simulated partial failure should be surfaced.');
|
||
|
|
}
|
||
|
|
notification_persistence_assert($pdo->rolledBack, 'A partial recipient failure must roll back the whole queue transaction.');
|
||
|
|
|
||
|
|
notification_persistence_assert($queue->markRead($pdo, 7, ['notification_id' => '9']), 'Mark-read should update a user-owned row.');
|
||
|
|
notification_persistence_assert($pdo->updateParams === ['id' => 9, 'user' => 7], 'Mark-read must use schema-aligned id and user_id predicates.');
|
||
|
|
notification_persistence_assert($queue->markUnread($pdo, '7', ['id' => '9']), 'Mark-unread should update a user-owned row.');
|
||
|
|
notification_persistence_assert($pdo->updateParams === ['id' => 9, 'user' => 7], 'Mark-unread must use schema-aligned id and user_id predicates.');
|
||
|
|
|
||
|
|
printf("Notification persistence tests: 6 passed\n");
|