Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions tests/SwaggerProvider.Tests/Schema.V2SchemaCompilationTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,110 @@ let ``v2 schema with integer enum property compiles``() =
// integer enum β€” Microsoft.OpenApi maps this to the integer base type
codeProp.PropertyType |> shouldEqual typeof<int32>

// ── Required vs optional properties ─────────────────────────────────────────

[<Fact>]
let ``v2 required property compiles to non-option type``() =
let schema =
"""{
"swagger": "2.0",
"info": { "title": "RequiredTest", "version": "1.0.0" },
"basePath": "/",
"paths": {},
"definitions": {
"Order": {
"type": "object",
"required": ["id"],
"properties": {
"id": { "type": "integer" },
"note": { "type": "string" }
}
}
}
}"""

let types = compileV2Schema schema
let orderType = types |> List.find(fun t -> t.Name = "Order")

getProp orderType "Id"
|> (fun p -> p.PropertyType)
|> shouldEqual typeof<int32>

[<Fact>]
let ``v2 optional property compiles to option type``() =
let schema =
"""{
"swagger": "2.0",
"info": { "title": "RequiredTest", "version": "1.0.0" },
"basePath": "/",
"paths": {},
"definitions": {
"Order": {
"type": "object",
"required": ["id"],
"properties": {
"id": { "type": "integer" },
"note": { "type": "string" }
}
}
}
}"""

let types = compileV2Schema schema
let orderType = types |> List.find(fun t -> t.Name = "Order")

getProp orderType "Note"
|> (fun p -> p.PropertyType)
|> shouldEqual typeof<string option>

// ── allOf inheritance: $ref + extra properties ─────────────────────────────

/// Swagger 2.0 allOf inheritance pattern: Dog extends Animal.
/// allOf contains a $ref to Animal and an inline schema with Dog's own properties.
let private v2InheritanceSchema =
"""{
"swagger": "2.0",
"info": { "title": "InheritanceTest", "version": "1.0.0" },
"basePath": "/",
"paths": {},
"definitions": {
"Animal": {
"type": "object",
"required": ["name"],
"properties": {
"name": { "type": "string" }
}
},
"Dog": {
"allOf": [
{ "$ref": "#/definitions/Animal" },
{
"type": "object",
"properties": {
"breed": { "type": "string" }
}
}
]
}
}
}"""

[<Fact>]
let ``v2 allOf inheritance emits the base type``() =
let types = compileV2Schema v2InheritanceSchema
types |> List.exists(fun t -> t.Name = "Animal") |> shouldEqual true

[<Fact>]
let ``v2 allOf inheritance emits the derived type``() =
let types = compileV2Schema v2InheritanceSchema
types |> List.exists(fun t -> t.Name = "Dog") |> shouldEqual true

[<Fact>]
let ``v2 allOf inheritance derived type has its own property``() =
let types = compileV2Schema v2InheritanceSchema
let dogType = types |> List.find(fun t -> t.Name = "Dog")
dogType.GetDeclaredProperty("Breed") |> isNull |> shouldEqual false

// ── ToString tests ───────────────────────────────────────────────────────────

[<Fact>]
Expand Down
129 changes: 129 additions & 0 deletions tests/SwaggerProvider.Tests/Schema.V3SchemaCompilationTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,132 @@ let ``property referencing an additionalProperties schema has Map type``() =
// collection types naturally express absence via null/empty.
let propType = dataProp.PropertyType
propType |> shouldEqual typeof<Map<string, string>>

// ── oneOf / anyOf with multiple $refs β†’ name alias (no wrapper type) ─────────

