blob: 4e9cb693760bd10c1bcd13d21458c1c12ff1305e [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package openapi
import (
"encoding/json"
"strings"
"testing"
)
import (
"dubbo.apache.org/dubbo-go/v3/protocol/triple/openapi/model"
)
// --- Encode tests ---
func TestEncoder_Encode_NilOpenAPI(t *testing.T) {
e := NewEncoder()
result, err := e.Encode(nil, "json", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result == "" {
t.Error("result should not be empty")
}
// Should produce valid JSON with default OpenAPI structure
var parsed map[string]any
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
t.Fatalf("result should be valid JSON: %v", err)
}
if v, ok := parsed["openapi"]; !ok || v != model.OpenAPIVersion30 {
t.Errorf("openapi version = %v, want %s", v, model.OpenAPIVersion30)
}
}
func TestEncoder_Encode_JSONPretty(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Info.Title = "Test API"
openAPI.Info.Version = "1.0.0"
result, err := e.Encode(openAPI, "json", true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(result, " ") {
t.Error("pretty JSON should contain indentation")
}
}
func TestEncoder_Encode_JSONCompact(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Info.Title = "Test API"
result, err := e.Encode(openAPI, "json", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if strings.Contains(result, "\n ") {
t.Error("compact JSON should not contain indentation")
}
}
func TestEncoder_Encode_YAML(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Info.Title = "Test API"
openAPI.Info.Version = "1.0.0"
result, err := e.Encode(openAPI, "yaml", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(result, "openapi:") {
t.Error("YAML output should contain 'openapi:' key")
}
if !strings.Contains(result, "title: Test API") {
t.Error("YAML output should contain title")
}
}
func TestEncoder_Encode_YML(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
result, err := e.Encode(openAPI, "yml", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !strings.Contains(result, "openapi:") {
t.Error("YML output should contain 'openapi:' key")
}
}
func TestEncoder_Encode_UnknownFormat_FallbackJSON(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Info.Title = "Test API"
result, err := e.Encode(openAPI, "xml", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Should fallback to JSON
var parsed map[string]any
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
t.Fatalf("unknown format should fallback to JSON, but got invalid JSON: %v", err)
}
}
func TestEncoder_Encode_EmptyFormat_DefaultJSON(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
result, err := e.Encode(openAPI, "", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var parsed map[string]any
if err := json.Unmarshal([]byte(result), &parsed); err != nil {
t.Fatalf("empty format should default to JSON: %v", err)
}
}
// --- toMap tests ---
func TestEncoder_toMap_FullOpenAPI(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Info.Title = "Test API"
openAPI.Info.Version = "1.0.0"
openAPI.Info.Description = "A test API"
openAPI.AddPath("/test", model.NewPathItem().SetOperation("POST",
model.NewOperation().SetOperationId("testOp")))
openAPI.Components = model.NewComponents()
openAPI.Components.AddSchema("TestSchema", model.NewSchema().SetType(model.SchemaTypeObject))
m := e.toMap(openAPI)
if m["openapi"] != model.OpenAPIVersion30 {
t.Errorf("openapi = %v, want %s", m["openapi"], model.OpenAPIVersion30)
}
if _, ok := m["info"]; !ok {
t.Error("info should be present")
}
if _, ok := m["paths"]; !ok {
t.Error("paths should be present")
}
if _, ok := m["components"]; !ok {
t.Error("components should be present")
}
}
func TestEncoder_toMap_NoPaths(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
m := e.toMap(openAPI)
if _, ok := m["paths"]; ok {
t.Error("paths should not be present when empty")
}
}
func TestEncoder_toMap_NoComponents(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Components = nil
m := e.toMap(openAPI)
if _, ok := m["components"]; ok {
t.Error("components should not be present when nil")
}
}
// --- infoToMap tests ---
func TestEncoder_infoToMap(t *testing.T) {
e := NewEncoder()
info := &model.Info{
Title: "Test",
Description: "Desc",
Version: "1.0",
}
m := e.infoToMap(info)
if m["title"] != "Test" {
t.Errorf("title = %v, want Test", m["title"])
}
if m["description"] != "Desc" {
t.Errorf("description = %v, want Desc", m["description"])
}
if m["version"] != "1.0" {
t.Errorf("version = %v, want 1.0", m["version"])
}
}
func TestEncoder_infoToMap_EmptyFields(t *testing.T) {
e := NewEncoder()
info := &model.Info{}
m := e.infoToMap(info)
if _, ok := m["title"]; ok {
t.Error("empty title should not be in map")
}
if _, ok := m["description"]; ok {
t.Error("empty description should not be in map")
}
if _, ok := m["version"]; ok {
t.Error("empty version should not be in map")
}
}
// --- schemaToMap tests ---
func TestEncoder_schemaToMap_Ref(t *testing.T) {
e := NewEncoder()
schema := &model.Schema{Ref: "#/components/schemas/MyType"}
m := e.schemaToMap(schema)
if m["$ref"] != "#/components/schemas/MyType" {
t.Errorf("$ref = %v, want #/components/schemas/MyType", m["$ref"])
}
// Ref schemas should only have $ref
if len(m) != 1 {
t.Errorf("ref schema should only have $ref field, got %d fields", len(m))
}
}
func TestEncoder_schemaToMap_FullSchema(t *testing.T) {
e := NewEncoder()
schema := model.NewSchema().
SetType(model.SchemaTypeObject).
SetFormat("email").
SetTitle("EmailSchema").
SetDescription("An email").
SetRequired(true).
SetExample("test@example.com")
schema.Enum = []any{"a", "b"}
m := e.schemaToMap(schema)
if m["type"] != "object" {
t.Errorf("type = %v, want object", m["type"])
}
if m["format"] != "email" {
t.Errorf("format = %v, want email", m["format"])
}
if m["title"] != "EmailSchema" {
t.Errorf("title = %v, want EmailSchema", m["title"])
}
if m["description"] != "An email" {
t.Errorf("description = %v", m["description"])
}
if m["required"] != true {
t.Errorf("required = %v, want true", m["required"])
}
if m["example"] != "test@example.com" {
t.Errorf("example = %v", m["example"])
}
}
func TestEncoder_schemaToMap_ArrayWithItems(t *testing.T) {
e := NewEncoder()
schema := model.NewSchema().
SetType(model.SchemaTypeArray).
SetItems(model.NewSchema().SetType(model.SchemaTypeString))
m := e.schemaToMap(schema)
if m["type"] != "array" {
t.Errorf("type = %v, want array", m["type"])
}
items, ok := m["items"].(map[string]any)
if !ok {
t.Fatal("items should be map[string]any")
}
if items["type"] != "string" {
t.Errorf("items.type = %v, want string", items["type"])
}
}
func TestEncoder_schemaToMap_ObjectWithProperties(t *testing.T) {
e := NewEncoder()
schema := model.NewSchema().SetType(model.SchemaTypeObject)
schema.AddProperty("name", model.NewSchema().SetType(model.SchemaTypeString))
schema.AddProperty("age", model.NewSchema().SetType(model.SchemaTypeInteger))
m := e.schemaToMap(schema)
props, ok := m["properties"].(map[string]any)
if !ok {
t.Fatal("properties should be map[string]any")
}
nameProp, ok := props["name"].(map[string]any)
if !ok {
t.Fatal("name property should be map[string]any")
}
if nameProp["type"] != "string" {
t.Errorf("name.type = %v, want string", nameProp["type"])
}
}
func TestEncoder_schemaToMap_AdditionalProperties_Schema(t *testing.T) {
e := NewEncoder()
// This is the bug fix test: additionalProperties with *Schema should be serialized properly
schema := model.NewSchema().
SetType(model.SchemaTypeObject).
SetAdditionalProperties(model.NewSchema().SetType(model.SchemaTypeInteger))
m := e.schemaToMap(schema)
ap, ok := m["additionalProperties"]
if !ok {
t.Fatal("additionalProperties should be present")
}
// After fix, additionalProperties should be a map, not a *model.Schema
apMap, ok := ap.(map[string]any)
if !ok {
t.Fatalf("additionalProperties should be map[string]any, got %T", ap)
}
if apMap["type"] != "integer" {
t.Errorf("additionalProperties.type = %v, want integer", apMap["type"])
}
}
func TestEncoder_schemaToMap_AdditionalProperties_Bool(t *testing.T) {
e := NewEncoder()
schema := model.NewSchema().
SetType(model.SchemaTypeObject).
SetAdditionalProperties(true)
m := e.schemaToMap(schema)
ap, ok := m["additionalProperties"]
if !ok {
t.Fatal("additionalProperties should be present")
}
if ap != true {
t.Errorf("additionalProperties = %v, want true", ap)
}
}
// --- operationToMap tests ---
func TestEncoder_operationToMap(t *testing.T) {
e := NewEncoder()
op := model.NewOperation().
SetOperationId("testOp").
AddTag("tag1")
op.SetRequestBody(model.NewRequestBody().SetRequired(true))
op.GetOrAddResponse("200").Description = "OK"
m := e.operationToMap(op)
if m["operationId"] != "testOp" {
t.Errorf("operationId = %v, want testOp", m["operationId"])
}
if _, ok := m["tags"]; !ok {
t.Error("tags should be present")
}
if _, ok := m["requestBody"]; !ok {
t.Error("requestBody should be present")
}
if _, ok := m["responses"]; !ok {
t.Error("responses should be present")
}
}
// --- componentsToMap tests ---
func TestEncoder_componentsToMap(t *testing.T) {
e := NewEncoder()
c := model.NewComponents()
c.AddSchema("User", model.NewSchema().SetType(model.SchemaTypeObject).SetTitle("User"))
c.AddSchema("Req", model.NewSchema().SetType(model.SchemaTypeObject).SetTitle("Req"))
m := e.componentsToMap(c)
schemas, ok := m["schemas"].(map[string]any)
if !ok {
t.Fatal("schemas should be map[string]any")
}
if len(schemas) != 2 {
t.Errorf("expected 2 schemas, got %d", len(schemas))
}
}
func TestEncoder_componentsToMap_Empty(t *testing.T) {
e := NewEncoder()
c := model.NewComponents()
m := e.componentsToMap(c)
if _, ok := m["schemas"]; ok {
t.Error("empty schemas should not be in map")
}
}
// --- round-trip test ---
func TestEncoder_RoundTrip_JSON(t *testing.T) {
e := NewEncoder()
openAPI := model.NewOpenAPI()
openAPI.Info.Title = "Round Trip API"
openAPI.Info.Version = "1.0.0"
openAPI.AddPath("/test", model.NewPathItem().SetOperation("POST",
model.NewOperation().SetOperationId("testOp")))
jsonStr, err := e.Encode(openAPI, "json", true)
if err != nil {
t.Fatalf("encode error: %v", err)
}
var parsed map[string]any
if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil {
t.Fatalf("unmarshal error: %v", err)
}
if parsed["openapi"] != model.OpenAPIVersion30 {
t.Errorf("openapi = %v, want %s", parsed["openapi"], model.OpenAPIVersion30)
}
info, ok := parsed["info"].(map[string]any)
if !ok {
t.Fatal("info should be map[string]any")
}
if info["title"] != "Round Trip API" {
t.Errorf("title = %v, want Round Trip API", info["title"])
}
}