Skip to content

Commit 8cee96a

Browse files
committed
must not stringify empty strings + add a test
1 parent 6f05959 commit 8cee96a

File tree

2 files changed

+148
-2
lines changed

2 files changed

+148
-2
lines changed

dbos/admin_server.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,15 @@ func toListWorkflowResponse(ws WorkflowStatus) (map[string]any, error) {
152152
result["StartedAt"] = nil
153153
}
154154

155-
if ws.Input != nil {
155+
if ws.Input != nil && ws.Input != "" {
156156
bytes, err := json.Marshal(ws.Input)
157157
if err != nil {
158158
return nil, fmt.Errorf("failed to marshal input: %w", err)
159159
}
160160
result["Input"] = string(bytes)
161161
}
162162

163-
if ws.Output != nil {
163+
if ws.Output != nil && ws.Output != "" {
164164
bytes, err := json.Marshal(ws.Output)
165165
if err != nil {
166166
return nil, fmt.Errorf("failed to marshal output: %w", err)

dbos/admin_server_test.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,152 @@ func TestAdminServer(t *testing.T) {
213213
}
214214
})
215215

216+
t.Run("List workflows input/output values", func(t *testing.T) {
217+
resetTestDatabase(t, databaseURL)
218+
ctx, err := NewDBOSContext(Config{
219+
DatabaseURL: databaseURL,
220+
AppName: "test-app",
221+
AdminServer: true,
222+
})
223+
require.NoError(t, err)
224+
225+
// Define a custom struct for testing
226+
type TestStruct struct {
227+
Name string `json:"name"`
228+
Value int `json:"value"`
229+
}
230+
231+
// Test workflow with int input/output
232+
intWorkflow := func(dbosCtx DBOSContext, input int) (int, error) {
233+
return input * 2, nil
234+
}
235+
RegisterWorkflow(ctx, intWorkflow)
236+
237+
// Test workflow with empty string input/output
238+
emptyStringWorkflow := func(dbosCtx DBOSContext, input string) (string, error) {
239+
return "", nil
240+
}
241+
RegisterWorkflow(ctx, emptyStringWorkflow)
242+
243+
// Test workflow with struct input/output
244+
structWorkflow := func(dbosCtx DBOSContext, input TestStruct) (TestStruct, error) {
245+
return TestStruct{Name: "output-" + input.Name, Value: input.Value * 2}, nil
246+
}
247+
RegisterWorkflow(ctx, structWorkflow)
248+
249+
err = ctx.Launch()
250+
require.NoError(t, err)
251+
252+
// Ensure cleanup
253+
defer func() {
254+
if ctx != nil {
255+
ctx.Cancel()
256+
}
257+
}()
258+
259+
// Give the server a moment to start
260+
time.Sleep(100 * time.Millisecond)
261+
262+
client := &http.Client{Timeout: 5 * time.Second}
263+
endpoint := fmt.Sprintf("http://localhost:3001/%s", strings.TrimPrefix(_WORKFLOWS_PATTERN, "POST /"))
264+
265+
// Create workflows with different input/output types
266+
// 1. Integer workflow
267+
intHandle, err := RunAsWorkflow(ctx, intWorkflow, 42)
268+
require.NoError(t, err, "Failed to create int workflow")
269+
intResult, err := intHandle.GetResult()
270+
require.NoError(t, err, "Failed to get int workflow result")
271+
assert.Equal(t, 84, intResult)
272+
273+
// 2. Empty string workflow
274+
emptyStringHandle, err := RunAsWorkflow(ctx, emptyStringWorkflow, "")
275+
require.NoError(t, err, "Failed to create empty string workflow")
276+
emptyStringResult, err := emptyStringHandle.GetResult()
277+
require.NoError(t, err, "Failed to get empty string workflow result")
278+
assert.Equal(t, "", emptyStringResult)
279+
280+
// 3. Struct workflow
281+
structInput := TestStruct{Name: "test", Value: 10}
282+
structHandle, err := RunAsWorkflow(ctx, structWorkflow, structInput)
283+
require.NoError(t, err, "Failed to create struct workflow")
284+
structResult, err := structHandle.GetResult()
285+
require.NoError(t, err, "Failed to get struct workflow result")
286+
assert.Equal(t, TestStruct{Name: "output-test", Value: 20}, structResult)
287+
288+
// Query workflows with input/output loading enabled
289+
// Filter by the workflow IDs we just created to avoid interference from other tests
290+
reqBody := map[string]any{
291+
"workflow_uuids": []string{
292+
intHandle.GetWorkflowID(),
293+
emptyStringHandle.GetWorkflowID(),
294+
structHandle.GetWorkflowID(),
295+
},
296+
"load_input": true,
297+
"load_output": true,
298+
"limit": 10,
299+
}
300+
req, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewBuffer(mustMarshal(reqBody)))
301+
require.NoError(t, err, "Failed to create request")
302+
req.Header.Set("Content-Type", "application/json")
303+
304+
resp, err := client.Do(req)
305+
require.NoError(t, err, "Failed to make request")
306+
defer resp.Body.Close()
307+
308+
assert.Equal(t, http.StatusOK, resp.StatusCode)
309+
310+
var workflows []map[string]any
311+
err = json.NewDecoder(resp.Body).Decode(&workflows)
312+
require.NoError(t, err, "Failed to decode workflows response")
313+
314+
// Should have exactly 3 workflows
315+
assert.Equal(t, 3, len(workflows), "Expected exactly 3 workflows")
316+
317+
// Verify each workflow's input/output marshaling
318+
for _, wf := range workflows {
319+
wfID := wf["WorkflowUUID"].(string)
320+
321+
// Check input and output fields exist and are strings (JSON marshaled)
322+
if wfID == intHandle.GetWorkflowID() {
323+
// Integer workflow: input and output should be marshaled as JSON strings
324+
inputStr, ok := wf["Input"].(string)
325+
require.True(t, ok, "Int workflow Input should be a string")
326+
assert.Equal(t, "42", inputStr, "Int workflow input should be marshaled as '42'")
327+
328+
outputStr, ok := wf["Output"].(string)
329+
require.True(t, ok, "Int workflow Output should be a string")
330+
assert.Equal(t, "84", outputStr, "Int workflow output should be marshaled as '84'")
331+
332+
} else if wfID == emptyStringHandle.GetWorkflowID() {
333+
// Empty string workflow: both input and output are empty strings
334+
// According to the logic, empty strings should not have Input/Output fields
335+
input, hasInput := wf["Input"]
336+
require.Equal(t, "", input)
337+
require.True(t, hasInput, "Empty string workflow should have Input field")
338+
339+
output, hasOutput := wf["Output"]
340+
require.True(t, hasOutput, "Empty string workflow should have Output field")
341+
require.Equal(t, "", output)
342+
343+
} else if wfID == structHandle.GetWorkflowID() {
344+
// Struct workflow: input and output should be marshaled as JSON strings
345+
inputStr, ok := wf["Input"].(string)
346+
require.True(t, ok, "Struct workflow Input should be a string")
347+
var inputStruct TestStruct
348+
err = json.Unmarshal([]byte(inputStr), &inputStruct)
349+
require.NoError(t, err, "Failed to unmarshal struct workflow input")
350+
assert.Equal(t, structInput, inputStruct, "Struct workflow input should match")
351+
352+
outputStr, ok := wf["Output"].(string)
353+
require.True(t, ok, "Struct workflow Output should be a string")
354+
var outputStruct TestStruct
355+
err = json.Unmarshal([]byte(outputStr), &outputStruct)
356+
require.NoError(t, err, "Failed to unmarshal struct workflow output")
357+
assert.Equal(t, TestStruct{Name: "output-test", Value: 20}, outputStruct, "Struct workflow output should match")
358+
}
359+
}
360+
})
361+
216362
t.Run("List endpoints time filtering", func(t *testing.T) {
217363
ctx, err := NewDBOSContext(Config{
218364
DatabaseURL: databaseURL,

0 commit comments

Comments
 (0)