feat: strengthen risk controls and review workflows
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user