mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
test(plugins): add unit tests for rustOutputType and isPrimitiveRustType functions
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
169bbb9d0a
commit
aa03d53524
@ -469,6 +469,10 @@ func rustConstType(goType string) string {
|
||||
// rustOutputType converts a Go type to Rust for capability method signatures.
|
||||
// It handles pointer types specially - for capability outputs, pointers become the base type
|
||||
// (not Option<T>) because Rust's Result<T, Error> already provides optional semantics.
|
||||
//
|
||||
// TODO: Pointer to primitive types (e.g., *string, *int32) are not handled correctly.
|
||||
// Currently "*string" returns "string" instead of "String". This would generate invalid
|
||||
// Rust code. No current capability uses this pattern, but it should be fixed if needed.
|
||||
func rustOutputType(goType string) string {
|
||||
// Strip pointer prefix - capability outputs use Result<T, Error> for optionality
|
||||
if strings.HasPrefix(goType, "*") {
|
||||
|
||||
@ -955,6 +955,206 @@ type OnInitOutput struct {
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Rust Generation", func() {
|
||||
Describe("rustOutputType", func() {
|
||||
It("should convert Go primitives to Rust primitives", func() {
|
||||
Expect(rustOutputType("bool")).To(Equal("bool"))
|
||||
Expect(rustOutputType("string")).To(Equal("String"))
|
||||
Expect(rustOutputType("int")).To(Equal("i32"))
|
||||
Expect(rustOutputType("int32")).To(Equal("i32"))
|
||||
Expect(rustOutputType("int64")).To(Equal("i64"))
|
||||
Expect(rustOutputType("float32")).To(Equal("f32"))
|
||||
Expect(rustOutputType("float64")).To(Equal("f64"))
|
||||
})
|
||||
|
||||
It("should strip pointer prefix", func() {
|
||||
// NOTE: This behavior is incorrect for pointer to primitives.
|
||||
// "*string" returns "string" instead of "String", which would generate
|
||||
// invalid Rust code. No current capability uses this pattern.
|
||||
// See TODO in rustOutputType function.
|
||||
Expect(rustOutputType("*string")).To(Equal("string"))
|
||||
Expect(rustOutputType("*MyStruct")).To(Equal("MyStruct"))
|
||||
})
|
||||
|
||||
It("should pass through unknown types", func() {
|
||||
Expect(rustOutputType("CustomType")).To(Equal("CustomType"))
|
||||
Expect(rustOutputType("MyStruct")).To(Equal("MyStruct"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("isPrimitiveRustType", func() {
|
||||
It("should return true for primitive Go types", func() {
|
||||
Expect(isPrimitiveRustType("bool")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("string")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("int")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("int32")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("int64")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("float32")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("float64")).To(BeTrue())
|
||||
})
|
||||
|
||||
It("should return false for non-primitive types", func() {
|
||||
Expect(isPrimitiveRustType("MyStruct")).To(BeFalse())
|
||||
Expect(isPrimitiveRustType("CustomType")).To(BeFalse())
|
||||
Expect(isPrimitiveRustType("[]string")).To(BeFalse())
|
||||
Expect(isPrimitiveRustType("map[string]int")).To(BeFalse())
|
||||
})
|
||||
|
||||
It("should handle pointer types by stripping prefix", func() {
|
||||
Expect(isPrimitiveRustType("*string")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("*int64")).To(BeTrue())
|
||||
Expect(isPrimitiveRustType("*MyStruct")).To(BeFalse())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GenerateCapabilityRust", func() {
|
||||
It("should generate valid Rust code with primitive output types", func() {
|
||||
cap := Capability{
|
||||
Name: "test",
|
||||
Interface: "TestAgent",
|
||||
Required: true,
|
||||
SourceFile: "test",
|
||||
Methods: []Export{
|
||||
{
|
||||
Name: "GetBool",
|
||||
ExportName: "nd_get_bool",
|
||||
Input: Param{Type: "BoolInput"},
|
||||
Output: Param{Type: "bool"},
|
||||
},
|
||||
{
|
||||
Name: "GetString",
|
||||
ExportName: "nd_get_string",
|
||||
Input: Param{Type: "StrInput"},
|
||||
Output: Param{Type: "string"},
|
||||
},
|
||||
{
|
||||
Name: "GetInt",
|
||||
ExportName: "nd_get_int",
|
||||
Input: Param{Type: "IntInput"},
|
||||
Output: Param{Type: "int32"},
|
||||
},
|
||||
},
|
||||
Structs: []StructDef{
|
||||
{Name: "BoolInput", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
||||
{Name: "StrInput", Fields: []FieldDef{{Name: "Key", Type: "string", JSONTag: "key"}}},
|
||||
{Name: "IntInput", Fields: []FieldDef{{Name: "Index", Type: "int32", JSONTag: "index"}}},
|
||||
},
|
||||
}
|
||||
|
||||
code, err := GenerateCapabilityRust(cap)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
codeStr := string(code)
|
||||
|
||||
// Check that primitive output types are not prefixed with $crate::
|
||||
// The template should use isPrimitiveRust to determine this
|
||||
Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<bool>>"))
|
||||
Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<String>>"))
|
||||
Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<i32>>"))
|
||||
|
||||
// Verify that primitive output types don't use $crate:: prefix in FnResult
|
||||
// The pattern "$crate::test::bool>" would indicate incorrect generation
|
||||
Expect(codeStr).NotTo(ContainSubstring("$crate::test::bool>"))
|
||||
Expect(codeStr).NotTo(ContainSubstring("$crate::test::String>"))
|
||||
Expect(codeStr).NotTo(ContainSubstring("$crate::test::i32>"))
|
||||
})
|
||||
|
||||
It("should generate valid Rust code with struct output types", func() {
|
||||
cap := Capability{
|
||||
Name: "metadata",
|
||||
Interface: "MetadataAgent",
|
||||
Required: true,
|
||||
SourceFile: "metadata",
|
||||
Methods: []Export{
|
||||
{
|
||||
Name: "GetArtist",
|
||||
ExportName: "nd_get_artist",
|
||||
Input: Param{Type: "ArtistInput"},
|
||||
Output: Param{Type: "ArtistOutput"},
|
||||
},
|
||||
},
|
||||
Structs: []StructDef{
|
||||
{Name: "ArtistInput", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
||||
{Name: "ArtistOutput", Fields: []FieldDef{{Name: "Name", Type: "string", JSONTag: "name"}}},
|
||||
},
|
||||
}
|
||||
|
||||
code, err := GenerateCapabilityRust(cap)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
codeStr := string(code)
|
||||
|
||||
// Non-primitive struct types should use $crate:: prefix
|
||||
Expect(codeStr).To(ContainSubstring("$crate::metadata::ArtistOutput"))
|
||||
})
|
||||
|
||||
It("should generate valid Rust code with pointer output types", func() {
|
||||
cap := Capability{
|
||||
Name: "test",
|
||||
Interface: "TestAgent",
|
||||
Required: true,
|
||||
SourceFile: "test",
|
||||
Methods: []Export{
|
||||
{
|
||||
Name: "GetOptionalStruct",
|
||||
ExportName: "nd_get_optional_struct",
|
||||
Input: Param{Type: "Input"},
|
||||
Output: Param{Type: "*Output"},
|
||||
},
|
||||
},
|
||||
Structs: []StructDef{
|
||||
{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
||||
{Name: "Output", Fields: []FieldDef{{Name: "Value", Type: "string", JSONTag: "value"}}},
|
||||
},
|
||||
}
|
||||
|
||||
code, err := GenerateCapabilityRust(cap)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
codeStr := string(code)
|
||||
|
||||
// Pointer to struct should strip pointer and use struct type with $crate::
|
||||
Expect(codeStr).To(ContainSubstring("$crate::test::Output>"))
|
||||
// Pointer output types should NOT have Option<> wrapping - Result handles optionality
|
||||
Expect(codeStr).NotTo(ContainSubstring("Option<"))
|
||||
})
|
||||
|
||||
It("should include all float types correctly", func() {
|
||||
cap := Capability{
|
||||
Name: "test",
|
||||
Interface: "TestAgent",
|
||||
Required: true,
|
||||
SourceFile: "test",
|
||||
Methods: []Export{
|
||||
{
|
||||
Name: "GetFloat32",
|
||||
ExportName: "nd_get_float32",
|
||||
Input: Param{Type: "Input"},
|
||||
Output: Param{Type: "float32"},
|
||||
},
|
||||
{
|
||||
Name: "GetFloat64",
|
||||
ExportName: "nd_get_float64",
|
||||
Input: Param{Type: "Input"},
|
||||
Output: Param{Type: "float64"},
|
||||
},
|
||||
},
|
||||
Structs: []StructDef{
|
||||
{Name: "Input", Fields: []FieldDef{{Name: "ID", Type: "string", JSONTag: "id"}}},
|
||||
},
|
||||
}
|
||||
|
||||
code, err := GenerateCapabilityRust(cap)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
codeStr := string(code)
|
||||
|
||||
Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<f32>>"))
|
||||
Expect(codeStr).To(ContainSubstring("FnResult<extism_pdk::Json<f64>>"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func writeFile(path, content string) error {
|
||||
return os.WriteFile(path, []byte(content), 0600)
|
||||
}
|
||||
|
||||
@ -1,373 +1,691 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestGenerateSchema(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
capability Capability
|
||||
wantErr bool
|
||||
validate func(t *testing.T, schema []byte)
|
||||
}{
|
||||
{
|
||||
name: "basic capability with one export",
|
||||
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"},
|
||||
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"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "TestOutput",
|
||||
Doc: "Output for test",
|
||||
Fields: []FieldDef{
|
||||
{Name: "Result", Type: "string", JSONTag: "result", Doc: "The result"},
|
||||
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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: func(t *testing.T, schema []byte) {
|
||||
var doc map[string]any
|
||||
require.NoError(t, yaml.Unmarshal(schema, &doc))
|
||||
}
|
||||
var err error
|
||||
schema, err = GenerateSchema(capability)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(schema).NotTo(BeEmpty())
|
||||
})
|
||||
|
||||
// Check version
|
||||
assert.Equal(t, "v1-draft", doc["version"])
|
||||
It("should have correct version", func() {
|
||||
doc := parseSchema(schema)
|
||||
Expect(doc["version"]).To(Equal("v1-draft"))
|
||||
})
|
||||
|
||||
// Check exports
|
||||
It("should include exports with description", func() {
|
||||
doc := parseSchema(schema)
|
||||
exports := doc["exports"].(map[string]any)
|
||||
assert.Contains(t, exports, "test_method")
|
||||
Expect(exports).To(HaveKey("test_method"))
|
||||
method := exports["test_method"].(map[string]any)
|
||||
assert.Equal(t, "Test method does something", method["description"])
|
||||
Expect(method["description"]).To(Equal("Test method does something"))
|
||||
})
|
||||
|
||||
// Check schemas
|
||||
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)
|
||||
assert.Contains(t, schemas, "TestInput")
|
||||
assert.Contains(t, schemas, "TestOutput")
|
||||
Expect(schemas).To(HaveKey("TestInput"))
|
||||
Expect(schemas).To(HaveKey("TestOutput"))
|
||||
})
|
||||
|
||||
// Check TestInput schema
|
||||
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)
|
||||
assert.Equal(t, "object", input["type"]) // Workaround for XTP code generator
|
||||
Expect(input["type"]).To(Equal("object")) // Workaround for XTP code generator
|
||||
props := input["properties"].(map[string]any)
|
||||
assert.Contains(t, props, "name")
|
||||
assert.Contains(t, props, "count")
|
||||
Expect(props).To(HaveKey("name"))
|
||||
Expect(props).To(HaveKey("count"))
|
||||
})
|
||||
|
||||
// Check required fields (non-pointer, non-omitempty)
|
||||
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)
|
||||
assert.Contains(t, required, "name")
|
||||
assert.Contains(t, required, "count")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "capability with pointer fields (nullable)",
|
||||
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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: func(t *testing.T, schema []byte) {
|
||||
var doc map[string]any
|
||||
require.NoError(t, yaml.Unmarshal(schema, &doc))
|
||||
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 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)
|
||||
|
||||
// Required field is not nullable
|
||||
requiredField := props["required"].(map[string]any)
|
||||
assert.NotContains(t, requiredField, "nullable")
|
||||
|
||||
// Optional pointer field is nullable
|
||||
optionalField := props["optional"].(map[string]any)
|
||||
assert.Equal(t, true, optionalField["nullable"])
|
||||
|
||||
// Check required array only has non-pointer fields
|
||||
required := input["required"].([]any)
|
||||
assert.Contains(t, required, "required")
|
||||
assert.NotContains(t, required, "optional")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "capability with enum",
|
||||
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"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: func(t *testing.T, schema []byte) {
|
||||
var doc map[string]any
|
||||
require.NoError(t, yaml.Unmarshal(schema, &doc))
|
||||
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())
|
||||
})
|
||||
|
||||
// Check enum is defined
|
||||
assert.Contains(t, schemas, "Status")
|
||||
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 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)
|
||||
assert.Equal(t, "string", status["type"])
|
||||
Expect(status["type"]).To(Equal("string"))
|
||||
enum := status["enum"].([]any)
|
||||
assert.ElementsMatch(t, []any{"pending", "active", "done"}, enum)
|
||||
Expect(enum).To(ConsistOf("pending", "active", "done"))
|
||||
})
|
||||
|
||||
// Check $ref in Input
|
||||
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)
|
||||
assert.Equal(t, "#/components/schemas/Status", statusRef["$ref"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "capability with array types",
|
||||
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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: func(t *testing.T, schema []byte) {
|
||||
var doc map[string]any
|
||||
require.NoError(t, yaml.Unmarshal(schema, &doc))
|
||||
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 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)
|
||||
|
||||
// Check string array
|
||||
tags := props["tags"].(map[string]any)
|
||||
assert.Equal(t, "array", tags["type"])
|
||||
Expect(tags["type"]).To(Equal("array"))
|
||||
tagItems := tags["items"].(map[string]any)
|
||||
assert.Equal(t, "string", tagItems["type"])
|
||||
Expect(tagItems["type"]).To(Equal("string"))
|
||||
})
|
||||
|
||||
// Check struct array (uses $ref)
|
||||
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)
|
||||
assert.Equal(t, "array", items["type"])
|
||||
Expect(items["type"]).To(Equal("array"))
|
||||
itemItems := items["items"].(map[string]any)
|
||||
assert.Equal(t, "#/components/schemas/Item", itemItems["$ref"])
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "capability with nullable ref",
|
||||
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"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validate: func(t *testing.T, schema []byte) {
|
||||
var doc map[string]any
|
||||
require.NoError(t, yaml.Unmarshal(schema, &doc))
|
||||
Expect(itemItems["$ref"]).To(Equal("#/components/schemas/Item"))
|
||||
})
|
||||
})
|
||||
|
||||
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())
|
||||
|
||||
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)
|
||||
|
||||
// Pointer to enum type should have $ref AND nullable
|
||||
status := props["status"].(map[string]any)
|
||||
assert.Equal(t, "#/components/schemas/ErrorType", status["$ref"])
|
||||
assert.Equal(t, true, status["nullable"])
|
||||
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", ""),
|
||||
)
|
||||
})
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
schema, err := GenerateSchema(tt.capability)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
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", 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())
|
||||
|
||||
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", 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())
|
||||
|
||||
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", 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())
|
||||
|
||||
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", 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())
|
||||
|
||||
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", 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())
|
||||
|
||||
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)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, schema)
|
||||
|
||||
if tt.validate != nil {
|
||||
tt.validate(t, schema)
|
||||
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())
|
||||
|
||||
schemas := getSchemas(schema)
|
||||
Expect(schemas).To(HaveKey("UsedInput"))
|
||||
Expect(schemas).To(HaveKey("UsedOutput"))
|
||||
Expect(schemas).NotTo(HaveKey("UnusedStruct"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoTypeToXTPTypeAndFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
goType string
|
||||
wantType string
|
||||
wantFormat string
|
||||
}{
|
||||
{"string", "string", ""},
|
||||
{"int", "integer", "int32"},
|
||||
{"int32", "integer", "int32"},
|
||||
{"int64", "integer", "int64"},
|
||||
{"float32", "number", "float"},
|
||||
{"float64", "number", "float"},
|
||||
{"bool", "boolean", ""},
|
||||
{"[]byte", "string", "byte"},
|
||||
// Unknown types default to object
|
||||
{"CustomType", "object", ""},
|
||||
}
|
||||
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())
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.goType, func(t *testing.T) {
|
||||
gotType, gotFormat := goTypeToXTPTypeAndFormat(tt.goType)
|
||||
assert.Equal(t, tt.wantType, gotType)
|
||||
assert.Equal(t, tt.wantFormat, gotFormat)
|
||||
schemas := getSchemas(schema)
|
||||
Expect(schemas).To(HaveKey("Input"))
|
||||
Expect(schemas).To(HaveKey("Output"))
|
||||
Expect(schemas).To(HaveKey("NestedType"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanDocForYAML(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
doc string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
doc: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "single line",
|
||||
doc: "Simple description",
|
||||
want: "Simple description",
|
||||
},
|
||||
{
|
||||
name: "multiline",
|
||||
doc: "First line\nSecond line",
|
||||
want: "First line\nSecond line",
|
||||
},
|
||||
{
|
||||
name: "trailing newline",
|
||||
doc: "Description\n",
|
||||
want: "Description",
|
||||
},
|
||||
{
|
||||
name: "whitespace",
|
||||
doc: " Description ",
|
||||
want: "Description",
|
||||
},
|
||||
}
|
||||
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())
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := cleanDocForYAML(tt.doc)
|
||||
assert.Equal(t, tt.want, got)
|
||||
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())
|
||||
|
||||
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())
|
||||
|
||||
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())
|
||||
|
||||
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"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user