mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 06:34:54 +08:00
feat(bat): 补全工作流校验与调度过滤
新增 parse clear-cache 和 i18n validate,补齐 schedule 的作用域过滤与单轮执行上限,并将列表过滤参数暴露给 bat-api dashboard。同步 RPC、OpenAPI、用户文档和回归测试。 Refs #43
This commit is contained in:
+23
-1
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bat-api/internal/backendrpc"
|
||||
@@ -161,7 +162,12 @@ func (s *Server) handleAdminSchedules(w http.ResponseWriter, r *http.Request) {
|
||||
writeErrorJSON(w, http.StatusServiceUnavailable, "schedule_backend_unavailable", "Rust bat schedule backend is unavailable")
|
||||
return
|
||||
}
|
||||
result, err := backend.ScheduleList(r.Context())
|
||||
params, err := scheduleListParams(r)
|
||||
if err != nil {
|
||||
writeErrorJSON(w, http.StatusBadRequest, "invalid_schedule_query", err.Error())
|
||||
return
|
||||
}
|
||||
result, err := backend.ScheduleList(r.Context(), params)
|
||||
if err != nil {
|
||||
s.writeControlBackendError(w, "schedule-list", err)
|
||||
return
|
||||
@@ -174,6 +180,22 @@ func (s *Server) handleAdminSchedules(w http.ResponseWriter, r *http.Request) {
|
||||
writeNoStoreJSON(w, http.StatusOK, result)
|
||||
}
|
||||
|
||||
func scheduleListParams(r *http.Request) (backendrpc.ScheduleListParams, error) {
|
||||
query := r.URL.Query()
|
||||
params := backendrpc.ScheduleListParams{
|
||||
ID: query.Get("id"),
|
||||
Group: query.Get("group"),
|
||||
}
|
||||
if raw := query.Get("enabled"); raw != "" {
|
||||
enabled, err := strconv.ParseBool(raw)
|
||||
if err != nil {
|
||||
return backendrpc.ScheduleListParams{}, errors.New("enabled must be a boolean")
|
||||
}
|
||||
params.Enabled = &enabled
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminScheduleControl(w http.ResponseWriter, r *http.Request, action string) {
|
||||
backend, ok := s.backend.(ScheduleBackend)
|
||||
if !ok || backend == nil {
|
||||
|
||||
@@ -590,12 +590,14 @@ func (b *controlBackend) CatalogRefresh(ctx context.Context, force bool) (*backe
|
||||
|
||||
type scheduleBackend struct {
|
||||
*controlBackend
|
||||
scheduleCalls []string
|
||||
scheduleRaw json.RawMessage
|
||||
scheduleCalls []string
|
||||
scheduleListParams []backendrpc.ScheduleListParams
|
||||
scheduleRaw json.RawMessage
|
||||
}
|
||||
|
||||
func (b *scheduleBackend) ScheduleList(ctx context.Context) (json.RawMessage, error) {
|
||||
func (b *scheduleBackend) ScheduleList(ctx context.Context, params backendrpc.ScheduleListParams) (json.RawMessage, error) {
|
||||
b.scheduleCalls = append(b.scheduleCalls, "schedule.list")
|
||||
b.scheduleListParams = append(b.scheduleListParams, params)
|
||||
return b.scheduleRaw, nil
|
||||
}
|
||||
|
||||
@@ -631,7 +633,7 @@ func TestAdminScheduleEndpointsProxyAuthenticatedRequests(t *testing.T) {
|
||||
}
|
||||
s := NewServer(cfg, backend, nil)
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin/schedules", nil)
|
||||
request := httptest.NewRequest(http.MethodGet, "/admin/schedules?id=nightly-pull&group=res&enabled=true", nil)
|
||||
request.Header.Set("Authorization", "Bearer schedule-token")
|
||||
recorder := httptest.NewRecorder()
|
||||
s.Handler().ServeHTTP(recorder, request)
|
||||
@@ -641,6 +643,13 @@ func TestAdminScheduleEndpointsProxyAuthenticatedRequests(t *testing.T) {
|
||||
if !json.Valid(recorder.Body.Bytes()) {
|
||||
t.Fatalf("list body is not JSON: %s", recorder.Body.String())
|
||||
}
|
||||
if len(backend.scheduleListParams) != 1 ||
|
||||
backend.scheduleListParams[0].ID != "nightly-pull" ||
|
||||
backend.scheduleListParams[0].Group != "res" ||
|
||||
backend.scheduleListParams[0].Enabled == nil ||
|
||||
!*backend.scheduleListParams[0].Enabled {
|
||||
t.Fatalf("list params=%#v", backend.scheduleListParams)
|
||||
}
|
||||
|
||||
request = httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
@@ -676,6 +685,14 @@ func TestAdminScheduleEndpointsProxyAuthenticatedRequests(t *testing.T) {
|
||||
if recorder.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("unauthenticated list status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
|
||||
request = httptest.NewRequest(http.MethodGet, "/admin/schedules?enabled=invalid", nil)
|
||||
request.Header.Set("Authorization", "Bearer schedule-token")
|
||||
recorder = httptest.NewRecorder()
|
||||
s.Handler().ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Fatalf("invalid query status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscoverCallsStatusBeforeDoctor(t *testing.T) {
|
||||
|
||||
@@ -118,6 +118,20 @@ paths:
|
||||
/admin/schedules:
|
||||
get:
|
||||
summary: List Rust-owned resource workflow schedules
|
||||
parameters:
|
||||
- name: id
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: group
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [res, parse, i18n]
|
||||
- name: enabled
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: Current schedule JSON report.
|
||||
@@ -167,6 +181,10 @@ paths:
|
||||
count:
|
||||
type: integer
|
||||
format: int64
|
||||
max_runs:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 1
|
||||
clear_args:
|
||||
type: boolean
|
||||
clear_every:
|
||||
|
||||
@@ -42,7 +42,7 @@ type ControlBackend interface {
|
||||
// dashboard. The JSON result remains Rust's report shape so the API does not
|
||||
// duplicate schedule state or invent a second schema.
|
||||
type ScheduleBackend interface {
|
||||
ScheduleList(ctx context.Context) (json.RawMessage, error)
|
||||
ScheduleList(ctx context.Context, params backendrpc.ScheduleListParams) (json.RawMessage, error)
|
||||
ScheduleAdd(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error)
|
||||
ScheduleUpdate(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error)
|
||||
ScheduleRemove(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error)
|
||||
@@ -90,8 +90,8 @@ func (r RPCClient) ResourceRepair(ctx context.Context) (*backendrpc.TaskAccepted
|
||||
func (r RPCClient) CatalogRefresh(ctx context.Context, force bool) (*backendrpc.TaskAccepted, error) {
|
||||
return r.Client.CatalogRefresh(ctx, force)
|
||||
}
|
||||
func (r RPCClient) ScheduleList(ctx context.Context) (json.RawMessage, error) {
|
||||
return r.Client.ScheduleList(ctx)
|
||||
func (r RPCClient) ScheduleList(ctx context.Context, params backendrpc.ScheduleListParams) (json.RawMessage, error) {
|
||||
return r.Client.ScheduleListFiltered(ctx, params)
|
||||
}
|
||||
func (r RPCClient) ScheduleAdd(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error) {
|
||||
return r.Client.ScheduleAdd(ctx, params)
|
||||
|
||||
@@ -249,10 +249,19 @@ type ScheduleMutationParams struct {
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
// ScheduleListParams filters the Rust-owned schedule store.
|
||||
type ScheduleListParams struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
// ScheduleRunParams selects a schedule or runs every due enabled schedule.
|
||||
type ScheduleRunParams struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Force bool `json:"force,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Force bool `json:"force,omitempty"`
|
||||
MaxRuns *uint64 `json:"max_runs,omitempty"`
|
||||
}
|
||||
|
||||
// Ack is returned by accepted daemon control methods.
|
||||
@@ -454,6 +463,10 @@ func (c *Client) ScheduleList(ctx context.Context) (json.RawMessage, error) {
|
||||
return c.rawData(ctx, "schedule.list", nil)
|
||||
}
|
||||
|
||||
func (c *Client) ScheduleListFiltered(ctx context.Context, params ScheduleListParams) (json.RawMessage, error) {
|
||||
return c.rawData(ctx, "schedule.list", params)
|
||||
}
|
||||
|
||||
func (c *Client) ScheduleAdd(ctx context.Context, params ScheduleMutationParams) (json.RawMessage, error) {
|
||||
return c.rawData(ctx, "schedule.add", params)
|
||||
}
|
||||
|
||||
@@ -298,6 +298,44 @@ func TestScheduleUpdateSendsMutationParams(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestScheduleRunSendsScopeAndMaxRuns(t *testing.T) {
|
||||
maxRuns := uint64(2)
|
||||
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
|
||||
if req.Method != "schedule.run" {
|
||||
t.Fatalf("method = %s", req.Method)
|
||||
}
|
||||
var params ScheduleRunParams
|
||||
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
||||
t.Fatalf("decode params: %v", err)
|
||||
}
|
||||
if params.ID != "nightly-pull" || params.Group != "res" || !params.Force ||
|
||||
params.MaxRuns == nil || *params.MaxRuns != maxRuns {
|
||||
t.Fatalf("params = %#v", params)
|
||||
}
|
||||
return testResponse{
|
||||
Result: testEnvelope{
|
||||
OK: true,
|
||||
Status: "ok",
|
||||
RequestID: "req-test-schedule-run",
|
||||
Data: map[string]any{"command": "schedule-run", "executed": []any{}},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
raw, err := client.ScheduleRun(context.Background(), ScheduleRunParams{
|
||||
ID: "nightly-pull",
|
||||
Group: "res",
|
||||
Force: true,
|
||||
MaxRuns: &maxRuns,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ScheduleRun error: %v", err)
|
||||
}
|
||||
if !json.Valid(raw) {
|
||||
t.Fatalf("invalid raw JSON: %s", string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationErrorReturnsAPIError(t *testing.T) {
|
||||
client := newTestClient(t, func(t *testing.T, req testRequest) testResponse {
|
||||
if req.Method != "task.status" {
|
||||
|
||||
Reference in New Issue
Block a user