From ebfc54e23030076ec92ba1d7ef7863deffc48e71 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 13:27:31 +0000 Subject: [PATCH 1/4] =?UTF-8?q?test:=20add=20direct=20unit=20tests=20for?= =?UTF-8?q?=20XmlDoc=20module=20(+32=20tests,=20357=E2=86=92389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add dedicated unit tests for the XmlDoc helper functions that were previously only exercised indirectly via full-schema compilation tests. New test modules in UtilsTests.fs: - BuildEnumDocTests: null/empty/single/multiple enum values, null node - CombineDescAndEnumTests: all four (null/value) × (None/Some) combinations - BuildXmlDocTests: summary, remarks (present/absent), param tags, returns tag, and XML escaping of & < > in summary and description New integration tests in Schema.XmlDocTests.fs: - tag when description differs from summary - No when description equals summary - XML escaping for & < > in generated method XmlDoc - Request body description surfaced as tag - 201 Created response description surfaced as tag Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Schema.XmlDocTests.fs | 154 ++++++++++++++++ tests/SwaggerProvider.Tests/UtilsTests.fs | 165 ++++++++++++++++++ 2 files changed, 319 insertions(+) diff --git a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs index bbea0112..98534f43 100644 --- a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs @@ -401,3 +401,157 @@ let ``no returns tag when there is no success response``() = let doc = getMethodXmlDoc noResponseSchema "Fire" doc.IsSome |> shouldEqual true doc.Value |> shouldNotContainText "" + +// ── tag from operation description ────────────────────────────────── + +let private withRemarkSchema = + """ /pets/{id}: + get: + operationId: getPet + summary: Get a pet + description: Returns the full pet record by its unique identifier. Includes all nested sub-resources. + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + "200": + description: OK + content: + application/json: + schema: + type: string +""" + +[] +let ``remarks tag appears when description differs from summary``() = + let doc = getMethodXmlDoc withRemarkSchema "GetPet" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "" + doc.Value |> shouldContainText "Returns the full pet record" + doc.Value |> shouldContainText "" + +[] +let ``summary tag is present when description and summary both set``() = + let doc = getMethodXmlDoc withRemarkSchema "GetPet" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "Get a pet" + +let private summaryEqualsDescSchema = + """ /status: + get: + operationId: getStatus + summary: Server status + description: Server status + responses: + "200": + description: OK + content: + application/json: + schema: + type: string +""" + +[] +let ``no remarks tag when description equals summary``() = + let doc = getMethodXmlDoc summaryEqualsDescSchema "GetStatus" + doc.IsSome |> shouldEqual true + doc.Value |> shouldNotContainText "" + +// ── XML special character escaping ─────────────────────────────────────────── + +let private xmlEscapeSchema = + """ /items: + get: + operationId: listItems + summary: List items (A & B) + description: Returns items where value < threshold or value > minimum + responses: + "200": + description: OK + content: + application/json: + schema: + type: string +""" + +[] +let ``ampersand in summary is escaped in XmlDoc``() = + let doc = getMethodXmlDoc xmlEscapeSchema "ListItems" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "&" + +[] +let ``less-than in description is escaped in XmlDoc``() = + let doc = getMethodXmlDoc xmlEscapeSchema "ListItems" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "<" + +[] +let ``greater-than in description is escaped in XmlDoc``() = + let doc = getMethodXmlDoc xmlEscapeSchema "ListItems" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText ">" + +// ── Request body description in param XmlDoc ───────────────────────────────── + +let private bodyWithDescSchema = + """ /items: + post: + operationId: createItem + summary: Create an item + requestBody: + description: The item payload to create + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + responses: + "201": + description: Created +""" + +[] +let ``request body description appears as param tag in method XmlDoc``() = + let doc = getMethodXmlDoc bodyWithDescSchema "CreateItem" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "The item payload to create" + +// ── 201 Created response as return type ────────────────────────────────────── + +let private createdResponseSchema = + """ /items: + post: + operationId: createItem + summary: Create an item + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + responses: + "201": + description: The created item + content: + application/json: + schema: + type: string +""" + +[] +let ``201 response description appears in returns tag``() = + let doc = getMethodXmlDoc createdResponseSchema "CreateItem" + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "" + doc.Value |> shouldContainText "The created item" + doc.Value |> shouldContainText "" diff --git a/tests/SwaggerProvider.Tests/UtilsTests.fs b/tests/SwaggerProvider.Tests/UtilsTests.fs index 59bd9eb2..e69da172 100644 --- a/tests/SwaggerProvider.Tests/UtilsTests.fs +++ b/tests/SwaggerProvider.Tests/UtilsTests.fs @@ -1,5 +1,7 @@ namespace SwaggerProvider.Tests.UtilsTests +open System.Collections.Generic +open System.Text.Json.Nodes open Xunit open FsUnitTyped open SwaggerProvider.Internal @@ -93,3 +95,166 @@ module UniqueNameGeneratorTests = let ``empty occupied names sequence behaves like default constructor``() = let gen = UniqueNameGenerator(occupiedNames = []) gen.MakeUnique "Foo" |> shouldEqual "Foo" + +// ── XmlDoc.buildEnumDoc ─────────────────────────────────────────────────────── + +/// Direct unit tests for the XmlDoc.buildEnumDoc helper, which converts a list of +/// JsonNode enum values into a human-readable "Allowed values: …" string. +module BuildEnumDocTests = + + [] + let ``null enum list returns None``() = + XmlDoc.buildEnumDoc null |> shouldEqual None + + [] + let ``empty enum list returns None``() = + let empty = List() :> IList + XmlDoc.buildEnumDoc empty |> shouldEqual None + + [] + let ``single string enum value produces Allowed values line``() = + let values = + List([| JsonValue.Create("active") :> JsonNode |]) :> IList + + let doc = XmlDoc.buildEnumDoc values + doc |> shouldEqual(Some "Allowed values: active") + + [] + let ``multiple string enum values are comma-separated``() = + let values = + List( + [| JsonValue.Create("active") :> JsonNode + JsonValue.Create("inactive") :> JsonNode + JsonValue.Create("pending") :> JsonNode |] + ) + :> IList + + let doc = XmlDoc.buildEnumDoc values + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "Allowed values:" + doc.Value |> shouldContainText "active" + doc.Value |> shouldContainText "inactive" + doc.Value |> shouldContainText "pending" + + [] + let ``integer enum values appear as numbers``() = + let values = + List([| JsonValue.Create(1) :> JsonNode; JsonValue.Create(2) :> JsonNode |]) :> IList + + let doc = XmlDoc.buildEnumDoc values + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "1" + doc.Value |> shouldContainText "2" + + [] + let ``null json node in enum list renders as null``() = + let values = List([| null |]) :> IList + let doc = XmlDoc.buildEnumDoc values + doc.IsSome |> shouldEqual true + doc.Value |> shouldContainText "null" + +// ── XmlDoc.combineDescAndEnum ───────────────────────────────────────────────── + +/// Direct unit tests for XmlDoc.combineDescAndEnum which merges a schema description +/// with optional enum documentation. +module CombineDescAndEnumTests = + + [] + let ``null description and None enum returns null``() = + XmlDoc.combineDescAndEnum null None |> shouldEqual null + + [] + let ``empty description and None enum returns null``() = + XmlDoc.combineDescAndEnum "" None |> shouldEqual null + + [] + let ``whitespace-only description and None enum returns null``() = + XmlDoc.combineDescAndEnum " " None |> shouldEqual null + + [] + let ``description only is returned unchanged``() = + XmlDoc.combineDescAndEnum "My description" None + |> shouldEqual "My description" + + [] + let ``enum doc only is returned when description is null``() = + XmlDoc.combineDescAndEnum null (Some "Allowed values: a, b") + |> shouldEqual "Allowed values: a, b" + + [] + let ``both description and enum doc are combined with newline``() = + let result = XmlDoc.combineDescAndEnum "The status" (Some "Allowed values: a, b") + result |> shouldContainText "The status" + result |> shouldContainText "Allowed values: a, b" + +// ── XmlDoc.buildXmlDoc ──────────────────────────────────────────────────────── + +/// Direct unit tests for XmlDoc.buildXmlDoc — the central doc-string builder +/// for summary, remarks, parameter, and returns tags. +module BuildXmlDocTests = + + [] + let ``summary only produces summary tag``() = + let doc = XmlDoc.buildXmlDoc "Get pet" null [] None + doc |> shouldContainText "Get pet" + + [] + let ``description equal to summary does not produce remarks tag``() = + let doc = XmlDoc.buildXmlDoc "Get pet" "Get pet" [] None + doc |> shouldNotContainText "" + + [] + let ``description different from summary produces remarks tag``() = + let doc = XmlDoc.buildXmlDoc "Get pet" "Returns the pet by ID" [] None + doc |> shouldContainText "Get pet" + doc |> shouldContainText "Returns the pet by ID" + + [] + let ``null description does not produce remarks tag``() = + let doc = XmlDoc.buildXmlDoc "Get pet" null [] None + doc |> shouldNotContainText "" + + [] + let ``param descriptions appear as param tags``() = + let doc = XmlDoc.buildXmlDoc "Op" null [ ("petId", "The pet identifier") ] None + + doc + |> shouldContainText "The pet identifier" + + [] + let ``null param description is omitted``() = + let doc = XmlDoc.buildXmlDoc "Op" null [ ("petId", null) ] None + doc |> shouldNotContainText "] + let ``returns tag appears when returnDoc is Some``() = + let doc = XmlDoc.buildXmlDoc "Op" null [] (Some "The pet") + doc |> shouldContainText "The pet" + + [] + let ``returns tag is absent when returnDoc is None``() = + let doc = XmlDoc.buildXmlDoc "Op" null [] None + doc |> shouldNotContainText "" + + [] + let ``ampersand in summary is XML-escaped``() = + let doc = XmlDoc.buildXmlDoc "Cats & Dogs" null [] None + doc |> shouldContainText "Cats & Dogs" + doc |> shouldNotContainText "Cats & Dogs" + + [] + let ``less-than in description is XML-escaped``() = + let doc = XmlDoc.buildXmlDoc "Op" "Value < threshold" [] None + doc |> shouldContainText "<" + doc |> shouldNotContainText "Value < threshold" + + [] + let ``greater-than in description is XML-escaped``() = + let doc = XmlDoc.buildXmlDoc "Op" "Value > threshold" [] None + doc |> shouldContainText ">" + doc |> shouldNotContainText "Value > threshold" + + [] + let ``empty summary and null description and no params returns empty string``() = + let doc = XmlDoc.buildXmlDoc "" null [] None + doc |> shouldEqual "" From 7f126843913f17fe7470fe351e76466f3f44aeab Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 4 May 2026 13:27:34 +0000 Subject: [PATCH 2/4] ci: trigger checks From 07b51b925f3edb249fd1dda5a48c0dc3c294eb99 Mon Sep 17 00:00:00 2001 From: Sergey Tihon Date: Mon, 4 May 2026 15:47:01 +0200 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs | 4 ++++ tests/SwaggerProvider.Tests/UtilsTests.fs | 13 ++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs index 98534f43..906c2569 100644 --- a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs @@ -522,6 +522,10 @@ let ``request body description appears as param tag in method XmlDoc``() = let doc = getMethodXmlDoc bodyWithDescSchema "CreateItem" doc.IsSome |> shouldEqual true doc.Value |> shouldContainText "The item payload to create" + System.Text.RegularExpressions.Regex.IsMatch( + doc.Value, + "]*>\\s*The item payload to create\\s*") + |> shouldEqual true // ── 201 Created response as return type ────────────────────────────────────── diff --git a/tests/SwaggerProvider.Tests/UtilsTests.fs b/tests/SwaggerProvider.Tests/UtilsTests.fs index e69da172..c5bf5285 100644 --- a/tests/SwaggerProvider.Tests/UtilsTests.fs +++ b/tests/SwaggerProvider.Tests/UtilsTests.fs @@ -129,12 +129,8 @@ module BuildEnumDocTests = ) :> IList - let doc = XmlDoc.buildEnumDoc values - doc.IsSome |> shouldEqual true - doc.Value |> shouldContainText "Allowed values:" - doc.Value |> shouldContainText "active" - doc.Value |> shouldContainText "inactive" - doc.Value |> shouldContainText "pending" + XmlDoc.buildEnumDoc values + |> shouldEqual(Some "Allowed values: active, inactive, pending") [] let ``integer enum values appear as numbers``() = @@ -183,9 +179,8 @@ module CombineDescAndEnumTests = [] let ``both description and enum doc are combined with newline``() = - let result = XmlDoc.combineDescAndEnum "The status" (Some "Allowed values: a, b") - result |> shouldContainText "The status" - result |> shouldContainText "Allowed values: a, b" + XmlDoc.combineDescAndEnum "The status" (Some "Allowed values: a, b") + |> shouldEqual "The status\nAllowed values: a, b" // ── XmlDoc.buildXmlDoc ──────────────────────────────────────────────────────── From 8cf921db66161568c6b4fde433645687ea56be8a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 13:51:08 +0000 Subject: [PATCH 4/4] fix: apply Fantomas formatting to Schema.XmlDocTests.fs Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/6e8b7eb1-abd8-4374-86c0-0283bc9afae8 Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> --- tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs index 906c2569..c5a09caf 100644 --- a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs @@ -522,9 +522,8 @@ let ``request body description appears as param tag in method XmlDoc``() = let doc = getMethodXmlDoc bodyWithDescSchema "CreateItem" doc.IsSome |> shouldEqual true doc.Value |> shouldContainText "The item payload to create" - System.Text.RegularExpressions.Regex.IsMatch( - doc.Value, - "]*>\\s*The item payload to create\\s*") + + System.Text.RegularExpressions.Regex.IsMatch(doc.Value, "]*>\\s*The item payload to create\\s*") |> shouldEqual true // ── 201 Created response as return type ──────────────────────────────────────