Skip to content

test: registerRefImMeta is exercised against the function the database has - #53

Merged
RussLaher merged 1 commit into
smdcfrom
smdc_refimmeta_contract_test_20260915
Sep 15, 2026
Merged

RussLaher merged 1 commit into
smdcfrom
smdc_refimmeta_contract_test_20260915

Conversation

@RussLaher

Copy link
Copy Markdown
Collaborator

The gap this closes

rapid_db.register_refimmeta sends twenty-four values positionally into a
stored function declared by rapid_systems migration 128. The stub tier asserts
the argument order against the method's own signature — which proves the call
is self-consistent, not that it matches the function the database actually
has. Nothing in this repository contains that function, so nothing outside the
contract tier could.

That distinction is not hypothetical here. products.py records the same class
of defect reaching production: refimcatalogs.cattype is a smallint, the
registrar sent the strings "sextractor"/"photutils", PostgreSQL refused
every reference-image registration at its first catalogue, and the unit suite
stayed green throughout because its fake database accepts whatever it is
handed. A twenty-four argument positional call is that shape with more places
to be wrong.

This is the follow-up named in Caltech-IPAC/rapid#51's merge commit as the
one thing that change left unverified end-to-end.

What it asserts

Five tests in pipeline/contract/test_refimmeta_registration.py:

  1. Every argument lands in the column it was meant for — column by column, not "a row exists", because a row-count check passes on a call whose arguments were rotated by one.
  2. A second registration updates rather than inserting — the replay property the registration consumer depends on when a watermark write does not commit.
  3. Metadata for an unregistered reference image is refused — through the wrapper, so the refusal is seen as exit_code 67, which is what products._check turns into RegistrationFailed.
  4. A NULL measurement is refused by the column — the database's half of the registrar's own refusal to build a half-known row.
  5. A refused call leaves the transaction aborted — see below.

Every value is distinct, and that is load-bearing. Two columns sharing a
value would make a swap between them invisible, which is exactly what an
argument-order test exists to catch. The real columns take binary32-exact
values (.25/.5/.75) so the round-trip comparison is exact and this suite
never fails on float formatting.

Test 5 is a finding, not a restatement

Writing these surfaced that register_refimmeta reports failure through
exit_code but leaves the transaction aborted — the next statement on that
connection raises InFailedSqlTransaction whatever it is.

The registration path is safe from this only because products._check raises
immediately after every call and the consumer's error path rolls back. A future
caller that "handled" exit_code 67 by carrying on would meet a wall of
errors naming nothing to do with reference images. It is asserted here so the
constraint is stated rather than rediscovered, and the tests that expect a
refusal take a SAVEPOINT for the same reason.

Proven able to fail

A test observed only passing proves nothing, so both were mutation-checked
against the wrapper, each mutation reverted:

Mutation Caught as
swap nsxcatsources/npucatsources (same type — only the value check can catch it) nsxcatsources expected 1234 got 1100; npucatsources expected 1100 got 1234
cast eight real parameters as integer clmean expected 1.25 got 1.0; … cov5percent expected 87.25 got 87.0

Fixture

fixture.make_refimage is new and deliberately lighter than
_diffimage_parents: refimages has no FK to exposures or l2files, so a
caller needing only a reference image should not mint both to get one. fid is
read from the seeded filters rather than invented, and version is max+1
because refimagespk is UNIQUE on (field, fid, ppid, version) — a fixed
version would make the second call in a suite collide with the first, and the
failure would look like the defect under test.

Verification

  • Contract tier: 744 passed, 15 skipped against PostgreSQL 18 + Q3C built from the full stream at rapid_systems main (e1896658, the commit carrying 128)
  • Stub tier: 2677 PASS
  • No production code changed — two files, both test-side.

What this still does not cover

The registrar's own assembly of those twenty-four arguments
(products._refimmeta_measurements and its REFIMMETA_MEASUREMENTS table) is
asserted in the stub tier against the mapping it declares. The two tiers
together cover the chain from provenance key to database column; neither covers
it alone. A full end-to-end register_reference_image against a real database
would need an attempt, a terminal record and published products, and is a
larger piece of work than this one.

…e has

