Class OpenApiTest
- All Implemented Interfaces:
org.springframework.beans.factory.InitializingBean
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidvoidEverything under/adminand/internalmust be reachable as internal surface.voidThe bearer schemePOST /loginmints has to be declared, or a generated client cannot express it and every caller hand-rolls the Authorization header.voidA published operation must declare every parameter the endpoint behind it accepts.voidACostlyroute can be refused when its budget is full, so its 503 belongs in the spec.voidvoidvoidEvery operation must carry a tag, because a tag is how a generated client is organised.voidEvery parameter must be described.voiddescriptionis REQUIRED on a Response Object in OpenAPI 3.0, so a document with one missing is invalid.voidvoidvoidvoidvoidvoidvoidNo Hibernate entity may appear in the published specification.voidA non-JSON response body has to say what it is.voidA property cannot be both required and nullable without saying nothing.voidswagger-core disambiguates two resource methods of the same name by appending_1, and which of the pair gets the suffix depends on scan order — so the generated client's method name for one of them is not stable across builds.voidEvery response container reachable from a path must say what itsdatapayload holds.voidvoidGET /resultSets/{id}/pvalueDistributionserves the stored histogram.voidAcontentmap is keyed by media type, and a media type has noqparameter — that belongs in anAcceptheader.voidEvery 503 the API can schedule a retry for must declare the header carrying that schedule.voidvoidvoidA successful JSON response must say what its body is.voidvoidThe wire speaks one language, and it is camelCase.
-
Constructor Details
-
OpenApiTest
public OpenApiTest()
-
-
Method Details
-
afterPropertiesSet
public void afterPropertiesSet()- Specified by:
afterPropertiesSetin interfaceorg.springframework.beans.factory.InitializingBean
-
testExternalDocumentationUrlIsReplaced
@Test public void testExternalDocumentationUrlIsReplaced() -
testInfoMatchContentOfOpenApiConfiguration
- Throws:
IOException
-
testEnsureThatAllEndpointHaveADefaultGetResponseOrIsARedirection
@Test public void testEnsureThatAllEndpointHaveADefaultGetResponseOrIsARedirection() -
testEnsureThatAllErrorResponsesUseResponseErrorObjectWithJsonMediaType
@Test public void testEnsureThatAllErrorResponsesUseResponseErrorObjectWithJsonMediaType() -
testPvalueDistributionAdvertisesTheStoredHistogramContract
@Test public void testPvalueDistributionAdvertisesTheStoredHistogramContract()GET /resultSets/{id}/pvalueDistributionserves the stored histogram. The spec has to say so: the default column is the uncorrected one,correctedis not on the menu at all, andbinsis capped at the stored bin count instead of the old 1..1000 range. -
testGetDatasetsCategories
@Test public void testGetDatasetsCategories() -
testFilterArgSchemas
@Test public void testFilterArgSchemas() -
testSortArgSchemas
@Test public void testSortArgSchemas() -
testLimitArgIs5000ForGetDatasetsAnnotations
@Test public void testLimitArgIs5000ForGetDatasetsAnnotations() -
testSearchableProperties
@Test public void testSearchableProperties() -
testExamplesFromClasspath
- Throws:
IOException
-
testWireNamesAreCamelCaseEverywhere
@Test public void testWireNamesAreCamelCaseEverywhere()The wire speaks one language, and it is camelCase.Every property name in every published schema, and every query parameter, must be camelCase. This is the guard for
c4d2d4ceb9/8b2c8b09ff, which collapsed the two conventions the API used to serve at once. It exists because the first sweep greppedgemma-restand missedGeoScrapeDryRunCandidate— a response class that lives in gemma-core but is serialized by a gemma-rest resource. A half-done rename is worse than either end state, and the half left undone was the response a downstream screening script consumed. Reading the spec instead of the source catches that class of miss regardless of which module the class lives in.Enum VALUES are deliberately not checked:
expression_experiment,gemma_intakeand friends are data, not keys, and renaming them would stop matching what is stored. This walks property names and parameter names only. -
testPaginatedResponsesDeclareTheirPayloadType
@Test public void testPaginatedResponsesDeclareTheirPayloadType()Every response container reachable from a path must say what itsdatapayload holds.An endpoint that supports both offset and cursor pagination returns
Objectand declares its shapes with@Schema(oneOf = {...}). Java erases type arguments, so naming a raw generic container there —PaginatedResponseDataObject.class— produces a schema whosedatais an array of bareobject, and the payload type is gone. A client generated from that deserializes to untyped dictionaries and reads nulls for every field instead of failing, which is why this is worth a build failure rather than a code review.The fix is to name a bound subclass from
OpenApiResponseTypesinstead. Since the method's return type isObject, nothing but this test checks that the declared container is also the one the method actually builds. -
testCostlyEndpointsDocumentTheirCapacity503
@Test public void testCostlyEndpointsDocumentTheirCapacity503()ACostlyroute can be refused when its budget is full, so its 503 belongs in the spec.Eight of the fourteen did not document one. The check runs off the annotation rather than a hand-kept list so a route marked
@Costlylater cannot quietly skip it; the operation is matched by operationId, which is the method name unless@Operationoverrides it. -
testOperationIdsAreNotAutoDisambiguated
@Test public void testOperationIdsAreNotAutoDisambiguated()swagger-core disambiguates two resource methods of the same name by appending_1, and which of the pair gets the suffix depends on scan order — so the generated client's method name for one of them is not stable across builds. Name both explicitly with@Operation(operationId = ...)instead. -
testBearerAuthIsDeclared
@Test public void testBearerAuthIsDeclared()The bearer schemePOST /loginmints has to be declared, or a generated client cannot express it and every caller hand-rolls the Authorization header. -
testResponseMediaTypesCarryNoQualityParameter
@Test public void testResponseMediaTypesCarryNoQualityParameter()Acontentmap is keyed by media type, and a media type has noqparameter — that belongs in anAcceptheader. A key carrying one matches nothing a client sends.They arrive from the JAX-RS quality-of-source weight: a resource serving both JSON and TSV writes
@Produces(TEXT_TAB_SEPARATED_VALUES_UTF8 + ";qs=0.9")to keep JSON the default, and swagger-core copies that into the spec as; q=0.9. The weight has to stay in the annotation, soOpenApiFactorystrips it on the way out; this pins that it happened. -
testNonJsonResponseBodiesAreTyped
@Test public void testNonJsonResponseBodiesAreTyped()A non-JSON response body has to say what it is.@Contentwith amediaTypeand noschemadefaults totype: object, which for a TSV download says the body is a JSON object — three of them did. The right declaration is@Schema(type = "string"). -
testSuccessfulJsonResponsesDeclareASchema
@Test public void testSuccessfulJsonResponsesDeclareASchema()A successful JSON response must say what its body is.testEnsureThatAllEndpointHaveADefaultGetResponseOrIsARedirectionalready requires a content block, but a content block with a null schema satisfies it — which is what sevenResponse-returning GETs had. Returning rawjakarta.ws.rs.core.Responsetells swagger-core nothing about the entity, so unless the method declares an@ApiResponsewith a schema the spec publishes the media type and stops there, and a generated client hands back an untyped blob.The write surface was exempted behind a shrink-only list when this was written; the list is gone because it reached zero.
-
testEveryResponseHasADescription
@Test public void testEveryResponseHasADescription()descriptionis REQUIRED on a Response Object in OpenAPI 3.0, so a document with one missing is invalid. 184 responses had none and another 59 carried swagger-core's "default response" placeholder, which is the same thing wearing a hat.Two exclusions.
GET /genes/probes/refreshpublishes*/*with no content swagger-core can attach a description to — swagger-api/swagger-core#4693, whichtestEnsureThatAllEndpointHaveADefaultGetResponseOrIsARedirectionalready skips for the same reason. The/custompaths are Jersey test fixtures fromUnknownQueryParameterFilterTestand friends: they are on this module's test classpath, so the scan picks them up here, and they are not in the deployed spec. -
testEveryOperationIsTagged
@Test public void testEveryOperationIsTagged()Every operation must carry a tag, because a tag is how a generated client is organised.204 of 286 operations had none, so swagger-codegen put them all in one
DefaultApi— a 170-method class with no structure, which is what gemmapy generates against today. Tags split that into one class per resource.The
/custompaths are excluded for the same reason as intestEveryResponseHasADescription(): they are Jersey fixtures on this module's test classpath, not deployed routes. -
testNoHibernateEntityIsPublishedAsASchema
@Test public void testNoHibernateEntityIsPublishedAsASchema()No Hibernate entity may appear in the published specification.AdminPipelineWebServiceused to returnPipelineJobBatchdirectly. Its jobs are a lazy@OneToMany, and each job holds a lazy@ManyToOnetoExpressionExperiment— so following one field pulled in the experiment graph and 44 entity schemas with 466 properties arrived in the document through it and nothing else. Nothing had marked them@JsonIgnore, and nothing initialized them either, so the same field was a serialization hazard as well as specification noise.The rule is the general one rather than a list of those 44: an entity reaching the wire is a missing value object, whichever entity it is.
-
testEveryParameterHasADescription
@Test public void testEveryParameterHasADescription()Every parameter must be described. A parameter is what a caller actually sets, so an undescribed one is the gap they hit first — 412 of 652 had nothing.Only parameters the specification publishes are covered, which is the right scope: a
@Parameter(hidden = true)legacy alias never reaches a client and has nothing to document. The/custompaths are test fixtures, excluded as elsewhere. -
testUndescribedSchemaPropertiesDoNotIncrease
@Test public void testUndescribedSchemaPropertiesDoNotIncrease()- See Also:
-
testCollapsedRoutesDeclareEveryParameterTheyAccept
@Test public void testCollapsedRoutesDeclareEveryParameterTheyAccept()A published operation must declare every parameter the endpoint behind it accepts.Two JAX-RS methods can serve one (verb, path) and differ only in
@Produces—GET /datasets/{dataset}/designdoes, one for JSON and one for TSV. swagger-core keeps a single operation for the pair, and it takes the winner's signature as well as its annotation. The JSON method wins there and accepts only{dataset}, so?quantitationType=and?useProcessedQuantitationType=— read by the TSV branch at runtime — were absent from the spec with nothing to indicate it.The file guards the response half of that merge with a comment asking that the two
@Operationannotations stay identical. A comment is not enforcement, and it said nothing about parameters. This is the enforcement, and it is written as the general rule: whatever an endpoint reads, the operation says so, however many methods implement it.Parameters marked
@Parameter(hidden = true)are excluded — a deliberately unpublished legacy alias is not a gap. -
testPublicWireNamesAreNotRenamedOrRemoved
@Test public void testPublicWireNamesAreNotRenamedOrRemoved()- See Also:
-
testAdminAndInternalSurfaceIsMarkedInternal
@Test public void testAdminAndInternalSurfaceIsMarkedInternal()Everything under/adminand/internalmust be reachable as internal surface.gemmapy prunes Gemma's specification before generating, and its filter is "drop the non-GET operations" — which keeps every admin read, so its published SDK carries admin_api, admin_pipeline_api and observability_api. Dropping writes is not the same as dropping admin surface, and there was no marker to drop the right thing by.
There is one now: the Admin, Admin/Pipeline and Internal/Pipeline tags carry
x-internal: true, so a consumer collects the internal tag names from the document's owntagslist and drops the operations carrying them — no hardcoded tag names, no path prefixes. This pins that every operation under those prefixes is actually covered, so a new admin endpoint cannot slip out of the marked set.swagger-codegen's own
-Dapis=<Tag>is not a substitute. It selects API classes but generates no models unless-Dmodelsis also passed, and passing both selected no APIs at all when tried — and it cannot drop the schemas that only the excluded operations reach. -
testNothingIsBothRequiredAndNullable
@Test public void testNothingIsBothRequiredAndNullable()A property cannot be both required and nullable without saying nothing.requiredmeans the key is always present;nullablemeans its value may be null. Both together is legal OpenAPI and occasionally meaningful, but in this specification it is almost always a mistake — a field marked required because it "is always set" while also carrying@Nullablemeans one of the two annotations is wrong about the wire.The required declarations here are the ones that can be proved rather than judged: a Java primitive cannot be null and Jackson always serializes it, so the key is always present. That rule is deliberately not applied wholesale —
requiredis direction-agnostic in OpenAPI 3.0, andExperimentalDesignValueObjectand everything under it is accepted as a request body byPUT /datasets/{dataset}/design, where marking a field required would start demanding it from clients.
-