/// OpenAPI 3.0 schema where Union uses oneOf with two $refs and no own properties.
/// Because OneOf.Count <> 1, the single-$ref collapse guard does not fire.
/// compileNewObject() then marks Union as a name alias and returns typeof<obj>, so no wrapper type is emitted.
let private oneOfMultiRefSchema =
"""{
"openapi": "3.0.0",
"info": { "title": "Test", "version": "1.0.0" },
"paths": {},
"components": {
"schemas": {
"Cat": {
"type": "object",
"properties": { "meow": { "type": "string" } }
},
"Dog": {
"type": "object",
"properties": { "bark": { "type": "string" } }
},
"Union": {
"oneOf": [
{ "$ref": "#/components/schemas/Cat" },
{ "$ref": "#/components/schemas/Dog" }
]
}
}
}
}"""

[<Fact>]
let ``oneOf with multiple refs does not emit a named wrapper type``() =
// With no own properties and no allOf, compileNewObject() marks Union as a name alias
// and returns typeof<obj> β€” no separate named type is emitted for the wrapper.
let types = compileV3Schema oneOfMultiRefSchema false
types |> List.exists(fun t -> t.Name = "Union") |> shouldEqual false

[<Fact>]
let ``oneOf with multiple refs emits the referenced component types``() =
let types = compileV3Schema oneOfMultiRefSchema false
types |> List.exists(fun t -> t.Name = "Cat") |> shouldEqual true
types |> List.exists(fun t -> t.Name = "Dog") |> shouldEqual true

/// OpenAPI 3.0 schema where AnyUnion uses anyOf with two $refs.
let private anyOfMultiRefSchema =
"""{
"openapi": "3.0.0",
"info": { "title": "Test", "version": "1.0.0" },
"paths": {},
"components": {
"schemas": {
"Fish": {
"type": "object",
"properties": { "fins": { "type": "integer" } }
},
"Bird": {
"type": "object",
"properties": { "wings": { "type": "integer" } }
},
"AnyUnion": {
"anyOf": [
{ "$ref": "#/components/schemas/Fish" },
{ "$ref": "#/components/schemas/Bird" }
]
}
}
}
}"""

[<Fact>]
let ``anyOf with multiple refs does not emit a named wrapper type``() =
// Same behaviour as oneOf multi-ref: no own properties β†’ compileNewObject() marks
// AnyUnion as a name alias and returns typeof<obj>.
let types = compileV3Schema anyOfMultiRefSchema false

types
|> List.exists(fun t -> t.Name = "AnyUnion")
|> shouldEqual false

[<Fact>]
let ``anyOf with multiple refs emits the referenced component types``() =
let types = compileV3Schema anyOfMultiRefSchema false
types |> List.exists(fun t -> t.Name = "Fish") |> shouldEqual true
types |> List.exists(fun t -> t.Name = "Bird") |> shouldEqual true

// ── allOf single $ref with extra wrapper properties β†’ new object type ─────────

/// OpenAPI 3.0 schema where Extended wraps Base via allOf with a single $ref,
/// but also declares its own extra property. The single-$ref collapse guard
/// requires Properties.Count = 0; here it has extra properties, so it fires
/// compileNewObject() instead and emits Extended as a new object type.
let private allOfSingleRefWithExtraPropsSchema =
"""{
"openapi": "3.0.0",
"info": { "title": "Test", "version": "1.0.0" },
"paths": {},
"components": {
"schemas": {
"Base": {
"type": "object",
"properties": {
"id": { "type": "integer" }
}
},
"Extended": {
"allOf": [ { "$ref": "#/components/schemas/Base" } ],
"properties": {
"extra": { "type": "string" }
}
}
}
}
}"""

[<Fact>]
let ``allOf single ref with extra wrapper properties emits a new named type``() =
// Because Extended has its own properties, the collapse guard fails and a new object type is emitted.
let types = compileV3Schema allOfSingleRefWithExtraPropsSchema false
types |> List.exists(fun t -> t.Name = "Extended") |> shouldEqual true

[<Fact>]
let ``allOf single ref with extra wrapper properties emits the extra property``() =
let types = compileV3Schema allOfSingleRefWithExtraPropsSchema false
let extendedType = types |> List.find(fun t -> t.Name = "Extended")

extendedType.GetDeclaredProperty("Extra")
|> isNull
|> shouldEqual false
Loading