mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-09-18 10:04:55 +08:00
fix(api): 补齐 bat-api 控制与后端 RPC
This commit is contained in:
+137
-1
@@ -548,6 +548,46 @@ func (f *fakeBackend) ResourceManifest(ctx context.Context, offset int, limit in
|
||||
return f.manifest, nil
|
||||
}
|
||||
|
||||
type controlBackend struct {
|
||||
*fakeBackend
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (b *controlBackend) DaemonReload(ctx context.Context) (*backendrpc.Ack, error) {
|
||||
b.calls = append(b.calls, "daemon.reload")
|
||||
return &backendrpc.Ack{Command: "reload", Status: "accepted"}, nil
|
||||
}
|
||||
|
||||
func (b *controlBackend) DaemonRestart(ctx context.Context) (*backendrpc.Ack, error) {
|
||||
b.calls = append(b.calls, "daemon.restart")
|
||||
return &backendrpc.Ack{Command: "restart", Status: "accepted"}, nil
|
||||
}
|
||||
|
||||
func (b *controlBackend) DaemonRefresh(ctx context.Context, force bool) (*backendrpc.Ack, error) {
|
||||
b.calls = append(b.calls, "daemon.refresh")
|
||||
return &backendrpc.Ack{Command: "refresh", Status: "accepted", Force: &force}, nil
|
||||
}
|
||||
|
||||
func (b *controlBackend) ResourceSync(ctx context.Context, force bool) (*backendrpc.TaskAccepted, error) {
|
||||
b.calls = append(b.calls, "resource.sync")
|
||||
return &backendrpc.TaskAccepted{TaskID: "task-sync-1", Kind: "resource.sync"}, nil
|
||||
}
|
||||
|
||||
func (b *controlBackend) ResourceVerify(ctx context.Context) (*backendrpc.TaskAccepted, error) {
|
||||
b.calls = append(b.calls, "resource.verify")
|
||||
return &backendrpc.TaskAccepted{TaskID: "task-verify-1", Kind: "resource.verify"}, nil
|
||||
}
|
||||
|
||||
func (b *controlBackend) ResourceRepair(ctx context.Context) (*backendrpc.TaskAccepted, error) {
|
||||
b.calls = append(b.calls, "resource.repair")
|
||||
return &backendrpc.TaskAccepted{TaskID: "task-repair-1", Kind: "resource.repair"}, nil
|
||||
}
|
||||
|
||||
func (b *controlBackend) CatalogRefresh(ctx context.Context, force bool) (*backendrpc.TaskAccepted, error) {
|
||||
b.calls = append(b.calls, "catalog.refresh")
|
||||
return &backendrpc.TaskAccepted{TaskID: "task-catalog-refresh-1", Kind: "catalog.refresh"}, nil
|
||||
}
|
||||
|
||||
func TestDiscoverCallsStatusBeforeDoctor(t *testing.T) {
|
||||
root := fixtureRoot(t)
|
||||
bytes := uint64(20)
|
||||
@@ -974,7 +1014,103 @@ func TestOpenAPIAndAdminReservedEndpoints(t *testing.T) {
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &admin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if admin.Status != "reserved" {
|
||||
if admin.Status != "available" {
|
||||
t.Fatalf("admin=%+v", admin)
|
||||
}
|
||||
if len(admin.Controls) == 0 || admin.Controls[0] != "/admin/control/reload" {
|
||||
t.Fatalf("admin controls=%v", admin.Controls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminControlForwardsAllowlistedActions(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.AuthToken = "control-token"
|
||||
if err := cfg.Normalize(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backend := &controlBackend{fakeBackend: &fakeBackend{}}
|
||||
s := NewServer(cfg, backend, nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
action string
|
||||
body string
|
||||
rpcMethod string
|
||||
call string
|
||||
}{
|
||||
{name: "reload", action: "reload", rpcMethod: "daemon.reload", call: "daemon.reload"},
|
||||
{name: "restart", action: "restart", rpcMethod: "daemon.restart", call: "daemon.restart"},
|
||||
{name: "force sync", action: "sync", body: `{"force":true}`, rpcMethod: "resource.sync", call: "resource.sync"},
|
||||
{name: "repair", action: "repair", rpcMethod: "resource.repair", call: "resource.repair"},
|
||||
{name: "catalog refresh", action: "catalog-refresh", rpcMethod: "catalog.refresh", call: "catalog.refresh"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodPost, "/admin/control/"+tc.action, strings.NewReader(tc.body))
|
||||
request.Header.Set("Authorization", "Bearer control-token")
|
||||
recorder := httptest.NewRecorder()
|
||||
s.Handler().ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusAccepted {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var response AdminControlResponse
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.Action != tc.action || response.RPCMethod != tc.rpcMethod || response.Status != "accepted" {
|
||||
t.Fatalf("response=%+v", response)
|
||||
}
|
||||
if len(backend.calls) == 0 || backend.calls[len(backend.calls)-1] != tc.call {
|
||||
t.Fatalf("calls=%v", backend.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminControlRejectsUnauthenticatedDangerousAndUnsupportedActions(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.AuthToken = "control-token"
|
||||
if err := cfg.Normalize(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
backend := &controlBackend{fakeBackend: &fakeBackend{}}
|
||||
s := NewServer(cfg, backend, nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
action string
|
||||
token string
|
||||
body string
|
||||
wantStatus int
|
||||
wantCode string
|
||||
}{
|
||||
{name: "missing token", action: "repair", wantStatus: http.StatusUnauthorized, wantCode: "unauthorized"},
|
||||
{name: "dangerous stop", action: "stop", token: "control-token", wantStatus: http.StatusForbidden, wantCode: "control_not_allowed"},
|
||||
{name: "unknown action", action: "arbitrary-rpc", token: "control-token", wantStatus: http.StatusNotFound, wantCode: "control_not_found"},
|
||||
{name: "invalid parameters", action: "repair", token: "control-token", body: `{"force":true}`, wantStatus: http.StatusBadRequest, wantCode: "invalid_control_params"},
|
||||
{name: "restart invalid parameters", action: "restart", token: "control-token", body: `{"force":true}`, wantStatus: http.StatusBadRequest, wantCode: "invalid_control_params"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodPost, "/admin/control/"+tc.action, strings.NewReader(tc.body))
|
||||
if tc.token != "" {
|
||||
request.Header.Set("Authorization", "Bearer "+tc.token)
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
s.Handler().ServeHTTP(recorder, request)
|
||||
if recorder.Code != tc.wantStatus {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
var response ErrorResponse
|
||||
if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.Error.Code != tc.wantCode {
|
||||
t.Fatalf("error=%+v", response.Error)
|
||||
}
|
||||
})
|
||||
}
|
||||
if len(backend.calls) != 0 {
|
||||
t.Fatalf("rejected actions reached backend: %v", backend.calls)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user