feca-mailshots-plugin/tests/integration/test_phase4_run_flow.php

185 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
require_once dirname(__DIR__, 2) . '/feca_mailshots_plugin/src/autoload.php';
use FecaMailshots\Application\DataSourceService;
use FecaMailshots\Application\DslCompiler;
use FecaMailshots\Application\DslValidator;
use FecaMailshots\Application\ImapAppender;
use FecaMailshots\Application\MailCredentialsProvider;
use FecaMailshots\Application\MailshotRunService;
use FecaMailshots\Application\SmtpSender;
use FecaMailshots\Application\TemplateRenderer;
use FecaMailshots\Domain\DslParser;
use FecaMailshots\Infrastructure\DatabaseSourceMetadataProvider;
use FecaMailshots\Infrastructure\Env;
use FecaMailshots\Infrastructure\PdoDatabaseRouter;
use FecaMailshots\Repository\LastRunRepository;
use FecaMailshots\Repository\MailshotQueryRepository;
use FecaMailshots\Repository\MailshotRepository;
final class FakeCreds implements MailCredentialsProvider {
public function credentials(): ?array {
return [
'smtp_host' => 'smtp.test.local',
'smtp_port' => 587,
'smtp_user' => 'user',
'smtp_password' => 'pass',
'smtp_from_email' => 'editor@example.org',
'smtp_from_name' => 'Editor',
'smtp_require_tls' => false,
'imap_host' => 'imap.test.local',
'imap_port' => 993,
'imap_user' => 'user',
'imap_password' => 'pass',
'imap_sent_folder' => 'Sent',
'imap_mailbox_flags' => '/imap/ssl',
];
}
}
final class FakeSmtp implements SmtpSender {
public int $count = 0;
public function send(array $credentials, array $to, array $cc, array $bcc, string $subject, string $htmlBody, ?string $replyTo = null): array {
$this->count++;
if ($to === []) {
throw new RuntimeException('No recipients');
}
return ['raw_mime' => "To: " . implode(',', $to) . "\r\nSubject: {$subject}\r\n\r\n{$htmlBody}"];
}
}
final class FakeImap implements ImapAppender {
public int $count = 0;
public function appendSent(array $credentials, string $rawMime, string $attemptId): void {
$this->count++;
}
}
Env::load(dirname(__DIR__, 2) . '/credentials/.env');
$dbConfig = [
'MYSQL_HOST' => '127.0.0.1',
'MYSQL_PORT' => (string) (getenv('MYSQL_TUNNEL_LOCAL_PORT') ?: '13306'),
'MYSQL_USER' => Env::require('REMOTE_MYSQL_USER'),
'MYSQL_PASSWORD' => Env::require('REMOTE_MYSQL_PASSWORD'),
'MAILSHOTS_REMOTE_MYSQL_DB' => Env::require('MAILSHOTS_REMOTE_MYSQL_DB'),
'MEMBERS_REMOTE_MYSQL_DB' => Env::require('MEMBERS_REMOTE_MYSQL_DB'),
];
$router = new PdoDatabaseRouter($dbConfig);
$metadata = new DatabaseSourceMetadataProvider($router);
$parser = new DslParser();
$validator = new DslValidator($metadata);
$compiler = new DslCompiler($metadata);
$queryRepo = new MailshotQueryRepository($router);
$mailshotRepo = new MailshotRepository($router);
$dataSourceService = new DataSourceService($queryRepo, $router, $parser, $validator, $compiler, $metadata, $mailshotRepo);
$lastRunRepo = new LastRunRepository($router);
$smtp = new FakeSmtp();
$imap = new FakeImap();
$run = new MailshotRunService(
$mailshotRepo,
$queryRepo,
$dataSourceService,
new TemplateRenderer(),
$smtp,
$imap,
new FakeCreds(),
$lastRunRepo
);
$membersDb = $router->membersDbName();
$stmt = $router->membersPdo()->prepare('SELECT table_name FROM information_schema.tables WHERE table_schema = :schema ORDER BY table_name ASC LIMIT 1');
$stmt->execute(['schema' => $membersDb]);
$table = $stmt->fetchColumn();
if (!is_string($table) || $table === '') {
fwrite(STDERR, "No source table in members db\n");
exit(1);
}
$dsl = $membersDb . '.' . $table;
$uniq = gmdate('Ymd_His') . '_' . bin2hex(random_bytes(3));
$queryName = 'phase4_ds_' . $uniq;
$queryId = null;
$mailshotId = null;
try {
$savedQ = $dataSourceService->save(null, $queryName, $dsl);
if (($savedQ['ok'] ?? false) !== true) {
fwrite(STDERR, 'Failed saving datasource: ' . json_encode($savedQ) . "\n");
exit(1);
}
$queryId = (int) $savedQ['id'];
$savedM = $mailshotRepo->create([
'Purpose' => 'Phase4 ' . $uniq,
'DataSource' => $queryName,
'CC' => '',
'BCC' => '',
'Subject' => 'Hello {{ id|default(ID|default("recipient")) }}',
'Message' => '<p>Hi {{ name|default(Name|default("there")) }}</p>',
'PDFAttachment' => '<p>PDF {{ id|default("none") }}</p>',
'AttachmentNames' => '[]',
'PDFFilenameDerivedFrom' => '',
'ReplyTo' => '',
]);
$mailshotId = (int) $savedM;
$render = $run->renderTest($mailshotId, 0);
if (($render['ok'] ?? false) !== true) {
fwrite(STDERR, 'renderTest failed: ' . json_encode($render) . "\n");
exit(1);
}
$sendBlank = $run->sendTest($mailshotId, 0, '');
if (($sendBlank['ok'] ?? true) !== false) {
fwrite(STDERR, "sendTest blank email should fail\n");
exit(1);
}
$sendTest = $run->sendTest($mailshotId, 0, 'receiver@example.org');
if (($sendTest['ok'] ?? false) !== true) {
fwrite(STDERR, 'sendTest failed: ' . json_encode($sendTest) . "\n");
exit(1);
}
$runRes = $run->runMailshot($mailshotId);
if (($runRes['ok'] ?? false) !== true) {
fwrite(STDERR, 'runMailshot failed: ' . json_encode($runRes) . "\n");
exit(1);
}
if ((int) ($runRes['attempted'] ?? 0) <= 0) {
fwrite(STDERR, "runMailshot attempted should be > 0\n");
exit(1);
}
$rows = $lastRunRepo->listForMailshot($mailshotId);
if (count($rows) !== (int) $runRes['attempted']) {
fwrite(STDERR, 'lastRun row count mismatch\n');
exit(1);
}
$retry = $run->retryFailed($mailshotId);
if (($retry['ok'] ?? false) !== true) {
fwrite(STDERR, 'retryFailed failed: ' . json_encode($retry) . "\n");
exit(1);
}
echo "Phase 4 run-flow integration test passed\n";
echo "attempted=" . (int) $runRes['attempted'] . "\n";
echo "smtp_calls={$smtp->count}\n";
echo "imap_calls={$imap->count}\n";
} finally {
if ($mailshotId !== null) {
try { $lastRunRepo->clearForMailshot($mailshotId); } catch (Throwable $e) {}
try { $mailshotRepo->delete($mailshotId); } catch (Throwable $e) {}
}
if ($queryId !== null) {
try { $queryRepo->delete($queryId); } catch (Throwable $e) {}
}
}