add game engine openapi
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGameOpenAPISpecValidates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
loadOpenAPISpec(t)
|
||||
}
|
||||
|
||||
func TestGameOpenAPISpecFreezesResponseSchemas(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
doc := loadOpenAPISpec(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
method string
|
||||
status int
|
||||
wantRef string
|
||||
}{
|
||||
{
|
||||
name: "get game status",
|
||||
path: "/api/v1/status",
|
||||
method: http.MethodGet,
|
||||
status: http.StatusOK,
|
||||
wantRef: "#/components/schemas/StateResponse",
|
||||
},
|
||||
{
|
||||
name: "init game",
|
||||
path: "/api/v1/init",
|
||||
method: http.MethodPost,
|
||||
status: http.StatusCreated,
|
||||
wantRef: "#/components/schemas/StateResponse",
|
||||
},
|
||||
{
|
||||
name: "get report",
|
||||
path: "/api/v1/report",
|
||||
method: http.MethodGet,
|
||||
status: http.StatusOK,
|
||||
wantRef: "#/components/schemas/Report",
|
||||
},
|
||||
{
|
||||
name: "generate turn",
|
||||
path: "/api/v1/turn",
|
||||
method: http.MethodPut,
|
||||
status: http.StatusOK,
|
||||
wantRef: "#/components/schemas/StateResponse",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
operation := getOpenAPIOperation(t, doc, tt.path, tt.method)
|
||||
assertSchemaRef(t, responseSchemaRef(t, operation, tt.status), tt.wantRef, tt.name+" response schema")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameOpenAPISpecFreezesInitRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
doc := loadOpenAPISpec(t)
|
||||
operation := getOpenAPIOperation(t, doc, "/api/v1/init", http.MethodPost)
|
||||
|
||||
assertSchemaRef(t, requestSchemaRef(t, operation), "#/components/schemas/InitRequest", "init request schema")
|
||||
|
||||
schema := componentSchemaRef(t, doc, "InitRequest")
|
||||
assertRequiredFields(t, schema, "races")
|
||||
|
||||
racesSchema := schema.Value.Properties["races"]
|
||||
require.NotNil(t, racesSchema, "InitRequest.races schema must exist")
|
||||
require.Equal(t, uint64(10), racesSchema.Value.MinItems, "InitRequest.races minItems must be 10")
|
||||
}
|
||||
|
||||
func TestGameOpenAPISpecFreezesCommandRequest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
doc := loadOpenAPISpec(t)
|
||||
|
||||
for _, path := range []string{"/api/v1/command", "/api/v1/order"} {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
operation := getOpenAPIOperation(t, doc, path, http.MethodPut)
|
||||
assertSchemaRef(t, requestSchemaRef(t, operation), "#/components/schemas/CommandRequest", path+" command request schema")
|
||||
})
|
||||
}
|
||||
|
||||
schema := componentSchemaRef(t, doc, "CommandRequest")
|
||||
assertRequiredFields(t, schema, "actor", "cmd")
|
||||
|
||||
cmdSchema := schema.Value.Properties["cmd"]
|
||||
require.NotNil(t, cmdSchema, "CommandRequest.cmd schema must exist")
|
||||
require.Equal(t, uint64(1), cmdSchema.Value.MinItems, "CommandRequest.cmd minItems must be 1")
|
||||
}
|
||||
|
||||
func TestGameOpenAPISpecCommandTypeEnumIsComplete(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
doc := loadOpenAPISpec(t)
|
||||
schema := componentSchemaRef(t, doc, "CommandType")
|
||||
|
||||
enumValues := make([]string, 0, len(schema.Value.Enum))
|
||||
for _, v := range schema.Value.Enum {
|
||||
s, ok := v.(string)
|
||||
require.True(t, ok, "CommandType enum entry must be a string")
|
||||
enumValues = append(enumValues, s)
|
||||
}
|
||||
|
||||
require.ElementsMatch(t, []string{
|
||||
"raceQuit",
|
||||
"raceVote",
|
||||
"raceRelation",
|
||||
"shipClassCreate",
|
||||
"shipClassMerge",
|
||||
"shipClassRemove",
|
||||
"shipGroupBreak",
|
||||
"shipGroupLoad",
|
||||
"shipGroupUnload",
|
||||
"shipGroupSend",
|
||||
"shipGroupUpgrade",
|
||||
"shipGroupMerge",
|
||||
"shipGroupDismantle",
|
||||
"shipGroupTransfer",
|
||||
"shipGroupJoinFleet",
|
||||
"fleetMerge",
|
||||
"fleetSend",
|
||||
"scienceCreate",
|
||||
"scienceRemove",
|
||||
"planetRename",
|
||||
"planetProduce",
|
||||
"planetRouteSet",
|
||||
"planetRouteRemove",
|
||||
}, enumValues)
|
||||
}
|
||||
|
||||
// helpers (modelled after user/openapi_contract_test.go)
|
||||
|
||||
func loadOpenAPISpec(t *testing.T) *openapi3.T {
|
||||
t.Helper()
|
||||
|
||||
_, thisFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
require.FailNow(t, "runtime.Caller failed")
|
||||
}
|
||||
|
||||
specPath := filepath.Join(filepath.Dir(thisFile), "openapi.yaml")
|
||||
loader := openapi3.NewLoader()
|
||||
doc, err := loader.LoadFromFile(specPath)
|
||||
if err != nil {
|
||||
require.Failf(t, "test failed", "load spec %s: %v", specPath, err)
|
||||
}
|
||||
if doc == nil {
|
||||
require.Failf(t, "test failed", "load spec %s: returned nil document", specPath)
|
||||
}
|
||||
if doc.Info == nil {
|
||||
require.Failf(t, "test failed", "load spec %s: missing info section", specPath)
|
||||
}
|
||||
if doc.Info.Version != "v1" {
|
||||
require.Failf(t, "test failed", "spec %s version = %q, want v1", specPath, doc.Info.Version)
|
||||
}
|
||||
if err := doc.Validate(context.Background()); err != nil {
|
||||
require.Failf(t, "test failed", "validate spec %s: %v", specPath, err)
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
func getOpenAPIOperation(t *testing.T, doc *openapi3.T, path string, method string) *openapi3.Operation {
|
||||
t.Helper()
|
||||
|
||||
if doc.Paths == nil {
|
||||
require.Failf(t, "test failed", "spec is missing paths while looking up %s %s", method, path)
|
||||
}
|
||||
pathItem := doc.Paths.Value(path)
|
||||
if pathItem == nil {
|
||||
require.Failf(t, "test failed", "spec is missing path %s", path)
|
||||
}
|
||||
operation := pathItem.GetOperation(method)
|
||||
if operation == nil {
|
||||
require.Failf(t, "test failed", "spec is missing %s operation for path %s", method, path)
|
||||
}
|
||||
|
||||
return operation
|
||||
}
|
||||
|
||||
func requestSchemaRef(t *testing.T, operation *openapi3.Operation) *openapi3.SchemaRef {
|
||||
t.Helper()
|
||||
|
||||
if operation.RequestBody == nil || operation.RequestBody.Value == nil {
|
||||
require.FailNow(t, "operation is missing request body")
|
||||
}
|
||||
mediaType := operation.RequestBody.Value.Content.Get("application/json")
|
||||
if mediaType == nil || mediaType.Schema == nil {
|
||||
require.FailNow(t, "operation is missing application/json request schema")
|
||||
}
|
||||
|
||||
return mediaType.Schema
|
||||
}
|
||||
|
||||
func responseSchemaRef(t *testing.T, operation *openapi3.Operation, status int) *openapi3.SchemaRef {
|
||||
t.Helper()
|
||||
|
||||
if operation.Responses == nil {
|
||||
require.Failf(t, "test failed", "operation is missing responses for status %d", status)
|
||||
}
|
||||
response := operation.Responses.Status(status)
|
||||
if response == nil || response.Value == nil {
|
||||
require.Failf(t, "test failed", "operation is missing response for status %d", status)
|
||||
}
|
||||
mediaType := response.Value.Content.Get("application/json")
|
||||
if mediaType == nil || mediaType.Schema == nil {
|
||||
require.Failf(t, "test failed", "operation response %d is missing application/json schema", status)
|
||||
}
|
||||
|
||||
return mediaType.Schema
|
||||
}
|
||||
|
||||
func componentSchemaRef(t *testing.T, doc *openapi3.T, name string) *openapi3.SchemaRef {
|
||||
t.Helper()
|
||||
|
||||
if doc.Components == nil {
|
||||
require.Failf(t, "test failed", "spec is missing components while looking up schema %s", name)
|
||||
}
|
||||
schema := doc.Components.Schemas[name]
|
||||
if schema == nil || schema.Value == nil {
|
||||
require.Failf(t, "test failed", "spec is missing schema %s", name)
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
func assertSchemaRef(t *testing.T, schemaRef *openapi3.SchemaRef, want string, name string) {
|
||||
t.Helper()
|
||||
|
||||
if schemaRef == nil {
|
||||
require.Failf(t, "test failed", "%s schema ref is nil", name)
|
||||
}
|
||||
if schemaRef.Ref != want {
|
||||
require.Failf(t, "test failed", "%s ref = %q, want %q", name, schemaRef.Ref, want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertRequiredFields(t *testing.T, schemaRef *openapi3.SchemaRef, fields ...string) {
|
||||
t.Helper()
|
||||
|
||||
required := append([]string(nil), schemaRef.Value.Required...)
|
||||
slices.Sort(required)
|
||||
want := append([]string(nil), fields...)
|
||||
slices.Sort(want)
|
||||
if !slices.Equal(required, want) {
|
||||
require.Failf(t, "test failed", "schema required fields = %v, want %v", required, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user