THE GAP. `rapid_db.register_refimmeta` sends twenty-four values
positionally into a stored function declared by rapid_systems migration
128. The stub tier asserts the argument order against the METHOD'S OWN
SIGNATURE — which proves the call is self-consistent, not that it matches
the function the database has. Nothing in this repository contains that
function, so nothing outside the contract tier could.

That distinction is not hypothetical. `products.py` records the same
class of defect reaching production: `refimcatalogs.cattype` is a
smallint, the registrar sent the strings "sextractor"/"photutils",
PostgreSQL refused every reference-image registration at its first
catalogue, and the unit suite stayed green because its fake database
accepts whatever it is handed. A twenty-four argument positional call is
that shape with more places to be wrong.

Five tests: every argument lands in the column it was meant for, a second
registration updates rather than inserting, metadata for an unregistered
reference image is refused, a NULL measurement is refused by the column,
and a refused call leaves the transaction aborted.

EVERY VALUE IS DISTINCT, and that is load-bearing rather than cosmetic:
two columns sharing a value would make a swap between them invisible,
which is exactly what an argument-order test exists to catch. The `real`
columns take binary32-exact values (.25/.5/.75) so the round-trip
comparison is exact and this suite never fails on float formatting.

THE FIFTH TEST IS A FINDING, not a restatement. Writing these surfaced
that `register_refimmeta` reports failure through `exit_code` but leaves
the transaction ABORTED — the next statement on that connection raises
InFailedSqlTransaction whatever it is. The registration path is safe from
this only because `products._check` raises immediately after each call;
a caller that "handled" exit_code 67 by carrying on would meet a wall of
unrelated-looking errors. Asserted so the constraint is stated rather
than rediscovered. The tests that expect a refusal take a SAVEPOINT for
the same reason.

`fixture.make_refimage` is new and deliberately lighter than
`_diffimage_parents`: `refimages` has no FK to `exposures` or `l2files`,
so a caller needing only a reference image should not mint both to get
one. `fid` is read from the seeded `filters`, and `version` is max+1
because `refimagespk` is UNIQUE on (field, fid, ppid, version).

PROVEN TO BE ABLE TO FAIL, not merely observed passing. Two mutations of
the wrapper, each reverted:
  - swapping `nsxcatsources`/`npucatsources` (same type, so only the
    value check can catch it) -> "nsxcatsources expected 1234 got 1100;
    npucatsources expected 1100 got 1234"
  - casting eight real parameters as integer -> "clmean expected 1.25 got
    1.0; ... cov5percent expected 87.25 got 87.0"

Contract tier: 744 passed, 15 skipped, against a PostgreSQL 18 + Q3C
built from rapid_systems main (e1896658). Stub tier: 2677 PASS.
@RussLaher
RussLaher merged commit 56d440d into smdc Sep 15, 2026
2 checks passed
RussLaher added a commit that referenced this pull request Sep 15, 2026
…egistration against the real functions

The residual gap named on #53. #53 proved the register_refimmeta WRAPPER
matches the function migration 128 declares; the stub tier proved the
registrar assembles its arguments from the provenance keys
REFIMMETA_MEASUREMENTS names. Neither covered the join — that a value a
reference-image stage recorded arrives in the column it was meant for,
through four stored functions, on one transaction.

addRefImage, updateRefImage, registerRefImCatalog and registerRefImMeta
all run here, so this covers the family rather than only the newest
member: it is the test that would have caught the cattype defect
products.py records.

Two things the file found about itself, both recorded rather than
quietly fixed:

  * attempt_id is a BIGINT. The first version passed a readable string
    and PostgreSQL refused it — addRefImage casts that argument to
    bigint. The stub tier's fake takes whatever it is given, so this
    would have surfaced on the first live registration.

  * The first mapping test COULD NOT FAIL: it read the expected values
    and the mapping from the same table in products.py, so swapping
    gmin/gmax there passed green. Found by mutation-testing the file,
    not by review. EXPECTED_MEASUREMENTS is now stated independently,
    with a separate test keeping the two honest in the other direction.

Four mutations, each caught by name and each reverted: swapped mapping,
collapsed cattypes, no refimmeta write, a pair dropped from the table.

Contract tier 750 passed / 15 skipped against rapid_systems e1896658 --
744 before this file, so the six demonstrably ran. Stub tier 2677 PASS.
One new file; no production code changed.
@rusholme
rusholme deleted the smdc_refimmeta_contract_test_20260915 branch September 19, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant