test: mock matcher understands UNION existence reads; revive dead update-path mocks

parse_and_match_mocks matched mock.type against only the first FROM table —
always 'entity' for hierarchy existence reads — so every merger update-case
mock was silently inert and those cases snapshotted the create path. Now each
UNION arm is matched independently (a mock matching any arm returns once,
mirroring UNION dedup) and the type check accepts any FROM/JOIN table.

Regenerated the five revived cases (their snapshots now show real update /
no-op replace / archive semantics) and added the two previously untestable
probe-disagreement cases: same-row dedup (updates, no error) and id-vs-lookup
conflict (TOO_MANY_LOOKUP_ROWS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-06 14:05:21 -04:00
parent a885713563
commit abf1d12e43
3 changed files with 343 additions and 431 deletions

View File

@ -1320,62 +1320,17 @@
" AND \"pronouns\" = 'they/them'))" " AND \"pronouns\" = 'they/them'))"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = '{{uuid:mocks.0.id}}'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:generated_0}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.\"organization\" (", "UPDATE agreego.\"person\" SET",
" \"id\",", " contact_id = 'abc-contact'",
" \"type\"", "WHERE",
")", " id = '{{uuid:mocks.0.id}}'"
"VALUES (",
" '{{uuid:generated_0}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:generated_0}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"contact_id\",",
" \"date_of_birth\",",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"pronouns\",",
" \"type\"",
")",
"VALUES (",
" 'abc-contact',",
" '{{timestamp}}',",
" 'LookupFirst',",
" '{{uuid:generated_0}}',",
" 'LookupLast',",
" 'they/them',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -1388,45 +1343,40 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,",
" '{", " '{",
" \"first_name\": \"LookupFirst\",", " \"contact_id\": \"old-contact\"",
" \"last_name\": \"LookupLast\",", " }',",
" \"date_of_birth\": \"{{timestamp}}\",", " '{",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }',", " }',",
" '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" '{{uuid:generated_1}}',", " 'update',",
" 'create',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"update\",",
" \"complete\": {", " \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",", " \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",", " \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",", " \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",", " \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"id\": \"{{uuid:generated_0}}\",",
" \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"created_at\": \"{{timestamp}}\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" },",
" \"old\": {",
" \"contact_id\": \"old-contact\"",
" }", " }",
"}'))" "}'))"
] ]
@ -1464,62 +1414,17 @@
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))" "(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = '{{uuid:mocks.0.id}}'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:data.id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.\"organization\" (", "UPDATE agreego.\"person\" SET",
" \"id\",", " contact_id = 'abc-contact'",
" \"type\"", "WHERE",
")", " id = '{{uuid:mocks.0.id}}'"
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"contact_id\",",
" \"date_of_birth\",",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"pronouns\",",
" \"type\"",
")",
"VALUES (",
" 'abc-contact',",
" '{{timestamp}}',",
" 'LookupFirst',",
" '{{uuid:data.id}}',",
" 'LookupLast',",
" 'they/them',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -1532,46 +1437,42 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,",
" '{", " '{",
" \"first_name\": \"LookupFirst\",", " \"contact_id\": \"old-contact\"",
" \"last_name\": \"LookupLast\",", " }',",
" \"date_of_birth\": \"{{timestamp}}\",", " '{",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }',", " }',",
" '{{uuid:data.id}}',", " '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" 'create',", " 'update',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"update\",",
" \"complete\": {", " \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",", " \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",", " \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",", " \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",", " \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"id\": \"{{uuid:data.id}}\",",
" \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"created_at\": \"{{timestamp}}\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"abc-contact\",", " \"contact_id\": \"abc-contact\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }", " },",
" \"old\": {",
" \"contact_id\": \"old-contact\"",
" },",
" \"replaces\": \"{{uuid:data.id}}\"",
"}'))" "}'))"
] ]
] ]
@ -1606,110 +1507,24 @@
[ [
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))" "(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:data.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))"
], ],
[
"INSERT INTO agreego.\"entity\" (",
" \"created_at\",",
" \"created_by\",",
" \"id\",",
" \"modified_at\",",
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:data.id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"organization\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:data.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"date_of_birth\",",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"pronouns\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" 'LookupFirst',",
" '{{uuid:data.id}}',",
" 'LookupLast',",
" 'they/them',",
" 'person'",
")"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" \"entity_id\",",
" \"id\",",
" \"kind\",",
" \"modified_at\",",
" \"modified_by\"",
")",
"VALUES (",
" NULL,",
" '{",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"type\": \"person\"",
" }',",
" '{{uuid:data.id}}',",
" '{{uuid:generated_0}}',",
" 'create',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"replace\",",
" \"complete\": {", " \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",", " \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",", " \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",", " \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",", " \"pronouns\": \"they/them\",",
" \"id\": \"{{uuid:data.id}}\",", " \"contact_id\": \"old-contact\",",
" \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"created_at\": \"{{timestamp}}\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" }", " },",
" \"replaces\": \"{{uuid:data.id}}\"",
"}'))" "}'))"
] ]
] ]
@ -1746,56 +1561,18 @@
" t1.id = '{{uuid:mocks.0.id}}')" " t1.id = '{{uuid:mocks.0.id}}')"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = '{{uuid:mocks.0.id}}'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" '{{uuid:mocks.0.id}}',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.\"organization\" (", "UPDATE agreego.\"person\" SET",
" \"id\",", " first_name = 'NewFirst',",
" \"type\"", " last_name = 'NewLast'",
")", "WHERE",
"VALUES (", " id = '{{uuid:mocks.0.id}}'"
" '{{uuid:mocks.0.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" '{{uuid:mocks.0.id}}',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"first_name\",",
" \"id\",",
" \"last_name\",",
" \"type\"",
")",
"VALUES (",
" 'NewFirst',",
" '{{uuid:mocks.0.id}}',",
" 'NewLast',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -1808,7 +1585,10 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,", " '{",
" \"first_name\": \"OldFirst\",",
" \"last_name\": \"OldLast\"",
" }',",
" '{", " '{",
" \"first_name\": \"NewFirst\",", " \"first_name\": \"NewFirst\",",
" \"last_name\": \"NewLast\",", " \"last_name\": \"NewLast\",",
@ -1816,21 +1596,19 @@
" }',", " }',",
" '{{uuid:mocks.0.id}}',", " '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" 'create',", " 'update',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"update\",",
" \"complete\": {", " \"complete\": {",
" \"first_name\": \"NewFirst\",",
" \"last_name\": \"NewLast\",",
" \"id\": \"{{uuid:mocks.0.id}}\",", " \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",", " \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",", " \"first_name\": \"NewFirst\",",
" \"created_at\": \"{{timestamp}}\",", " \"last_name\": \"NewLast\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
@ -1838,6 +1616,10 @@
" \"first_name\": \"NewFirst\",", " \"first_name\": \"NewFirst\",",
" \"last_name\": \"NewLast\",", " \"last_name\": \"NewLast\",",
" \"type\": \"person\"", " \"type\": \"person\"",
" },",
" \"old\": {",
" \"first_name\": \"OldFirst\",",
" \"last_name\": \"OldLast\"",
" }", " }",
"}'))" "}'))"
] ]
@ -3006,54 +2788,12 @@
" t1.id = 'abc-archived')" " t1.id = 'abc-archived')"
], ],
[ [
"INSERT INTO agreego.\"entity\" (", "UPDATE agreego.\"entity\" SET",
" \"archived\",", " archived = true,",
" \"created_at\",", " modified_at = '{{timestamp}}',",
" \"created_by\",", " modified_by = '00000000-0000-0000-0000-000000000000'",
" \"id\",", "WHERE",
" \"modified_at\",", " id = 'abc-archived'"
" \"modified_by\",",
" \"type\"",
")",
"VALUES (",
" true,",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'abc-archived',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"organization\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" 'abc-archived',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"user\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" 'abc-archived',",
" 'person'",
")"
],
[
"INSERT INTO agreego.\"person\" (",
" \"id\",",
" \"type\"",
")",
"VALUES (",
" 'abc-archived',",
" 'person'",
")"
], ],
[ [
"INSERT INTO agreego.change (", "INSERT INTO agreego.change (",
@ -3066,33 +2806,38 @@
" \"modified_by\"", " \"modified_by\"",
")", ")",
"VALUES (", "VALUES (",
" NULL,", " '{",
" \"archived\": false",
" }',",
" '{", " '{",
" \"archived\": true,", " \"archived\": true,",
" \"type\": \"person\"", " \"type\": \"person\"",
" }',", " }',",
" 'abc-archived',", " 'abc-archived',",
" '{{uuid:generated_0}}',", " '{{uuid:generated_0}}',",
" 'create',", " 'delete',",
" '{{timestamp}}',", " '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'", " '00000000-0000-0000-0000-000000000000'",
")" ")"
], ],
[ [
"(SELECT pg_notify('entity', '{", "(SELECT pg_notify('entity', '{",
" \"kind\": \"create\",", " \"kind\": \"delete\",",
" \"complete\": {", " \"complete\": {",
" \"archived\": true,",
" \"id\": \"abc-archived\",", " \"id\": \"abc-archived\",",
" \"type\": \"person\",", " \"type\": \"person\",",
" \"created_by\": \"00000000-0000-0000-0000-000000000000\",", " \"first_name\": \"ArchivedFirst\",",
" \"created_at\": \"{{timestamp}}\",", " \"last_name\": \"ArchivedLast\",",
" \"archived\": true,",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",", " \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"", " \"modified_at\": \"{{timestamp}}\"",
" },", " },",
" \"new\": {", " \"new\": {",
" \"archived\": true,", " \"archived\": true,",
" \"type\": \"person\"", " \"type\": \"person\"",
" },",
" \"old\": {",
" \"archived\": false",
" }", " }",
"}'))" "}'))"
] ]
@ -4027,6 +3772,143 @@
] ]
] ]
} }
},
{
"description": "Existence probes hit the same row via id and lookup key (dedup, updates)",
"action": "merge",
"data": {
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "LookupFirst",
"last_name": "LookupLast",
"date_of_birth": "1990-01-01T00:00:00Z",
"pronouns": "they/them",
"contact_id": "new-contact"
},
"mocks": [
{
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "LookupFirst",
"last_name": "LookupLast",
"date_of_birth": "1990-01-01T00:00:00Z",
"pronouns": "they/them",
"contact_id": "old-contact"
}
],
"schema_id": "person",
"expect": {
"success": true,
"sql": [
[
"(SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE t1.id = '{{uuid:mocks.0.id}}' UNION SELECT to_jsonb(t1.*) || to_jsonb(t2.*) || to_jsonb(t3.*) || to_jsonb(t4.*) FROM agreego.\"entity\" t1 LEFT JOIN agreego.\"organization\" t2 ON t2.id = t1.id LEFT JOIN agreego.\"user\" t3 ON t3.id = t1.id LEFT JOIN agreego.\"person\" t4 ON t4.id = t1.id WHERE (\"first_name\" = 'LookupFirst' AND \"last_name\" = 'LookupLast' AND \"date_of_birth\" = '{{timestamp}}' AND \"pronouns\" = 'they/them'))"
],
[
"UPDATE agreego.\"entity\" SET",
" modified_at = '{{timestamp}}',",
" modified_by = '00000000-0000-0000-0000-000000000000'",
"WHERE",
" id = '{{uuid:mocks.0.id}}'"
],
[
"UPDATE agreego.\"person\" SET",
" contact_id = 'new-contact'",
"WHERE",
" id = '{{uuid:mocks.0.id}}'"
],
[
"INSERT INTO agreego.change (",
" \"old\",",
" \"new\",",
" \"entity_id\",",
" \"id\",",
" \"kind\",",
" \"modified_at\",",
" \"modified_by\"",
")",
"VALUES (",
" '{",
" \"contact_id\": \"old-contact\"",
" }',",
" '{",
" \"contact_id\": \"new-contact\",",
" \"type\": \"person\"",
" }',",
" '{{uuid:mocks.0.id}}',",
" '{{uuid:generated_0}}',",
" 'update',",
" '{{timestamp}}',",
" '00000000-0000-0000-0000-000000000000'",
")"
],
[
"(SELECT pg_notify('entity', '{",
" \"kind\": \"update\",",
" \"complete\": {",
" \"id\": \"{{uuid:mocks.0.id}}\",",
" \"type\": \"person\",",
" \"first_name\": \"LookupFirst\",",
" \"last_name\": \"LookupLast\",",
" \"date_of_birth\": \"{{timestamp}}\",",
" \"pronouns\": \"they/them\",",
" \"contact_id\": \"new-contact\",",
" \"modified_by\": \"00000000-0000-0000-0000-000000000000\",",
" \"modified_at\": \"{{timestamp}}\"",
" },",
" \"new\": {",
" \"contact_id\": \"new-contact\",",
" \"type\": \"person\"",
" },",
" \"old\": {",
" \"contact_id\": \"old-contact\"",
" }",
"}'))"
]
]
}
},
{
"description": "Id probe and lookup probe find different rows (TOO_MANY_LOOKUP_ROWS)",
"action": "merge",
"data": {
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "OtherFirst",
"last_name": "OtherLast",
"date_of_birth": "1991-02-02T00:00:00Z",
"pronouns": "she/her"
},
"mocks": [
{
"id": "22222222-2222-2222-2222-222222222222",
"type": "person",
"first_name": "LookupFirst",
"last_name": "LookupLast",
"date_of_birth": "1990-01-01T00:00:00Z",
"pronouns": "they/them"
},
{
"id": "44444444-4444-4444-4444-444444444444",
"type": "person",
"first_name": "OtherFirst",
"last_name": "OtherLast",
"date_of_birth": "1991-02-02T00:00:00Z",
"pronouns": "she/her"
}
],
"schema_id": "person",
"expect": {
"success": false,
"errors": [
{
"code": "MERGE_FAILED",
"values": {
"error": "TOO_MANY_LOOKUP_ROWS: Lookup for person found too many existing rows"
},
"details": {}
}
]
}
} }
] ]
} }

View File

@ -116,99 +116,17 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
return None; return None;
} }
// 1. Extract table name // The merger's existence read emits one SELECT per probe (id / lookup key)
let table_regex = Regex::new(r#"(?i)\s+FROM\s+(?:[a-zA-Z_]\w*\.)?"?([a-zA-Z_]\w*)"?"#).ok()?; // joined by UNION. Match each arm independently against its own tables and
let table = if let Some(caps) = table_regex.captures(sql) { // WHERE clause; a mock matching any arm is returned once, mirroring UNION's
caps.get(1)?.as_str() // dedup semantics.
} else { let union_regex = Regex::new(r"(?i)\s+UNION\s+(?:ALL\s+)?").ok()?;
return None; let arms: Vec<&str> = union_regex.split(sql).collect();
};
// 2. Extract WHERE conditions string
let mut where_clause = String::new();
if let Some(where_idx) = sql_upper.find(" WHERE ") {
let mut where_end = sql_upper.find(" ORDER BY ").unwrap_or(sql_upper.len());
if let Some(limit_idx) = sql_upper.find(" LIMIT ") {
if limit_idx < where_end {
where_end = limit_idx;
}
}
where_clause = sql[where_idx + 7..where_end].to_string();
}
// 3. Find matching mocks
let mut matches = Vec::new(); let mut matches = Vec::new();
let or_regex = Regex::new(r"(?i)\s+OR\s+").ok()?;
let and_regex = Regex::new(r"(?i)\s+AND\s+").ok()?;
for mock in mocks { for mock in mocks {
if let Some(mock_obj) = mock.as_object() { if let Some(mock_obj) = mock.as_object() {
if let Some(t) = mock_obj.get("type") { if arms.iter().any(|arm| arm_matches_mock(arm, mock_obj)) {
if t.as_str() != Some(table) {
continue;
}
}
if where_clause.is_empty() {
matches.push(mock.clone());
continue;
}
let or_parts = or_regex.split(&where_clause);
let mut any_branch_matched = false;
for or_part in or_parts {
let branch_str = or_part.replace('(', "").replace(')', "");
let mut branch_matches = true;
for part in and_regex.split(&branch_str) {
if let Some(eq_idx) = part.find('=') {
let left = part[..eq_idx]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let right = part[eq_idx + 1..].trim().trim_matches('\'');
let mock_val_str = match mock_obj.get(left) {
Some(Value::String(s)) => s.clone(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::Bool(b)) => b.to_string(),
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != right {
branch_matches = false;
break;
}
} else if part.to_uppercase().contains(" IS NULL") {
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let mock_val_str = match mock_obj.get(left) {
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != "null" {
branch_matches = false;
break;
}
}
}
if branch_matches {
any_branch_matched = true;
break;
}
}
if any_branch_matched {
matches.push(mock.clone()); matches.push(mock.clone());
} }
} }
@ -216,3 +134,103 @@ fn parse_and_match_mocks(sql: &str, mocks: &[Value]) -> Option<Vec<Value>> {
Some(matches) Some(matches)
} }
#[cfg(test)]
fn arm_matches_mock(arm: &str, mock_obj: &serde_json::Map<String, Value>) -> bool {
let arm_upper = arm.to_uppercase();
// 1. The mock's type must name one of the arm's tables. Hierarchy reads
// SELECT FROM the root (entity) and JOIN the subtype tables, so every
// FROM/JOIN table counts, not just the first.
let table_regex = match Regex::new(r#"(?i)\b(?:FROM|JOIN)\s+(?:[a-zA-Z_]\w*\.)?"?([a-zA-Z_]\w*)"?"#) {
Ok(r) => r,
Err(_) => return false,
};
let tables: Vec<&str> = table_regex.captures_iter(arm).filter_map(|c| c.get(1)).map(|m| m.as_str()).collect();
if tables.is_empty() {
return false;
}
if let Some(t) = mock_obj.get("type") {
if !tables.iter().any(|table| t.as_str() == Some(table)) {
return false;
}
}
// 2. Extract this arm's WHERE conditions
let mut where_clause = String::new();
if let Some(where_idx) = arm_upper.find(" WHERE ") {
let mut where_end = arm_upper.find(" ORDER BY ").unwrap_or(arm_upper.len());
if let Some(limit_idx) = arm_upper.find(" LIMIT ") {
if limit_idx < where_end {
where_end = limit_idx;
}
}
where_clause = arm[where_idx + 7..where_end].to_string();
}
if where_clause.is_empty() {
return true;
}
// 3. Match the mock against the conditions
let or_regex = match Regex::new(r"(?i)\s+OR\s+") {
Ok(r) => r,
Err(_) => return false,
};
let and_regex = match Regex::new(r"(?i)\s+AND\s+") {
Ok(r) => r,
Err(_) => return false,
};
for or_part in or_regex.split(&where_clause) {
let branch_str = or_part.replace('(', "").replace(')', "");
let mut branch_matches = true;
for part in and_regex.split(&branch_str) {
if let Some(eq_idx) = part.find('=') {
let left = part[..eq_idx]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let right = part[eq_idx + 1..].trim().trim_matches('\'');
let mock_val_str = match mock_obj.get(left) {
Some(Value::String(s)) => s.clone(),
Some(Value::Number(n)) => n.to_string(),
Some(Value::Bool(b)) => b.to_string(),
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != right {
branch_matches = false;
break;
}
} else if part.to_uppercase().contains(" IS NULL") {
let left = part[..part.to_uppercase().find(" IS NULL").unwrap()]
.trim()
.split('.')
.last()
.unwrap_or("")
.trim_matches('"');
let mock_val_str = match mock_obj.get(left) {
Some(Value::Null) => "null".to_string(),
_ => "".to_string(),
};
if mock_val_str != "null" {
branch_matches = false;
break;
}
}
}
if branch_matches {
return true;
}
}
false
}

View File

@ -7660,3 +7660,15 @@ fn test_merger_0_15() {
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR")); let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 15).unwrap(); crate::tests::runner::run_test_case(&path, 0, 15).unwrap();
} }
#[test]
fn test_merger_0_16() {
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 16).unwrap();
}
#[test]
fn test_merger_0_17() {
let path = format!("{}/fixtures/merger.json", env!("CARGO_MANIFEST_DIR"));
crate::tests::runner::run_test_case(&path, 0, 17).unwrap();
}