feat(bat): 完善工作流调度与 dashboard RPC
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

补全资源拉取、解析、翻译、重打包和本地化发布命令,支持单次、限定次数与周期调度。移除 TUI 计划并通过 schedule.* RPC 暴露给 bat-api dashboard。

Closes #43
This commit is contained in:
2026-08-03 22:18:52 +08:00
parent 3b103be8a9
commit 0784d5b532
27 changed files with 2931 additions and 58 deletions
+90
View File
@@ -588,6 +588,96 @@ func (b *controlBackend) CatalogRefresh(ctx context.Context, force bool) (*backe
return &backendrpc.TaskAccepted{TaskID: "task-catalog-refresh-1", Kind: "catalog.refresh"}, nil
}
type scheduleBackend struct {
*controlBackend
scheduleCalls []string
scheduleRaw json.RawMessage
}
func (b *scheduleBackend) ScheduleList(ctx context.Context) (json.RawMessage, error) {
b.scheduleCalls = append(b.scheduleCalls, "schedule.list")
return b.scheduleRaw, nil
}
func (b *scheduleBackend) ScheduleAdd(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error) {
b.scheduleCalls = append(b.scheduleCalls, "schedule.add")
return b.scheduleRaw, nil
}
func (b *scheduleBackend) ScheduleUpdate(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error) {
b.scheduleCalls = append(b.scheduleCalls, "schedule.update")
return b.scheduleRaw, nil
}
func (b *scheduleBackend) ScheduleRemove(ctx context.Context, params backendrpc.ScheduleMutationParams) (json.RawMessage, error) {
b.scheduleCalls = append(b.scheduleCalls, "schedule.remove")
return b.scheduleRaw, nil
}
func (b *scheduleBackend) ScheduleRun(ctx context.Context, params backendrpc.ScheduleRunParams) (json.RawMessage, error) {
b.scheduleCalls = append(b.scheduleCalls, "schedule.run")
return b.scheduleRaw, nil
}
func TestAdminScheduleEndpointsProxyAuthenticatedRequests(t *testing.T) {
cfg := DefaultConfig()
cfg.AuthToken = "schedule-token"
if err := cfg.Normalize(); err != nil {
t.Fatal(err)
}
backend := &scheduleBackend{
controlBackend: &controlBackend{fakeBackend: &fakeBackend{}},
scheduleRaw: json.RawMessage(`{"command":"schedule-list","status":"ok","schedules":[]}`),
}
s := NewServer(cfg, backend, nil)
request := httptest.NewRequest(http.MethodGet, "/admin/schedules", nil)
request.Header.Set("Authorization", "Bearer schedule-token")
recorder := httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("list status=%d body=%s", recorder.Code, recorder.Body.String())
}
if !json.Valid(recorder.Body.Bytes()) {
t.Fatalf("list body is not JSON: %s", recorder.Body.String())
}
request = httptest.NewRequest(
http.MethodPost,
"/admin/control/schedule-update",
strings.NewReader(`{"id":"nightly-pull","every_seconds":3600,"clear_every":false}`),
)
request.Header.Set("Authorization", "Bearer schedule-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusAccepted {
t.Fatalf("update status=%d body=%s", recorder.Code, recorder.Body.String())
}
if len(backend.scheduleCalls) != 2 ||
backend.scheduleCalls[0] != "schedule.list" ||
backend.scheduleCalls[1] != "schedule.update" {
t.Fatalf("schedule calls=%v", backend.scheduleCalls)
}
request = httptest.NewRequest(http.MethodPost, "/admin/control/schedule-run", nil)
request.Header.Set("Authorization", "Bearer schedule-token")
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusAccepted {
t.Fatalf("run status=%d body=%s", recorder.Code, recorder.Body.String())
}
if len(backend.scheduleCalls) != 3 || backend.scheduleCalls[2] != "schedule.run" {
t.Fatalf("schedule calls=%v", backend.scheduleCalls)
}
request = httptest.NewRequest(http.MethodGet, "/admin/schedules", nil)
recorder = httptest.NewRecorder()
s.Handler().ServeHTTP(recorder, request)
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("unauthenticated list status=%d body=%s", recorder.Code, recorder.Body.String())
}
}
func TestDiscoverCallsStatusBeforeDoctor(t *testing.T) {
root := fixtureRoot(t)
bytes := uint64(20)