Class DatasetsWebServiceTest
- All Implemented Interfaces:
org.springframework.beans.factory.Aware, org.springframework.context.ApplicationContextAware
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidvoidvoidThe single-tag add is ground-checked too: it is always an add, so every term it carries is new.voidvoidvoidvoidThe sample-level add goes through the same gate — sample characteristics carry statements as well.voidtestAnAgentDecisionNamesThePersonAndRecordsTheAgentAsJudge(CurationDecisionService curationDecisionService) The agent's part is recorded in the judge kind, which defaults to AGENT for an agent caller.voidtestAnAgentRecordingADecisionMustNameThePersonItActsFor(CurationDecisionService curationDecisionService) An agent recording a decision must name the person it acts for, and never its own account.voidThe field binds at all — a payload carrying it is not rejected as an unknown property.voidClearing needs the clear flag: a null field alone is "absent", so a caller that omits a field can never wipe a column by accident.voidThe FLATTENED spelling counts as echoing it too — a secondstatements[]item under the samegemmaId, which is how Gemma SERIALIZES a compound statement and therefore how uib sends it back.voidEchoing the pair explicitly keeps it, which is what a client that read the row sends back.voidThe id that IS on the factor value is accepted, so the refusal is about membership, not about deleting.voidNo false positive: a statement with no stored pair may omit it, which is every ordinary statement.voidThe commit and the preflight acceptonBehalfOf.voidA delete naming nothing on the dataset is a malformed body: 400, like the design section, not a 409.voidFactor values and statements the document does not mention reach the service unchanged.voidDropping a pair stays possible, but only when asked for in so many words.voidDeclaring it accepts the same tag — the gate is the declaration, not the absence of a URI.voidThe hook may sit in the row's SECOND predicate/object pair — a tag whose first claim is ungrounded and whose second reaches a term is still hooked.voidvoidvoidvoidvoidvoidvoidThe preflight has to be handed the PLAN, not only the design payload.voidA design factor-value statement carries its own code, normalized to the enum name.voidvoidvoidvoidA partial statement item is refused rather than applied.voidBoth spellings of the same pair on one tag is ambiguous, so it is a 400 rather than a silent winner.voidThe first pair is held to the same rule.voidA tag's statement goes through the same guard as a factor value's.voidHalf a pair is not a claim: a dangling predicate would sit on a production row with nothing to render it.voidA declared free-text tag that hangs off nothing is refused.void🛑 The hook has to reach a TERM.voidAn experiment tag with novalue.uriis refused unless the caller says the free text is deliberate.voidA row holds two pairs, so a third claim is refused rather than silently dropped.voidAn unknown code is a 400, not a silent drop.voidA bad code inside the design tree is rejected too, located in the design tree.voidvoidvoidvoidvoidA tag's second pair can be spelled explicitly too, which is the only way to state one on a tag whose statement is new.voidA stated evidence code reaches the Characteristic instead of being left to the add path's default.voidThe tag'ssupportingEvidencereaches the Characteristic handed to the service.voidA tag carrying two statements must store BOTH pairs.voidvoidOmitting the field leaves the Characteristic's code null, which is what hands the row toExpressionExperimentWriteServiceImpl#addCharacteristic'sICfallback — the code every tag written through this route has carried.voidA tag carrying nested statements must reach the commit as aStatement, not a bare Characteristic.voidA NEW statement can carry a second predicate-object pair.voidAsking to drop it and sending it are contradictory, so neither wins silently.voidAdeletedIdsentry naming a statement that is not on that factor value is refused.voidA factordeletedIdsnaming a factor of another dataset is refused the same way.voidA factor valuedeletedIdsentry that is not a value of that factor is refused.voidOmittingevidenceCodeon a statement that has one is refused.voidAgemmaIdfactor item that omits evidence the factor HAS is refused, not applied as a clear.voidThe same rule one level down: a factor value item that omits the value's evidence is refused.voidOmitting evidence on a row that HAS evidence is refused; sending[]clears it deliberately.void🛑 Omitting the second predicate-object pair on agemmaIdstatement that HAS one is refused (Paul, 2026-09-11: "Guard it like evidence.voidsampleCharacteristics has the same add/delete-only shape and discarded decoration the same way, so it gets the same refusal —bioassayShortNameincluded, which on a keep-marker reads like "move this characteristic to that sample" and does nothing.voidA tags item bearing a gemmaId is a keep-marker: the section is add/delete only, so the mapper reads the id and nothing else.voidA design-section factor-value statement with an ungrounded term is rejected, located in the design tree.voidA tag whose label doesn't match its URI is rejected with a structured, per-slot 400.voidvoidvoidvoidvoidA body naming no field at all is a caller bug, not a silent no-op.voidA factor value can name its samples by BioMaterial id.voidA sample id belonging to another dataset is refused before anything is written.voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidAs for cellTypeAssignment: the JSON output of cellLevelCharacteristics does not read cell ids.voidThe JSON output of cellTypeAssignment does not read cell ids, so it must not load them.voidvoidvoidA non-administrator does not learn that curation is under way.voidcurationPendingis the curation lock's unexpired lease and nothing else: it must read true while a lock is held, and the response must still name nobody.voidA free dataset reads false, not null: the GET always consults the lock, so the reader can tell "nobody is curating" from "this path did not look".voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidThe size guard: 30,000 points drawn from ten distinct coordinates come back as ten.voidA point whose mean or variance is not finite has no position on the scatter, so it is dropped rather than keyed into the grid.voidMean-variance is the heaviest diagnostics payload — one mean and one variance per probe.voidThe thinned arrays are what actually goes on the wire.voidTwo points that land in the same grid cell collapse to the first of them, and the pairing survives: the fixture makes every variance twice its mean, so an entry dropped from one array and not the other would re-pair every point after it and break that relation.voidPoints that each get their own cell come back untouched — thinning only ever removes a point that would be drawn on top of one already sent.voidvoidvoidvoidvoidvoidvoidvoidvoidThe sample-correlation step carries the filter attrition recorded when the matrix was computed.voidThe preprocess step carries what the processed-vector creation did to the data.voidvoidvoidvoidvoidvoidvoidvoidA payload that does not parse must not take the status read down with it.voidEvery correlation matrix computed before the payload existed carries no payload at all.voidvoidvoidvoidvoidvoidvoidvoidvoidvoid🛑 Temporary, pending the per-cell-type design.voidThe same dataset, not single-cell, still serves the matrix — the gate is the flag, not the route.voidvoidvoidsample.statementsis 21.5% of the samples response and carries the same rows assample.characteristicsplus a predicate and object, so a client that renders only subjects can decline it.voidThe predicted-outlier flag costs the dataset's whole N×N sample-correlation matrix to compute, which is why it is opt-in: the cost is set by the correlation analysis, not the page size, and on the largest datasets it exceeds the request timeout.void🛑limitwas bound by the route and then used only on the cursor branch, so a client asking for a page got the whole dataset instead, with nothing in the body saying so.void🛑 The route was not compressed, and that — not any single field — was the size of the problem.voidThe QT-narrowed sample listings sort by assay name and restrict to aBioAssayDimension, neither of which anid-only cursor can express, so they stay unpaginated — and alimitthere is refused rather than dropped.voidAn exclusion the route does not offer is a 400, not a silently ignored parameter.voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidExpression levels are a data-download surface, so rounding here is the default andprecise=trueis the opt-out.voidvoidvoidvoidCold cache: the tabular single-cell data is generated ONCE, streaming to the caller and populating the cache file in the same pass — never by racing a fire-and-forget background build against an in-band stream of the same data, which on this endpoint meant two concurrent full scans of the largest payloads in the system.voidvoidThe document is served as an object, not as the string it is stored as.voidA dataset that does not exist is still a 404, which is what the null case must not look like.void🛑 Nothing harvested is the normal state for most of the corpus, not an error.voidThe bulk route lives at the LITERAL `/datasets/pipelineStatus` with ids in a query param.voidvoidvoidvoidvoidvoidvoidThe sibling subset listing has the same shape and takes a barelimitthe same way.voidSVD loadings serialize at a mean of ~20 characters each and nothing consumes the digits below it, so this route rounds with no opt-out.voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidOmitting bioAssayIds applies to every sample, rather than to none.voidThe case this was expanded for: a pre-2018 Affymetrix `rma value` recorded as LINEAR when RMA output is log2.voidPreference did not change, so no preferred-data event may be emitted for it.voidrepresentation describes the stored values rather than how to read them, so patching it would misdescribe the vectors.voidA patch that asks for what the record already says changes nothing, so it is a bad request rather than a 200 with an audit event nobody can interpret.voidA DEA that FAILED and was then followed by a design change stays `failed`.voidbatchInfo comes from scan dates and file headers.voidvoidA DEA that succeeded and then had the design change under it is still there, and its own event still says it succeeded — but it no longer describes the design it was computed from.voidA design change BEFORE the analysis is the normal order: the DEA already reflects it.voidEvery step but batchInfo is computed from the samples and the design, so a design change makes all of them stale -- not just the DEA.voidFlagging an outlier changes the analyzed sample set and no longer reprocesses the dataset inline, so the SampleRemovalEvent it records is what marks the computed results as owed a re-run.voidNothing triaged leaves both fields null rather than defaulting — that is how a caller tells "not triaged" from "triaged Fine", which a boolean could not express.voidAn unswitched dataset answers with an empty list, never with its current platform.void`?original=true` must actually route.voidA word outside the three in use round-trips instead of 400ing.voidThe baseline-relevance hint is accepted on the write side and reaches the design mapper.voidThe preflight is the call a client makes FIRST, so it has to accept every document the commit would —clearSecondPairincluded — and refuse the ones it would refuse.voidvoidvoidThe preflight shares the mapper, so it refuses exactly what the commit refuses.voidPreflight enforces the same gate, so a client catches the failure on the dry run.voidThe preflight enforces the same gate, so a client catches a bad code on the dry run.voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidcab, 2026-09-13: 15 of 21 restore points on gemma2 answered 400, each on a statement a commit had given a second pair after the snapshot was taken.voidA snapshot captured before snapshots recorded pairs cannot say whether the statement had one, so the restore keeps the live pair rather than clearing what the snapshot never saw (Paul's ruling, 2026-09-13).voidA restore replays a SNAPSHOT, and a snapshot records the content of every row — including the rows the restore only has to leave alone.voidA restore re-creates a tag its snapshot holds, and the free-text experiment-tag checks do not apply to it (Paul's ruling, 2026-09-13).voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidNo regressed matrix: `best` falls back to the full one and says so, rather than implying regression.void`matrix=full` takes the full one even when a regressed one exists — that is the whole point.void`matrix=regressed` never silently substitutes the full one; a dataset without one 404s.voidThe default is `best`, and the response says which of the two it actually got.voidThree decimals, and a masked cell stays masked.voidThe route is curator-or-admin.voidvoidThe candidate ids come from an audit query that has no ACL clause, so what keeps a private dataset out of the response is the ACL-filtered load it is passed through.voidThe corpus-wide read of the samestalerule the per-dataset route applies: which datasets owe pipeline work, and which steps.voidThe disconfirming half: the same two events in the other order.void🛑 A subset with no factor values is normal, not an error.voidneedsAttention is the pre-agent curator flag and must NOT move with triage: the two are separate signals, and collapsing them was the thing this design turned down.voidAn unknown vocabulary value becomes a 400 that names what IS accepted, not a 500.voidvoidvoidvoidvoidvoid🛑 A tag that is already stored is NOT re-checked, even when the stored URI is malformed.voidvoidvoidvoidvoidvoidA tag write reaches the four statement URI columns, so its new terms are ground-checked the way the curation commit's are: an object URI that resolves nowhere is a 400 and nothing is written.voidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidvoidMethods inherited from class BaseJerseyTest5
configure, configureClient, getTestContainerFactory, setApplicationContext, setUp, tearDownMethods inherited from class org.glassfish.jersey.test.JerseyTest
client, close, closeIfNotNull, configureDeployment, disable, enable, forceDisable, forceEnable, forceSet, getAsyncTimeoutMultiplier, getBaseUri, getClient, getLastLoggedRecord, getLoggedRecords, getPort, getSslContext, getSslParameters, isEnabled, set, set, setClient, target, target
-
Constructor Details
-
DatasetsWebServiceTest
public DatasetsWebServiceTest()
-
-
Method Details
-
testSetSampleLibraryStrategyReachesTheService
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testSetSampleLibraryStrategyReachesTheService() -
testOmittedBioAssayIdsMeansEverySample
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testOmittedBioAssayIdsMeansEverySample()Omitting bioAssayIds applies to every sample, rather than to none. -
testForeignBioAssayIdIsRefused
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testForeignBioAssayIdIsRefused()A sample id belonging to another dataset is refused before anything is written. Without this the route would happily set a column on a sample the caller never named a dataset for. -
testUnknownLibraryStrategyBecomesA400
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testUnknownLibraryStrategyBecomesA400()An unknown vocabulary value becomes a 400 that names what IS accepted, not a 500. -
testEmptyBodyIsRefused
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testEmptyBodyIsRefused()A body naming no field at all is a caller bug, not a silent no-op. -
testClearingRequiresTheClearFlag
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testClearingRequiresTheClearFlag()Clearing needs the clear flag: a null field alone is "absent", so a caller that omits a field can never wipe a column by accident. -
testSampleMetadataRouteIsCuratorOrAdmin
The route is curator-or-admin.🛑 Asserted on the ANNOTATION, not by calling the route as a GROUP_USER and expecting 403.
@PreAuthorizeis enforced byMethodSecurityConfigin gemma-core, which this test context does not import — no test in this class wires method security, so a call as an ordinary user returns 200 here and would prove nothing either way. Reflection still fails if someone drops or weakens the annotation, which is the regression worth catching at this level.- Throws:
NoSuchMethodException
-
setUpMocks
- Throws:
TimeoutException
-
resetMocks
@AfterEach public void resetMocks() -
testCommitRejectsUngroundedTerm
@Test public void testCommitRejectsUngroundedTerm()A tag whose label doesn't match its URI is rejected with a structured, per-slot 400. -
testCommitRejectsUngroundedDesignStatementTerm
@Test public void testCommitRejectsUngroundedDesignStatementTerm()A design-section factor-value statement with an ungrounded term is rejected, located in the design tree. -
testCommitRefusesDeletedIdThatIsNotOnThatFactorValue
@Test public void testCommitRefusesDeletedIdThatIsNotOnThatFactorValue()AdeletedIdsentry naming a statement that is not on that factor value is refused.The delete is a suppression of the carry-forward, so an id that is not among the factor value's current statements suppresses nothing and the commit answered 200 with
deleted: 0— which reads exactly like a delete that worked. A caller recorded eight such deletions against eid 6146 on 2026-09-01; the ids were realCHARACTERISTICrows on no factor value of that dataset. -
testCommitAcceptsDeletedIdThatIsOnThatFactorValue
@Test public void testCommitAcceptsDeletedIdThatIsOnThatFactorValue()The id that IS on the factor value is accepted, so the refusal is about membership, not about deleting. -
testCommitRefusesFactorDeletedIdThatIsNotOnThisDataset
@Test public void testCommitRefusesFactorDeletedIdThatIsNotOnThisDataset()A factordeletedIdsnaming a factor of another dataset is refused the same way. -
testPreflightAcceptsTheBaselineRelevanceHint
@Test public void testPreflightAcceptsTheBaselineRelevanceHint()The baseline-relevance hint is accepted on the write side and reaches the design mapper.It was published on
ExperimentalFactorandExperimentalFactorValueObjectand absent fromFactorCommit, so the curation UI's "Tick to override: no baseline" checkbox and its reason box had nowhere to land: every preflight carrying them came back400 Unrecognized field "baselineRelevance" … not marked as ignorable— a readable, renderable, unsettable field (cab, 2026-09-04, GSE32473 factor 13474). -
testPreflightAcceptsAnUnfamiliarBaselineRelevanceValue
@Test public void testPreflightAcceptsAnUnfamiliarBaselineRelevanceValue()A word outside the three in use round-trips instead of 400ing. The hint's vocabulary has moved once already, and a closedallowableValueswould make the next word a schema change and a deploy (cab, 2026-09-04: "don't lock us into any kind of enums"). -
testCommitAndPreflightAcceptOnBehalfOf
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testCommitAndPreflightAcceptOnBehalfOf()The commit and the preflight acceptonBehalfOf. Both refused it with400 Unknown query parameterwhilesignrequired it, so a relay sending the parameter uniformly across the chain — which is the sane client — was broken on two of its three calls (uib, 2026-09-05, on GSE7866).It is not decoration on the commit:
actingAsis what attributes the restore point to the curator instead of the courier, and what keepsrequireNoForeignCurationLockfrom refusing a commit relayed for the very curator who holds the lock. -
testAnAgentRecordingADecisionMustNameThePersonItActsFor
@Test @WithMockUser(username="gemmaAgent", authorities="GROUP_AGENT") public void testAnAgentRecordingADecisionMustNameThePersonItActsFor(@Autowired CurationDecisionService curationDecisionService) An agent recording a decision must name the person it acts for, and never its own account. Paul, 2026-09-15, ofCURATION_DECISION.DECIDED_BY: "it should be the person". All 1,074 rows on gemd named gemmaAgent. -
testAnAgentDecisionNamesThePersonAndRecordsTheAgentAsJudge
@Test @WithMockUser(username="gemmaAgent", authorities="GROUP_AGENT") public void testAnAgentDecisionNamesThePersonAndRecordsTheAgentAsJudge(@Autowired CurationDecisionService curationDecisionService) The agent's part is recorded in the judge kind, which defaults to AGENT for an agent caller. -
testFactorValueSamplesCanBeNamedByBioMaterialId
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testFactorValueSamplesCanBeNamedByBioMaterialId()A factor value can name its samples by BioMaterial id.The old contract took names only, and the names it took were the GSM accession and the bioassay short name — never the
bioMaterialNamethatGET /datasets/{id}/designhands back, so a client echoing what it read was refused (uib, GSE7866). Worse, a single-cell sub-bioassay has NO accession by construction — 15 of 15 null on GSE124952 subset 68405 — so on those datasets no string could name a sample at all. Paul: "Gemma must do it by its own ID for the sample … the id is the primary key." -
testBioMaterialIdBindsOnBothCommitSections
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testBioMaterialIdBindsOnBothCommitSections()The field binds at all — a payload carrying it is not rejected as an unknown property. -
testPreflightRejectsUngroundedTerm
@Test public void testPreflightRejectsUngroundedTerm()Preflight enforces the same gate, so a client catches the failure on the dry run. -
testGetDatasets
@Test public void testGetDatasets() -
testGetDatasetsWithQuery
- Throws:
SearchExceptionTimeoutException
-
testGetDatasetsWithQueryAndSort
- Throws:
SearchExceptionTimeoutException
-
testGetDatasetsWithEmptyQuery
@Test public void testGetDatasetsWithEmptyQuery() -
testGetDatasetsWhenSliceHasNoLimit
@Test public void testGetDatasetsWhenSliceHasNoLimit() -
testGetDatasetsOrderedByGeeqScore
@Test public void testGetDatasetsOrderedByGeeqScore() -
testGetDatasetsPlatformsUsageStatistics
- Throws:
TimeoutException
-
testGetDatasetsAnnotationsWithRetainMentionedTerms
- Throws:
TimeoutException
-
testGetDatasetsAnnotations
- Throws:
TimeoutException
-
testGetDatasetsAnnotationWhenLimitExceedHardCap
@Test public void testGetDatasetsAnnotationWhenLimitExceedHardCap() -
testGetDatasetsAnnotationsWhenMaxFrequencyIsSuppliedLimitMustUseMaximum
@Test public void testGetDatasetsAnnotationsWhenMaxFrequencyIsSuppliedLimitMustUseMaximum() throws TimeoutException- Throws:
TimeoutException
-
testGetDatasetsAnnotationsWithLimitIsSupplied
- Throws:
TimeoutException
-
testGetDatasetsAnnotationsForUncategorizedTerms
- Throws:
TimeoutException
-
testGetDatasetsCategories
@Test public void testGetDatasetsCategories() -
testGetDatasetQuantitationTypes
@Test public void testGetDatasetQuantitationTypes() -
testGetDatasetProcessedExpression
@Test public void testGetDatasetProcessedExpression() throws IOException, URISyntaxException, InterruptedException, TimeoutException, FilteringException -
testGetDatasetProcessedExpressionWhenNoProcessedVectorsExist
@Test public void testGetDatasetProcessedExpressionWhenNoProcessedVectorsExist() -
testGetDatasetRawExpression
@Test public void testGetDatasetRawExpression() throws IOException, URISyntaxException, InterruptedException, TimeoutException -
testGetDatasetRawExpressionByQuantitationTypeWhenQtIsNotFromTheDataset
@Test public void testGetDatasetRawExpressionByQuantitationTypeWhenQtIsNotFromTheDataset() -
testGetDatasetRawExpressionByQuantitationType
@Test public void testGetDatasetRawExpressionByQuantitationType() throws IOException, URISyntaxException, InterruptedException, TimeoutException -
testGetBlacklistedDatasets
@Test public void testGetBlacklistedDatasets() -
testGetDatasetAnnotations
@Test public void testGetDatasetAnnotations() -
testGetDatasetAnnotationsExcludingFreeText
@Test public void testGetDatasetAnnotationsExcludingFreeText() -
testAddDatasetAnnotationTag
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationTag() -
testAddDatasetAnnotationTagDuplicateReturnsConflict
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationTagDuplicateReturnsConflict() -
testAddDatasetAnnotationTagAcceptsAndDropsAnnotationSetId
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationTagAcceptsAndDropsAnnotationSetId() -
testRemoveDatasetAnnotationTag
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testRemoveDatasetAnnotationTag() -
testRemoveDatasetAnnotationTagNotFound
@Test @WithMockUser(authorities="GROUP_CURATOR") public void testRemoveDatasetAnnotationTagNotFound() -
testUpdateDatasetAnnotations
@Test @WithMockUser public void testUpdateDatasetAnnotations() -
testUpdateDatasetAnnotationsRejectsUngroundedStatementTerm
@Test @WithMockUser public void testUpdateDatasetAnnotationsRejectsUngroundedStatementTerm()A tag write reaches the four statement URI columns, so its new terms are ground-checked the way the curation commit's are: an object URI that resolves nowhere is a 400 and nothing is written. Before this, the identical payload was a 400 onPUT /datasets/{id}/curationand a 200 here — which is howOBJECT_URI = 'Prethalamus'reached three factor values in production. -
testUpdateDatasetAnnotationsDoesNotRecheckAnAlreadyStoredTag
@Test @WithMockUser public void testUpdateDatasetAnnotationsDoesNotRecheckAnAlreadyStoredTag()🛑 A tag that is already stored is NOT re-checked, even when the stored URI is malformed. A set-replace carries the whole desired set, so a client editing one tag echoes back every other one as served — including the 105 colon-form URIs the read serves verbatim (they are not in the migration shim). Checking the whole desired set would answer 400 to the unrelated edit. Same rule as the commit's carry-forward items; identity isCharacteristicUtils.sameTag, the predicate the service diffs on. -
testAddDatasetAnnotationRejectsUngroundedTerm
@Test @WithMockUser public void testAddDatasetAnnotationRejectsUngroundedTerm()The single-tag add is ground-checked too: it is always an add, so every term it carries is new. -
testAddSampleCharacteristicRejectsUngroundedTerm
@Test @WithMockUser public void testAddSampleCharacteristicRejectsUngroundedTerm()The sample-level add goes through the same gate — sample characteristics carry statements as well. -
testUpdateDatasetAnnotationsAcceptsEmptyListAsClear
@Test @WithMockUser public void testUpdateDatasetAnnotationsAcceptsEmptyListAsClear() -
testUpdateDatasetAnnotationsMissingBody
@Test @WithMockUser public void testUpdateDatasetAnnotationsMissingBody() -
testUpdateDatasetAnnotationsRejectsBlankCategory
@Test @WithMockUser public void testUpdateDatasetAnnotationsRejectsBlankCategory() -
testUpdateDatasetAnnotationsRejectsBlankValue
@Test @WithMockUser public void testUpdateDatasetAnnotationsRejectsBlankValue() -
testUpdateDatasetAnnotationsConstructsStatementWhenPredicateAndObjectPresent
@Test @WithMockUser public void testUpdateDatasetAnnotationsConstructsStatementWhenPredicateAndObjectPresent() -
testUpdateDatasetAnnotationsConstructsStatementWithSecondPair
@Test @WithMockUser public void testUpdateDatasetAnnotationsConstructsStatementWithSecondPair() -
testUpdateDatasetAnnotationsRefusesAPredicateWithoutAnObject
@Test @WithMockUser public void testUpdateDatasetAnnotationsRefusesAPredicateWithoutAnObject() -
testUpdateDatasetAnnotationsStaysPlainCharacteristicWhenNoStatementFields
@Test @WithMockUser public void testUpdateDatasetAnnotationsStaysPlainCharacteristicWhenNoStatementFields() -
testUpdateDatasetAnnotationsCarriesSupportingEvidence
@Test @WithMockUser public void testUpdateDatasetAnnotationsCarriesSupportingEvidence() -
testUpdateDatasetAnnotationsNoEvidenceLeavesSupportingEvidenceNull
@Test @WithMockUser public void testUpdateDatasetAnnotationsNoEvidenceLeavesSupportingEvidenceNull() -
testGetDatasetsDifferentialAnalysisResultsExpressionForGene
@Test public void testGetDatasetsDifferentialAnalysisResultsExpressionForGene() -
testGetDatasetsDifferentialAnalysisResultsExpressionForGeneInTaxa
@Test public void testGetDatasetsDifferentialAnalysisResultsExpressionForGeneInTaxa() -
testGetDatasetsAnalysisResultSets
@Test public void testGetDatasetsAnalysisResultSets() -
testRefreshDataset
@Test @WithMockUser public void testRefreshDataset() -
testRefreshDatasetWithDefaultsDoesNotEvictCaches
@Test @WithMockUser public void testRefreshDatasetWithDefaultsDoesNotEvictCaches() -
testRefreshDatasetVectorsOnly
@Test @WithMockUser public void testRefreshDatasetVectorsOnly() -
testRefreshDatasetReportsOnly
@Test @WithMockUser public void testRefreshDatasetReportsOnly() -
testRefreshDatasetNotFound
@Test @WithMockUser public void testRefreshDatasetNotFound() -
testGetDatasetSingleCellData
@Test public void testGetDatasetSingleCellData() throws InterruptedException, TimeoutException, URISyntaxException, IOException -
testGetDatasetSingleCellDataWhenCacheIsCold
Cold cache: the tabular single-cell data is generated ONCE, streaming to the caller and populating the cache file in the same pass — never by racing a fire-and-forget background build against an in-band stream of the same data, which on this endpoint meant two concurrent full scans of the largest payloads in the system.- Throws:
Exception
-
testGetDatasetCellTypeAssignmentAsJsonDoesNotLoadCellIds
@Test public void testGetDatasetCellTypeAssignmentAsJsonDoesNotLoadCellIds()The JSON output of cellTypeAssignment does not read cell ids, so it must not load them. On MSSM_Cohort (3.7 million cells, 2026-09-18) that load was about 4 s of an 880-byte response. -
testGetDatasetCellLevelCharacteristicsAsJsonDoesNotLoadCellIds
@Test public void testGetDatasetCellLevelCharacteristicsAsJsonDoesNotLoadCellIds()As for cellTypeAssignment: the JSON output of cellLevelCharacteristics does not read cell ids. -
testGetDatasetSingleCellDataAsDownload
@Test public void testGetDatasetSingleCellDataAsDownload() throws InterruptedException, TimeoutException, URISyntaxException, IOException -
testGetDatasetSingleCellDataAsMex
@Test public void testGetDatasetSingleCellDataAsMex() throws InterruptedException, TimeoutException, URISyntaxException, IOException -
testGetDatasetSubSetGroups
@Test public void testGetDatasetSubSetGroups() -
testGetDatasetSubSetGroup
@Test public void testGetDatasetSubSetGroup() -
testGetDatasetSubSets
@Test public void testGetDatasetSubSets() -
testGetDatasetSubSet
@Test public void testGetDatasetSubSet() -
testGetDatasetSamplesIsCompressed
@Test public void testGetDatasetSamplesIsCompressed()🛑 The route was not compressed, and that — not any single field — was the size of the problem.GET /datasets/3937/samplessent 5,381,688 bytes for 278 samples with noContent-Encoding, and the same body gzips to 144,390 — a 37x reduction with no client change and no field removed, larger than every trim in this commit put together. Compression here is opt-in per endpoint via@GZIP, so a heavy new route is uncompressed by default and nothing says so; the annotation is the whole gate. Measured on production,b5c6747f68. -
testGetDatasetSamplesCanExcludeStatements
@Test public void testGetDatasetSamplesCanExcludeStatements()sample.statementsis 21.5% of the samples response and carries the same rows assample.characteristicsplus a predicate and object, so a client that renders only subjects can decline it. Excluded means absent, not empty — seeBioMaterialValueObject.getStatements(). -
testGetDatasetSamplesHonoursALimitByPaginating
@Test public void testGetDatasetSamplesHonoursALimitByPaginating()🛑limitwas bound by the route and then used only on the cursor branch, so a client asking for a page got the whole dataset instead, with nothing in the body saying so.Measured on production (
gemma2, dataset 7332 = GSE2109, 2158 samples):?limit=20returned all 2158 assays, byte-identical to the no-parameter call. Silently truncating the legacy body to 20 would have lost 2138 rows just as invisibly — it has nototalElementsand nonextCursorin which to declare it — solimitnow selects cursor mode, whose wrapper can. A barelimitis also the only way into cursor mode from a standing start: neither listing has an offset mode, so nothing they can return without a cursor carries anextCursorto continue from. -
testGetDatasetSubSetSamplesHonoursALimitByPaginating
@Test public void testGetDatasetSubSetSamplesHonoursALimitByPaginating()The sibling subset listing has the same shape and takes a barelimitthe same way. -
testGetDatasetSamplesRejectsALimitOnTheQuantitationTypeNarrowedListing
@Test public void testGetDatasetSamplesRejectsALimitOnTheQuantitationTypeNarrowedListing()The QT-narrowed sample listings sort by assay name and restrict to aBioAssayDimension, neither of which anid-only cursor can express, so they stay unpaginated — and alimitthere is refused rather than dropped. -
testGetDatasetSamplesRejectsAnUnsupportedExclusion
@Test public void testGetDatasetSamplesRejectsAnUnsupportedExclusion()An exclusion the route does not offer is a 400, not a silently ignored parameter. -
testGetDatasetSamplesDoesNotComputePredictedOutliersByDefault
@Test public void testGetDatasetSamplesDoesNotComputePredictedOutliersByDefault()The predicted-outlier flag costs the dataset's whole N×N sample-correlation matrix to compute, which is why it is opt-in: the cost is set by the correlation analysis, not the page size, and on the largest datasets it exceeds the request timeout. The assertion is that the detection service is not consulted at all by default — a cheaper-but-still-called path would still load the matrix. -
testGetDatasetSubSetSamples
@Test public void testGetDatasetSubSetSamples() -
testPreviewDatasetDesignChangeNoOp
@Test public void testPreviewDatasetDesignChangeNoOp() -
testPreviewDatasetDesignChangeWithBlockers
@Test public void testPreviewDatasetDesignChangeWithBlockers() -
testPreviewDatasetDesignChangeReportsDeletions
@Test public void testPreviewDatasetDesignChangeReportsDeletions() -
testPreviewDatasetDesignChangeWithEmptyBodyIs400
@Test public void testPreviewDatasetDesignChangeWithEmptyBodyIs400() -
testPreviewDatasetDesignChangeWithUnknownDatasetIs404
@Test public void testPreviewDatasetDesignChangeWithUnknownDatasetIs404() -
testGetDatasetAuditEvents
@Test public void testGetDatasetAuditEvents() -
testGetDatasetAuditEventsWhenEmpty
@Test public void testGetDatasetAuditEventsWhenEmpty() -
testGetDatasetAuditEventsWithUnknownDatasetIs404
@Test public void testGetDatasetAuditEventsWithUnknownDatasetIs404() -
testGetDatasetAuditEventsCompactFalseReturnsFullList
@Test public void testGetDatasetAuditEventsCompactFalseReturnsFullList() -
testGetDatasetAuditEventsCompactCollapsesConsecutiveSameTypeAndPerformer
@Test public void testGetDatasetAuditEventsCompactCollapsesConsecutiveSameTypeAndPerformer() -
testGetDatasetAuditEventsCompactAlternatingTypesProducesAllSolo
@Test public void testGetDatasetAuditEventsCompactAlternatingTypesProducesAllSolo() -
testGetDatasetAuditEventsCompactMixedAABA
@Test public void testGetDatasetAuditEventsCompactMixedAABA() -
testGetDatasetAuditEventsCompactOnEmptyIsEmpty
@Test public void testGetDatasetAuditEventsCompactOnEmptyIsEmpty() -
testGetDatasetAuditEventsExcludeEmptyDropsTypelessBlankEvents
@Test public void testGetDatasetAuditEventsExcludeEmptyDropsTypelessBlankEvents() -
testGetDatasetAuditEventsExcludeEmptyComposesWithCompact
@Test public void testGetDatasetAuditEventsExcludeEmptyComposesWithCompact() -
testGetDatasetCurationDetails
@Test @WithMockUser public void testGetDatasetCurationDetails() -
testGetDatasetCurationDetailsExposesCurationNoteForAdmin
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetCurationDetailsExposesCurationNoteForAdmin() -
testGetDatasetCurationDetailsReportsCurationPendingWhileLocked
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetCurationDetailsReportsCurationPendingWhileLocked()curationPendingis the curation lock's unexpired lease and nothing else: it must read true while a lock is held, and the response must still name nobody. The holder is served by/datasets/{id}/curation/lock, which is authenticated; this field is readable by anyone who can read the dataset. -
testPatchQuantitationTypeCorrectsTheScale
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPatchQuantitationTypeCorrectsTheScale()The case this was expanded for: a pre-2018 Affymetrix `rma value` recorded as LINEAR when RMA output is log2. The correction changes the record, not the numbers, so it must reachquantitationTypeService.updateand say what moved. -
testPatchQuantitationTypeDoesNotTouchPreference
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPatchQuantitationTypeDoesNotTouchPreference()Preference did not change, so no preferred-data event may be emitted for it. Routing a descriptive patch through updateQuantitationType would emit one, because that path reads the preferred flag and cannot tell "already preferred" from "just became preferred". -
testPatchQuantitationTypeRefusesRepresentation
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPatchQuantitationTypeRefusesRepresentation()representation describes the stored values rather than how to read them, so patching it would misdescribe the vectors. Refused with a reason rather than silently ignored. -
testPatchQuantitationTypeRejectsANoOp
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPatchQuantitationTypeRejectsANoOp()A patch that asks for what the record already says changes nothing, so it is a bad request rather than a 200 with an audit event nobody can interpret. -
testGetDatasetCurationDetailsHidesCurationPendingFromNonAdmins
@Test @WithMockUser public void testGetDatasetCurationDetailsHidesCurationPendingFromNonAdmins()A non-administrator does not learn that curation is under way. The lock is consulted either way -- what changes is whether the answer is kept -- so the field reads null rather than false, which would assert something untrue. Null is how curationNote already behaves for a non-administrator. -
testGetDatasetCurationDetailsReportsNoCurationPendingWhenUnlocked
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetCurationDetailsReportsNoCurationPendingWhenUnlocked()A free dataset reads false, not null: the GET always consults the lock, so the reader can tell "nobody is curating" from "this path did not look". -
testGetDatasetCurationDetailsWithUnknownDatasetIs404
@Test @WithMockUser public void testGetDatasetCurationDetailsWithUnknownDatasetIs404() -
testUpdateDatasetCurationDetailsSetsTroubled
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsSetsTroubled() -
testUpdateDatasetCurationDetailsClearsTroubled
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsClearsTroubled() -
testUpdateDatasetCurationDetailsSkipsNoOpTroubled
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsSkipsNoOpTroubled() -
testUpdateDatasetCurationDetailsSetsNeedsAttention
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsSetsNeedsAttention() -
testUpdateDatasetCurationDetailsClearsNeedsAttention
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsClearsNeedsAttention() -
testUpdateDatasetCurationDetailsUpdatesCurationNote
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsUpdatesCurationNote() -
testUpdateDatasetCurationDetailsAppliesMultipleChanges
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsAppliesMultipleChanges() -
testUpdateDatasetCurationDetailsWithEmptyBodyIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsWithEmptyBodyIs400() -
testUpdateDatasetCurationDetailsWithUnknownDatasetIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetCurationDetailsWithUnknownDatasetIs404() -
testUpdateDatasetPermissionsMakesPublic
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetPermissionsMakesPublic() -
testUpdateDatasetPermissionsMakePublicOnAlreadyPublicRecordsNoEvent
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetPermissionsMakePublicOnAlreadyPublicRecordsNoEvent() -
testUpdateDatasetPermissionsMakesPrivate
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetPermissionsMakesPrivate() -
testUpdateDatasetPermissionsReturnsCurrentStateWhenIsPublicOmitted
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetPermissionsReturnsCurrentStateWhenIsPublicOmitted() -
testMakeDatasetPublicFlipsTheAcl
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testMakeDatasetPublicFlipsTheAcl() -
testMakeDatasetPublicOnAlreadyPublicIsNoOpNoEvent
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testMakeDatasetPublicOnAlreadyPublicIsNoOpNoEvent() -
testMakeDatasetPrivateRecordsMakePrivateEvent
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testMakeDatasetPrivateRecordsMakePrivateEvent() -
testMakeDatasetPrivateOnAlreadyPrivateIsNoOpNoEvent
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testMakeDatasetPrivateOnAlreadyPrivateIsNoOpNoEvent() -
testUpdateDatasetPermissionsWithEmptyBodyIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetPermissionsWithEmptyBodyIs400() -
testUpdateDatasetPermissionsWithUnknownDatasetIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testUpdateDatasetPermissionsWithUnknownDatasetIs404() -
testDeleteDatasetRawDataHappyPath
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testDeleteDatasetRawDataHappyPath() -
testDeleteDatasetRawDataWithoutConfirmIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testDeleteDatasetRawDataWithoutConfirmIs400() -
testDeleteDatasetProcessedDataHappyPath
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testDeleteDatasetProcessedDataHappyPath() -
testDeleteDatasetProcessedDataWithoutConfirmIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testDeleteDatasetProcessedDataWithoutConfirmIs400() -
testGetDatasetPipelineStatusReturnsAllStepsWithNotRunOrNotApplicable
@Test @WithMockUser public void testGetDatasetPipelineStatusReturnsAllStepsWithNotRunOrNotApplicable() -
testGetDatasetPipelineStatusMissingValueApplicableForTwoColor
@Test @WithMockUser public void testGetDatasetPipelineStatusMissingValueApplicableForTwoColor() -
testGetDatasetPipelineStatusPreprocessOk
@Test @WithMockUser public void testGetDatasetPipelineStatusPreprocessOk() -
testGetDatasetPipelineStatusCarriesFilterAttrition
@Test @WithMockUser public void testGetDatasetPipelineStatusCarriesFilterAttrition() throws ExceptionThe sample-correlation step carries the filter attrition recorded when the matrix was computed.The JSON is produced the way
AuditedAspectproduces it -- through the polymorphicAuditEventPayloadtype, so the@typediscriminator is present. That is the half that breaks silently: a reader whose mapper has not been told about the subtype cannot resolve the type id, and the endpoint would answer with the step present and the attrition quietly absent.- Throws:
Exception
-
testGetDatasetPipelineStatusCarriesProcessedVectorDetails
@Test @WithMockUser public void testGetDatasetPipelineStatusCarriesProcessedVectorDetails() throws ExceptionThe preprocess step carries what the processed-vector creation did to the data. This payload has been written since the Phase C audit migration and nothing served it, which is why the diagnostics footer had nothing to show for "normalization".It also guards the reader against a trap the single-payload version had: the status read walks the latest event of every step, so a mapper registered for only one payload record cannot resolve any of the others and turns each into a parse failure -- silently, since the step still reports.
- Throws:
Exception
-
testGetDatasetPipelineStatusWithoutFilterAttritionStillReportsTheStep
@Test @WithMockUser public void testGetDatasetPipelineStatusWithoutFilterAttritionStillReportsTheStep()Every correlation matrix computed before the payload existed carries no payload at all. That reads as absent, not as an error and not as "nothing was filtered" -- and the rest of the step still reports. -
testGetDatasetPipelineStatusSurvivesAnUnparseableAuditPayload
@Test @WithMockUser public void testGetDatasetPipelineStatusSurvivesAnUnparseableAuditPayload()A payload that does not parse must not take the status read down with it. -
testGetDatasetPipelineStatusPcaFailed
@Test @WithMockUser public void testGetDatasetPipelineStatusPcaFailed() -
testGetDatasetPipelineStatusPicksLatestEvent
@Test @WithMockUser public void testGetDatasetPipelineStatusPicksLatestEvent() -
testPipelineStatusDeaGoesStaleWhenTheDesignChangedAfterIt
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPipelineStatusDeaGoesStaleWhenTheDesignChangedAfterIt()A DEA that succeeded and then had the design change under it is still there, and its own event still says it succeeded — but it no longer describes the design it was computed from. `stale` is that state.🛑 Not `notRun`: the analysis was not deleted. A design change that invalidates an analysis deletes it, and the step then reads `notRun` with nothing left to describe. This is the surviving case.
-
testPipelineStatusDeaStaysOkWhenTheDesignChangedBeforeIt
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPipelineStatusDeaStaysOkWhenTheDesignChangedBeforeIt()A design change BEFORE the analysis is the normal order: the DEA already reflects it. -
testPipelineStatusEveryDataDerivedStepGoesStaleOnADesignChange
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPipelineStatusEveryDataDerivedStepGoesStaleOnADesignChange()Every step but batchInfo is computed from the samples and the design, so a design change makes all of them stale -- not just the DEA. Paul, 2026-08-27: *"everything you mention needs to be redone if sample-sets and experimental designs are changed, but we want to do it later"*. This test used to assert the opposite; DesignChangeEvent is emitted only for a real change (the no-op branch suppresses it), so a relabel does not reach here. -
testPipelineStatusGoesStaleWhenAnOutlierWasFlaggedAfterTheRun
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPipelineStatusGoesStaleWhenAnOutlierWasFlaggedAfterTheRun()Flagging an outlier changes the analyzed sample set and no longer reprocesses the dataset inline, so the SampleRemovalEvent it records is what marks the computed results as owed a re-run. -
testPipelineStatusBatchInfoIsNeverStale
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPipelineStatusBatchInfoIsNeverStale()batchInfo comes from scan dates and file headers. Neither a design edit nor an outlier flag touches those, so it must not be swept up when the rule widened to every other step. -
testPipelineStatusAFailedDeaStaysFailedAfterADesignChange
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testPipelineStatusAFailedDeaStaysFailedAfterADesignChange()A DEA that FAILED and was then followed by a design change stays `failed`. Re-running is the move either way, and `stale` would hide that the last attempt did not succeed. -
testStaleStepsListsADatasetWhoseRunPredatesTheDesignChange
@Test @WithMockUser public void testStaleStepsListsADatasetWhoseRunPredatesTheDesignChange()The corpus-wide read of the samestalerule the per-dataset route applies: which datasets owe pipeline work, and which steps. A PCA that succeeded and then had the design change under it is the canonical row. -
testStaleStepsOmitsADatasetWhoseRunPostdatesTheDesignChange
@Test @WithMockUser public void testStaleStepsOmitsADatasetWhoseRunPostdatesTheDesignChange()The disconfirming half: the same two events in the other order. The design change happened BEFORE the run, so the run already reflects it and nothing is owed. Without this the route could report every dataset that has ever had a design change and still look right. -
testStaleStepsDropsACandidateTheCallerCannotRead
@Test @WithMockUser public void testStaleStepsDropsACandidateTheCallerCannotRead()The candidate ids come from an audit query that has no ACL clause, so what keeps a private dataset out of the response is the ACL-filtered load it is passed through. Here the load returns nothing for the candidate: the row must not appear, and the audit fan-out must not even be attempted for it. -
testGetDatasetPipelineStatusIncludesConvenienceFields
@Test @WithMockUser public void testGetDatasetPipelineStatusIncludesConvenienceFields() -
testGetDatasetPipelineStatusAdminSeesCurationNote
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetPipelineStatusAdminSeesCurationNote() -
testGetDatasetPipelineStatusNonAdminDoesNotSeeCurationNote
@Test @WithMockUser public void testGetDatasetPipelineStatusNonAdminDoesNotSeeCurationNote() -
testGetDatasetPipelineStatusWithUnknownDatasetIs404
@Test @WithMockUser public void testGetDatasetPipelineStatusWithUnknownDatasetIs404() -
testGetDatasetGeeq
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetGeeq() -
testGetDatasetGeeqWithoutEvent
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetGeeqWithoutEvent() -
testGetDatasetGeeqWhenNotComputedIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetGeeqWhenNotComputedIs404() -
testGetDatasetGeeqWithUnknownDatasetIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testGetDatasetGeeqWithUnknownDatasetIs404() -
testRecomputeDatasetGeeqDefaultModeIsAll
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRecomputeDatasetGeeqDefaultModeIsAll() -
testRecomputeDatasetGeeqWithSpecificMode
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRecomputeDatasetGeeqWithSpecificMode() -
testRecomputeDatasetGeeqIncludesLastComputed
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRecomputeDatasetGeeqIncludesLastComputed() -
testRecomputeDatasetGeeqWithUnknownDatasetIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRecomputeDatasetGeeqWithUnknownDatasetIs404() -
testRunDatasetPreprocess
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetPreprocess() -
testRunDatasetPreprocessWithUnknownDatasetIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetPreprocessWithUnknownDatasetIs404() -
testRunDatasetDiagnosticsSetsFlag
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetDiagnosticsSetsFlag() -
testRunDatasetBatchInformationFetch
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetBatchInformationFetch() -
testRunDatasetGeeqWithModeBatch
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetGeeqWithModeBatch() -
testRunDatasetSwitchPlatformWithTargetShortName
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetSwitchPlatformWithTargetShortName() -
testRunDatasetSwitchPlatformWithoutBodyAutoDetectsMerged
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetSwitchPlatformWithoutBodyAutoDetectsMerged() -
testRunDatasetSwitchPlatformUnknownShortNameIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetSwitchPlatformUnknownShortNameIs400() -
testRunDatasetDifferentialAnalysisWithoutBodyUsesAllNonBatchFactors
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetDifferentialAnalysisWithoutBodyUsesAllNonBatchFactors() -
testRunDatasetDifferentialAnalysisWithoutDesignIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetDifferentialAnalysisWithoutDesignIs400() -
testRunDatasetDifferentialAnalysisRejectsUnknownFactorId
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetDifferentialAnalysisRejectsUnknownFactorId() -
testRunDatasetDifferentialAnalysisRejectsSubsetFactorInFactorIds
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRunDatasetDifferentialAnalysisRejectsSubsetFactorInFactorIds() -
testRedoDatasetDifferentialAnalysis
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRedoDatasetDifferentialAnalysis() -
testRedoDatasetDifferentialAnalysisWithUnknownAnalysisIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRedoDatasetDifferentialAnalysisWithUnknownAnalysisIs404() -
testRemoveDatasetDifferentialAnalysis
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRemoveDatasetDifferentialAnalysis() -
testRemoveDatasetDifferentialAnalysisWithUnknownAnalysisIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRemoveDatasetDifferentialAnalysisWithUnknownAnalysisIs404() -
testGetDatasetDesignJson
@Test public void testGetDatasetDesignJson() -
testGetDatasetDesignDefaultIsJson
@Test public void testGetDatasetDesignDefaultIsJson() -
testGetDatasetDesignJsonReturnsNotFoundWhenDesignMissing
@Test public void testGetDatasetDesignJsonReturnsNotFoundWhenDesignMissing() -
testGetDatasetDesignJsonWithUnknownDatasetIs404
@Test public void testGetDatasetDesignJsonWithUnknownDatasetIs404() -
testReplaceDatasetDesignHappyPath
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testReplaceDatasetDesignHappyPath() -
testReplaceDatasetDesignReturnsBadRequestOnBlockers
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testReplaceDatasetDesignReturnsBadRequestOnBlockers() -
testReplaceDatasetDesignReturns409WhenForceRequired
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testReplaceDatasetDesignReturns409WhenForceRequired() -
testReplaceDatasetDesignWithForceAppliesEvenWithCascade
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testReplaceDatasetDesignWithForceAppliesEvenWithCascade() -
testReplaceDatasetDesignWithEmptyBodyIs400
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testReplaceDatasetDesignWithEmptyBodyIs400() -
testReplaceDatasetDesignWithUnknownDatasetIs404
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testReplaceDatasetDesignWithUnknownDatasetIs404() -
testGetDatasetSampleCorrelation
@Test public void testGetDatasetSampleCorrelation() -
testGetDatasetSampleCorrelationWhenNoneIs404
@Test public void testGetDatasetSampleCorrelationWhenNoneIs404() -
testSampleCorrelationValuesAreRoundedAndKeepNaN
@Test public void testSampleCorrelationValuesAreRoundedAndKeepNaN()Three decimals, and a masked cell stays masked.Math.roundon NaN yields 0.0, which would read as "these two samples do not correlate" — the opposite of "we do not know". -
testSampleCorrelationSaysWhichMatrixItReturned
@Test public void testSampleCorrelationSaysWhichMatrixItReturned()The default is `best`, and the response says which of the two it actually got. -
testSampleCorrelationBestFallsBackToFullAndSaysSo
@Test public void testSampleCorrelationBestFallsBackToFullAndSaysSo()No regressed matrix: `best` falls back to the full one and says so, rather than implying regression. -
testSampleCorrelationFullIsServedOnRequestEvenWhenRegressedExists
@Test public void testSampleCorrelationFullIsServedOnRequestEvenWhenRegressedExists()`matrix=full` takes the full one even when a regressed one exists — that is the whole point. -
testSampleCorrelationRegressedDoesNotFallBack
@Test public void testSampleCorrelationRegressedDoesNotFallBack()`matrix=regressed` never silently substitutes the full one; a dataset without one 404s. -
testGetDatasetSampleCorrelationIsWithheldForSingleCell
@Test public void testGetDatasetSampleCorrelationIsWithheldForSingleCell()🛑 Temporary, pending the per-cell-type design. A single-cell dataset's correlation matrix is the pseudo-bulk grid (samples × cell types), so its correlations are taken across cell types and the median-correlation outlier rule reads a rare cell type as an outlier. The assertion that matters is that the matrix is never even loaded: returning it and letting the caller decide is exactly what we are stopping, and a 404 reached after the load would still cost ~100 MB. -
testGetDatasetSampleCorrelationStillServedWhenNotSingleCell
@Test public void testGetDatasetSampleCorrelationStillServedWhenNotSingleCell()The same dataset, not single-cell, still serves the matrix — the gate is the flag, not the route. -
testGetDatasetSampleCorrelationWhenDatasetMissingIs404
@Test public void testGetDatasetSampleCorrelationWhenDatasetMissingIs404() -
testGetDatasetMeanVariance
@Test public void testGetDatasetMeanVariance() -
testGetDatasetMeanVarianceWhenNoneIs404
@Test public void testGetDatasetMeanVarianceWhenNoneIs404() -
testGetDatasetMeanVarianceWhenDatasetMissingIs404
@Test public void testGetDatasetMeanVarianceWhenDatasetMissingIs404() -
testGetDatasetMeanVarianceIsRoundedAndLeavesTheEntityArrayAlone
@Test public void testGetDatasetMeanVarianceIsRoundedAndLeavesTheEntityArrayAlone()Mean-variance is the heaviest diagnostics payload — one mean and one variance per probe. It has no precision opt-out.The second half of the assertion is the part that matters:
MeanVarianceRelation.getMeans()hands back the loaded entity's own array, so rounding in place would corrupt it for every later reader. -
testGetDatasetMeanVarianceKeepsOnePointPerCellAndStaysIndexParallel
@Test public void testGetDatasetMeanVarianceKeepsOnePointPerCellAndStaysIndexParallel()Two points that land in the same grid cell collapse to the first of them, and the pairing survives: the fixture makes every variance twice its mean, so an entry dropped from one array and not the other would re-pair every point after it and break that relation. -
testGetDatasetMeanVarianceLeavesWellSeparatedPointsAlone
@Test public void testGetDatasetMeanVarianceLeavesWellSeparatedPointsAlone()Points that each get their own cell come back untouched — thinning only ever removes a point that would be drawn on top of one already sent. -
testGetDatasetMeanVarianceDropsNonFinitePoints
@Test public void testGetDatasetMeanVarianceDropsNonFinitePoints()A point whose mean or variance is not finite has no position on the scatter, so it is dropped rather than keyed into the grid. Both arrays lose it together. -
testGetDatasetMeanVarianceCollapsesAHeavilyOverplottedDataset
@Test public void testGetDatasetMeanVarianceCollapsesAHeavilyOverplottedDataset()The size guard: 30,000 points drawn from ten distinct coordinates come back as ten. This is the shape of the real saving — eid 1 sends 22,283 points of which 93% land where one has already been painted. -
testGetDatasetMeanVarianceIsThinnedOnTheWire
@Test public void testGetDatasetMeanVarianceIsThinnedOnTheWire()The thinned arrays are what actually goes on the wire. -
testGetDatasetSvdIsRoundedAndLeavesTheResultArraysAlone
@Test public void testGetDatasetSvdIsRoundedAndLeavesTheResultArraysAlone()SVD loadings serialize at a mean of ~20 characters each and nothing consumes the digits below it, so this route rounds with no opt-out.SVDResult.getVariances()/getVMatrix().getRawMatrix()are the result's own arrays, hence the non-mutation half. -
testGetDatasetsExpressionLevelsForGeneIsRoundedUnlessPreciseIsAsked
@Test public void testGetDatasetsExpressionLevelsForGeneIsRoundedUnlessPreciseIsAsked()Expression levels are a data-download surface, so rounding here is the default andprecise=trueis the opt-out. Both branches are asserted on the wire. -
testListDatasetMetadataFilesEmpty
- Throws:
IOException
-
testListDatasetMetadataFilesReturnsAvailable
- Throws:
IOException
-
testGetDatasetMetadataFileWithUnknownTypeIs400
@Test public void testGetDatasetMetadataFileWithUnknownTypeIs400() -
testGetDatasetMetadataFileForDirectoryTypeIs400
@Test public void testGetDatasetMetadataFileForDirectoryTypeIs400() -
testGetDatasetMetadataFileWhenAbsentIs404
- Throws:
IOException
-
testGetDatasetMetadataFileServesPayload
- Throws:
IOException
-
testGetDatasetMetadataFileWhenDatasetMissingIs404
@Test public void testGetDatasetMetadataFileWhenDatasetMissingIs404() -
testGetDatasetSvdLoadings
@Test public void testGetDatasetSvdLoadings() -
testGetDatasetSvdLoadingsWhenNoSvdIs404
@Test public void testGetDatasetSvdLoadingsWhenNoSvdIs404() -
testGetDatasetSvdLoadingsWithMissingPcIs400
@Test public void testGetDatasetSvdLoadingsWithMissingPcIs400() -
testGetDatasetSvdLoadingsWithPcZeroIs400
@Test public void testGetDatasetSvdLoadingsWithPcZeroIs400() -
testGetDatasetSvdLoadingsWithTopOverCapIs400
@Test public void testGetDatasetSvdLoadingsWithTopOverCapIs400() -
testGetDatasetSvdLoadingsWithUnknownDirectionIs400
@Test public void testGetDatasetSvdLoadingsWithUnknownDirectionIs400() -
testGetDatasetAllPublications
@Test public void testGetDatasetAllPublications() -
testUpdateDatasetPublications
@Test @WithMockUser public void testUpdateDatasetPublications() -
testUpdateDatasetPublicationsCarriesEvidence
@Test @WithMockUser public void testUpdateDatasetPublicationsCarriesEvidence() -
testUpdateDatasetPublicationsRecordsRejection
@Test @WithMockUser public void testUpdateDatasetPublicationsRecordsRejection() -
testUpdateDatasetPublicationsOmittingRejectedLeavesThemUntouched
@Test @WithMockUser public void testUpdateDatasetPublicationsOmittingRejectedLeavesThemUntouched() -
testUpdateDatasetPublicationsEmptyRejectedListStillClearsThem
@Test @WithMockUser public void testUpdateDatasetPublicationsEmptyRejectedListStillClearsThem() -
testUpdateDatasetPublicationsRejectedByHigherAuthorityIsConflict
@Test @WithMockUser public void testUpdateDatasetPublicationsRejectedByHigherAuthorityIsConflict() -
testUpdateDatasetPublicationsRejectsUnknownSource
@Test @WithMockUser public void testUpdateDatasetPublicationsRejectsUnknownSource() -
testUpdateDatasetPublicationsByDoi
@Test @WithMockUser public void testUpdateDatasetPublicationsByDoi() -
testUpdateDatasetPublicationsRejectsBothPubMedIdAndDoi
@Test @WithMockUser public void testUpdateDatasetPublicationsRejectsBothPubMedIdAndDoi() -
testUpdateDatasetPublicationsClearAll
@Test @WithMockUser public void testUpdateDatasetPublicationsClearAll() -
testUpdateDatasetPublicationsMissingOtherList
@Test @WithMockUser public void testUpdateDatasetPublicationsMissingOtherList() -
testUpdateDatasetPublicationsRejectsBlankPubMedId
@Test @WithMockUser public void testUpdateDatasetPublicationsRejectsBlankPubMedId() -
testUpdateDatasetPublicationsUnresolvablePubMedIdIsBadRequest
@Test @WithMockUser public void testUpdateDatasetPublicationsUnresolvablePubMedIdIsBadRequest() -
testUpdateDatasetBasics
@Test @WithMockUser public void testUpdateDatasetBasics() -
testUpdateDatasetBasicsDescriptionOnly
@Test @WithMockUser public void testUpdateDatasetBasicsDescriptionOnly() -
testUpdateDatasetBasicsRejectsEmptyBody
@Test @WithMockUser public void testUpdateDatasetBasicsRejectsEmptyBody() -
testUpdateDatasetBasicsRejectsBlankName
@Test @WithMockUser public void testUpdateDatasetBasicsRejectsBlankName() -
testCommitCurationPublications
@Test @WithMockUser public void testCommitCurationPublications() -
testCommitCurationBasics
@Test @WithMockUser public void testCommitCurationBasics() -
testCommitCurationTagWithStatementsReachesTheCommitAsAStatement
@Test @WithMockUser public void testCommitCurationTagWithStatementsReachesTheCommitAsAStatement()A tag carrying nested statements must reach the commit as aStatement, not a bare Characteristic. cab reported it accepted and silently dropped: preflight said created=1, the commit returned 200 and minted a snapshot, and the stored row had PREDICATE NULL (GSE104324, CHARACTERISTIC 56965512, discriminator NULL = a plain Characteristic). -
testCommitCurationTagKeepsBothStatements
@Test @WithMockUser public void testCommitCurationTagKeepsBothStatements()A tag carrying two statements must store BOTH pairs. The converter used to readitems[0]and discard the rest, so a two-statement tag silently became a one-statement tag — cab lost six statements across five tags that way (2026-08-31) and only found it by reading SECOND_PREDICATE in the database, because a tag that lost a claim looks exactly like one that never made it. -
testCommitCurationRejectsAThirdStatementOnATag
@Test @WithMockUser public void testCommitCurationRejectsAThirdStatementOnATag()A row holds two pairs, so a third claim is refused rather than silently dropped. -
testCommitCurationRejectsAnUndeclaredUngroundedTag
@Test @WithMockUser public void testCommitCurationRejectsAnUndeclaredUngroundedTag()An experiment tag with novalue.uriis refused unless the caller says the free text is deliberate. Paul's ruling, 2026-09-01: reject, but give the client a way to declare intent — an ungrounded tag is usually an oversight, and after the fact it cannot be told apart from a grounding somebody meant to do and forgot. -
testCommitCurationAcceptsADeclaredFreeTextTagThatIsHooked
@Test @WithMockUser public void testCommitCurationAcceptsADeclaredFreeTextTagThatIsHooked()Declaring it accepts the same tag — the gate is the declaration, not the absence of a URI. -
testCommitCurationRejectsADeclaredFreeTextTagWithNoHook
@Test @WithMockUser public void testCommitCurationRejectsADeclaredFreeTextTagWithNoHook()A declared free-text tag that hangs off nothing is refused. Paul's ruling, 2026-09-06: a free-text experiment tag must give the reader grounded context for the text, so the declaration alone stopped being enough —freeTextIntendedsays the missing URI was deliberate, and says nothing about whether the annotation reaches the ontology anywhere. -
testCommitCurationRejectsAFreeTextTagWhoseHookObjectIsUngrounded
@Test @WithMockUser public void testCommitCurationRejectsAFreeTextTagWhoseHookObjectIsUngrounded()🛑 The hook has to reach a TERM. A predicate paired with an object that is itself free text leaves the tag exactly as unreachable as it was, so it is refused — this is the case that separates "has a statement" from "is grounded", and the check is worthless if it passes. -
testCommitCurationAcceptsAFreeTextTagHookedOnItsSecondPair
@Test @WithMockUser public void testCommitCurationAcceptsAFreeTextTagHookedOnItsSecondPair()The hook may sit in the row's SECOND predicate/object pair — a tag whose first claim is ungrounded and whose second reaches a term is still hooked. -
testCommitCurationRejectsUnknownSection
@Test @WithMockUser public void testCommitCurationRejectsUnknownSection() -
testCommitRefusesOmittedEvidenceCodeOnAStatementThatHasOne
@Test @WithMockUser public void testCommitRefusesOmittedEvidenceCodeOnAStatementThatHasOne()OmittingevidenceCodeon a statement that has one is refused.Same hazard as
supportingEvidencebeside it and initially shipped without the guard: an omission cleared a storedICand reportedupdated: 1, indistinguishable from the edit the caller meant. The clear is spelled""here rather than an absent key, because the field is a String and Jackson cannot tell a missing key from an explicit null. -
testCommitRefusesOmittedSecondPairOnAStatementThatHasOne
@Test @WithMockUser public void testCommitRefusesOmittedSecondPairOnAStatementThatHasOne()🛑 Omitting the second predicate-object pair on agemmaIdstatement that HAS one is refused (Paul, 2026-09-11: "Guard it like evidence. It's too dangerous."). applyStatementFields writes the pair from the payload unconditionally, so before this the omission cleared it and the commit reportedupdated: 1-- the same report a successful edit gets. 9,338 production rows carry a pair. -
testCommitAcceptsASecondPairEchoedExplicitly
@Test @WithMockUser public void testCommitAcceptsASecondPairEchoedExplicitly()Echoing the pair explicitly keeps it, which is what a client that read the row sends back. -
testCommitAcceptsASecondPairEchoedAsTheFlattenedSecondItem
@Test @WithMockUser public void testCommitAcceptsASecondPairEchoedAsTheFlattenedSecondItem()The FLATTENED spelling counts as echoing it too — a secondstatements[]item under the samegemmaId, which is how Gemma SERIALIZES a compound statement and therefore how uib sends it back. -
testCommitClearsASecondPairWhenClearSecondPairIsSent
@Test @WithMockUser public void testCommitClearsASecondPairWhenClearSecondPairIsSent()Dropping a pair stays possible, but only when asked for in so many words. -
testPreflightAppliesTheSameSecondPairRule
@Test @WithMockUser public void testPreflightAppliesTheSameSecondPairRule()The preflight is the call a client makes FIRST, so it has to accept every document the commit would —clearSecondPairincluded — and refuse the ones it would refuse. Both routes go throughdoCommitCuration, so this pins the sharing rather than trusting it (uib asked, 2026-09-11). -
testCommitRefusesClearSecondPairTogetherWithAPair
@Test @WithMockUser public void testCommitRefusesClearSecondPairTogetherWithAPair()Asking to drop it and sending it are contradictory, so neither wins silently. -
testCommitAllowsOmittingTheSecondPairWhenTheStoredRowHasNone
@Test @WithMockUser public void testCommitAllowsOmittingTheSecondPairWhenTheStoredRowHasNone()No false positive: a statement with no stored pair may omit it, which is every ordinary statement. -
testCommitRefusesOmittedEvidenceOnAStatementThatHasSome
@Test @WithMockUser public void testCommitRefusesOmittedEvidenceOnAStatementThatHasSome()Omitting evidence on a row that HAS evidence is refused; sending[]clears it deliberately.The design section is full-record replacement, so an absent key clears — harmless on a row holding none, silent destruction on one that does. Absent and
[]are distinguishable because the field is a JsonNode, so the deliberate clear stays available while the accidental one is refused. -
testCommitCurationRefusesAGemmaIdStatementWithNoSubject
@Test @WithMockUser public void testCommitCurationRefusesAGemmaIdStatementWithNoSubject()A partial statement item is refused rather than applied.A
gemmaIdstatement is updated in place from the fields it carries, so an item sending only one field writes null over the rest. On 2026-09-05 an item carrying onlysupportingEvidenceblanked a live statement's subject, subjectUri and category, dropped the annotation out of/annotations, and still reportedupdated: 1— indistinguishable from success. -
testCommitCurationDesignPreflightIsGivenTheDeferredBindings
@Test @WithMockUser public void testCommitCurationDesignPreflightIsGivenTheDeferredBindings()The preflight has to be handed the PLAN, not only the design payload. A sample bound to a factor value the commit creates lives nowhere else: the design VO names factor values by id and the new one has none until the first apply pass makes it, so the binding waits inDesignCommitPlan.pendingAssignments. A report built without it counts no changed biomaterials for a create whose bindings do land. -
testCommitCurationDesignCreatesFactor
@Test @WithMockUser public void testCommitCurationDesignCreatesFactor() -
testCommitCurationDesignDeletesByDeletedIds
@Test @WithMockUser public void testCommitCurationDesignDeletesByDeletedIds() -
testCommitCarriesForwardUnmentionedFactorValuesAndStatements
@Test @WithMockUser public void testCommitCarriesForwardUnmentionedFactorValuesAndStatements()Factor values and statements the document does not mention reach the service unchanged. -
testCommitRefusesOmittedEvidenceOnAFactorThatHasSome
@Test @WithMockUser public void testCommitRefusesOmittedEvidenceOnAFactorThatHasSome()AgemmaIdfactor item that omits evidence the factor HAS is refused, not applied as a clear. -
testCommitRefusesOmittedEvidenceOnAFactorValueThatHasSome
@Test @WithMockUser public void testCommitRefusesOmittedEvidenceOnAFactorValueThatHasSome()The same rule one level down: a factor value item that omits the value's evidence is refused. -
testCommitRefusesFactorValueDeletedIdThatIsNotOnThatFactor
@Test public void testCommitRefusesFactorValueDeletedIdThatIsNotOnThatFactor()A factor valuedeletedIdsentry that is not a value of that factor is refused. -
testCommitCurationDesignNullSamplesLeavesAssignmentUntouched
@Test @WithMockUser public void testCommitCurationDesignNullSamplesLeavesAssignmentUntouched() -
testCommitCurationDesignEmptySamplesClearsAssignment
@Test @WithMockUser public void testCommitCurationDesignEmptySamplesClearsAssignment() -
testCommitCurationDesignForceGateIs409
@Test @WithMockUser public void testCommitCurationDesignForceGateIs409() -
testPreflightCurationDesignIsDryRun
@Test @WithMockUser public void testPreflightCurationDesignIsDryRun() -
testCommitCurationTags
@Test @WithMockUser public void testCommitCurationTags() -
testCommitAnswers400WhenADeletedIdNamesNothingOnTheDataset
@Test @WithMockUser public void testCommitAnswers400WhenADeletedIdNamesNothingOnTheDataset()A delete naming nothing on the dataset is a malformed body: 400, like the design section, not a 409. -
testCommitRejectsDecoratedTagKeepMarker
@Test @WithMockUser public void testCommitRejectsDecoratedTagKeepMarker()A tags item bearing a gemmaId is a keep-marker: the section is add/delete only, so the mapper reads the id and nothing else. Decorating one used to be a 200 for an edit that never happened — a client that setsupportingEvidenceon an existing tag was told it had, and had not — so any other field is now a 400. Every offending field is named in one response: a caller told about them one at a time strips its payload one round trip at a time, and this fires mid-campaign. -
testPreflightRejectsDecoratedTagKeepMarker
@Test @WithMockUser public void testPreflightRejectsDecoratedTagKeepMarker()The preflight shares the mapper, so it refuses exactly what the commit refuses. A dry run that accepted a payload the commit rejects would stop being a rehearsal. -
testCommitRejectsDecoratedKeepMarkersInEverySectionAtOnce
@Test @WithMockUser public void testCommitRejectsDecoratedKeepMarkersInEverySectionAtOnce()sampleCharacteristics has the same add/delete-only shape and discarded decoration the same way, so it gets the same refusal —bioassayShortNameincluded, which on a keep-marker reads like "move this characteristic to that sample" and does nothing. Both sections report in one response, the way term violations do. -
testRestoreOfASnapshotWithSurvivingTagsIsNotRefusedAsDecorated
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRestoreOfASnapshotWithSurvivingTagsIsNotRefusedAsDecorated()A restore replays a SNAPSHOT, and a snapshot records the content of every row — including the rows the restore only has to leave alone. The reconciliation strips that content off the items that keep their id, so the document the restore builds for itself is legal under the keep-marker rule the commit enforces. Nothing is lost: a decorated keep-marker's content was never applied.Guards the one way this rule could break Gemma's own writes rather than a client's.
-
testRestoreRecreatesAFreeTextTagWithNoHook
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRestoreRecreatesAFreeTextTagWithNoHook()A restore re-creates a tag its snapshot holds, and the free-text experiment-tag checks do not apply to it (Paul's ruling, 2026-09-13). uib measured the refusal on gemma2: snapshot 2116 of dataset 2706, onCBA/J x C57Bl/6J, which has no URI and no hook. The same tag onPUT /curationis still refused —testCommitCurationRejectsADeclaredFreeTextTagWithNoHook(). -
testRestoreClearsASecondPairAddedAfterTheSnapshot
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRestoreClearsASecondPairAddedAfterTheSnapshot()cab, 2026-09-13: 15 of 21 restore points on gemma2 answered 400, each on a statement a commit had given a second pair after the snapshot was taken. A snapshot that records pairs is the target state, so the live pair is cleared (Paul's ruling, 2026-09-13). -
testRestoreOfASnapshotFromBeforePairsWereRecordedKeepsTheLivePair
@Test @WithMockUser(authorities="GROUP_ADMIN") public void testRestoreOfASnapshotFromBeforePairsWereRecordedKeepsTheLivePair()A snapshot captured before snapshots recorded pairs cannot say whether the statement had one, so the restore keeps the live pair rather than clearing what the snapshot never saw (Paul's ruling, 2026-09-13). Together with the test above, this separates the two eras: clearing always fails this one, keeping always fails that one. -
testCommitCurationTagCarriesSupportingEvidence
@Test @WithMockUser public void testCommitCurationTagCarriesSupportingEvidence()The tag'ssupportingEvidencereaches the Characteristic handed to the service. The section had no coverage at all for this field — the only evidence guard was on design statements — so a mapper that accepted it and built a Characteristic without it would have been invisible here. -
testCommitCurationTagCarriesEvidenceCode
@Test @WithMockUser public void testCommitCurationTagCarriesEvidenceCode()A stated evidence code reaches the Characteristic instead of being left to the add path's default. -
testCommitCurationTagWithoutEvidenceCodeLeavesItUnset
@Test @WithMockUser public void testCommitCurationTagWithoutEvidenceCodeLeavesItUnset()Omitting the field leaves the Characteristic's code null, which is what hands the row toExpressionExperimentWriteServiceImpl#addCharacteristic'sICfallback — the code every tag written through this route has carried. The guard is that the mapper stamps NOTHING of its own: a server that picked a code here (from the caller's identity, say) would put a value nobody chose on the row. -
testCommitCurationRejectsUnknownEvidenceCode
@Test @WithMockUser public void testCommitCurationRejectsUnknownEvidenceCode()An unknown code is a 400, not a silent drop. Dropping it would leave the row on the server default while the caller believed it had set one — the failure this field exists to end. -
testPreflightRejectsUnknownEvidenceCode
@Test @WithMockUser public void testPreflightRejectsUnknownEvidenceCode()The preflight enforces the same gate, so a client catches a bad code on the dry run. -
testCommitCurationDesignStatementCarriesEvidenceCode
@Test @WithMockUser public void testCommitCurationDesignStatementCarriesEvidenceCode()A design factor-value statement carries its own code, normalized to the enum name. Sent lowercase here: the apply compares the proposed code against the stored uppercase one, so an un-normalized"iea"would read as a change on every re-send. -
testCommitCurationRejectsUnknownEvidenceCodeOnADesignStatement
@Test @WithMockUser public void testCommitCurationRejectsUnknownEvidenceCodeOnADesignStatement()A bad code inside the design tree is rejected too, located in the design tree. -
testCommitCurationSampleCharacteristics
@Test @WithMockUser public void testCommitCurationSampleCharacteristics() -
testCommitCurationSampleCharacteristicsUnknownSampleIs400
@Test @WithMockUser public void testCommitCurationSampleCharacteristicsUnknownSampleIs400() -
testCommitCurationDetailsNote
@Test @WithMockUser public void testCommitCurationDetailsNote() -
testCommitCurationDetailsFlagsAre400
@Test @WithMockUser public void testCommitCurationDetailsFlagsAre400() -
testPreflightCurationIsDryRun
@Test @WithMockUser public void testPreflightCurationIsDryRun() -
testCommitCurationStaleBaselineIs409
@Test @WithMockUser public void testCommitCurationStaleBaselineIs409() -
testGetDatasetsPipelineStatusReturnsAnEntryPerDatasetAlongsideTheSingleRoute
@Test @WithMockUser public void testGetDatasetsPipelineStatusReturnsAnEntryPerDatasetAlongsideTheSingleRoute()The bulk route lives at the LITERAL `/datasets/pipelineStatus` with ids in a query param. `/datasets/{datasets}/pipelineStatus` would be the same JAX-RS template as the single-dataset route — a path parameter's name does not distinguish it — so this test also pins that the two coexist rather than shadowing one another. -
testPipelineStatusCarriesTheEffectiveTriageVerdictAndJudgeKind
@Test @WithMockUser public void testPipelineStatusCarriesTheEffectiveTriageVerdictAndJudgeKind() -
testPipelineStatusTriageIsNullWhenNothingHasBeenTriaged
@Test @WithMockUser public void testPipelineStatusTriageIsNullWhenNothingHasBeenTriaged()Nothing triaged leaves both fields null rather than defaulting — that is how a caller tells "not triaged" from "triaged Fine", which a boolean could not express. -
testTriageDoesNotTouchTheCuratorNeedsAttentionFlag
@Test @WithMockUser public void testTriageDoesNotTouchTheCuratorNeedsAttentionFlag()needsAttention is the pre-agent curator flag and must NOT move with triage: the two are separate signals, and collapsing them was the thing this design turned down. -
testPlatformsOriginalRoutesToTheOriginalPlatforms
@Test @WithMockUser public void testPlatformsOriginalRoutesToTheOriginalPlatforms()`?original=true` must actually route. A declared parameter that is quietly ignored is the failure mode uib caught on element search — the caller sees a 200 and a plausible body, and cannot tell that the question they asked was dropped. -
testPlatformsOriginalIsEmptyRatherThanEchoingTheCurrentPlatform
@Test @WithMockUser public void testPlatformsOriginalIsEmptyRatherThanEchoingTheCurrentPlatform()An unswitched dataset answers with an empty list, never with its current platform. -
testGetDatasetSourceMetadata
@Test public void testGetDatasetSourceMetadata()The document is served as an object, not as the string it is stored as. A string would make every consumer parse it themselves, and the envelope around it is already JSON. -
testGetDatasetSourceMetadataWhenNoneHasBeenHarvested
@Test public void testGetDatasetSourceMetadataWhenNoneHasBeenHarvested()🛑 Nothing harvested is the normal state for most of the corpus, not an error. A 404 here would be indistinguishable from a dataset that does not exist, and would make an ordinary experiment look broken in the curation UI. -
testGetDatasetSourceMetadataForAnUnknownDataset
@Test public void testGetDatasetSourceMetadataForAnUnknownDataset()A dataset that does not exist is still a 404, which is what the null case must not look like. -
testSubsetWithNoFactorValuesYieldsAnEmptyListNotANullPointer
@Test public void testSubsetWithNoFactorValuesYieldsAnEmptyListNotANullPointer()🛑 A subset with no factor values is normal, not an error. Single-cell subsets are cut from a cell-level characteristic and never carry one, so the map lookup feeding the VO misses and hands over null — which made /subSetGroups a 500 on exactly those datasets (44580 failed, factor-cut 38390 succeeded). -
testCommitCurationWritesASecondPairOnANewStatement
@Test @WithMockUser public void testCommitCurationWritesASecondPairOnANewStatement()A NEW statement can carry a second predicate-object pair.Until
StatementCommitgained the fields there was no spelling for this. The flattened form — twostatements[]entries sharing one id — is re-joined byunflattenStatements, but that keys on a non-null id, so an id-less row passed through as two separate single-clause statements. cab measured 9,031 production rows carrying a pair no client could create (2026-09-08). -
testCommitCurationRefusesHalfASecondPair
@Test @WithMockUser public void testCommitCurationRefusesHalfASecondPair()Half a pair is not a claim: a dangling predicate would sit on a production row with nothing to render it. -
testCommitCurationRefusesHalfAFirstPair
@Test @WithMockUser public void testCommitCurationRefusesHalfAFirstPair()The first pair is held to the same rule. Six factor-value statements reached production with a predicate and no object. -
testCommitCurationRefusesHalfAPairOnATagStatement
@Test @WithMockUser public void testCommitCurationRefusesHalfAPairOnATagStatement()A tag's statement goes through the same guard as a factor value's. -
testCommitCurationTagAcceptsAnExplicitSecondPair
@Test @WithMockUser public void testCommitCurationTagAcceptsAnExplicitSecondPair()A tag's second pair can be spelled explicitly too, which is the only way to state one on a tag whose statement is new. The two-item form keeps working; using both at once is refused rather than merged. -
testCommitCurationRefusesBothSpellingsOfATagsSecondPair
@Test @WithMockUser public void testCommitCurationRefusesBothSpellingsOfATagsSecondPair()Both spellings of the same pair on one tag is ambiguous, so it is a 400 rather than a silent winner.
-