feat: strengthen risk controls and review workflows

This commit is contained in:
hectorzhao
2026-07-26 13:08:12 +08:00
parent b461532075
commit 2ce682c3fc
34 changed files with 2167 additions and 390 deletions
+29
View File
@@ -86,6 +86,11 @@ type DownstreamRecoveryOverview struct {
Statuses []inbound.DownstreamRecoveryStatus `json:"statuses"`
}
type DisconnectDownstreamAccountCommand struct {
Account string `json:"account"`
Reason string `json:"reason"`
}
func Register(mux *http.ServeMux, server Server) {
if server.HTTPClient == nil {
server.HTTPClient = &http.Client{Timeout: 10 * time.Second}
@@ -110,6 +115,30 @@ func Register(mux *http.ServeMux, server Server) {
mux.HandleFunc("/downstream/recovery-candidates", server.handleDownstreamRecoveryCandidates)
mux.HandleFunc("/downstream/recovery-statuses", server.handleDownstreamRecoveryStatuses)
mux.HandleFunc("/downstream/recovery-overview", server.handleDownstreamRecoveryOverview)
mux.HandleFunc("/downstream/connections/disconnect", server.handleDisconnectDownstreamAccount)
}
func (s Server) handleDisconnectDownstreamAccount(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var command DisconnectDownstreamAccountCommand
if err := json.NewDecoder(r.Body).Decode(&command); err != nil {
http.Error(w, fmt.Sprintf("invalid downstream disconnect command: %v", err), http.StatusBadRequest)
return
}
if command.Account == "" {
http.Error(w, "account is required", http.StatusBadRequest)
return
}
disconnected := inbound.DisconnectAccount(command.Account)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"account": command.Account,
"disconnected": disconnected,
"reason": command.Reason,
})
}
func (s Server) handleConnectChannel(w http.ResponseWriter, r *http.Request) {
+26
View File
@@ -173,6 +173,32 @@ func TestDisconnectChannelStopsSupplierPool(t *testing.T) {
}
}
func TestDisconnectDownstreamAccountEndpointIsAccountScoped(t *testing.T) {
handler := handlerWithServer(Server{})
resp := httptest.NewRecorder()
req := httptest.NewRequest(
http.MethodPost,
"/downstream/connections/disconnect",
strings.NewReader(`{"account":"100001","reason":"application_disabled"}`),
)
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusOK {
t.Fatalf("unexpected response status: %d body=%s", resp.Code, resp.Body.String())
}
var payload struct {
Account string `json:"account"`
Disconnected int `json:"disconnected"`
}
if err := json.Unmarshal(resp.Body.Bytes(), &payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload.Account != "100001" || payload.Disconnected != 0 {
t.Fatalf("unexpected downstream disconnect response: %+v", payload)
}
}
func TestRecoveryCandidatesEndpointReturnsView(t *testing.T) {
handler := handlerWithServer(Server{
RecoveryCandidates: func(context.Context) ([]inbound.DownstreamPresence, error) {