diff --git a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs index bbea0112..c5a09caf 100644 --- a/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs +++ b/tests/SwaggerProvider.Tests/Schema.XmlDocTests.fs @@ -401,3 +401,160 @@ 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" + + System.Text.RegularExpressions.Regex.IsMatch(doc.Value, "]*>\\s*The item payload to create\\s*") + |> shouldEqual true + +// ── 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..c5bf5285 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,161 @@ 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 + + XmlDoc.buildEnumDoc values + |> shouldEqual(Some "Allowed values: active, inactive, 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``() = + XmlDoc.combineDescAndEnum "The status" (Some "Allowed values: a, b") + |> shouldEqual "The status\nAllowed 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 ""