'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); $attachmentRepo = new AttachmentRepository($router); $pdfRepo = new PdfAssetRepository($router); $lastRunRepo = new LastRunRepository($router); $mailshotService = new MailshotService($mailshotRepo, $queryRepo, $attachmentRepo, $lastRunRepo, $dataSourceService); $attachmentService = new AttachmentService($attachmentRepo); $pdfService = new PdfAssetService($pdfRepo); $unique = gmdate('Ymd_His') . '_' . bin2hex(random_bytes(3)); $queryName = 'phase3_ds_' . $unique; $attachmentId = null; $pdfId = null; $mailshotId = null; $queryId = null; $lastRunId = null; $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]); $firstTable = $stmt->fetchColumn(); if (!is_string($firstTable) || $firstTable === '') { fwrite(STDERR, "No source table in members db\n"); exit(1); } $dsl = $membersDb . '.' . $firstTable; try { $saveDs = $dataSourceService->save(null, $queryName, $dsl); if (($saveDs['ok'] ?? false) !== true) { fwrite(STDERR, 'Failed to save datasource: ' . json_encode($saveDs) . "\n"); exit(1); } $queryId = (int) ($saveDs['id'] ?? 0); $tokenProbe = $mailshotService->tokenInsertionData($queryName); $recipientEmailField = ''; foreach (($tokenProbe['tokens'] ?? []) as $token) { if (!is_array($token) || !isset($token['field'])) { continue; } $field = (string) $token['field']; if (stripos($field, 'email') !== false) { $recipientEmailField = $field; break; } if ($recipientEmailField === '') { $recipientEmailField = $field; } } if ($recipientEmailField === '') { fwrite(STDERR, "Expected at least one token field for datasource\n"); exit(1); } $saveAtt = $attachmentService->save(null, [ 'name' => 'phase3_att_' . $unique, 'file_name' => 'terms.txt', 'mime_type' => 'text/plain', 'file_bytes_base64' => base64_encode('phase3 attachment bytes'), ]); if (($saveAtt['ok'] ?? false) !== true) { fwrite(STDERR, 'Failed to save attachment: ' . json_encode($saveAtt) . "\n"); exit(1); } $attachmentId = (int) ($saveAtt['id'] ?? 0); $savePdf = $pdfService->save(null, [ 'name' => 'phase3_pdf_' . $unique, 'file_name' => 'logo.png', 'mime_type' => 'image/png', 'file_bytes_base64' => base64_encode('fakepngbytes'), 'width_mm' => '20', 'height_mm' => '10', 'justification' => 'left', ]); if (($savePdf['ok'] ?? false) !== true) { fwrite(STDERR, 'Failed to save pdf asset: ' . json_encode($savePdf) . "\n"); exit(1); } $pdfId = (int) ($savePdf['id'] ?? 0); $saveMailshot = $mailshotService->save(null, [ 'Purpose' => 'Phase3 Mailshot ' . $unique, 'DataSource' => $queryName, 'RecipientEmailField' => $recipientEmailField, 'Subject' => 'Subject {{ ' . strtolower($firstTable) . '_id }}', 'Message' => '
Hello
', 'AttachmentNames' => json_encode(['phase3_att_' . $unique], JSON_UNESCAPED_SLASHES), ]); if (($saveMailshot['ok'] ?? false) !== true) { fwrite(STDERR, 'Failed to save mailshot: ' . json_encode($saveMailshot) . "\n"); exit(1); } $mailshotId = (int) ($saveMailshot['id'] ?? 0); $tokenData = $mailshotService->tokenInsertionData($queryName); if (($tokenData['errors'] ?? []) !== []) { fwrite(STDERR, 'Token generation failed: ' . json_encode($tokenData) . "\n"); exit(1); } if (count($tokenData['tokens'] ?? []) === 0) { fwrite(STDERR, "Expected token list for datasource\n"); exit(1); } $lastRunId = $lastRunRepo->create([ 'mailshot_id' => $mailshotId, 'data_source' => $queryName, 'row_index' => 1, 'recipient_key' => 'phase3-key', 'recipient_key_field' => 'ID', 'recipient_email_last' => 'a@example.org', 'status' => 'failed', 'error_message' => 'smtp failure', 'attempt_count' => 1, ]); $rows = $mailshotService->lastRun($mailshotId); if (count($rows) < 1) { fwrite(STDERR, "Expected last_run rows\n"); exit(1); } $mailshotService->clearLastRun($mailshotId); $lastRunId = null; $rowsAfter = $mailshotService->lastRun($mailshotId); if (count($rowsAfter) !== 0) { fwrite(STDERR, "Expected no last_run rows after clear\n"); exit(1); } echo "Phase 3 DB integration test passed\n"; echo "query_name={$queryName}\n"; echo "mailshot_id={$mailshotId}\n"; } finally { if ($mailshotId !== null) { try { $mailshotService->delete($mailshotId); } catch (Throwable $e) {} } if ($attachmentId !== null) { try { $attachmentService->delete($attachmentId); } catch (Throwable $e) {} } if ($pdfId !== null) { try { $pdfService->delete($pdfId); } catch (Throwable $e) {} } if ($queryId !== null) { try { $queryRepo->delete($queryId); } catch (Throwable $e) {} } }