969c0480ba
Engine wire change: Report.battle switched from []uuid.UUID to
[]BattleSummary{id, planet, shots} so the map can place battle
markers without N extra fetches. FBS schema + generated Go/TS
regenerated; transcoder + report controller updated; openapi
adds the BattleSummary schema with a freeze test.
Backend gateway forwards engine GET /api/v1/battle/:turn/:uuid as
/api/v1/user/games/{game_id}/battles/{turn}/{battle_id} (handler
plus engineclient.FetchBattle, contract test stub, openapi spec).
UI:
- BattleViewer (lib/battle-player/) is a logically isolated SVG
radial scene that consumes a BattleReport prop. Planet at the
centre, races on the outer ring at equal angular spacing, race
clusters by (race, className) with <class>:<numLeft> labels;
observer groups (inBattle: false) are not drawn; eliminated
races drop out and survivors re-distribute on the next frame.
- Shot line per frame: red on destroyed, green otherwise; erased
on the next frame. Playback controls: play/pause + step ± +
rewind + 1x/2x/4x speed (400/200/100 ms per frame).
- Page wrapper (lib/active-view/battle.svelte) loads BattleReport
via api/battle-fetch.ts; synthetic-gameId prefix routes to a
fixture loader, otherwise REST through the gateway. Always-
visible <ol> text protocol satisfies the accessibility ask.
- section-battles.svelte links every battle UUID into the viewer.
- map/battle-markers.ts: yellow X cross of 2 LinePrim through the
corners of the planet's circumscribed square (stroke width
clamps from 1 px at 1 shot to 5 px at 100+ shots); bombing
marker is a stroke-only ring (yellow when damaged, red when
wiped). Wired into state-binding.ts; click handler dispatches
battle clicks to the viewer and bombing clicks to the matching
Reports row.
- i18n keys for the viewer in en + ru.
Docs: ui/docs/battle-viewer-ux.md, FUNCTIONAL.md §6.5 + ru
mirror, ui/PLAN.md Phase 27 decisions + deferred TODOs (push
event, richer class visuals, animated re-distribution).
Tests: Vitest unit (radial layout + timeline frame builder +
marker stroke formula + marker primitives), Playwright e2e for
the viewer (Reports link → viewer, playback step, not-found),
backend engineclient FetchBattle (200 / 404 / bad input), engine
openapi freezes (BattleReport, BattleReportGroup,
BattleActionReport, BattleSummary, Report.battle items).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
517 lines
16 KiB
Go
517 lines
16 KiB
Go
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: "admin get game status",
|
|
path: "/api/v1/admin/status",
|
|
method: http.MethodGet,
|
|
status: http.StatusOK,
|
|
wantRef: "#/components/schemas/StateResponse",
|
|
},
|
|
{
|
|
name: "admin init game",
|
|
path: "/api/v1/admin/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: "admin generate turn",
|
|
path: "/api/v1/admin/turn",
|
|
method: http.MethodPut,
|
|
status: http.StatusOK,
|
|
wantRef: "#/components/schemas/StateResponse",
|
|
},
|
|
{
|
|
name: "put order",
|
|
path: "/api/v1/order",
|
|
method: http.MethodPut,
|
|
status: http.StatusAccepted,
|
|
wantRef: "#/components/schemas/UserGamesOrder",
|
|
},
|
|
{
|
|
name: "get order",
|
|
path: "/api/v1/order",
|
|
method: http.MethodGet,
|
|
status: http.StatusOK,
|
|
wantRef: "#/components/schemas/UserGamesOrder",
|
|
},
|
|
{
|
|
name: "healthz probe",
|
|
path: "/healthz",
|
|
method: http.MethodGet,
|
|
status: http.StatusOK,
|
|
wantRef: "#/components/schemas/HealthzResponse",
|
|
},
|
|
{
|
|
name: "get battle",
|
|
path: "/api/v1/battle/{turn}/{uuid}",
|
|
method: http.MethodGet,
|
|
status: http.StatusOK,
|
|
wantRef: "#/components/schemas/BattleReport",
|
|
},
|
|
}
|
|
|
|
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 TestGameOpenAPISpecFreezesEmptyResponses(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
status int
|
|
}{
|
|
{
|
|
name: "command accepted",
|
|
path: "/api/v1/command",
|
|
method: http.MethodPut,
|
|
status: http.StatusAccepted,
|
|
},
|
|
{
|
|
name: "get order no content",
|
|
path: "/api/v1/order",
|
|
method: http.MethodGet,
|
|
status: http.StatusNoContent,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
operation := getOpenAPIOperation(t, doc, tt.path, tt.method)
|
|
require.NotNil(t, operation.Responses, "operation must declare responses")
|
|
response := operation.Responses.Status(tt.status)
|
|
require.NotNil(t, response, "operation must declare %d response", tt.status)
|
|
require.NotNil(t, response.Value, "%d response must have a value", tt.status)
|
|
require.Empty(t, response.Value.Content, "%d response must carry no body", tt.status)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesUserGamesOrder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
schema := componentSchemaRef(t, doc, "UserGamesOrder")
|
|
|
|
assertRequiredFields(t, schema, "game_id", "updatedAt", "cmd")
|
|
|
|
gameIDSchema := schema.Value.Properties["game_id"]
|
|
require.NotNil(t, gameIDSchema, "UserGamesOrder.game_id schema must exist")
|
|
require.Equal(t, "uuid", gameIDSchema.Value.Format, "UserGamesOrder.game_id format must be uuid")
|
|
|
|
updatedAtSchema := schema.Value.Properties["updatedAt"]
|
|
require.NotNil(t, updatedAtSchema, "UserGamesOrder.updatedAt schema must exist")
|
|
require.True(t, updatedAtSchema.Value.Type.Is("integer"), "UserGamesOrder.updatedAt must be integer")
|
|
require.Equal(t, "int64", updatedAtSchema.Value.Format, "UserGamesOrder.updatedAt format must be int64")
|
|
|
|
cmdSchema := schema.Value.Properties["cmd"]
|
|
require.NotNil(t, cmdSchema, "UserGamesOrder.cmd schema must exist")
|
|
require.True(t, cmdSchema.Value.Type.Is("array"), "UserGamesOrder.cmd must be array")
|
|
require.NotNil(t, cmdSchema.Value.Items, "UserGamesOrder.cmd items must be defined")
|
|
assertSchemaRef(t, cmdSchema.Value.Items, "#/components/schemas/Command", "UserGamesOrder.cmd items schema")
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesGetOrderOperation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
operation := getOpenAPIOperation(t, doc, "/api/v1/order", http.MethodGet)
|
|
|
|
require.Equal(t, "getOrder", operation.OperationID, "GET /api/v1/order operation id")
|
|
|
|
paramRefs := make(map[string]bool)
|
|
for _, p := range operation.Parameters {
|
|
require.NotNil(t, p.Value, "parameter must have value")
|
|
paramRefs[p.Ref] = true
|
|
}
|
|
require.True(t, paramRefs["#/components/parameters/PlayerParam"], "GET /api/v1/order must reference PlayerParam")
|
|
require.True(t, paramRefs["#/components/parameters/TurnParam"], "GET /api/v1/order must reference TurnParam")
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesInitRequest(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
operation := getOpenAPIOperation(t, doc, "/api/v1/admin/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 TestGameOpenAPISpecFreezesAdminOperationIDs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
|
|
tests := []struct {
|
|
path string
|
|
method string
|
|
opID string
|
|
}{
|
|
{"/api/v1/admin/init", http.MethodPost, "adminInitGame"},
|
|
{"/api/v1/admin/status", http.MethodGet, "adminGetGameStatus"},
|
|
{"/api/v1/admin/turn", http.MethodPut, "adminGenerateTurn"},
|
|
{"/api/v1/admin/race/banish", http.MethodPost, "adminBanishRace"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.opID, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
operation := getOpenAPIOperation(t, doc, tt.path, tt.method)
|
|
require.Equal(t, tt.opID, operation.OperationID, "operation id for %s %s", tt.method, tt.path)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesBanishRequest(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
operation := getOpenAPIOperation(t, doc, "/api/v1/admin/race/banish", http.MethodPost)
|
|
|
|
assertSchemaRef(t, requestSchemaRef(t, operation), "#/components/schemas/BanishRequest", "banish request schema")
|
|
|
|
if operation.Responses == nil {
|
|
require.FailNow(t, "banish operation is missing responses")
|
|
}
|
|
noContent := operation.Responses.Status(http.StatusNoContent)
|
|
require.NotNil(t, noContent, "banish operation must declare 204 response")
|
|
require.NotNil(t, noContent.Value, "banish 204 response must have a value")
|
|
|
|
schema := componentSchemaRef(t, doc, "BanishRequest")
|
|
assertRequiredFields(t, schema, "race_name")
|
|
|
|
raceNameSchema := schema.Value.Properties["race_name"]
|
|
require.NotNil(t, raceNameSchema, "BanishRequest.race_name schema must exist")
|
|
require.Equal(t, uint64(1), raceNameSchema.Value.MinLength, "BanishRequest.race_name minLength must be 1")
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesStateResponseFinished(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
schema := componentSchemaRef(t, doc, "StateResponse")
|
|
|
|
assertRequiredFields(t, schema, "id", "turn", "stage", "player", "finished")
|
|
|
|
finishedSchema := schema.Value.Properties["finished"]
|
|
require.NotNil(t, finishedSchema, "StateResponse.finished schema must exist")
|
|
require.True(t, finishedSchema.Value.Type.Is("boolean"), "StateResponse.finished must be boolean")
|
|
}
|
|
|
|
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 TestGameOpenAPISpecFreezesGetBattleOperation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
operation := getOpenAPIOperation(t, doc, "/api/v1/battle/{turn}/{uuid}", http.MethodGet)
|
|
|
|
require.Equal(t, "getBattle", operation.OperationID, "GET /api/v1/battle/{turn}/{uuid} operation id")
|
|
|
|
paramRefs := make(map[string]bool)
|
|
for _, p := range operation.Parameters {
|
|
require.NotNil(t, p.Value, "parameter must have value")
|
|
paramRefs[p.Ref] = true
|
|
}
|
|
require.True(t, paramRefs["#/components/parameters/BattleTurnParam"], "GET /api/v1/battle/{turn}/{uuid} must reference BattleTurnParam")
|
|
require.True(t, paramRefs["#/components/parameters/BattleIDParam"], "GET /api/v1/battle/{turn}/{uuid} must reference BattleIDParam")
|
|
|
|
require.NotNil(t, operation.Responses, "operation must declare responses")
|
|
notFound := operation.Responses.Status(http.StatusNotFound)
|
|
require.NotNil(t, notFound, "operation must declare 404 response")
|
|
require.NotNil(t, notFound.Value, "404 response must have a value")
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesBattleReport(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
|
|
reportSchema := componentSchemaRef(t, doc, "BattleReport")
|
|
assertRequiredFields(t, reportSchema, "id", "planet", "planetName", "races", "ships", "protocol")
|
|
|
|
groupSchema := componentSchemaRef(t, doc, "BattleReportGroup")
|
|
assertRequiredFields(t, groupSchema, "race", "className", "tech", "num", "numLeft", "loadType", "loadQuantity", "inBattle")
|
|
|
|
actionSchema := componentSchemaRef(t, doc, "BattleActionReport")
|
|
assertRequiredFields(t, actionSchema, "a", "sa", "d", "sd", "x")
|
|
|
|
protocolSchema := reportSchema.Value.Properties["protocol"]
|
|
require.NotNil(t, protocolSchema, "BattleReport.protocol schema must exist")
|
|
require.True(t, protocolSchema.Value.Type.Is("array"), "BattleReport.protocol must be array")
|
|
require.NotNil(t, protocolSchema.Value.Items, "BattleReport.protocol items must be defined")
|
|
assertSchemaRef(t, protocolSchema.Value.Items, "#/components/schemas/BattleActionReport", "BattleReport.protocol items schema")
|
|
|
|
shipsSchema := reportSchema.Value.Properties["ships"]
|
|
require.NotNil(t, shipsSchema, "BattleReport.ships schema must exist")
|
|
require.True(t, shipsSchema.Value.Type.Is("object"), "BattleReport.ships must be object")
|
|
require.NotNil(t, shipsSchema.Value.AdditionalProperties.Schema, "BattleReport.ships additionalProperties must be a schema")
|
|
assertSchemaRef(t, shipsSchema.Value.AdditionalProperties.Schema, "#/components/schemas/BattleReportGroup", "BattleReport.ships additionalProperties schema")
|
|
}
|
|
|
|
func TestGameOpenAPISpecFreezesBattleSummary(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
|
|
summary := componentSchemaRef(t, doc, "BattleSummary")
|
|
assertRequiredFields(t, summary, "id", "planet", "shots")
|
|
|
|
report := componentSchemaRef(t, doc, "Report")
|
|
battle := report.Value.Properties["battle"]
|
|
require.NotNil(t, battle, "Report.battle schema must exist")
|
|
require.True(t, battle.Value.Type.Is("array"), "Report.battle must be array")
|
|
require.NotNil(t, battle.Value.Items, "Report.battle items must be defined")
|
|
assertSchemaRef(t, battle.Value.Items, "#/components/schemas/BattleSummary", "Report.battle items schema")
|
|
}
|
|
|
|
func TestGameOpenAPISpecHealthzStatusEnum(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
doc := loadOpenAPISpec(t)
|
|
schema := componentSchemaRef(t, doc, "HealthzResponse")
|
|
|
|
assertRequiredFields(t, schema, "status")
|
|
|
|
statusSchema := schema.Value.Properties["status"]
|
|
require.NotNil(t, statusSchema, "HealthzResponse.status schema must exist")
|
|
require.Equal(t, []any{"ok"}, statusSchema.Value.Enum, "HealthzResponse.status enum must be [\"ok\"]")
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|