Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions api/serverless/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4854,10 +4854,9 @@ components:
endpointPath:
$ref: '#/components/schemas/EndpointPath'
output:
type: object
additionalProperties: true
nullable: true
description: Inference result payload on success; null otherwise.
description: >
Inference result payload on success; null otherwise. May be any JSON
value, including an object, array, string, number, boolean, or null.
error:
type: string
nullable: true
Expand Down
6 changes: 3 additions & 3 deletions internal/api/serverless/gen/client.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 49 additions & 1 deletion internal/api/serverless/tasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,42 @@ func TestInvokeSync_Completed(t *testing.T) {
if err != nil {
t.Fatalf("InvokeSync: %v", err)
}
if task.Status != TaskStatusCompleted || task.Output == nil || (*task.Output)["ok"] != true {
output, ok := task.Output.(map[string]any)
if task.Status != TaskStatusCompleted || !ok || output["ok"] != true {
t.Fatalf("unexpected task: %+v", task)
}
}

func TestInvokeAsync_CompletedDuplicateTaskAcceptsAnyJSONOutput(t *testing.T) {
tests := []struct {
name string
output string
}{
{name: "scalar", output: `"done"`},
{name: "list", output: `["done",1,false]`},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
body := `{"id":"` + testTaskID + `","status":"completed","appId":"my-app","endpointPath":"infer","createdAt":"2026-07-30T12:00:00Z","output":` + tc.output + `}`
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
_, _ = w.Write([]byte(body))
}))
defer srv.Close()

c := newClient("test-key", srv.URL, slog.Default(), srv.Client())
task, err := c.InvokeAsync(context.Background(), testAppID, testEndpoint, testTaskID, nil)
if err != nil {
t.Fatalf("InvokeAsync: %v", err)
}
if task.Output == nil {
t.Fatal("expected output to be decoded")
}
})
}
}

func TestInvokeAsync_GeneratesTaskID(t *testing.T) {
var gotID string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -275,6 +306,23 @@ func TestListTasks_CursorAndStatus(t *testing.T) {
}
}

func TestListTasks_AcceptsScalarOutput(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"data":[{"id":"` + testTaskID + `","status":"completed","appId":"my-app","endpointPath":"infer","createdAt":"2026-07-30T12:00:00Z","output":"done"}]}`))
}))
defer srv.Close()

c := newClient("test-key", srv.URL, slog.Default(), srv.Client())
page, err := c.ListTasks(context.Background(), testAppID, nil)
if err != nil {
t.Fatalf("ListTasks: %v", err)
}
if len(page.Data) != 1 || page.Data[0].Output != "done" {
t.Fatalf("unexpected tasks: %+v", page.Data)
}
}

func TestListTasks_EmptyPageKeepsCursor(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
Expand Down
13 changes: 12 additions & 1 deletion internal/cmd/serverless/apps_tasks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestTaskResult_IncludesOutputAndError(t *testing.T) {
AppId: testAppID,
Status: serverlessapi.TaskStatusFailed,
Error: &errMsg,
Output: &output,
Output: output,
CreatedAt: created,
CompletedAt: &completed,
}
Expand All @@ -54,6 +54,17 @@ func TestTaskResult_IncludesOutputAndError(t *testing.T) {
}
}

func TestTaskResult_RendersScalarOutput(t *testing.T) {
r := taskResult{Output: "done"}
rows := r.Rows()
for _, row := range rows {
if row[0] == "Output" && row[1] == `"done"` {
return
}
}
t.Fatalf("scalar output missing from rows: %#v", rows)
}

func TestTasksResult_Headers(t *testing.T) {
r := tasksResult{
{Id: testTaskID, Status: serverlessapi.TaskStatusPending, CreatedAt: time.Date(2026, 7, 30, 12, 0, 0, 0, time.UTC)},
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/serverless/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func (r taskResult) Rows() [][]any {
{colError, formatOptionalString(r.Error)},
}
if r.Output != nil {
rows = append(rows, []any{"Output", formatJSONValue(*r.Output)})
rows = append(rows, []any{"Output", formatJSONValue(r.Output)})
}
return rows
}
Expand Down
Loading