From b40d857b4a20f5003caf88bcf1c1a9421879ef68 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 May 2026 13:00:16 -0400 Subject: [PATCH 1/6] feat: add dollar-sign prefixed query parameter Spector tests Add DollarSign interface to parameters/query with scenarios for $filter, $top/$skip, and $orderby query parameters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../specs/parameters/query/main.tsp | 56 +++++++++++++++++++ .../specs/parameters/query/mockapi.ts | 36 ++++++++++++ 2 files changed, 92 insertions(+) diff --git a/packages/http-specs/specs/parameters/query/main.tsp b/packages/http-specs/specs/parameters/query/main.tsp index da74b81fcbe..fc2674daa61 100644 --- a/packages/http-specs/specs/parameters/query/main.tsp +++ b/packages/http-specs/specs/parameters/query/main.tsp @@ -20,3 +20,59 @@ interface Constant { queryParam: "constantValue", ): void; } + +@doc("Dollar-sign prefixed query parameter verification") +@route("/dollar-sign") +interface DollarSign { + @scenario + @scenarioDoc(""" + Send a request with a `$filter` query parameter. + + Expected query parameter: + - `$filter` = "status eq 'active'" + + Expected response status code: 204 + """) + @route("/filter") + @get + filter( + @query("$filter") + filter: string, + ): void; + + @scenario + @scenarioDoc(""" + Send a request with `$top` and `$skip` query parameters. + + Expected query parameters: + - `$top` = 10 + - `$skip` = 5 + + Expected response status code: 204 + """) + @route("/top-and-skip") + @get + topAndSkip( + @query("$top") + top: int32, + + @query("$skip") + skip: int32, + ): void; + + @scenario + @scenarioDoc(""" + Send a request with a `$orderby` query parameter. + + Expected query parameter: + - `$orderby` = "name asc" + + Expected response status code: 204 + """) + @route("/orderby") + @get + orderby( + @query("$orderby") + orderby: string, + ): void; +} diff --git a/packages/http-specs/specs/parameters/query/mockapi.ts b/packages/http-specs/specs/parameters/query/mockapi.ts index 262bb6cde41..515792058e2 100644 --- a/packages/http-specs/specs/parameters/query/mockapi.ts +++ b/packages/http-specs/specs/parameters/query/mockapi.ts @@ -13,3 +13,39 @@ Scenarios.Parameters_Query_Constant_post = passOnSuccess({ }, kind: "MockApiDefinition", }); + +Scenarios.Parameters_Query_DollarSign_filter = passOnSuccess({ + uri: "/parameters/query/dollar-sign/filter", + method: "get", + request: { + query: { $filter: "status eq 'active'" }, + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +}); + +Scenarios.Parameters_Query_DollarSign_topAndSkip = passOnSuccess({ + uri: "/parameters/query/dollar-sign/top-and-skip", + method: "get", + request: { + query: { $top: "10", $skip: "5" }, + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +}); + +Scenarios.Parameters_Query_DollarSign_orderby = passOnSuccess({ + uri: "/parameters/query/dollar-sign/orderby", + method: "get", + request: { + query: { $orderby: "name asc" }, + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +}); From 33a2d3351bb60f152a083d1ac0c5da38e1a84091 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 May 2026 13:30:08 -0400 Subject: [PATCH 2/6] feat: add discriminated type without subtypes Spector test Add Fish model with @discriminator but no extending models to single-discriminator tests. Covers get and put scenarios. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../inheritance/single-discriminator/main.tsp | 32 +++++++++++++++++++ .../single-discriminator/mockapi.ts | 28 ++++++++++++++++ 2 files changed, 60 insertions(+) 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", +}); From 036e75be3bea0a6fab2717056851289ae70a09ae Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 May 2026 13:34:41 -0400 Subject: [PATCH 3/6] docs: regenerate spec-summary.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/http-specs/spec-summary.md | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/http-specs/spec-summary.md b/packages/http-specs/spec-summary.md index c623a91cc95..ab050d44620 100644 --- a/packages/http-specs/spec-summary.md +++ b/packages/http-specs/spec-summary.md @@ -1534,6 +1534,43 @@ Second request path: Expect to handle a constant value for query and mock api returns nothing +### Parameters_Query_DollarSign_filter + +- Endpoint: `get /parameters/query/dollar-sign/filter` + +Send a request with a `$filter` query parameter. + +Expected query parameter: + +- `$filter` = "status eq 'active'" + +Expected response status code: 204 + +### Parameters_Query_DollarSign_orderby + +- Endpoint: `get /parameters/query/dollar-sign/orderby` + +Send a request with a `$orderby` query parameter. + +Expected query parameter: + +- `$orderby` = "name asc" + +Expected response status code: 204 + +### Parameters_Query_DollarSign_topAndSkip + +- Endpoint: `get /parameters/query/dollar-sign/top-and-skip` + +Send a request with `$top` and `$skip` query parameters. + +Expected query parameters: + +- `$top` = 10 +- `$skip` = 5 + +Expected response status code: 204 + ### Parameters_Spread_Alias_spreadAsRequestBody - Endpoint: `put /parameters/spread/alias/request-body` @@ -6226,6 +6263,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 +6327,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` From a180e1b4cce04bfb58ae41acbd1bd904811b273e Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Fri, 29 May 2026 13:38:51 -0400 Subject: [PATCH 4/6] add changeset --- ...feat-cross-language-spector-tests-2026-4-29-13-38-16.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/feat-cross-language-spector-tests-2026-4-29-13-38-16.md 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 From c630cefc529d10233877cc9f356c2b43f562ef49 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 1 Jun 2026 11:16:47 -0400 Subject: [PATCH 5/6] feat(http-specs): add nested @bodyRoot parameter test Add parameters/body-root scenario for @bodyRoot nested inside a wrapper model, testing that emitters resolve the accessor path through the wrapper (e.g. body.param) rather than referencing the property name directly. Identified from: Azure/autorest.typescript#3961 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- packages/http-specs/spec-summary.md | 16 +++++++++ .../specs/parameters/body-root/main.tsp | 34 +++++++++++++++++++ .../specs/parameters/body-root/mockapi.ts | 19 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 packages/http-specs/specs/parameters/body-root/main.tsp create mode 100644 packages/http-specs/specs/parameters/body-root/mockapi.ts diff --git a/packages/http-specs/spec-summary.md b/packages/http-specs/spec-summary.md index ab050d44620..e282abcdd22 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` 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..7c63112f1e6 --- /dev/null +++ b/packages/http-specs/specs/parameters/body-root/main.tsp @@ -0,0 +1,34 @@ +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", +}); From ad40f3b5e2e99c654b63cd3005a99fd8e500ac60 Mon Sep 17 00:00:00 2001 From: iscai-msft Date: Mon, 1 Jun 2026 12:24:30 -0400 Subject: [PATCH 6/6] format --- .../http-specs/specs/parameters/body-root/main.tsp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/http-specs/specs/parameters/body-root/main.tsp b/packages/http-specs/specs/parameters/body-root/main.tsp index 7c63112f1e6..032160573cc 100644 --- a/packages/http-specs/specs/parameters/body-root/main.tsp +++ b/packages/http-specs/specs/parameters/body-root/main.tsp @@ -17,11 +17,11 @@ model BodyRootModel { @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 } @@ -29,6 +29,8 @@ model BodyRootModel { """) @route("/nested") @post -op nested(body: { - @bodyRoot bodyRootParameters: BodyRootModel; -}): NoContentResponse; +op nested( + body: { + @bodyRoot bodyRootParameters: BodyRootModel; + }, +): NoContentResponse;