feat: add audited task review and linked manual retries
This commit is contained in:
@@ -32,6 +32,21 @@ func historyQuery(r *http.Request, paged bool) (service.HistoryPageInput, error)
|
||||
return in, nil
|
||||
}
|
||||
func registerHistory(mux *http.ServeMux, s *service.Service) {
|
||||
mux.HandleFunc("GET /v1/tasks/{task}/operations", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
|
||||
in, err := historyQuery(r, true)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out, err := s.ListTaskOperations(r.Context(), t, core.ID(r.PathValue("task")), in)
|
||||
return out, 200, err
|
||||
}))
|
||||
mux.HandleFunc("GET /v1/tasks/{task}/operations/{operation}", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
|
||||
if _, err := historyQuery(r, false); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out, err := s.GetTaskOperation(r.Context(), t, core.ID(r.PathValue("task")), core.ID(r.PathValue("operation")))
|
||||
return out, 200, err
|
||||
}))
|
||||
mux.HandleFunc("GET /v1/tasks/{task}/attempts", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
|
||||
in, err := historyQuery(r, true)
|
||||
if err != nil {
|
||||
|
||||
39
internal/transport/httpserver/recovery_test.go
Normal file
39
internal/transport/httpserver/recovery_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"git.yuntuo.hk/k2safe/orbit/pulse/service"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRecoveryJSONRequiresExplicitConfirmation(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
body string
|
||||
retry, valid bool
|
||||
}{
|
||||
{`{"execution_settled":true,"expected_revision":"9007199254740993"}`, false, true},
|
||||
{`{"execution_settled":false}`, false, true},
|
||||
{`{}`, false, false},
|
||||
{`{"execution_settled":null}`, false, false},
|
||||
{`{"ExecutionSettled":true}`, false, false},
|
||||
{`{"execution_settled":true,"actor_id":"spoof"}`, false, false},
|
||||
{`{"execution_settled":true,"execution_settled":false}`, false, false},
|
||||
{`{"execution_settled":true,"expected_revision":9007199254740993}`, false, false},
|
||||
{`{"safe_to_retry":true}`, true, true},
|
||||
{`{"safe_to_retry":null}`, true, false},
|
||||
{`{}`, true, false},
|
||||
{`{"safe_to_retry":true,"owner_id":"spoof"}`, true, false},
|
||||
} {
|
||||
r := httptest.NewRequest("POST", "/v1/tasks/example:review", strings.NewReader(tc.body))
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
var in any = &service.ReviewInput{}
|
||||
if tc.retry {
|
||||
in = &service.ManualRetryInput{}
|
||||
}
|
||||
err := decodeBody(r, in)
|
||||
if (err == nil) != tc.valid {
|
||||
t.Fatalf("%s: %v", tc.body, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,30 @@ func registerTasks(mux *http.ServeMux, s *service.Service) {
|
||||
}))
|
||||
mux.HandleFunc("POST /v1/tasks/{command}", wrap(func(r *http.Request, t auth.Token) (any, int, error) {
|
||||
command := r.PathValue("command")
|
||||
if strings.HasSuffix(command, ":review") {
|
||||
var in service.ReviewInput
|
||||
if err := decodeBody(r, &in); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
key, err := idempotencyKey(r)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out, err := s.ReviewTask(r.Context(), t, core.ID(strings.TrimSuffix(command, ":review")), key, in)
|
||||
return out, 200, err
|
||||
}
|
||||
if strings.HasSuffix(command, ":retry") {
|
||||
var in service.ManualRetryInput
|
||||
if err := decodeBody(r, &in); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
key, err := idempotencyKey(r)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
out, err := s.RetryTask(r.Context(), t, core.ID(strings.TrimSuffix(command, ":retry")), key, in)
|
||||
return out, createdStatus(out.Deduplicated), err
|
||||
}
|
||||
if !strings.HasSuffix(command, ":cancel") {
|
||||
return nil, 0, fault.NotFound
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user