6fd044fb09
Signed-off-by: Deluan <deluan@navidrome.org>
762 lines
25 KiB
Go
762 lines
25 KiB
Go
package internal
|
|
|
|
import (
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var _ = Describe("XTP Schema Generation", func() {
|
|
parseSchema := func(schema []byte) map[string]any {
|
|
var doc map[string]any
|
|
Expect(yaml.Unmarshal(schema, &doc)).To(Succeed())
|
|
return doc
|
|
}
|
|
|
|
Describe("GenerateSchema", func() {
|
|
Context("basic capability with one export", func() {
|
|
var schema []byte
|
|
|
|
BeforeEach(func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
Doc: "Test capability",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{
|
|
ExportName: "test_method",
|
|
Doc: "Test method does something",
|
|
Input: NewParam("input", "TestInput"),
|
|
Output: NewParam("output", "TestOutput"),
|
|
},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "TestInput",
|
|
Doc: "Input for test",
|
|
Fields: []FieldDef{
|
|
{Name: "Name", Type: "string", JSONTag: "name", Doc: "The name"},
|
|
{Name: "Count", Type: "int", JSONTag: "count", Doc: "The count"},
|
|
},
|
|
},
|
|
{
|
|
Name: "TestOutput",
|
|
Doc: "Output for test",
|
|
Fields: []FieldDef{
|
|
{Name: "Result", Type: "string", JSONTag: "result", Doc: "The result"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
schema, err = GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(schema).NotTo(BeEmpty())
|
|
})
|
|
|
|
It("should validate against XTP JSONSchema", func() {
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
})
|
|
|
|
It("should have correct version", func() {
|
|
doc := parseSchema(schema)
|
|
Expect(doc["version"]).To(Equal("v1-draft"))
|
|
})
|
|
|
|
It("should include exports with description", func() {
|
|
doc := parseSchema(schema)
|
|
exports := doc["exports"].(map[string]any)
|
|
Expect(exports).To(HaveKey("test_method"))
|
|
method := exports["test_method"].(map[string]any)
|
|
Expect(method["description"]).To(Equal("Test method does something"))
|
|
})
|
|
|
|
It("should include schemas for input and output types", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
Expect(schemas).To(HaveKey("TestInput"))
|
|
Expect(schemas).To(HaveKey("TestOutput"))
|
|
})
|
|
|
|
It("should define input schema with correct properties", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["TestInput"].(map[string]any)
|
|
// Per XTP spec, ObjectSchema does NOT have a type field - only properties, required, description
|
|
Expect(input).NotTo(HaveKey("type"))
|
|
props := input["properties"].(map[string]any)
|
|
Expect(props).To(HaveKey("name"))
|
|
Expect(props).To(HaveKey("count"))
|
|
})
|
|
|
|
It("should mark non-pointer, non-omitempty fields as required", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["TestInput"].(map[string]any)
|
|
required := input["required"].([]any)
|
|
Expect(required).To(ContainElement("name"))
|
|
Expect(required).To(ContainElement("count"))
|
|
})
|
|
})
|
|
|
|
Context("capability with pointer fields (nullable)", func() {
|
|
var schema []byte
|
|
|
|
BeforeEach(func() {
|
|
capability := Capability{
|
|
Name: "nullable_test",
|
|
SourceFile: "nullable_test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "Input",
|
|
Fields: []FieldDef{
|
|
{Name: "Required", Type: "string", JSONTag: "required"},
|
|
{Name: "Optional", Type: "*string", JSONTag: "optional,omitempty", OmitEmpty: true},
|
|
},
|
|
},
|
|
{
|
|
Name: "Output",
|
|
Fields: []FieldDef{
|
|
{Name: "Value", Type: "string", JSONTag: "value"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
schema, err = GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("should validate against XTP JSONSchema", func() {
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
})
|
|
|
|
It("should not mark required field as nullable", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
props := input["properties"].(map[string]any)
|
|
requiredField := props["required"].(map[string]any)
|
|
Expect(requiredField).NotTo(HaveKey("nullable"))
|
|
})
|
|
|
|
It("should mark optional pointer field as nullable", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
props := input["properties"].(map[string]any)
|
|
optionalField := props["optional"].(map[string]any)
|
|
Expect(optionalField["nullable"]).To(BeTrue())
|
|
})
|
|
|
|
It("should only include non-pointer fields in required array", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
required := input["required"].([]any)
|
|
Expect(required).To(ContainElement("required"))
|
|
Expect(required).NotTo(ContainElement("optional"))
|
|
})
|
|
})
|
|
|
|
Context("capability with enum", func() {
|
|
var schema []byte
|
|
|
|
BeforeEach(func() {
|
|
capability := Capability{
|
|
Name: "enum_test",
|
|
SourceFile: "enum_test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "Input",
|
|
Fields: []FieldDef{
|
|
{Name: "Status", Type: "Status", JSONTag: "status"},
|
|
},
|
|
},
|
|
{
|
|
Name: "Output",
|
|
Fields: []FieldDef{
|
|
{Name: "Value", Type: "string", JSONTag: "value"},
|
|
},
|
|
},
|
|
},
|
|
TypeAliases: []TypeAlias{
|
|
{Name: "Status", Type: "string", Doc: "Status type"},
|
|
},
|
|
Consts: []ConstGroup{
|
|
{
|
|
Type: "Status",
|
|
Values: []ConstDef{
|
|
{Name: "StatusPending", Value: `"pending"`},
|
|
{Name: "StatusActive", Value: `"active"`},
|
|
{Name: "StatusDone", Value: `"done"`},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
schema, err = GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("should validate against XTP JSONSchema", func() {
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
})
|
|
|
|
It("should define enum type with correct values", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
Expect(schemas).To(HaveKey("Status"))
|
|
status := schemas["Status"].(map[string]any)
|
|
Expect(status["type"]).To(Equal("string"))
|
|
enum := status["enum"].([]any)
|
|
Expect(enum).To(ConsistOf("pending", "active", "done"))
|
|
})
|
|
|
|
It("should use $ref for enum field in struct", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
props := input["properties"].(map[string]any)
|
|
statusRef := props["status"].(map[string]any)
|
|
Expect(statusRef["$ref"]).To(Equal("#/components/schemas/Status"))
|
|
})
|
|
})
|
|
|
|
Context("capability with array types", func() {
|
|
var schema []byte
|
|
|
|
BeforeEach(func() {
|
|
capability := Capability{
|
|
Name: "array_test",
|
|
SourceFile: "array_test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "Input",
|
|
Fields: []FieldDef{
|
|
{Name: "Tags", Type: "[]string", JSONTag: "tags"},
|
|
{Name: "Items", Type: "[]Item", JSONTag: "items"},
|
|
},
|
|
},
|
|
{
|
|
Name: "Output",
|
|
Fields: []FieldDef{
|
|
{Name: "Value", Type: "string", JSONTag: "value"},
|
|
},
|
|
},
|
|
{
|
|
Name: "Item",
|
|
Fields: []FieldDef{
|
|
{Name: "ID", Type: "string", JSONTag: "id"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
schema, err = GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
})
|
|
|
|
It("should validate against XTP JSONSchema", func() {
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
})
|
|
|
|
It("should define string array with primitive type", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
props := input["properties"].(map[string]any)
|
|
tags := props["tags"].(map[string]any)
|
|
Expect(tags["type"]).To(Equal("array"))
|
|
tagItems := tags["items"].(map[string]any)
|
|
Expect(tagItems["type"]).To(Equal("string"))
|
|
})
|
|
|
|
It("should define struct array with $ref", func() {
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
props := input["properties"].(map[string]any)
|
|
items := props["items"].(map[string]any)
|
|
Expect(items["type"]).To(Equal("array"))
|
|
itemItems := items["items"].(map[string]any)
|
|
Expect(itemItems["$ref"]).To(Equal("#/components/schemas/Item"))
|
|
})
|
|
})
|
|
|
|
Context("capability with []byte field", func() {
|
|
It("should map []byte to string with byte format, not array", func() {
|
|
capability := Capability{
|
|
Name: "byte_test",
|
|
SourceFile: "byte_test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "Input",
|
|
Fields: []FieldDef{
|
|
{Name: "Data", Type: "[]byte", JSONTag: "data"},
|
|
},
|
|
},
|
|
{
|
|
Name: "Output",
|
|
Fields: []FieldDef{
|
|
{Name: "Value", Type: "string", JSONTag: "value"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
input := schemas["Input"].(map[string]any)
|
|
props := input["properties"].(map[string]any)
|
|
data := props["data"].(map[string]any)
|
|
Expect(data["type"]).To(Equal("string"))
|
|
Expect(data["format"]).To(Equal("byte"))
|
|
Expect(data).NotTo(HaveKey("items"))
|
|
})
|
|
})
|
|
|
|
Context("capability with nullable ref", func() {
|
|
It("should mark pointer to enum as nullable with $ref", func() {
|
|
capability := Capability{
|
|
Name: "nullable_ref_test",
|
|
SourceFile: "nullable_ref_test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "Input",
|
|
Fields: []FieldDef{
|
|
{Name: "Value", Type: "string", JSONTag: "value"},
|
|
},
|
|
},
|
|
{
|
|
Name: "Output",
|
|
Fields: []FieldDef{
|
|
{Name: "Status", Type: "*ErrorType", JSONTag: "status,omitempty", OmitEmpty: true},
|
|
},
|
|
},
|
|
},
|
|
TypeAliases: []TypeAlias{
|
|
{Name: "ErrorType", Type: "string"},
|
|
},
|
|
Consts: []ConstGroup{
|
|
{
|
|
Type: "ErrorType",
|
|
Values: []ConstDef{
|
|
{Name: "ErrorNone", Value: `"none"`},
|
|
{Name: "ErrorFatal", Value: `"fatal"`},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
// Validate against XTP JSONSchema
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
output := schemas["Output"].(map[string]any)
|
|
props := output["properties"].(map[string]any)
|
|
status := props["status"].(map[string]any)
|
|
Expect(status["$ref"]).To(Equal("#/components/schemas/ErrorType"))
|
|
Expect(status["nullable"]).To(BeTrue())
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("goTypeToXTPTypeAndFormat", func() {
|
|
DescribeTable("should convert Go types to XTP types",
|
|
func(goType, wantType, wantFormat string) {
|
|
gotType, gotFormat := goTypeToXTPTypeAndFormat(goType)
|
|
Expect(gotType).To(Equal(wantType))
|
|
Expect(gotFormat).To(Equal(wantFormat))
|
|
},
|
|
Entry("string", "string", "string", ""),
|
|
Entry("int", "int", "integer", "int32"),
|
|
Entry("int32", "int32", "integer", "int32"),
|
|
Entry("int64", "int64", "integer", "int64"),
|
|
Entry("float32", "float32", "number", "float"),
|
|
Entry("float64", "float64", "number", "float"),
|
|
Entry("bool", "bool", "boolean", ""),
|
|
Entry("[]byte", "[]byte", "string", "byte"),
|
|
Entry("unknown types default to object", "CustomType", "object", ""),
|
|
)
|
|
})
|
|
|
|
Describe("cleanDocForYAML", func() {
|
|
DescribeTable("should clean documentation strings",
|
|
func(doc, want string) {
|
|
Expect(cleanDocForYAML(doc)).To(Equal(want))
|
|
},
|
|
Entry("empty", "", ""),
|
|
Entry("single line", "Simple description", "Simple description"),
|
|
Entry("multiline", "First line\nSecond line", "First line\nSecond line"),
|
|
Entry("trailing newline", "Description\n", "Description"),
|
|
Entry("whitespace", " Description ", "Description"),
|
|
)
|
|
})
|
|
|
|
Describe("isPrimitiveGoType", func() {
|
|
DescribeTable("should identify primitive Go types",
|
|
func(goType string, want bool) {
|
|
Expect(isPrimitiveGoType(goType)).To(Equal(want))
|
|
},
|
|
Entry("bool", "bool", true),
|
|
Entry("string", "string", true),
|
|
Entry("int", "int", true),
|
|
Entry("int32", "int32", true),
|
|
Entry("int64", "int64", true),
|
|
Entry("float32", "float32", true),
|
|
Entry("float64", "float64", true),
|
|
Entry("[]byte", "[]byte", true),
|
|
Entry("custom type", "CustomType", false),
|
|
Entry("struct type", "MyStruct", false),
|
|
Entry("slice of string", "[]string", false),
|
|
Entry("map type", "map[string]int", false),
|
|
)
|
|
})
|
|
|
|
Describe("GenerateSchema with primitive output types", func() {
|
|
inputStruct := StructDef{
|
|
Name: "Input",
|
|
Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}},
|
|
}
|
|
|
|
Context("export with primitive string output", func() {
|
|
It("should use type instead of $ref and validate against XTP JSONSchema", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "get_name", Input: NewParam("input", "Input"), Output: NewParam("output", "string")},
|
|
},
|
|
Structs: []StructDef{inputStruct},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(schema).NotTo(BeEmpty())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
exports := doc["exports"].(map[string]any)
|
|
method := exports["get_name"].(map[string]any)
|
|
output := method["output"].(map[string]any)
|
|
Expect(output["type"]).To(Equal("string"))
|
|
Expect(output).NotTo(HaveKey("$ref"))
|
|
Expect(output["contentType"]).To(Equal("application/json"))
|
|
})
|
|
})
|
|
|
|
Context("export with primitive bool output", func() {
|
|
It("should use boolean type and validate against XTP JSONSchema", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "is_valid", Input: NewParam("input", "Input"), Output: NewParam("output", "bool")},
|
|
},
|
|
Structs: []StructDef{inputStruct},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
exports := doc["exports"].(map[string]any)
|
|
method := exports["is_valid"].(map[string]any)
|
|
output := method["output"].(map[string]any)
|
|
Expect(output["type"]).To(Equal("boolean"))
|
|
Expect(output).NotTo(HaveKey("$ref"))
|
|
})
|
|
})
|
|
|
|
Context("export with primitive int output", func() {
|
|
It("should use integer type and validate against XTP JSONSchema", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "get_count", Input: NewParam("input", "Input"), Output: NewParam("output", "int32")},
|
|
},
|
|
Structs: []StructDef{inputStruct},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
exports := doc["exports"].(map[string]any)
|
|
method := exports["get_count"].(map[string]any)
|
|
output := method["output"].(map[string]any)
|
|
Expect(output["type"]).To(Equal("integer"))
|
|
Expect(output).NotTo(HaveKey("$ref"))
|
|
})
|
|
})
|
|
|
|
Context("export with pointer to primitive output", func() {
|
|
It("should strip pointer and use primitive type and validate against XTP JSONSchema", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "get_optional_string", Input: NewParam("input", "Input"), Output: NewParam("output", "*string")},
|
|
},
|
|
Structs: []StructDef{inputStruct},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
exports := doc["exports"].(map[string]any)
|
|
method := exports["get_optional_string"].(map[string]any)
|
|
output := method["output"].(map[string]any)
|
|
Expect(output["type"]).To(Equal("string"))
|
|
Expect(output).NotTo(HaveKey("$ref"))
|
|
})
|
|
})
|
|
|
|
Context("export with struct output", func() {
|
|
It("should still use $ref and validate against XTP JSONSchema", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "get_result", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
inputStruct,
|
|
{Name: "Output", Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}}},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
exports := doc["exports"].(map[string]any)
|
|
method := exports["get_result"].(map[string]any)
|
|
output := method["output"].(map[string]any)
|
|
Expect(output["$ref"]).To(Equal("#/components/schemas/Output"))
|
|
Expect(output).NotTo(HaveKey("type"))
|
|
})
|
|
})
|
|
})
|
|
|
|
Describe("collectUsedTypes", func() {
|
|
getSchemas := func(schema []byte) map[string]any {
|
|
doc := parseSchema(schema)
|
|
components, hasComponents := doc["components"].(map[string]any)
|
|
if !hasComponents {
|
|
return make(map[string]any)
|
|
}
|
|
schemas, ok := components["schemas"].(map[string]any)
|
|
if !ok {
|
|
return make(map[string]any)
|
|
}
|
|
return schemas
|
|
}
|
|
|
|
It("should only include types referenced by exports", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "UsedInput"), Output: NewParam("output", "UsedOutput")},
|
|
},
|
|
Structs: []StructDef{
|
|
{Name: "UsedInput", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
|
{Name: "UsedOutput", Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}}},
|
|
{Name: "UnusedStruct", Fields: []FieldDef{{Name: "Foo", Type: "string", JSONTag: "foo"}}},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
schemas := getSchemas(schema)
|
|
Expect(schemas).To(HaveKey("UsedInput"))
|
|
Expect(schemas).To(HaveKey("UsedOutput"))
|
|
Expect(schemas).NotTo(HaveKey("UnusedStruct"))
|
|
})
|
|
|
|
It("should include transitively referenced types", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
|
{Name: "Output", Fields: []FieldDef{{Name: "Nested", Type: "NestedType", JSONTag: "nested"}}},
|
|
{Name: "NestedType", Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}}},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
schemas := getSchemas(schema)
|
|
Expect(schemas).To(HaveKey("Input"))
|
|
Expect(schemas).To(HaveKey("Output"))
|
|
Expect(schemas).To(HaveKey("NestedType"))
|
|
})
|
|
|
|
It("should include array element types", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
|
{Name: "Output", Fields: []FieldDef{{Name: "Items", Type: "[]Item", JSONTag: "items"}}},
|
|
{Name: "Item", Fields: []FieldDef{{Name: "Name", Type: "string", JSONTag: "name"}}},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
schemas := getSchemas(schema)
|
|
Expect(schemas).To(HaveKey("Input"))
|
|
Expect(schemas).To(HaveKey("Output"))
|
|
Expect(schemas).To(HaveKey("Item"))
|
|
})
|
|
|
|
It("should include pointer types", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
|
{Name: "Output", Fields: []FieldDef{{Name: "Optional", Type: "*OptionalType", JSONTag: "optional"}}},
|
|
{Name: "OptionalType", Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}}},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
schemas := getSchemas(schema)
|
|
Expect(schemas).To(HaveKey("Input"))
|
|
Expect(schemas).To(HaveKey("Output"))
|
|
Expect(schemas).To(HaveKey("OptionalType"))
|
|
})
|
|
|
|
It("should exclude primitive output types from schema", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "string")},
|
|
},
|
|
Structs: []StructDef{
|
|
{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
|
},
|
|
}
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
schemas := getSchemas(schema)
|
|
Expect(schemas).To(HaveKey("Input"))
|
|
})
|
|
})
|
|
|
|
Describe("GenerateSchema enum filtering", func() {
|
|
It("should only include enums that are actually used by exports", func() {
|
|
capability := Capability{
|
|
Name: "test",
|
|
SourceFile: "test",
|
|
Methods: []Export{
|
|
{ExportName: "test", Input: NewParam("input", "Input"), Output: NewParam("output", "Output")},
|
|
},
|
|
Structs: []StructDef{
|
|
{
|
|
Name: "Input",
|
|
Fields: []FieldDef{{Name: "Status", Type: "UsedStatus", JSONTag: "status"}},
|
|
},
|
|
{
|
|
Name: "Output",
|
|
Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}},
|
|
},
|
|
},
|
|
TypeAliases: []TypeAlias{
|
|
{Name: "UsedStatus", Type: "string"},
|
|
{Name: "UnusedStatus", Type: "string"},
|
|
},
|
|
Consts: []ConstGroup{
|
|
{
|
|
Type: "UsedStatus",
|
|
Values: []ConstDef{
|
|
{Name: "StatusActive", Value: `"active"`},
|
|
{Name: "StatusInactive", Value: `"inactive"`},
|
|
},
|
|
},
|
|
{
|
|
Type: "UnusedStatus",
|
|
Values: []ConstDef{
|
|
{Name: "UnusedPending", Value: `"pending"`},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
schema, err := GenerateSchema(capability)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(ValidateXTPSchema(schema)).To(Succeed())
|
|
|
|
doc := parseSchema(schema)
|
|
components := doc["components"].(map[string]any)
|
|
schemas := components["schemas"].(map[string]any)
|
|
|
|
// UsedStatus should be included because it's referenced by Input
|
|
Expect(schemas).To(HaveKey("UsedStatus"))
|
|
usedStatus := schemas["UsedStatus"].(map[string]any)
|
|
Expect(usedStatus["type"]).To(Equal("string"))
|
|
enum := usedStatus["enum"].([]any)
|
|
Expect(enum).To(ConsistOf("active", "inactive"))
|
|
|
|
// UnusedStatus should NOT be included
|
|
Expect(schemas).NotTo(HaveKey("UnusedStatus"))
|
|
})
|
|
})
|
|
})
|