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)
This commit is contained in:
203
internal/tools/swagger2openapi/main_test.go
Normal file
203
internal/tools/swagger2openapi/main_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user