Test coverage hardening - #303
Open
Okanmercan99 wants to merge 7 commits into
Open
Conversation
check-test-tiers.sh gains a fourth invariant. ignifyr-rxnorm declared no scalatest-maven-plugin, so its two suites compiled and ran nowhere; they now run in the short tier against a locally bound RxNorm stub instead of the live API, which also reaches the not-found and non-200 branches.
* substituteBatchParameters rewrote the prefix of a longer parameter name: with `year` and `yearEnd` defined, `$yearEnd` became `2020End`. * FileStreamInputArchiver called listFiles() on a checkpoint directory that does not exist until Spark's first commit. The NPE escaped into the shared archiving TimerTask and stopped archiving for every job in the process. * getLastCommitOffset threw on an empty or absent commit directory. * nextArg let a trailing valueless flag overwrite the command, so `run --job` was reported as "unknown command --job".
writeCsvHeaders mapped over the write instead of flatMapping, so the returned Future completed while the file was still being written and the mapping-context header endpoint could answer OK over stale content. This is what the 5s sleep in MappingContextEndpointTest was working around.
Two fixtures still emitted R4-shaped FHIR that the srdc/onfhir:r5 test server rejects, while their sibling under some-folder-1 was already migrated. The care-site mapping wrote nothing at all and other-observation lost 3 of its 14 resources -- both invisible, because the tests only checked that the cleanup delete answered 200. They now read the written resources back. MedicationAdministration.effectiveDateTime -> occurenceDateTime MedicationAdministration.context -> encounter MedicationAdministration.medicationCodeableConcept -> medication.concept MedicationAdministration.category -> array (0..* in R5) Procedure.performedDateTime -> occurrenceDateTime Organization.address -> contact[].address Every replacement was verified against the R5 container. Note the spec's own inconsistency: MedicationAdministration.occurence[x] has one r, Procedure.occurrence[x] has two.
… routing Adds the ExtensionRegistry fail-fast guards (duplicate key, second single-capability provider, conflicting spark-conf key), the batch-completion contract registerBatchJob documents, env-var resolution, the Spark<->FHIR type table, SinkHandler's result classification and mapping-job (de)serialisation. ignifyr-common gets its first test source set: it had none, so nothing there was covered -- including the cst: FHIRPath library the server activates. Four helpers widened from private to package-private so a fail-fast contract can be asserted without a second classloader.
The five execution-control routes (status, list, stop, deschedule, stop mapping) had no coverage at all, and the 404-vs-empty-list distinction the web UI relies on was unasserted. Also adds FolderDBInitializer's two startup paths including its refusal to start on a dangling reference, the error handler's exception path (as opposed to the already-covered rejection path), and MetadataService's one-second bound on the redcap lookup.
The widest untested surfaces were plain lookup tables where a wrong entry is silent: REDCap's 35-branch data type table, Kafka's schema-driven value coercion, and Firely's HTTP-400 batch attribution. Also adds the two sub-registry duplicate-content-type guards, the file reader's distinct option and hdfs:// path resolution, the scheduler's incremental-sync file, and the MapMarker-to-LogstashMarker rewrite that feeds the Kibana dashboard. StreamingSinkHandlerTest had no expectation at all -- it now verifies the writer was handed a further chunk after one threw. The Kafka coercion moved to the companion object so the UDF carries no reference to the SparkSession when it is serialised to the executors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gaps left by a coverage review of the whole reactor. The short tier
goes from ~161 to 373 tests, all Docker-free and network-free.
Writing those tests exposed eight defects, each fixed here alongside the test
that catches it. Two were proven by reverting the fix and watching the new test
go red.
Why
The review compared every main source against what the suites actually assert.
Three patterns came out:
resolution, the job stop/deschedule endpoints, the
cst:FHIRPath library.ExtensionRegistry's three fail-fastrules, the batch future's archiving guarantee,
FhirRepositoryWriter'sserver-specific error handling. All documented in CLAUDE.md as "don't break
this"; none asserted anywhere.
StreamingSinkHandlerTesthad noexpectation;
ignifyr-common,ignifyr-server-commonandignifyr-rxnormran zero tests under Maven (rxnorm's two suites compiled and were never
executed).
Defects fixed
engine/FhirMappingTaskyearandyearEnddefined,$yearEndbecame2020Endengine/FileStreamInputArchiverlistFiles()on a not-yet-created checkpoint dir threw an NPE inside the shared archiving TimerTask, killing the timer and stopping archiving for every job in the processengine/SparkUtilgetLastCommitOffsetthrew on an empty/absent commit directoryengine/CommandLineInterfacerun --jobreported "unknown command --job"server/CsvUtilwriteCsvHeadersreturned a Future that completed before the write, so the header endpoint could answer OK over stale content (this is what the 5s sleep inMappingContextEndpointTestwas papering over)connector-sqltestsdeletereturned 200, which is how the two fixture defects above stayed invisibleOn the R4→R5 fixture migration
some-folder-1's copy of the same mapping was already migrated, so this is ahalf-finished migration rather than a design choice. onFHIR reports only the
first unrecognised element per resource, so it surfaced one layer at a time over
three long-tier runs:
MedicationAdministration.effectiveDateTime -> occurenceDateTime
MedicationAdministration.context -> encounter
MedicationAdministration.medicationCodeableConcept -> medication.concept
MedicationAdministration.category -> array (0..* in R5)
Procedure.performedDateTime -> occurrenceDateTime
Organization.address -> contact[].address
Every replacement was verified by POSTing candidate shapes to an
srdc/onfhir:r5container, not from the spec by memory — note R5 spells
MedicationAdministration.occurence[x]with onerandProcedure.occurrence[x]with two.Reviewing commit by commit
The branch is split so each commit builds and passes on its own, and each fix
ships with its guard.
ci: fail the build when a module's test sources never runfix(engine): four defects the new unit tests exposedfix(server): complete the CSV header write before answeringmap→flatMapfix(testkit): finish the R4-to-R5 migration of the mapping fixturesSqlSourceTestassertionstest(engine): cover the registry guards, job serialisation and sink routingignifyr-common's first test source settest(server): cover execution control, startup and error handlingtest(plugins): cover the lookup tables and sub-registry guardsTwo structural changes worth a second look:
privateto package-private, purely so a fail-fastcontract could be asserted without standing up a second classloader.
KafkaSourceReader's coercion moved to the companion object. This is notcosmetic: as an instance method the UDF closure would capture the reader and
therefore the
SparkSession, breaking executor serialisation. Logic isbyte-identical.
Deliberately not changed — your call
Three behaviours are pinned by tests rather than corrected, because changing them
is a product decision, not a cleanup:
SchemaConverter's asymmetric round-trip.instantreads asTimestampTypeand writes back asdateTime;unsignedInt→LongType→integer64. Changing either table rewrites the StructureDefinitions the serverexports for existing projects.
RedCapUtil.recordIdFielddefaults to"", and the CLI path takes thatdefault, so CLI-extracted schemas get a record-id field with an empty
idanda trailing-dot path. Schemas already on disk carry that shape.
holds no parsable instant (a crash mid-write leaves exactly such a file).
Falling back to
initialTimecould re-import a large window.Also left alone:
procedure-occurrence-mapping.jsonhas an unbalanced paren inits
occurrenceDateTimeexpression. ANTLR recovers, so it evaluates — but it ispre-existing and unrelated to this PR.
Verification