From 88c78577c74ab09b68d1509ea9f15f8683ecc7f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:47:42 +0000 Subject: [PATCH 1/7] Initial plan From 2bb1b95df78680c92307504e1135727d67ae8a6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:54:53 +0000 Subject: [PATCH 2/7] fix: remove broken prettier.format() call in xmlBodyEquals that caused UndefinedParserError Co-authored-by: weidongxu-microsoft <53292327+weidongxu-microsoft@users.noreply.github.com> --- packages/spec-api/src/request-validations.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/spec-api/src/request-validations.ts b/packages/spec-api/src/request-validations.ts index 223b439ce2a..3dc7f7260ab 100644 --- a/packages/spec-api/src/request-validations.ts +++ b/packages/spec-api/src/request-validations.ts @@ -1,5 +1,4 @@ import deepEqual from "deep-equal"; -import * as prettier from "prettier"; import { parseString } from "xml2js"; import { CollectionFormat, RequestExt } from "./types.js"; import { ValidationError } from "./validation-error.js"; @@ -66,11 +65,7 @@ export const validateXmlBodyEquals = (request: RequestExt, expectedBody: string) }); if (!deepEqual(actualParsedBody, expectedParsedBody, { strict: true })) { - throw new ValidationError( - BODY_NOT_EQUAL_ERROR_MESSAGE, - prettier.format(expectedBody), - prettier.format(request.body), - ); + throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedBody, request.rawBody); } }; From 42d17aa7ec5593e1af76771073d1029329556905 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 08:55:39 +0000 Subject: [PATCH 3/7] fix: update ModelWithDatetime rfc3339 value to match client output (no .000 milliseconds) Co-authored-by: weidongxu-microsoft <53292327+weidongxu-microsoft@users.noreply.github.com> --- packages/http-specs/specs/payload/xml/main.tsp | 2 +- packages/http-specs/specs/payload/xml/mockapi.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/http-specs/specs/payload/xml/main.tsp b/packages/http-specs/specs/payload/xml/main.tsp index d768dfb2d2d..a3ccdc8964b 100644 --- a/packages/http-specs/specs/payload/xml/main.tsp +++ b/packages/http-specs/specs/payload/xml/main.tsp @@ -364,7 +364,7 @@ interface ModelWithDatetimeValue ModelWithDatetime, """ - 2022-08-26T18:38:00.000Z + 2022-08-26T18:38:00Z Fri, 26 Aug 2022 14:38:00 GMT """ diff --git a/packages/http-specs/specs/payload/xml/mockapi.ts b/packages/http-specs/specs/payload/xml/mockapi.ts index 1560c5e84ba..c7fccb850b8 100644 --- a/packages/http-specs/specs/payload/xml/mockapi.ts +++ b/packages/http-specs/specs/payload/xml/mockapi.ts @@ -131,7 +131,7 @@ export const modelWithEnum = ` export const modelWithDatetime = ` - 2022-08-26T18:38:00.000Z + 2022-08-26T18:38:00Z Fri, 26 Aug 2022 14:38:00 GMT `; From 20a9187fe76b5df307de420a356ada2ea14a754d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:05:03 +0000 Subject: [PATCH 4/7] fix(spec-api): normalize RFC3339 UTC datetimes in XML body comparison Co-authored-by: weidongxu-microsoft <53292327+weidongxu-microsoft@users.noreply.github.com> --- .../http-specs/specs/payload/xml/main.tsp | 2 +- .../http-specs/specs/payload/xml/mockapi.ts | 2 +- packages/spec-api/src/request-validations.ts | 24 ++++++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/packages/http-specs/specs/payload/xml/main.tsp b/packages/http-specs/specs/payload/xml/main.tsp index a3ccdc8964b..d768dfb2d2d 100644 --- a/packages/http-specs/specs/payload/xml/main.tsp +++ b/packages/http-specs/specs/payload/xml/main.tsp @@ -364,7 +364,7 @@ interface ModelWithDatetimeValue ModelWithDatetime, """ - 2022-08-26T18:38:00Z + 2022-08-26T18:38:00.000Z Fri, 26 Aug 2022 14:38:00 GMT """ diff --git a/packages/http-specs/specs/payload/xml/mockapi.ts b/packages/http-specs/specs/payload/xml/mockapi.ts index c7fccb850b8..1560c5e84ba 100644 --- a/packages/http-specs/specs/payload/xml/mockapi.ts +++ b/packages/http-specs/specs/payload/xml/mockapi.ts @@ -131,7 +131,7 @@ export const modelWithEnum = ` export const modelWithDatetime = ` - 2022-08-26T18:38:00Z + 2022-08-26T18:38:00.000Z Fri, 26 Aug 2022 14:38:00 GMT `; diff --git a/packages/spec-api/src/request-validations.ts b/packages/spec-api/src/request-validations.ts index 3dc7f7260ab..c8ad094e3cf 100644 --- a/packages/spec-api/src/request-validations.ts +++ b/packages/spec-api/src/request-validations.ts @@ -64,11 +64,33 @@ export const validateXmlBodyEquals = (request: RequestExt, expectedBody: string) expectedParsedBody = result; }); - if (!deepEqual(actualParsedBody, expectedParsedBody, { strict: true })) { + if (!deepEqual(normalizeXmlDatetimes(actualParsedBody), normalizeXmlDatetimes(expectedParsedBody), { strict: true })) { throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedBody, request.rawBody); } }; +/** + * Recursively walk a parsed-XML object (from xml2js) and normalize RFC3339 UTC datetime strings + * so that equivalent forms compare equal (e.g. "2022-08-26T18:38:00.000Z" == "2022-08-26T18:38:00Z"). + */ +function normalizeXmlDatetimes(value: unknown): unknown { + if (typeof value === "string") { + // Strip trailing zero milliseconds from UTC RFC3339 timestamps: .000Z -> Z + return value.replace(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\.0+Z$/, "$1Z"); + } + if (Array.isArray(value)) { + return value.map(normalizeXmlDatetimes); + } + if (value !== null && typeof value === "object") { + const result: Record = {}; + for (const [k, v] of Object.entries(value as Record)) { + result[k] = normalizeXmlDatetimes(v); + } + return result; + } + return value; +} + export const validateCoercedDateBodyEquals = ( request: RequestExt, expectedBody: unknown | undefined, From 7e3ef5d29eb5f386a0033ee384948e83c242ebe9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:29:19 +0000 Subject: [PATCH 5/7] fix(http-specs): accept both RFC3339 UTC datetime forms in ModelWithDatetime XML test Co-authored-by: weidongxu-microsoft <53292327+weidongxu-microsoft@users.noreply.github.com> --- .../http-specs/specs/payload/xml/mockapi.ts | 54 ++++++++++++++++--- packages/spec-api/src/request-validations.ts | 24 +-------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/packages/http-specs/specs/payload/xml/mockapi.ts b/packages/http-specs/specs/payload/xml/mockapi.ts index 1560c5e84ba..de2d3f85834 100644 --- a/packages/http-specs/specs/payload/xml/mockapi.ts +++ b/packages/http-specs/specs/payload/xml/mockapi.ts @@ -136,6 +136,16 @@ export const modelWithDatetime = ` `; +// Some clients serialize UTC datetimes without trailing zero milliseconds. Both +// "2022-08-26T18:38:00.000Z" and "2022-08-26T18:38:00Z" are valid RFC3339 representations +// of the same instant; accept either form. +const modelWithDatetimeNoMs = ` + + 2022-08-26T18:38:00Z + Fri, 26 Aug 2022 14:38:00 GMT + +`; + function createServerTests(uri: string, data?: any) { return { get: passOnSuccess({ @@ -251,12 +261,44 @@ const Payload_Xml_ModelWithEnum = createServerTests("/payload/xml/modelWithEnum" Scenarios.Payload_Xml_ModelWithEnumValue_get = Payload_Xml_ModelWithEnum.get; Scenarios.Payload_Xml_ModelWithEnumValue_put = Payload_Xml_ModelWithEnum.put; -const Payload_Xml_ModelWithDatetime = createServerTests( - "/payload/xml/modelWithDatetime", - modelWithDatetime, -); -Scenarios.Payload_Xml_ModelWithDatetimeValue_get = Payload_Xml_ModelWithDatetime.get; -Scenarios.Payload_Xml_ModelWithDatetimeValue_put = Payload_Xml_ModelWithDatetime.put; +Scenarios.Payload_Xml_ModelWithDatetimeValue_get = passOnSuccess({ + uri: "/payload/xml/modelWithDatetime", + method: "get", + request: {}, + response: { + status: 200, + body: xml(modelWithDatetime), + }, + kind: "MockApiDefinition", +}); + +Scenarios.Payload_Xml_ModelWithDatetimeValue_put = passOnSuccess({ + uri: "/payload/xml/modelWithDatetime", + method: "put", + request: { + body: xml(modelWithDatetime), + }, + handler: (req: MockRequest) => { + req.expect.containsHeader("content-type", "application/xml"); + // Accept both "2022-08-26T18:38:00.000Z" and "2022-08-26T18:38:00Z" as equivalent UTC datetimes. + let firstError: unknown; + try { + req.expect.xmlBodyEquals(modelWithDatetime); + } catch (e) { + firstError = e; + } + if (firstError !== undefined) { + req.expect.xmlBodyEquals(modelWithDatetimeNoMs); + } + return { + status: 204, + }; + }, + response: { + status: 204, + }, + kind: "MockApiDefinition", +}); export const xmlError = ` diff --git a/packages/spec-api/src/request-validations.ts b/packages/spec-api/src/request-validations.ts index c8ad094e3cf..3dc7f7260ab 100644 --- a/packages/spec-api/src/request-validations.ts +++ b/packages/spec-api/src/request-validations.ts @@ -64,33 +64,11 @@ export const validateXmlBodyEquals = (request: RequestExt, expectedBody: string) expectedParsedBody = result; }); - if (!deepEqual(normalizeXmlDatetimes(actualParsedBody), normalizeXmlDatetimes(expectedParsedBody), { strict: true })) { + if (!deepEqual(actualParsedBody, expectedParsedBody, { strict: true })) { throw new ValidationError(BODY_NOT_EQUAL_ERROR_MESSAGE, expectedBody, request.rawBody); } }; -/** - * Recursively walk a parsed-XML object (from xml2js) and normalize RFC3339 UTC datetime strings - * so that equivalent forms compare equal (e.g. "2022-08-26T18:38:00.000Z" == "2022-08-26T18:38:00Z"). - */ -function normalizeXmlDatetimes(value: unknown): unknown { - if (typeof value === "string") { - // Strip trailing zero milliseconds from UTC RFC3339 timestamps: .000Z -> Z - return value.replace(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\.0+Z$/, "$1Z"); - } - if (Array.isArray(value)) { - return value.map(normalizeXmlDatetimes); - } - if (value !== null && typeof value === "object") { - const result: Record = {}; - for (const [k, v] of Object.entries(value as Record)) { - result[k] = normalizeXmlDatetimes(v); - } - return result; - } - return value; -} - export const validateCoercedDateBodyEquals = ( request: RequestExt, expectedBody: unknown | undefined, From 812d87334583563b26d70db76404080467761c1a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 11 Mar 2026 09:39:46 +0000 Subject: [PATCH 6/7] chore: add changelog for RFC3339 datetime fix Co-authored-by: weidongxu-microsoft <53292327+weidongxu-microsoft@users.noreply.github.com> --- .../copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md diff --git a/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md b/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md new file mode 100644 index 00000000000..549e7540ff9 --- /dev/null +++ b/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md @@ -0,0 +1,8 @@ +--- +changeKind: fix +packages: + - "@typespec/http-specs" + - "@typespec/spec-api" +--- + +Accept both `2022-08-26T18:38:00.000Z` and `2022-08-26T18:38:00Z` as valid RFC3339 UTC datetime forms in the `ModelWithDatetime` XML scenario \ No newline at end of file From 0f8a294c742fb386db421456f2e7c1dc041a0206 Mon Sep 17 00:00:00 2001 From: Weidong Xu Date: Wed, 11 Mar 2026 20:09:34 +0800 Subject: [PATCH 7/7] remove changelog by agent, add 2 by hand --- ...> copilot-fix-xml-test-case-error-2026-2-11-20-7-16.md} | 3 +-- .../copilot-fix-xml-test-case-error-2026-2-11-20-8-54.md | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) rename .chronus/changes/{copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md => copilot-fix-xml-test-case-error-2026-2-11-20-7-16.md} (83%) create mode 100644 .chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-8-54.md diff --git a/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md b/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-7-16.md similarity index 83% rename from .chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md rename to .chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-7-16.md index 549e7540ff9..37a44acbc51 100644 --- a/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-9-39-28.md +++ b/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-7-16.md @@ -2,7 +2,6 @@ changeKind: fix packages: - "@typespec/http-specs" - - "@typespec/spec-api" --- -Accept both `2022-08-26T18:38:00.000Z` and `2022-08-26T18:38:00Z` as valid RFC3339 UTC datetime forms in the `ModelWithDatetime` XML scenario \ No newline at end of file +Accept both `2022-08-26T18:38:00.000Z` and `2022-08-26T18:38:00Z` as valid RFC3339 UTC datetime forms in the `ModelWithDatetime` XML scenario. \ No newline at end of file diff --git a/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-8-54.md b/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-8-54.md new file mode 100644 index 00000000000..4446f699c26 --- /dev/null +++ b/.chronus/changes/copilot-fix-xml-test-case-error-2026-2-11-20-8-54.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/spec-api" +--- + +Remove prettier used for ValidationError message, in validateXmlBodyEquals. \ No newline at end of file