diff --git a/.chronus/changes/feat-cross-language-spector-tests-2026-4-29-13-38-16.md b/.chronus/changes/feat-cross-language-spector-tests-2026-4-29-13-38-16.md new file mode 100644 index 00000000000..a7855ad925c --- /dev/null +++ b/.chronus/changes/feat-cross-language-spector-tests-2026-4-29-13-38-16.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/http-specs" +--- + +add test for discriminator model without subtypes and query params with `$` prefixes \ No newline at end of file diff --git a/packages/http-specs/spec-summary.md b/packages/http-specs/spec-summary.md index c623a91cc95..4e14a769856 100644 --- a/packages/http-specs/spec-summary.md +++ b/packages/http-specs/spec-summary.md @@ -1455,6 +1455,22 @@ Expected request body: { "name": "foo" } ``` +### Parameters_BodyRoot_nested + +- Endpoint: `post /parameters/body-root/nested` + +Test case for a `@bodyRoot` parameter nested inside a wrapper model. + +Emitters must resolve the accessor path through the wrapper (e.g. +`body.bodyRootParameters`) rather than referencing the property name +directly. + +Expected request body: + +```json +{ "category": "widget", "linkType": "hard", "wasSuccessful": true } +``` + ### Parameters_CollectionFormat_Header_csv - Endpoint: `get /parameters/collection-format/header/csv` @@ -1534,6 +1550,18 @@ Second request path: Expect to handle a constant value for query and mock api returns nothing +### Parameters_Query_Prefix_dollarSign + +- Endpoint: `get /parameters/query/prefix/dollarSign` + +Send a request with a dollar-sign prefixed`$filter` query parameter. + +Expected query parameter: + +- `$filter` = "status eq 'active'" + +Expected response status code: 204 + ### Parameters_Spread_Alias_spreadAsRequestBody - Endpoint: `put /parameters/spread/alias/request-body` @@ -6226,6 +6254,18 @@ Expected response body: { "wingspan": 1, "kind": "sparrow" } ``` +### Type_Model_Inheritance_SingleDiscriminator_getNoSubtypesModel + +- Endpoint: `get /type/model/inheritance/single-discriminator/no-subtypes/model` + +Generate and receive a discriminated model that has no defined subtypes. +The base model declares a discriminator but no models extend it. +Expected response body: + +```json +{ "kind": "salmon", "size": 10 } +``` + ### Type_Model_Inheritance_SingleDiscriminator_getRecursiveModel - Endpoint: `get /type/model/inheritance/single-discriminator/recursivemodel` @@ -6278,6 +6318,17 @@ Expected input body: { "wingspan": 1, "kind": "sparrow" } ``` +### Type_Model_Inheritance_SingleDiscriminator_putNoSubtypesModel + +- Endpoint: `put /type/model/inheritance/single-discriminator/no-subtypes/model` + +Send a discriminated model that has no defined subtypes. +Expected input body: + +```json +{ "kind": "salmon", "size": 10 } +``` + ### Type_Model_Inheritance_SingleDiscriminator_putRecursiveModel - Endpoint: `put /type/model/inheritance/single-discriminator/recursivemodel` diff --git a/packages/http-specs/specs/parameters/body-root/main.tsp b/packages/http-specs/specs/parameters/body-root/main.tsp new file mode 100644 index 00000000000..032160573cc --- /dev/null +++ b/packages/http-specs/specs/parameters/body-root/main.tsp @@ -0,0 +1,36 @@ +import "@typespec/http"; +import "@typespec/spector"; + +using Http; +using Spector; + +@doc("Test for @bodyRoot parameter patterns.") +@scenarioService("/parameters/body-root") +namespace Parameters.BodyRoot; + +model BodyRootModel { + category?: string; + linkType?: string; + wasSuccessful?: boolean; +} + +@scenario +@scenarioDoc(""" + Test case for a \`@bodyRoot\` parameter nested inside a wrapper model. + + Emitters must resolve the accessor path through the wrapper (e.g. + \`body.bodyRootParameters\`) rather than referencing the property name + directly. + + Expected request body: + \`\`\`json + { "category": "widget", "linkType": "hard", "wasSuccessful": true } + \`\`\` + """) +@route("/nested") +@post +op nested( + body: { + @bodyRoot bodyRootParameters: BodyRootModel; + }, +): NoContentResponse; diff --git a/packages/http-specs/specs/parameters/body-root/mockapi.ts b/packages/http-specs/specs/parameters/body-root/mockapi.ts new file mode 100644 index 00000000000..b808fb0cc0e --- /dev/null +++ b/packages/http-specs/specs/parameters/body-root/mockapi.ts @@ -0,0 +1,19 @@ +import { json, passOnSuccess, ScenarioMockApi } from "@typespec/spec-api"; + +export const Scenarios: Record = {}; + +Scenarios.Parameters_BodyRoot_nested = passOnSuccess({ + uri: "/parameters/body-root/nested", + method: "post", + request: { + body: json({ + category: "widget", + linkType: "hard", + wasSuccessful: true, + }), + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +}); diff --git a/packages/http-specs/specs/parameters/query/main.tsp b/packages/http-specs/specs/parameters/query/main.tsp index da74b81fcbe..3c759a23fdf 100644 --- a/packages/http-specs/specs/parameters/query/main.tsp +++ b/packages/http-specs/specs/parameters/query/main.tsp @@ -20,3 +20,23 @@ interface Constant { queryParam: "constantValue", ): void; } + +@doc("Prefixed query parameter verification") +@route("/prefix") +interface Prefix { + @scenario + @scenarioDoc(""" + Send a request with a dollar-sign prefixed`$filter` query parameter. + + Expected query parameter: + - `$filter` = "status eq 'active'" + + Expected response status code: 204 + """) + @route("/dollarSign") + @get + dollarSign( + @query("$filter") + filter: string, + ): void; +} diff --git a/packages/http-specs/specs/parameters/query/mockapi.ts b/packages/http-specs/specs/parameters/query/mockapi.ts index 262bb6cde41..5d63f3c743f 100644 --- a/packages/http-specs/specs/parameters/query/mockapi.ts +++ b/packages/http-specs/specs/parameters/query/mockapi.ts @@ -13,3 +13,15 @@ Scenarios.Parameters_Query_Constant_post = passOnSuccess({ }, kind: "MockApiDefinition", }); + +Scenarios.Parameters_Query_Prefix_dollarSign = passOnSuccess({ + uri: "/parameters/query/prefix/dollar-sign", + method: "get", + request: { + query: { $filter: "status eq 'active'" }, + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +}); diff --git a/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp b/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp index 0a396d731ad..519b9576535 100644 --- a/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp +++ b/packages/http-specs/specs/type/model/inheritance/single-discriminator/main.tsp @@ -49,6 +49,13 @@ model TRex extends Dinosaur { kind: "t-rex"; } +@doc("A discriminated model with no defined subtypes. The discriminator is declared but no models extend it.") +@discriminator("kind") +model Fish { + kind: string; + size: int32; +} + @scenario @route("/model") @scenarioDoc(""" @@ -170,3 +177,28 @@ op getWrongDiscriminator(): Bird; """) @get op getLegacyModel(): Dinosaur; + +@scenario +@route("/no-subtypes/model") +@scenarioDoc(""" + Generate and receive a discriminated model that has no defined subtypes. + The base model declares a discriminator but no models extend it. + Expected response body: + ```json + {"kind": "salmon", "size": 10} + ``` + """) +@get +op getNoSubtypesModel(): Fish; + +@scenario +@route("/no-subtypes/model") +@scenarioDoc(""" + Send a discriminated model that has no defined subtypes. + Expected input body: + ```json + {"kind": "salmon", "size": 10} + ``` + """) +@put +op putNoSubtypesModel(@body input: Fish): NoContentResponse; diff --git a/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts b/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts index f8adcf9c0f5..f4547522583 100644 --- a/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts +++ b/packages/http-specs/specs/type/model/inheritance/single-discriminator/mockapi.ts @@ -101,3 +101,31 @@ Scenarios.Type_Model_Inheritance_SingleDiscriminator_getLegacyModel = passOnSucc }, kind: "MockApiDefinition", }); + +const noSubtypesBody = { + kind: "salmon", + size: 10, +}; + +Scenarios.Type_Model_Inheritance_SingleDiscriminator_getNoSubtypesModel = passOnSuccess({ + uri: "/type/model/inheritance/single-discriminator/no-subtypes/model", + method: "get", + request: {}, + response: { + status: 200, + body: json(noSubtypesBody), + }, + kind: "MockApiDefinition", +}); + +Scenarios.Type_Model_Inheritance_SingleDiscriminator_putNoSubtypesModel = passOnSuccess({ + uri: "/type/model/inheritance/single-discriminator/no-subtypes/model", + method: "put", + request: { + body: json(noSubtypesBody), + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +});