feat(process): add wait API endpoint
This commit is contained in:
parent
720104babc
commit
cf9291d095
2 changed files with 101 additions and 0 deletions
|
|
@ -98,6 +98,7 @@ func (p *ProcessProvider) RegisterRoutes(rg *gin.RouterGroup) {
|
|||
rg.GET("/processes", p.listProcesses)
|
||||
rg.GET("/processes/:id", p.getProcess)
|
||||
rg.GET("/processes/:id/output", p.getProcessOutput)
|
||||
rg.POST("/processes/:id/wait", p.waitProcess)
|
||||
rg.POST("/processes/:id/input", p.inputProcess)
|
||||
rg.POST("/processes/:id/close-stdin", p.closeProcessStdin)
|
||||
rg.POST("/processes/:id/kill", p.killProcess)
|
||||
|
|
@ -231,6 +232,28 @@ func (p *ProcessProvider) Describe() []api.RouteDescription {
|
|||
"type": "string",
|
||||
},
|
||||
},
|
||||
{
|
||||
Method: "POST",
|
||||
Path: "/processes/:id/wait",
|
||||
Summary: "Wait for a managed process",
|
||||
Description: "Blocks until the process exits and returns the final process snapshot. Non-zero exits include the snapshot in the error details payload.",
|
||||
Tags: []string{"process"},
|
||||
Response: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"id": map[string]any{"type": "string"},
|
||||
"command": map[string]any{"type": "string"},
|
||||
"args": map[string]any{"type": "array"},
|
||||
"dir": map[string]any{"type": "string"},
|
||||
"startedAt": map[string]any{"type": "string", "format": "date-time"},
|
||||
"running": map[string]any{"type": "boolean"},
|
||||
"status": map[string]any{"type": "string"},
|
||||
"exitCode": map[string]any{"type": "integer"},
|
||||
"duration": map[string]any{"type": "integer"},
|
||||
"pid": map[string]any{"type": "integer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Method: "POST",
|
||||
Path: "/processes/:id/input",
|
||||
|
|
@ -456,6 +479,28 @@ func (p *ProcessProvider) getProcessOutput(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, api.OK(output))
|
||||
}
|
||||
|
||||
func (p *ProcessProvider) waitProcess(c *gin.Context) {
|
||||
if p.service == nil {
|
||||
c.JSON(http.StatusServiceUnavailable, api.Fail("service_unavailable", "process service is not configured"))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := p.service.Wait(c.Param("id"))
|
||||
if err != nil {
|
||||
status := http.StatusInternalServerError
|
||||
switch {
|
||||
case err == process.ErrProcessNotFound:
|
||||
status = http.StatusNotFound
|
||||
case info.Status == process.StatusExited || info.Status == process.StatusKilled:
|
||||
status = http.StatusConflict
|
||||
}
|
||||
c.JSON(status, api.FailWithDetails("wait_failed", err.Error(), info))
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, api.OK(info))
|
||||
}
|
||||
|
||||
type processInputRequest struct {
|
||||
Input string `json:"input"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -320,6 +320,60 @@ func TestProcessProvider_GetProcessOutput_Good(t *testing.T) {
|
|||
assert.Contains(t, resp.Data, "output-check")
|
||||
}
|
||||
|
||||
func TestProcessProvider_WaitProcess_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "echo", "wait-check")
|
||||
require.NoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/wait", nil)
|
||||
require.NoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var resp goapi.Response[process.Info]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.True(t, resp.Success)
|
||||
assert.Equal(t, proc.ID, resp.Data.ID)
|
||||
assert.Equal(t, process.StatusExited, resp.Data.Status)
|
||||
assert.Equal(t, 0, resp.Data.ExitCode)
|
||||
}
|
||||
|
||||
func TestProcessProvider_WaitProcess_NonZeroExit_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "sh", "-c", "exit 7")
|
||||
require.NoError(t, err)
|
||||
|
||||
p := processapi.NewProvider(nil, svc, nil)
|
||||
r := setupRouter(p)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/process/processes/"+proc.ID+"/wait", nil)
|
||||
require.NoError(t, err)
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, w.Code)
|
||||
|
||||
var resp goapi.Response[any]
|
||||
err = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
require.False(t, resp.Success)
|
||||
require.NotNil(t, resp.Error)
|
||||
assert.Equal(t, "wait_failed", resp.Error.Code)
|
||||
assert.Contains(t, resp.Error.Message, "process exited with code 7")
|
||||
|
||||
details, ok := resp.Error.Details.(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "exited", details["status"])
|
||||
assert.Equal(t, float64(7), details["exitCode"])
|
||||
assert.Equal(t, proc.ID, details["id"])
|
||||
}
|
||||
|
||||
func TestProcessProvider_InputAndCloseStdin_Good(t *testing.T) {
|
||||
svc := newTestProcessService(t)
|
||||
proc, err := svc.Start(context.Background(), "cat")
|
||||
|
|
@ -484,6 +538,7 @@ func TestProcessProvider_ProcessRoutes_Unavailable(t *testing.T) {
|
|||
"/api/process/processes",
|
||||
"/api/process/processes/anything",
|
||||
"/api/process/processes/anything/output",
|
||||
"/api/process/processes/anything/wait",
|
||||
"/api/process/processes/anything/input",
|
||||
"/api/process/processes/anything/close-stdin",
|
||||
"/api/process/processes/anything/kill",
|
||||
|
|
@ -494,6 +549,7 @@ func TestProcessProvider_ProcessRoutes_Unavailable(t *testing.T) {
|
|||
method := "GET"
|
||||
switch {
|
||||
case strings.HasSuffix(path, "/kill"),
|
||||
strings.HasSuffix(path, "/wait"),
|
||||
strings.HasSuffix(path, "/input"),
|
||||
strings.HasSuffix(path, "/close-stdin"):
|
||||
method = "POST"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue