testing dsl changes
This commit is contained in:
parent
c5bfde12a6
commit
535a798d62
10
docs/todo.md
10
docs/todo.md
|
|
@ -1,3 +1,9 @@
|
|||
Split advertiser name into forename and surname
|
||||
Split advertiser name into forename and surname - done in twig in message
|
||||
|
||||
Add ad sizes into mailmerge
|
||||
Add ad sizes into mailmerge - done
|
||||
|
||||
Why does filter ads and advertisers where issue(125) and ads.Price != 0 and not (advertisers.Categories contains 'discount') identify fewer records than expected. - donei
|
||||
|
||||
|
||||
|
||||
tig
|
||||
|
|
|
|||
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/feca_mailshots_plugin/src/autoload.php';
|
||||
|
||||
use FecaMailshots\Application\DslCompiler;
|
||||
use FecaMailshots\Application\DslValidator;
|
||||
use FecaMailshots\Application\SourceMetadataProvider;
|
||||
use FecaMailshots\Domain\DslParser;
|
||||
use FecaMailshots\Infrastructure\DatabaseRouter;
|
||||
use FecaMailshots\Infrastructure\DatabaseSourceMetadataProvider;
|
||||
|
||||
/** @param mixed $actual @param mixed $expected */
|
||||
function feca_assert_same($actual, $expected, string $message): void
|
||||
{
|
||||
if ($actual !== $expected) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
$message . "\nExpected: " . json_encode($expected) . "\nActual: " . json_encode($actual) . "\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function feca_assert_contains(string $haystack, string $needle, string $message): void
|
||||
{
|
||||
if (strpos($haystack, $needle) === false) {
|
||||
fwrite(STDERR, $message . "\nMissing: {$needle}\nSQL: {$haystack}\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$parser = new DslParser();
|
||||
|
||||
$customMetadata = new class implements SourceMetadataProvider {
|
||||
/** @var array<string, list<string>> */
|
||||
private array $fields = [
|
||||
'MixedSchema.MixedTable' => ['MixedId', 'Label'],
|
||||
'OtherSchema.OtherTable' => ['ParentId', 'Description'],
|
||||
];
|
||||
|
||||
public function sourceExists(string $source): bool
|
||||
{
|
||||
return isset($this->fields[$source]);
|
||||
}
|
||||
|
||||
public function isBuiltInSource(string $source): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function sourceFields(string $source): array
|
||||
{
|
||||
return $this->fields[$source] ?? [];
|
||||
}
|
||||
|
||||
public function hasEmailField(string $source): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function joinPath(string $left, string $right): ?array
|
||||
{
|
||||
if ($left === 'MixedSchema.MixedTable' && $right === 'OtherSchema.OtherTable') {
|
||||
return [
|
||||
'left' => 'MixedSchema.MixedTable.MixedId',
|
||||
'right' => 'OtherSchema.OtherTable.ParentId',
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function allKnownSources(): array
|
||||
{
|
||||
return array_keys($this->fields);
|
||||
}
|
||||
|
||||
public function sourceTable(string $source): ?string
|
||||
{
|
||||
return $this->sourceExists($source) ? $source : null;
|
||||
}
|
||||
|
||||
public function fieldSql(string $source, string $field, string $alias): ?string
|
||||
{
|
||||
if (!in_array($field, $this->fields[$source] ?? [], true)) {
|
||||
return null;
|
||||
}
|
||||
return $alias . '.`' . str_replace('`', '``', $field) . '`';
|
||||
}
|
||||
};
|
||||
|
||||
$customDsl = 'MixedSchema.MixedTable and OtherSchema.OtherTable '
|
||||
. 'where MixedSchema.MixedTable.MixedId = OtherSchema.OtherTable.ParentId';
|
||||
$customAst = $parser->parse($customDsl);
|
||||
feca_assert_same(
|
||||
$customAst['sources'],
|
||||
['MixedSchema.MixedTable', 'OtherSchema.OtherTable'],
|
||||
'Expected custom schema and table case to be preserved'
|
||||
);
|
||||
feca_assert_same(
|
||||
$customAst['where'][0]['lhs']['source'] ?? null,
|
||||
'MixedSchema.MixedTable',
|
||||
'Expected left field source case to be preserved'
|
||||
);
|
||||
feca_assert_same(
|
||||
$customAst['where'][0]['rhs']['source'] ?? null,
|
||||
'OtherSchema.OtherTable',
|
||||
'Expected right field source case to be preserved'
|
||||
);
|
||||
|
||||
$customValidator = new DslValidator($customMetadata);
|
||||
$customValidation = $customValidator->validate($customAst);
|
||||
feca_assert_same($customValidation['errors'], [], 'Expected two custom sources to validate');
|
||||
|
||||
$customSql = (string) ((new DslCompiler($customMetadata))->compile($customAst)['sql'] ?? '');
|
||||
feca_assert_contains(
|
||||
$customSql,
|
||||
'INNER JOIN `OtherSchema`.`OtherTable` AS s_otherschema_othertable '
|
||||
. 'ON s_mixedschema_mixedtable.`MixedId` = s_otherschema_othertable.`ParentId`',
|
||||
'Expected schema-qualified join references to split at their final dot'
|
||||
);
|
||||
feca_assert_contains(
|
||||
$customSql,
|
||||
"COALESCE(s_mixedschema_mixedtable.`MixedId`, '') = "
|
||||
. "COALESCE(s_otherschema_othertable.`ParentId`, '')",
|
||||
'Expected schema-qualified field comparison to use NULL-as-empty semantics'
|
||||
);
|
||||
|
||||
$comparisonMetadata = new class implements SourceMetadataProvider {
|
||||
public function sourceExists(string $source): bool
|
||||
{
|
||||
return $source === 'records';
|
||||
}
|
||||
|
||||
public function isBuiltInSource(string $source): bool
|
||||
{
|
||||
return $source === 'records';
|
||||
}
|
||||
|
||||
public function sourceFields(string $source): array
|
||||
{
|
||||
return $source === 'records' ? ['left_value', 'right_value'] : [];
|
||||
}
|
||||
|
||||
public function hasEmailField(string $source): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function joinPath(string $left, string $right): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function allKnownSources(): array
|
||||
{
|
||||
return ['records'];
|
||||
}
|
||||
|
||||
public function sourceTable(string $source): ?string
|
||||
{
|
||||
return $source === 'records' ? 'records' : null;
|
||||
}
|
||||
|
||||
public function fieldSql(string $source, string $field, string $alias): ?string
|
||||
{
|
||||
if ($source !== 'records' || !in_array($field, $this->sourceFields($source), true)) {
|
||||
return null;
|
||||
}
|
||||
return $alias . '.`' . $field . '`';
|
||||
}
|
||||
};
|
||||
|
||||
$comparisonCompiler = new DslCompiler($comparisonMetadata);
|
||||
$literalCases = [
|
||||
"records.left_value = 'text'" => ["COALESCE(s_records.`left_value`, '') = ?", 'text'],
|
||||
"records.left_value != 'text'" => ["COALESCE(s_records.`left_value`, '') != ?", 'text'],
|
||||
"records.left_value contains 'text'" => ["COALESCE(s_records.`left_value`, '') LIKE ?", '%text%'],
|
||||
"records.left_value starts-with 'text'" => ["COALESCE(s_records.`left_value`, '') LIKE ?", 'text%'],
|
||||
"records.left_value ends-with 'text'" => ["COALESCE(s_records.`left_value`, '') LIKE ?", '%text'],
|
||||
];
|
||||
foreach ($literalCases as $predicate => [$sqlNeedle, $expectedParam]) {
|
||||
$compiled = $comparisonCompiler->compile($parser->parse('records where ' . $predicate));
|
||||
feca_assert_contains((string) $compiled['sql'], $sqlNeedle, 'Expected literal comparison to normalize NULL');
|
||||
feca_assert_same($compiled['params'], [$expectedParam], 'Expected literal comparison parameter');
|
||||
}
|
||||
|
||||
foreach (['=', '!='] as $operator) {
|
||||
$compiled = $comparisonCompiler->compile(
|
||||
$parser->parse("records where records.left_value {$operator} records.right_value")
|
||||
);
|
||||
feca_assert_contains(
|
||||
(string) $compiled['sql'],
|
||||
"COALESCE(s_records.`left_value`, '') {$operator} COALESCE(s_records.`right_value`, '')",
|
||||
'Expected field comparison to normalize NULL on both sides'
|
||||
);
|
||||
feca_assert_same($compiled['params'], [], 'Expected no field comparison parameters');
|
||||
}
|
||||
|
||||
$router = new class implements DatabaseRouter {
|
||||
public function mailshotsPdo(): PDO
|
||||
{
|
||||
throw new RuntimeException('Unexpected mailshots database access');
|
||||
}
|
||||
|
||||
public function membersPdo(): PDO
|
||||
{
|
||||
throw new RuntimeException('Unexpected members database access');
|
||||
}
|
||||
|
||||
public function mailshotsDbName(): string
|
||||
{
|
||||
return 'mailshots_test';
|
||||
}
|
||||
|
||||
public function membersDbName(): string
|
||||
{
|
||||
return 'members_test';
|
||||
}
|
||||
|
||||
public function fenDbName(): string
|
||||
{
|
||||
return 'FenCaseSensitive';
|
||||
}
|
||||
};
|
||||
|
||||
$databaseMetadata = new DatabaseSourceMetadataProvider($router);
|
||||
feca_assert_same($databaseMetadata->sourceExists('ad_sizes'), true, 'Expected ad_sizes to be a known source');
|
||||
feca_assert_same($databaseMetadata->isBuiltInSource('ad_sizes'), true, 'Expected ad_sizes to be built in');
|
||||
feca_assert_same(
|
||||
$databaseMetadata->sourceTable('ad_sizes'),
|
||||
'FenCaseSensitive.Ad_Sizes',
|
||||
'Expected ad_sizes to map to the configured FEN database and physical table'
|
||||
);
|
||||
feca_assert_same(
|
||||
$databaseMetadata->joinPath('ads', 'ad_sizes'),
|
||||
['left' => 'ads.AdSize', 'right' => 'ad_sizes.SizeName'],
|
||||
'Expected forward ads/ad_sizes join metadata'
|
||||
);
|
||||
feca_assert_same(
|
||||
$databaseMetadata->joinPath('ad_sizes', 'ads'),
|
||||
['left' => 'ad_sizes.SizeName', 'right' => 'ads.AdSize'],
|
||||
'Expected reverse ads/ad_sizes join metadata'
|
||||
);
|
||||
|
||||
echo "Recent DSL change regression tests passed\n";
|
||||
Loading…
Reference in New Issue