test(api): 处理器测试的每次请求在包结束后对照 openapi 校验状态码、响应与 2xx 请求体,SetupAllowed 纳入一致性比对,补齐漏记的状态码并修正空列表回 null
This commit is contained in:
11 files changed
+937
-28
No files matched your search
+174
-9
@@ -12,15 +12,52 @@
|
||||
# from (internalAPIRoutes / externalAPIRoutes in internal/api/api.go). A route
|
||||
# added, removed, re-faced, or re-tiered without updating this file fails
|
||||
# `go test ./...`. So path, method, face and tier are as trustworthy as the code.
|
||||
# * The request/response BODY schemas below are hand-maintained from the Go
|
||||
# handler types and are NOT yet schema-validated against live traffic. Treat
|
||||
# them as documentation (SHAPE-ASSERTED), not as a contract test.
|
||||
# * Which operations a setup-lockdown session may still use (x-felis-setup-allowed)
|
||||
# is checked the same way against the SetupAllowed flag in those tables.
|
||||
# * The named response schemas are compared field by field with the Go structs
|
||||
# the handlers encode (internal/api/openapi_parity_test.go).
|
||||
# * Every request the handler tests send is held to this file once the package
|
||||
# has run (internal/api/openapi_contract_test.go): the operation (or
|
||||
# x-felis-common-responses) must list the status that came back, a JSON response
|
||||
# must fit the schema for that status and carry no property it does not name,
|
||||
# and the JSON request behind a 2xx must fit the requestBody. Statuses and
|
||||
# bodies no test reaches are still hand-maintained.
|
||||
#
|
||||
# The deployment zone (RootDomain, spec §2) never appears here — `example.test`
|
||||
# is a placeholder, per the no-hardcoded-domain red line.
|
||||
|
||||
openapi: 3.1.0
|
||||
|
||||
# Answers any operation can give under the stated condition, declared once here
|
||||
# instead of under every operation. when: any | internal (served on the internal
|
||||
# face) | session (takes the session cookie) | setup-locked (takes the session
|
||||
# cookie and is not x-felis-setup-allowed) | json-body (takes a JSON requestBody).
|
||||
# code, when set, is the error code that answer carries.
|
||||
x-felis-common-responses:
|
||||
- status: 500
|
||||
when: any
|
||||
response: { $ref: '#/components/responses/InternalError' }
|
||||
- status: 403
|
||||
when: internal
|
||||
code: wrong_caller
|
||||
response: { $ref: '#/components/responses/WrongCaller' }
|
||||
- status: 403
|
||||
when: session
|
||||
code: forbidden
|
||||
response: { $ref: '#/components/responses/StaffOnlyHost' }
|
||||
- status: 403
|
||||
when: setup-locked
|
||||
code: setup_required
|
||||
response: { $ref: '#/components/responses/SetupRequired' }
|
||||
- status: 413
|
||||
when: json-body
|
||||
code: too_large
|
||||
response: { $ref: '#/components/responses/TooLarge' }
|
||||
- status: 415
|
||||
when: json-body
|
||||
code: unsupported_media_type
|
||||
response: { $ref: '#/components/responses/UnsupportedMediaType' }
|
||||
|
||||
info:
|
||||
title: felis-api
|
||||
version: 4.1.0
|
||||
@@ -124,6 +161,41 @@ components:
|
||||
responses:
|
||||
NoContent:
|
||||
description: Success, no body.
|
||||
InternalError:
|
||||
description: An unexpected failure (internal); the details are in the server log under the request id.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
WrongCaller:
|
||||
description: A genuine service token for a caller this operation does not serve (wrong_caller).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
StaffOnlyHost:
|
||||
description: A session of a player (not admin or owner) on the operator console host, refused before any handler (forbidden).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
SetupRequired:
|
||||
description: The session belongs to an account still in first-run setup, which may use only the x-felis-setup-allowed operations until it has a durable sign-in (setup_required).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
TooLarge:
|
||||
description: The JSON body is over 1 MiB (too_large).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
UnsupportedMediaType:
|
||||
description: A body sent with a Content-Type other than application/json (unsupported_media_type).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
InsufficientStorage:
|
||||
description: The store this writes to is full — the backup archive (backup_store_full), the world volume (volume_full) or the upload area (uploads_full).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
BadRequest:
|
||||
description: Malformed or invalid request (validation, bad body, unknown field).
|
||||
content:
|
||||
@@ -976,6 +1048,11 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
description: The node is at its running-server cap (at_capacity).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/internal/servers/{name}/status:
|
||||
get:
|
||||
@@ -1259,6 +1336,11 @@ paths:
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'409':
|
||||
description: The linked account holds a staff role and is never reclaimed (protected_admin).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/internal/player/blacklist/{mc_uuid}:
|
||||
get:
|
||||
@@ -1553,6 +1635,11 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
description: The node is at its running-server cap (at_capacity).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/servers/{name}/stop:
|
||||
post:
|
||||
@@ -1697,11 +1784,21 @@ paths:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown server.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: Server not running.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'429':
|
||||
description: This session already holds as many console streams as it may (too_many_streams).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
|
||||
@@ -2314,7 +2411,7 @@ paths:
|
||||
required: [user_id, role]
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
role: { type: string, enum: [user, admin] }
|
||||
role: { type: string, enum: [owner, admin, user] }
|
||||
'400':
|
||||
description: >-
|
||||
Invalid email or missing assertion (bad_request); or the login could not be
|
||||
@@ -2464,7 +2561,7 @@ paths:
|
||||
required: [user_id, role]
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
role: { type: string, enum: [user, admin] }
|
||||
role: { type: string, enum: [owner, admin, user] }
|
||||
'400':
|
||||
description: >-
|
||||
Missing login_id or assertion (bad_request); or the login could not be
|
||||
@@ -2599,7 +2696,7 @@ paths:
|
||||
required: [user_id, role]
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
role: { type: string, enum: [user, admin] }
|
||||
role: { type: string, enum: [owner, admin, user] }
|
||||
'400':
|
||||
description: >-
|
||||
A valid email and code are required (bad_request); or the code is wrong,
|
||||
@@ -2756,7 +2853,7 @@ paths:
|
||||
required: [user_id, role]
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
role: { type: string, enum: [user, admin] }
|
||||
role: { type: string, enum: [owner, admin, user] }
|
||||
'400':
|
||||
description: >-
|
||||
request_id and code are required (bad_request); or the login could not be
|
||||
@@ -2814,7 +2911,7 @@ paths:
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
username: { type: string }
|
||||
role: { type: string, enum: [user, admin] }
|
||||
role: { type: string, enum: [owner, admin, user] }
|
||||
email: { type: string }
|
||||
email_verified: { type: boolean }
|
||||
has_passkey: { type: boolean }
|
||||
@@ -2851,6 +2948,7 @@ paths:
|
||||
API is fenced until setup completes).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -2863,7 +2961,7 @@ paths:
|
||||
properties:
|
||||
user_id: { type: string }
|
||||
username: { type: string }
|
||||
role: { type: string, enum: [user, admin] }
|
||||
role: { type: string, enum: [owner, admin, user] }
|
||||
email: { type: string }
|
||||
email_verified: { type: boolean }
|
||||
has_passkey: { type: boolean }
|
||||
@@ -2970,6 +3068,7 @@ paths:
|
||||
access.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -3003,6 +3102,11 @@ paths:
|
||||
nudges unverified accounts through the email-OTP flow.
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'503':
|
||||
description: The account settings could not be read (auth_unavailable).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/me/servers:
|
||||
get:
|
||||
@@ -3011,6 +3115,7 @@ paths:
|
||||
summary: List the servers the caller owns or may claim.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -3227,6 +3332,11 @@ paths:
|
||||
safety_snapshot:
|
||||
type: boolean
|
||||
description: Whether a safety snapshot runs first. False when the request turned it off, or when this install cannot take one.
|
||||
'400':
|
||||
description: Malformed server name (bad_name) or request body.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
@@ -3272,6 +3382,11 @@ paths:
|
||||
properties:
|
||||
name: { type: string }
|
||||
status: { type: string, const: backing_up }
|
||||
'400':
|
||||
description: Malformed server name (bad_name).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
@@ -3286,8 +3401,15 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'429':
|
||||
description: Another backup of this server started within the cooldown (backup_cooldown).
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'503':
|
||||
$ref: '#/components/responses/ServiceUnavailable'
|
||||
'507':
|
||||
$ref: '#/components/responses/InsufficientStorage'
|
||||
|
||||
# -------------------------------------------------- async job status (app) ---
|
||||
/api/v1/servers/{name}/jobs:
|
||||
@@ -3496,6 +3618,8 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'507':
|
||||
$ref: '#/components/responses/InsufficientStorage'
|
||||
put:
|
||||
tags: [files]
|
||||
operationId: writeServerFile
|
||||
@@ -3817,6 +3941,11 @@ paths:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown user.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
put:
|
||||
tags: [users]
|
||||
operationId: setQuotas
|
||||
@@ -3849,6 +3978,11 @@ paths:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown user.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
|
||||
/api/v1/users/{id}/sessions:
|
||||
get:
|
||||
@@ -4010,6 +4144,11 @@ paths:
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
$ref: '#/components/responses/Forbidden'
|
||||
'404':
|
||||
description: Unknown user.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'409':
|
||||
description: UUID is already linked to a different user.
|
||||
content:
|
||||
@@ -4055,6 +4194,7 @@ paths:
|
||||
summary: Report account-link status and in-game instructions (web side, spec §10).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -4077,6 +4217,7 @@ paths:
|
||||
summary: Consume an in-game link code and bind the account.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -4128,6 +4269,7 @@ paths:
|
||||
session must have reauthed within 5 minutes (403 reauth_required).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -4191,6 +4333,7 @@ paths:
|
||||
consumed, or mismatched code is a 400.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -4219,6 +4362,11 @@ paths:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'409':
|
||||
description: Another account already proved this address (email_taken); the code is not consumed.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'429':
|
||||
description: >-
|
||||
Too many incorrect attempts on this code (otp_locked), or the account's
|
||||
@@ -4241,6 +4389,7 @@ paths:
|
||||
must have reauthed within 5 minutes (403 reauth_required).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -4286,6 +4435,7 @@ paths:
|
||||
instance.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -4320,6 +4470,7 @@ paths:
|
||||
configured.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -4368,6 +4519,7 @@ paths:
|
||||
not need the WebAuthn verifier, so it succeeds even where begin/finish report 503.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -4400,6 +4552,7 @@ paths:
|
||||
(403 reauth_required).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
parameters:
|
||||
- name: id
|
||||
@@ -4439,6 +4592,7 @@ paths:
|
||||
op-login or a passkey).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -4467,6 +4621,7 @@ paths:
|
||||
bound to a fresh reauth-purpose challenge.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'200':
|
||||
@@ -4494,6 +4649,7 @@ paths:
|
||||
clone check (a cloned authenticator is 400 passkey_login_invalid).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -4529,6 +4685,7 @@ paths:
|
||||
signing in again (403 staff_reauth).
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
responses:
|
||||
'202':
|
||||
@@ -4579,6 +4736,7 @@ paths:
|
||||
summary: Redeem the reauth code and mark this session reauthed for 5 minutes.
|
||||
x-felis-face: [external]
|
||||
x-felis-tier: app
|
||||
x-felis-setup-allowed: true
|
||||
security: [{ sessionCookie: [] }]
|
||||
requestBody:
|
||||
required: true
|
||||
@@ -5412,6 +5570,11 @@ paths:
|
||||
content:
|
||||
text/event-stream:
|
||||
schema: { type: string }
|
||||
'400':
|
||||
description: Malformed build id (bad_request). The id becomes a label-selector value, so it is refused before any lookup.
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Error' }
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
@@ -5595,6 +5758,8 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/Submission' }
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'401':
|
||||
$ref: '#/components/responses/Unauthorized'
|
||||
'403':
|
||||
|
||||
Reference in new issue
Block a user