From 599c66189cd04b3cece545697ea948c801a64089 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Wed, 8 Jul 2026 13:41:14 +0700 Subject: [PATCH] Parse optional account_ids in run handler --- internal/httpapi/run.go | 33 ++++++++++++++++++++++++++++++++- internal/httpapi/run_test.go | 22 ++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/internal/httpapi/run.go b/internal/httpapi/run.go index b4f79af..79583df 100644 --- a/internal/httpapi/run.go +++ b/internal/httpapi/run.go @@ -1,8 +1,11 @@ package httpapi import ( + "bytes" "context" + "encoding/json" "errors" + "io" "net/http" "github.com/vasyansk/imap-copier/internal/crypto" @@ -63,13 +66,41 @@ func (s *Server) handleTestAccounts(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusAccepted) } +// parseRunAccountIDs reads an optional {"account_ids":[...]} run body. An empty +// body means "all accounts" and yields a nil slice. Malformed JSON is an error. +func parseRunAccountIDs(r *http.Request) ([]int64, error) { + raw, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + if len(bytes.TrimSpace(raw)) == 0 { + return nil, nil + } + var body struct { + AccountIDs []int64 `json:"account_ids"` + } + if err := json.Unmarshal(raw, &body); err != nil { + return nil, err + } + return body.AccountIDs, nil +} + func (s *Server) handleRun(w http.ResponseWriter, r *http.Request) { taskID, err := pathID(r, "id") if err != nil { http.Error(w, "bad id", http.StatusBadRequest) return } - runID, err := s.orch.Run(r.Context(), taskID, "manual", nil) + accountIDs, err := parseRunAccountIDs(r) + if err != nil { + http.Error(w, "bad request body", http.StatusBadRequest) + return + } + runID, err := s.orch.Run(r.Context(), taskID, "manual", accountIDs) + if errors.Is(err, orchestrator.ErrNoAccountsSelected) { + http.Error(w, "no matching accounts selected", http.StatusBadRequest) + return + } if errors.Is(err, orchestrator.ErrNotTested) { http.Error(w, "accounts must pass connection tests first", http.StatusConflict) return diff --git a/internal/httpapi/run_test.go b/internal/httpapi/run_test.go index 2e7e04b..fa23914 100644 --- a/internal/httpapi/run_test.go +++ b/internal/httpapi/run_test.go @@ -26,3 +26,25 @@ func TestImportCSVFailsOnBadEncKey(t *testing.T) { t.Fatalf("import must fail on bad EncKey, got %d", rw.Code) } } + +func TestParseRunAccountIDs(t *testing.T) { + // empty body => nil (run all) + req := httptest.NewRequest("POST", "/api/tasks/1/run", strings.NewReader("")) + ids, err := parseRunAccountIDs(req) + if err != nil || ids != nil { + t.Fatalf("empty body must yield nil ids, got %v err=%v", ids, err) + } + + // explicit selection + req = httptest.NewRequest("POST", "/api/tasks/1/run", strings.NewReader(`{"account_ids":[3,7]}`)) + ids, err = parseRunAccountIDs(req) + if err != nil || len(ids) != 2 || ids[0] != 3 || ids[1] != 7 { + t.Fatalf("must parse account_ids, got %v err=%v", ids, err) + } + + // malformed JSON => error + req = httptest.NewRequest("POST", "/api/tasks/1/run", strings.NewReader(`{bad`)) + if _, err := parseRunAccountIDs(req); err == nil { + t.Fatal("malformed body must error") + } +}