Files
summercms/internal/tools/swagger2openapi/main_test.go
Jakub Zych ef448da1cc test(10-05): cover every Phase 10 Go change with branch-level tests
- bouncer TestPhase10CookieGuard: cookie read without Bearer, Bearer wins,
  empty cookie, frontend audience and blacklisted jti rejected
- boardwalk TestPhase10BoardwalkServing: HEAD, query strings, encoded
  traversal, index by name, nested prefix, MIME fallback, constructor errors
- cabana TestPhase10Coverage: mounted unsafe routes vs the CSRF walk, option
  and filter edges, read-only labels, relation message defaults, bundle
  fallback locale, cookie refresh of an expired token in the refresh window
- phrasebook override precedence, new locale, Bundle merge order, Forms shapes
- surf prefix collision for deeper paths and the default /backend prefix
- swagger2openapi TestUnionRewrite and converter branch tests
- framework tests no longer name the application (acme fixtures instead)
2026-09-27 18:05:28 +02:00

204 lines
7.9 KiB
Go

package main
import (
"encoding/json"
"reflect"
"testing"
)
func decode(t *testing.T, raw string) map[string]any {
t.Helper()
var doc map[string]any
if err := json.Unmarshal([]byte(raw), &doc); err != nil {
t.Fatal(err)
}
return doc
}
// TestUnionRewrite proves the two opaque cabana types become the documented
// unions (so openapi-typescript emits exact TS types) and that every other
// component is left untouched.
func TestUnionRewrite(t *testing.T) {
doc := decode(t, `{
"swagger": "2.0",
"definitions": {
"cabana.jsonScalar": {"type": "object"},
"cabana.fieldContext": {"type": "object"},
"cabana.FormField": {
"type": "object",
"properties": {
"default": {"$ref": "#/definitions/cabana.jsonScalar"},
"context": {"$ref": "#/definitions/cabana.fieldContext"},
"name": {"type": "string"}
}
},
"acme.Other": {"type": "object", "properties": {"x": {"type": "integer"}}}
}
}`)
untouchedOther := decode(t, `{"type": "object", "properties": {"x": {"type": "integer"}}}`)
out := swagger2openapi(doc)
schemas := out["components"].(map[string]any)["schemas"].(map[string]any)
wantScalar := decode(t, `{"nullable": true, "oneOf": [{"type": "string"}, {"type": "number"}, {"type": "boolean"}]}`)
if got := schemas["cabana.jsonScalar"]; !reflect.DeepEqual(got, wantScalar) {
t.Fatalf("jsonScalar = %#v", got)
}
wantContext := decode(t, `{"oneOf": [{"type": "string"}, {"type": "array", "items": {"type": "string"}}]}`)
if got := schemas["cabana.fieldContext"]; !reflect.DeepEqual(got, wantContext) {
t.Fatalf("fieldContext = %#v", got)
}
if got := schemas["acme.Other"]; !reflect.DeepEqual(got, untouchedOther) {
t.Fatalf("an unrelated component changed: %#v", got)
}
props := schemas["cabana.FormField"].(map[string]any)["properties"].(map[string]any)
if ref := props["default"].(map[string]any)["$ref"]; ref != "#/components/schemas/cabana.jsonScalar" {
t.Fatalf("ref not rewritten: %v", ref)
}
if props["name"].(map[string]any)["type"] != "string" {
t.Fatalf("plain property changed: %v", props["name"])
}
// Documents without the opaque types are not given them.
bare := swagger2openapi(decode(t, `{"definitions": {"acme.Other": {"type": "object"}}}`))
if _, ok := bare["components"].(map[string]any)["schemas"].(map[string]any)["cabana.jsonScalar"]; ok {
t.Fatal("rewriteOpaque added a union the document did not declare")
}
}
func TestConvertOperations(t *testing.T) {
doc := decode(t, `{
"swagger": "2.0",
"info": {"title": "Admin", "version": "1"},
"tags": [{"name": "admin"}],
"basePath": "/",
"paths": {
"/{vendor}/items": {
"parameters": [],
"get": {
"parameters": [
{"name": "vendor", "in": "path", "required": true, "type": "string"},
{"name": "page", "in": "query", "type": "integer", "minimum": 1},
{"name": "filter", "in": "query", "type": "object"}
],
"responses": {"200": {"description": "OK", "schema": {"$ref": "#/definitions/acme.List"}}, "204": {"description": "empty"}}
},
"post": {
"consumes": ["application/json"],
"produces": ["application/json"],
"parameters": [{"name": "body", "in": "body", "required": true, "description": "record", "schema": {"$ref": "#/definitions/acme.Rec"}}],
"responses": {"201": {"description": "Created", "schema": {"type": "object"}}}
},
"x-note": "kept"
}
},
"definitions": {"acme.List": {"type": "object"}, "acme.Rec": {"type": "object"}},
"securityDefinitions": {
"bearer": {"type": "apiKey", "name": "Authorization", "in": "header"},
"oauth": {"type": "oauth2", "flow": "accessCode", "authorizationUrl": "https://a", "tokenUrl": "https://t", "scopes": {"read": "r"}}
},
"security": [{"bearer": []}]
}`)
out := swagger2openapi(doc)
if out["openapi"] != "3.0.3" || out["info"].(map[string]any)["title"] != "Admin" || out["tags"] == nil || out["security"] == nil {
t.Fatalf("top level = %#v", out)
}
if _, ok := out["servers"]; ok {
t.Fatal("a root basePath without host produced servers")
}
item := out["paths"].(map[string]any)["/{vendor}/items"].(map[string]any)
if item["x-note"] != "kept" {
t.Fatal("extension key dropped")
}
get := item["get"].(map[string]any)
params := get["parameters"].([]any)
page := params[1].(map[string]any)
if page["schema"].(map[string]any)["minimum"] != float64(1) || page["minimum"] != nil {
t.Fatalf("schema keys not moved: %#v", page)
}
filter := params[2].(map[string]any)
if filter["style"] != "deepObject" || filter["explode"] != true ||
!reflect.DeepEqual(filter["schema"], map[string]any{"type": "object", "additionalProperties": map[string]any{"type": "string"}}) {
t.Fatalf("object query param = %#v", filter)
}
ok200 := get["responses"].(map[string]any)["200"].(map[string]any)
ref := ok200["content"].(map[string]any)["application/json"].(map[string]any)["schema"].(map[string]any)["$ref"]
if ref != "#/components/schemas/acme.List" {
t.Fatalf("response ref = %v", ref)
}
if _, ok := get["responses"].(map[string]any)["204"].(map[string]any)["content"]; ok {
t.Fatal("a response without schema got content")
}
post := item["post"].(map[string]any)
body := post["requestBody"].(map[string]any)
if body["required"] != true || body["description"] != "record" || post["parameters"] != nil {
t.Fatalf("request body = %#v params=%v", body, post["parameters"])
}
flows := out["components"].(map[string]any)["securitySchemes"].(map[string]any)["oauth"].(map[string]any)["flows"].(map[string]any)
code := flows["authorizationCode"].(map[string]any)
if code["tokenUrl"] != "https://t" || code["authorizationUrl"] != "https://a" {
t.Fatalf("oauth flows = %#v", flows)
}
}
func TestConvertServers(t *testing.T) {
cases := []struct {
doc string
want []any
}{
{`{}`, nil},
{`{"host": "api.test"}`, []any{map[string]any{"url": "https://api.test"}}},
{`{"host": "api.test", "basePath": "/v1", "schemes": ["http", "https"]}`, []any{
map[string]any{"url": "http://api.test/v1"}, map[string]any{"url": "https://api.test/v1"},
}},
{`{"basePath": "/v1"}`, []any{map[string]any{"url": "/v1"}}},
}
for _, tc := range cases {
if got := convertServers(decode(t, tc.doc)); !reflect.DeepEqual(got, tc.want) {
t.Fatalf("%s servers = %#v, want %#v", tc.doc, got, tc.want)
}
}
}
func TestOAuthFlowNames(t *testing.T) {
for flow, want := range map[string]string{
"implicit": "implicit",
"password": "password",
"application": "clientCredentials",
"accessCode": "authorizationCode",
} {
out := convertSecurity(map[string]any{"o": map[string]any{"type": "oauth2", "flow": flow, "scopes": map[string]any{}}})
flows := out["o"].(map[string]any)["flows"].(map[string]any)
if _, ok := flows[want]; !ok || len(flows) != 1 {
t.Fatalf("%s -> %#v", flow, flows)
}
}
if out := convertSecurity(map[string]any{"raw": "x"}); out["raw"] != "x" {
t.Fatal("non-object security scheme not kept")
}
}
func TestMalformedShapesPassThrough(t *testing.T) {
paths := convertPaths(map[string]any{"/a": "raw", "/b": map[string]any{"get": "raw", "produces": []any{"text/plain", 1, ""}}})
if paths["/a"] != "raw" || paths["/b"].(map[string]any)["get"] != "raw" {
t.Fatalf("malformed paths = %#v", paths)
}
op := convertOperation(map[string]any{"responses": "raw", "parameters": "raw", "summary": "s"}, nil, nil)
if op["responses"] != "raw" || op["summary"] != "s" {
t.Fatalf("malformed op = %#v", op)
}
params, body := splitParameters([]any{"loose"}, []string{"application/json"})
if len(params) != 1 || body != nil {
t.Fatalf("loose parameter = %#v %#v", params, body)
}
if res := convertResponses(map[string]any{"200": "raw"}, nil); res["200"] != "raw" {
t.Fatal("raw response not kept")
}
if got := stringList([]any{"a", 2, "", "b"}); !reflect.DeepEqual(got, []string{"a", "b"}) {
t.Fatalf("stringList = %v", got)
}
if got := rewriteRefs(map[string]any{"$ref": "#/other/x"}); got.(map[string]any)["$ref"] != "#/other/x" {
t.Fatalf("foreign ref rewritten: %v", got)
}
}