Class AnnotationsWebServiceTest

java.lang.Object
org.glassfish.jersey.test.JerseyTest
ubic.gemma.rest.util.BaseJerseyTest5
ubic.gemma.rest.AnnotationsWebServiceTest
All Implemented Interfaces:
org.springframework.beans.factory.Aware, org.springframework.context.ApplicationContextAware

@ContextConfiguration @TestExecutionListeners(value=org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener.class, mergeMode=MERGE_WITH_DEFAULTS) public class AnnotationsWebServiceTest extends BaseJerseyTest5
Author:
poirigui
  • Constructor Details

    • AnnotationsWebServiceTest

      public AnnotationsWebServiceTest()
  • Method Details

    • setUpMocks

      @BeforeEach public void setUpMocks()
    • resetMocks

      @AfterEach public void resetMocks()
    • testSearchTaxonDatasets

      @Test @WithMockUser public void testSearchTaxonDatasets() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testParents

      @Test public void testParents() throws TimeoutException
      Throws:
      TimeoutException
    • testParentsWhenTermIsNotFound

      @Test public void testParentsWhenTermIsNotFound() throws TimeoutException
      Throws:
      TimeoutException
    • testParentsWhenInferenceTimeout

      @Test public void testParentsWhenInferenceTimeout() throws TimeoutException
      Throws:
      TimeoutException
    • testChildren

      @Test public void testChildren() throws TimeoutException
      Throws:
      TimeoutException
    • testAddDatasetAnnotation

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotation()
    • testAddDatasetAnnotationDuplicateReturns409

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationDuplicateReturns409()
    • testAddDatasetAnnotationRejectsBlankCategory

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationRejectsBlankCategory()
    • testAddDatasetAnnotationRejectsBadEvidenceCode

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationRejectsBadEvidenceCode()
    • testAddDatasetAnnotationCreatesStatementWhenPredicateOrObjectSet

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationCreatesStatementWhenPredicateOrObjectSet()
    • testAddDatasetAnnotationCreatesCharacteristicWhenNoStatementFieldsSet

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationCreatesCharacteristicWhenNoStatementFieldsSet()
    • testAddDatasetAnnotationRejectsSecondPredicateWithoutFirst

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testAddDatasetAnnotationRejectsSecondPredicateWithoutFirst()
    • testRemoveDatasetAnnotation

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testRemoveDatasetAnnotation()
    • testRemoveDatasetAnnotationNotFound

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testRemoveDatasetAnnotationNotFound()
    • testReplaceDatasetAnnotationsIdempotentNoOp

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testReplaceDatasetAnnotationsIdempotentNoOp()
    • testReplaceDatasetAnnotationsAppliesDiff

      @Test @WithMockUser(authorities="GROUP_CURATOR") public void testReplaceDatasetAnnotationsAppliesDiff()
    • testParentsPopulatesUsageCount

      @Test public void testParentsPopulatesUsageCount() throws TimeoutException
      Throws:
      TimeoutException
    • testChildrenPopulatesUsageCount

      @Test public void testChildrenPopulatesUsageCount() throws TimeoutException
      Throws:
      TimeoutException
    • testParentsReportsZeroWhenNoExperimentsMatch

      @Test public void testParentsReportsZeroWhenNoExperimentsMatch() throws TimeoutException
      Throws:
      TimeoutException
    • testParentsSkipsCountLookupWhenAllTermsLackUris

      @Test public void testParentsSkipsCountLookupWhenAllTermsLackUris() throws TimeoutException
      Throws:
      TimeoutException
    • testParentsMarksWhetherTheTermWasReachedBySubClassOfAlone

      @Test public void testParentsMarksWhetherTheTermWasReachedBySubClassOfAlone() throws TimeoutException
      CHEBI's shape: vancomycin reaches glycopeptide by rdfs:subClassOf and antibacterial drug only by has_role, which Gemma follows as an additional property. Both land in one list, and viaSubClassOf is the only thing that tells them apart — a client that wants a compound's roles without its upper-ontology structure cannot recover the split from labels.
      Throws:
      TimeoutException
    • testParentsLeavesViaSubClassOfNullOnAUrilessTerm

      @Test public void testParentsLeavesViaSubClassOfNullOnAUrilessTerm() throws TimeoutException
      No URI, nothing to match against the subClassOf-only walk — unknown, not "reached by a role".
      Throws:
      TimeoutException
    • testChildrenMarksWhetherTheTermWasReachedBySubClassOfAlone

      @Test public void testChildrenMarksWhetherTheTermWasReachedBySubClassOfAlone() throws TimeoutException
      The children walk carries the same marker, on the same two-walk mechanism.
      Throws:
      TimeoutException
    • testSearchAnnotationsPopulatesUsageCount

      @Test public void testSearchAnnotationsPopulatesUsageCount() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSuppressedDesignationReportsWhatItRuledOut

      @Test public void testSuppressedDesignationReportsWhatItRuledOut() throws SearchException, TimeoutException
      A designation query that retrieves only near-matches must report WHICH terms it ruled out, not merely that nothing matched. An empty data array is indistinguishable from "the ontology wasn't loaded" or "this call never ran", and on its own it does not stop a downstream stage from proposing one of these very terms from its own index.
      Throws:
      SearchException
      TimeoutException
    • testExactLabelDoesNotSuppressNegativeEvidence

      @Test public void testExactLabelDoesNotSuppressNegativeEvidence() throws SearchException, TimeoutException
      exact_label filters the POSITIVE list; negativeEvidence reports the VERDICT. Passing both must still yield the verdict — the agents send exact_label=true on every call, so an interaction that swallows the signal means they never receive it at all, while appearing to have asked for it.
      Throws:
      SearchException
      TimeoutException
    • testNegativeEvidenceAbsentForDescriptiveQuery

      @Test public void testNegativeEvidenceAbsentForDescriptiveQuery() throws SearchException, TimeoutException
      The confident negative must not be claimed when identity matching never ran — a descriptive query keeps its near-matches, so absence of a match there says nothing.
      Throws:
      SearchException
      TimeoutException
    • testExactSynonymEarnsTheExactTier

      @Test public void testExactSynonymEarnsTheExactTier() throws Exception
      Reported by CAB 2026-08-11: `H1` is a declared exact synonym of EFO_0003042 ("H1-hESC", 18 corpus uses), but the relevance tiers read the hit's LABEL only, so on "h1-hesc" it scored as a prefix match and came back at rank 7 -- behind two CHEBI histamine-receptor ligands with no corpus use at all. A caller cannot act on that: the row it wants is indistinguishable from the noise it has to filter. An exact synonym is an exact match, and now earns the exact tier.
      Throws:
      Exception
    • testLexicalCatalogueHitIsWorthOneTierLessThanAnOntologyHit

      @Test public void testLexicalCatalogueHitIsWorthOneTierLessThanAnOntologyHit() throws Exception
      Throws:
      Exception
    • testLexicalCatalogueHitAlsoSinksBelowANonExactOntologyHit

      @Test public void testLexicalCatalogueHitAlsoSinksBelowANonExactOntologyHit() throws Exception
      Throws:
      Exception
    • testCategoryPromotionSurvivesTheRankingStrategy

      @Test public void testCategoryPromotionSurvivesTheRankingStrategy() throws Exception
      Throws:
      Exception
    • testExactMatchTierSurvivesTheRankingStrategy

      @Test public void testExactMatchTierSurvivesTheRankingStrategy() throws Exception
      The exact tier has the same problem the category promotion had: it is established on the candidate list and then thrown away by strategy.rank(), which re-sorts everything on its own score. Measured on frink 2026-08-16 before the fix, Myelopathy returned spinal cord injury above myelopathy — the latter matched on its PREFERRED LABEL and lost anyway, because the former carries far more corpus usage and composite weights usage. An exact match losing to a lexical neighbour is not a ranking preference, it is the ranking being wrong.
      Throws:
      Exception
    • testCategoryExclusionRemovesAndReportsOutOfCategoryHits

      @Test public void testCategoryExclusionRemovesAndReportsOutOfCategoryHits() throws Exception
      An excluded namespace leaves data but is REPORTED, not deleted — a gene symbol answered with the disease it causes is the measured failure, and an over-firing rule has to be visible rather than silent.
      Throws:
      Exception
    • testSearchAnnotationsBatchResolvesEachLabelIndependently

      @Test public void testSearchAnnotationsBatchResolvesEachLabelIndependently() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsBatchEmptyReturns400

      @Test public void testSearchAnnotationsBatchEmptyReturns400()
    • testSearchAnnotationsIncludeGenesFalseSkipsGeneFanout

      @Test public void testSearchAnnotationsIncludeGenesFalseSkipsGeneFanout() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testGetAnnotationCategories

      @Test public void testGetAnnotationCategories()
    • testGetAnnotationCategoriesEmpty

      @Test public void testGetAnnotationCategoriesEmpty()
    • testGetAnnotationPredicates

      @Test public void testGetAnnotationPredicates()
    • testGetAnnotationPredicatesEmpty

      @Test public void testGetAnnotationPredicatesEmpty()
    • testGetAnnotationTerm

      @Test public void testGetAnnotationTerm() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermIncludesSynonymsAndAlternativeIds

      @Test public void testGetAnnotationTermIncludesSynonymsAndAlternativeIds() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermWithholdsLiteratureCitationsUnlessAsked

      @Test public void testGetAnnotationTermWithholdsLiteratureCitationsUnlessAsked() throws TimeoutException
      🛑 Literature citations are withheld from dbXrefs unless asked for, and always counted.

      Measured on CHEBI_45783 imatinib: 63 cross-references, of which 51 are pubmed: and every identifier that names a record — cas, drugbank, drugcentral, kegg.drug — appears exactly once. The citations push the clickable ones off any bounded view, so uib was capping the list client-side and every other consumer would have written the same rule.

      Counted rather than dropped silently: a caller has to be able to tell "cites nothing" from "cites fifty-one things you did not ask for".

      Throws:
      TimeoutException
    • testGetAnnotationTermExposesReplacementForObsoleteTerm

      @Test public void testGetAnnotationTermExposesReplacementForObsoleteTerm() throws TimeoutException
      The EFO case that motivated the field (uib, 2026-08-16): EFO_0000408 obsolete_disease names MONDO_0000001 as its successor, and EFO's OWL carries a label for that class, so no second lookup is needed.
      Throws:
      TimeoutException
    • testGetAnnotationTermResolvesLiteralReplacementAndItsLabel

      @Test public void testGetAnnotationTermResolvesLiteralReplacementAndItsLabel() throws TimeoutException
      Same axiom written as a literal rather than an rdf:resource — the OBO→OWL conversions disagree on this, and reading only the resource form empties the field for whole ontologies. With no in-model label, the successor's label comes from resolving it through the service.
      Throws:
      TimeoutException
    • testGetAnnotationTermExpandsCurieValuedReplacement

      @Test public void testGetAnnotationTermExpandsCurieValuedReplacement() throws TimeoutException
      A CURIE-valued literal (CLO:0000457) is canonicalised, not dropped.
      Throws:
      TimeoutException
    • testGetAnnotationTermExposesConsiderCandidates

      @Test public void testGetAnnotationTermExposesConsiderCandidates() throws TimeoutException
      A term that was split rather than merged names candidates, not a replacement.
      Throws:
      TimeoutException
    • testGetAnnotationTermSkipsObsolescenceLookupForLiveTerm

      @Test public void testGetAnnotationTermSkipsObsolescenceLookupForLiveTerm() throws TimeoutException
      A live term is never probed for obsolescence axioms — it declares none, and probing would cost a successor lookup per call for a field that is always null.
      Throws:
      TimeoutException
    • testGetAnnotationTermReportsZeroWhenNoExperimentsMatch

      @Test public void testGetAnnotationTermReportsZeroWhenNoExperimentsMatch() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermSkipsCountLookupWhenTermHasNoUri

      @Test public void testGetAnnotationTermSkipsCountLookupWhenTermHasNoUri() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermNotFound

      @Test public void testGetAnnotationTermNotFound() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermWithBlankUriIs400

      @Test public void testGetAnnotationTermWithBlankUriIs400() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermWithMissingUriIs400

      @Test public void testGetAnnotationTermWithMissingUriIs400() throws TimeoutException
      Throws:
      TimeoutException
    • testGetAnnotationTermWhenLookupTimesOut

      @Test public void testGetAnnotationTermWhenLookupTimesOut() throws TimeoutException
      Throws:
      TimeoutException
    • testSearchAnnotationsEnrichesTopNWithDefinitionAndParents

      @Test public void testSearchAnnotationsEnrichesTopNWithDefinitionAndParents() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testGetAnnotationTermPopulatesParents

      @Test public void testGetAnnotationTermPopulatesParents() throws TimeoutException
      Throws:
      TimeoutException
    • testSearchAnnotationsDefaultLimitIs20

      @Test public void testSearchAnnotationsDefaultLimitIs20() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsRespectsExplicitLimit

      @Test public void testSearchAnnotationsRespectsExplicitLimit() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsLimitOver100Returns400

      @Test public void testSearchAnnotationsLimitOver100Returns400() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsLimitBelow1Returns400

      @Test public void testSearchAnnotationsLimitBelow1Returns400() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsAttributesPreferredLabelMatch

      @Test public void testSearchAnnotationsAttributesPreferredLabelMatch() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsAttributesLabelTokensForNonEqualPartialLabel

      @Test public void testSearchAnnotationsAttributesLabelTokensForNonEqualPartialLabel() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsAttributesSynonymTokensWhenLabelLacksTokens

      @Test public void testSearchAnnotationsAttributesSynonymTokensWhenLabelLacksTokens() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsAttributesLabelPrefixForTypeahead

      @Test public void testSearchAnnotationsAttributesLabelPrefixForTypeahead() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsSurfacesGeneByOfficialName

      @Test public void testSearchAnnotationsSurfacesGeneByOfficialName() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsSurfacesGeneByAlias

      @Test public void testSearchAnnotationsSurfacesGeneByAlias() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsDropsGeneFanoutWhenURIAlreadyInOntologyResults

      @Test public void testSearchAnnotationsDropsGeneFanoutWhenURIAlreadyInOntologyResults() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsDedupesGenesAcrossProbes

      @Test public void testSearchAnnotationsDedupesGenesAcrossProbes() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsExactOntologyLabelOutranksGeneAliasCollision

      @Test public void testSearchAnnotationsExactOntologyLabelOutranksGeneAliasCollision() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsPopulatesPriorCategoriesFromCorpusHistory

      @Test public void testSearchAnnotationsPopulatesPriorCategoriesFromCorpusHistory() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsHyphenInsensitiveMec2FindsCloCellLine

      @Test public void testSearchAnnotationsHyphenInsensitiveMec2FindsCloCellLine() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsHyphenInsensitiveMec2DashFindsEfoBareLabel

      @Test public void testSearchAnnotationsHyphenInsensitiveMec2DashFindsEfoBareLabel() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsExactLabelFilterRespectsHyphenAndCellSuffixCanonicalForm

      @Test public void testSearchAnnotationsExactLabelFilterRespectsHyphenAndCellSuffixCanonicalForm() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsCanonicaliseDoesNotRegressNonHyphenatedQueries

      @Test public void testSearchAnnotationsCanonicaliseDoesNotRegressNonHyphenatedQueries() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsSymbolMatchStillPrependsAboveOntologyHits

      @Test public void testSearchAnnotationsSymbolMatchStillPrependsAboveOntologyHits() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsAttributesSynonymMatch

      @Test public void testSearchAnnotationsAttributesSynonymMatch() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsLeavesEnrichmentNullForPostTopN

      @Test public void testSearchAnnotationsLeavesEnrichmentNullForPostTopN() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsAttributesAFreeTextRowFromItsOwnLabel

      @Test public void testSearchAnnotationsAttributesAFreeTextRowFromItsOwnLabel() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsUnknownRankReturns400

      @Test public void testSearchAnnotationsUnknownRankReturns400() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsReportsTheTallyItIsGiven

      @Test public void testSearchAnnotationsReportsTheTallyItIsGiven() throws SearchException, TimeoutException
      Counting one experiment once, however many annotation rows or levels carry the term, is now the database's job — countExperimentsByUris does it with a count(distinct ...) over the union of URI columns, so there is no per-class map left for this layer to collapse. The dedup itself is pinned by CharacteristicDaoTest#testCountExperimentsByUrisCountsAnExperimentOnceAcrossColumns, which exercises it against a real schema; what remains testable here is that the endpoint asks for exactly the candidate URIs and reports the tally it is handed, unmodified.
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsCanonicaliesInputOrderByUri

      @Test public void testSearchAnnotationsCanonicaliesInputOrderByUri() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsPrefixesFilterAppliesBeforeRanking

      @Test public void testSearchAnnotationsPrefixesFilterAppliesBeforeRanking() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsUpstreamReturns400WhenUrlUnset

      @Test public void testSearchAnnotationsUpstreamReturns400WhenUrlUnset() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsPrefixesFilterRespectsLimit

      @Test public void testSearchAnnotationsPrefixesFilterRespectsLimit() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsPrefersCloOverEfoForBareCellLineQuery

      @Test public void testSearchAnnotationsPrefersCloOverEfoForBareCellLineQuery() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsPrefersCloOverEfoForCellSuffixQuery

      @Test public void testSearchAnnotationsPrefersCloOverEfoForCellSuffixQuery() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testSearchAnnotationsCellLineTiebreakerDoesNotAffectNonCellLineQueries

      @Test public void testSearchAnnotationsCellLineTiebreakerDoesNotAffectNonCellLineQueries() throws SearchException, TimeoutException
      Throws:
      SearchException
      TimeoutException
    • testAddDatasetAnnotationCarriesSupportingEvidence

      @Test @WithMockUser public void testAddDatasetAnnotationCarriesSupportingEvidence()
    • testAddDatasetAnnotationWithoutEvidenceLeavesSupportingEvidenceNull

      @Test @WithMockUser public void testAddDatasetAnnotationWithoutEvidenceLeavesSupportingEvidenceNull()
    • testAddDatasetAnnotationCarriesSupportingEvidenceOnAStatement

      @Test @WithMockUser public void testAddDatasetAnnotationCarriesSupportingEvidenceOnAStatement()
    • testReplaceDatasetAnnotationsCarriesSupportingEvidence

      @Test @WithMockUser public void testReplaceDatasetAnnotationsCarriesSupportingEvidence()
    • testImpliesRefusesRefutedEvenWhenTheParameterIsSent

      @Test @WithMockUser public void testImpliesRefusesRefutedEvenWhenTheParameterIsSent()
      🛑 The gate reads /relations/implies. A refuted row arriving there would let the curation pipeline inhibit or license on a finding that says the opposite of what it appears to say — MGI reporting that a genotype does NOT model a disease, rendered as an assertion, because the predicate is stored assertively and the negation lives only in status.

      includeRefuted is exposed on /relations for auditability. This pins the asymmetry: the parameter must not reach the implies query even when a caller sends it. Since UnknownQueryParameterFilter landed the request is refused outright rather than answered with the parameter dropped, so the query is never built at all. Declaring includeRefuted on this route — whether directly or by moving both handlers onto a shared builder — turns this 400 back into a 200 and fails here.

    • testRelationsHonoursIncludeRefutedWhenAsked

      @Test @WithMockUser public void testRelationsHonoursIncludeRefutedWhenAsked()
      The other half: on /relations the parameter does reach the query, or it is decorative.
    • testRelationsPassesTheSubjectBreadthBarToTheQuery

      @Test @WithMockUser public void testRelationsPassesTheSubjectBreadthBarToTheQuery()
      A query parameter that never reaches the query is decorative and nothing fails — the same shape the two tests above pin for includeRefuted.
    • testRelationsAppliesTheSubjectBreadthBarByDefaultForADataset

      @Test @WithMockUser public void testRelationsAppliesTheSubjectBreadthBarByDefaultForADataset()
      The bar defaults ON for a dataset-seeded read, where the fan-out was measured.
    • testAnExplicitZeroTurnsTheSubjectBreadthBarOff

      @Test @WithMockUser public void testAnExplicitZeroTurnsTheSubjectBreadthBarOff()
      ...and a caller who asks for 0 gets 0. Without this the default could not be turned off, which is the difference between a default and a policy.
    • testASingleCellDatasetDropsCellTypeSubjects

      @Test @WithMockUser public void testASingleCellDatasetDropsCellTypeSubjects()
      On a single-cell dataset the cell types belong to the CELLS, so what they imply is not a fact about the experiment. Measured on GSE295459 (eid 58319): the forward walk returned 14 rows, and all 8 organism parts among them were a soma location or part-of of a cell-type subject — an antibiotic study described by brain anatomy. Paul ruled the cut belongs here rather than at render time, where the terms would still reach audit and export.
    • testABulkDatasetKeepsCellTypeSubjects

      @Test @WithMockUser public void testABulkDatasetKeepsCellTypeSubjects()
      🛑 The control that keeps the cut honest. A constant cell type on a BULK experiment IS an experiment-level property, and what it implies is worth having — so the filter keys on isSingleCell and never on the category alone.
    • testIncludeCellTypeSubjectsTurnsTheSingleCellCutOff

      @Test @WithMockUser public void testIncludeCellTypeSubjectsTurnsTheSingleCellCutOff()
      ...and a caller who asks for them gets them back. Without this the cut could not be turned off, and a suppression with no way to see what it removed is indistinguishable from an experiment that never carried the term — the next person asking why the cell type is missing would have nothing to read.
    • testATermSeededReadIsNeverCellTypeFiltered

      @Test @WithMockUser public void testATermSeededReadIsNeverCellTypeFiltered()
      A term-seeded read carries no dataset, so there is nothing to call single-cell and no cut.
    • testATermSeededReadIsNotBarredByDefault

      @Test @WithMockUser public void testATermSeededReadIsNotBarredByDefault()
      Positive control for the two above: a term-seeded read is NOT barred. A term card asking what is known about DMSO wants its role list; that is the answer there, not the noise.
    • testRelationsExcludesRefutedByDefault

      @Test @WithMockUser public void testRelationsExcludesRefutedByDefault()
      And it stays off unless asked, so exposing it changed no existing caller's results.
    • testCanonicalUrisAreQueryable

      @Test public void testCanonicalUrisAreQueryable()
      The canonicalization table has to be reachable by a client that resolves terms before asking Gemma — cab seeded a two-row local redirect table because ours was not queryable, and two authorities on one question is the thing this endpoint exists to prevent.
    • testCanonicalUrisAnswersForATwinTheCorpusNeverUsed

      @Test public void testCanonicalUrisAnswersForATwinTheCorpusNeverUsed()
      🛑 The regression guard for the whole point of this endpoint: a twin the CORPUS HAS NEVER USED must still be answered for.

      CLO_0001199 ('22RV1 cell') has zero annotations in Gemma; its twin CLO_0001200 ('22Rv1 cell') has 19. A table built from the terms the corpus uses cannot see the zero-usage member at all — and that is exactly the member cab's Tier-0 synonym table mints out of file order and asks us about. The first cut of this table was corpus-anchored, this row was missing, and following our own instruction to generate from it would have regressed the case that prompted it.

    • testCanonicalUrisCarryTheDecidingRule

      @Test public void testCanonicalUrisCarryTheDecidingRule()
      Every row says which rule decided it, so a consumer can tell an ontology-intrinsic answer (R3 xref, R4 definition) from one that rests on our curators' spelling habits (R5 usage).
    • testCanonicalUrisFilterByUri

      @Test public void testCanonicalUrisFilterByUri()
      Filtering by URI returns that one row, and an unmapped URI returns none rather than a guess.