Skip to content

Commit 19994c9

Browse files
committed
admin server: test WorkflowSteps
1 parent 7e0141f commit 19994c9

File tree

1 file changed

+169
-0
lines changed

1 file changed

+169
-0
lines changed

dbos/admin_server_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import (
1717
"go.uber.org/goleak"
1818
)
1919

20+
// TestStepResult is a custom struct for testing step outputs
21+
type TestStepResult struct {
22+
Message string `json:"message"`
23+
Count int `json:"count"`
24+
Success bool `json:"success"`
25+
}
26+
2027
func TestAdminServer(t *testing.T) {
2128
defer goleak.VerifyNone(t,
2229
goleak.IgnoreAnyFunction("github.com/jackc/pgx/v5/pgxpool.(*Pool).backgroundHealthCheck"),
@@ -726,6 +733,168 @@ func TestAdminServer(t *testing.T) {
726733
assert.Equal(t, queue.Name, queueName, "Expected queue name to be 'test-queue'")
727734
})
728735

736+
t.Run("WorkflowSteps", func(t *testing.T) {
737+
resetTestDatabase(t, databaseURL)
738+
ctx, err := NewDBOSContext(context.Background(), Config{
739+
DatabaseURL: databaseURL,
740+
AppName: "test-app",
741+
AdminServer: true,
742+
})
743+
require.NoError(t, err)
744+
745+
// Test workflow with multiple steps - simpler version that won't fail on serialization
746+
testWorkflow := func(dbosCtx DBOSContext, input string) (string, error) {
747+
// Step 1: Return a string
748+
stepResult1, err := RunAsStep(dbosCtx, func(ctx context.Context) (string, error) {
749+
return "step1-output", nil
750+
}, WithStepName("stringStep"))
751+
if err != nil {
752+
return "", err
753+
}
754+
755+
// Step 2: Return a user-defined struct
756+
stepResult2, err := RunAsStep(dbosCtx, func(ctx context.Context) (TestStepResult, error) {
757+
return TestStepResult{
758+
Message: "structured data",
759+
Count: 100,
760+
Success: true,
761+
}, nil
762+
}, WithStepName("structStep"))
763+
if err != nil {
764+
return "", err
765+
}
766+
767+
// Step 3: Return an error - but we don't abort on error to test error marshaling
768+
_, _ = RunAsStep(dbosCtx, func(ctx context.Context) (string, error) {
769+
return "", fmt.Errorf("deliberate error for testing")
770+
}, WithStepName("errorStep"))
771+
772+
// Step 4: Return empty string (to test empty value handling)
773+
stepResult4, err := RunAsStep(dbosCtx, func(ctx context.Context) (string, error) {
774+
return "", nil
775+
}, WithStepName("emptyStep"))
776+
if err != nil {
777+
return "", err
778+
}
779+
780+
// Combine results
781+
return fmt.Sprintf("workflow complete: %s, struct(%s,%d,%v), %s", stepResult1, stepResult2.Message, stepResult2.Count, stepResult2.Success, stepResult4), nil
782+
}
783+
784+
RegisterWorkflow(ctx, testWorkflow)
785+
786+
err = Launch(ctx)
787+
require.NoError(t, err)
788+
789+
// Ensure cleanup
790+
defer func() {
791+
if ctx != nil {
792+
Shutdown(ctx, 1*time.Minute)
793+
}
794+
}()
795+
796+
// Give the server a moment to start
797+
time.Sleep(100 * time.Millisecond)
798+
799+
client := &http.Client{Timeout: 5 * time.Second}
800+
801+
// Create and run the workflow
802+
handle, err := RunWorkflow(ctx, testWorkflow, "test-input")
803+
require.NoError(t, err, "Failed to create workflow")
804+
805+
// Wait for workflow to complete
806+
result, err := handle.GetResult()
807+
require.NoError(t, err, "Workflow should complete successfully")
808+
t.Logf("Workflow result: %s", result)
809+
810+
// Call the workflow steps endpoint
811+
workflowID := handle.GetWorkflowID()
812+
endpoint := fmt.Sprintf("http://localhost:%d/workflows/%s/steps", _DEFAULT_ADMIN_SERVER_PORT, workflowID)
813+
req, err := http.NewRequest("GET", endpoint, nil)
814+
require.NoError(t, err, "Failed to create request")
815+
816+
resp, err := client.Do(req)
817+
require.NoError(t, err, "Failed to make request")
818+
defer resp.Body.Close()
819+
820+
assert.Equal(t, http.StatusOK, resp.StatusCode, "Expected 200 OK from steps endpoint")
821+
822+
// Decode the response
823+
var steps []map[string]any
824+
err = json.NewDecoder(resp.Body).Decode(&steps)
825+
require.NoError(t, err, "Failed to decode steps response")
826+
827+
// Should have 4 steps
828+
assert.Equal(t, 4, len(steps), "Expected exactly 4 steps")
829+
830+
// Verify each step's output/error is properly marshaled
831+
for i, step := range steps {
832+
functionName, ok := step["function_name"].(string)
833+
require.True(t, ok, "function_name should be a string for step %d", i)
834+
835+
t.Logf("Step %d (%s): output=%v, error=%v", i, functionName, step["output"], step["error"])
836+
837+
switch functionName {
838+
case "stringStep":
839+
// String output should be marshaled as JSON string
840+
outputStr, ok := step["output"].(string)
841+
require.True(t, ok, "String step output should be a JSON string")
842+
843+
var unmarshaledOutput string
844+
err = json.Unmarshal([]byte(outputStr), &unmarshaledOutput)
845+
require.NoError(t, err, "Failed to unmarshal string step output")
846+
assert.Equal(t, "step1-output", unmarshaledOutput, "String step output should match")
847+
848+
assert.Nil(t, step["error"], "String step should have no error")
849+
850+
case "structStep":
851+
// Struct output should be marshaled as JSON string
852+
outputStr, ok := step["output"].(string)
853+
require.True(t, ok, "Struct step output should be a JSON string")
854+
855+
var unmarshaledOutput TestStepResult
856+
err = json.Unmarshal([]byte(outputStr), &unmarshaledOutput)
857+
require.NoError(t, err, "Failed to unmarshal struct step output")
858+
assert.Equal(t, TestStepResult{
859+
Message: "structured data",
860+
Count: 100,
861+
Success: true,
862+
}, unmarshaledOutput, "Struct step output should match")
863+
864+
assert.Nil(t, step["error"], "Struct step should have no error")
865+
866+
case "errorStep":
867+
// Error step should have error marshaled as JSON string
868+
errorStr, ok := step["error"].(string)
869+
require.True(t, ok, "Error step error should be a JSON string")
870+
871+
var unmarshaledError string
872+
err = json.Unmarshal([]byte(errorStr), &unmarshaledError)
873+
require.NoError(t, err, "Failed to unmarshal error step error")
874+
assert.Contains(t, unmarshaledError, "deliberate error for testing", "Error message should be preserved")
875+
876+
case "emptyStep":
877+
// Empty string might be returned as nil or as an empty JSON string
878+
output := step["output"]
879+
if output == nil {
880+
// Empty string was not included in response (which is fine)
881+
t.Logf("Empty step output was nil (not included)")
882+
} else {
883+
// If it was included, it should be marshaled as JSON string `""`
884+
outputStr, ok := output.(string)
885+
require.True(t, ok, "If present, empty step output should be a JSON string")
886+
887+
var unmarshaledOutput string
888+
err = json.Unmarshal([]byte(outputStr), &unmarshaledOutput)
889+
require.NoError(t, err, "Failed to unmarshal empty step output")
890+
assert.Equal(t, "", unmarshaledOutput, "Empty step output should be empty string")
891+
}
892+
893+
assert.Nil(t, step["error"], "Empty step should have no error")
894+
}
895+
}
896+
})
897+
729898
t.Run("TestDeactivate", func(t *testing.T) {
730899
resetTestDatabase(t, databaseURL)
731900
ctx, err := NewDBOSContext(context.Background(), Config{

0 commit comments

Comments
 (0)