feat(bat): 补全工作流校验与调度过滤
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

新增 parse clear-cache 和 i18n validate,补齐 schedule 的作用域过滤与单轮执行上限,并将列表过滤参数暴露给 bat-api dashboard。同步 RPC、OpenAPI、用户文档和回归测试。

Refs #43
This commit is contained in:
2026-08-03 23:47:59 +08:00
parent 0784d5b532
commit 1933d6acb0
19 changed files with 714 additions and 49 deletions
+23 -1
View File
@@ -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 {
+21 -4
View File
@@ -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) {
+18
View File
@@ -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:
+3 -3
View File
@@ -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)