//go:build !nointegration // +build !nointegration package temporal import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" ) func init() { // Skip all tests in this file if Temporal server not available if !isTemporalAvailable() { // Tests will be skipped } } // TestHandler_StartWorkflow tests the START_WORKFLOW operation func TestHandler_StartWorkflow(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "START_WORKFLOW", Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test_workflow_1", "workflow_type": "TestWorkflow", "task_queue": "test_queue", "input": map[string]interface{}{ "test_data": "value", }, }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if !response.Success { t.Errorf("Expected success response") } if response.Action != "START_WORKFLOW" { t.Errorf("Expected action START_WORKFLOW") } } // TestHandler_DescribeWorkflow tests the DESCRIBE_WORKFLOW operation func TestHandler_DescribeWorkflow(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "DESCRIBE_WORKFLOW", Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test_workflow_1", "run_id": "run_abc123", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != "DESCRIBE_WORKFLOW" { t.Errorf("Expected action DESCRIBE_WORKFLOW") } } // TestHandler_ListWorkflows tests the LIST_WORKFLOWS operation func TestHandler_ListWorkflows(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "LIST_WORKFLOWS", Namespace: "default", Payload: map[string]interface{}{ "status": "RUNNING", "page_size": 50, }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } } // TestHandler_RequestValidation tests request validation func TestHandler_RequestValidation(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") tests := []struct { name string method string body interface{} expectedStatus int }{ { name: "Invalid method (GET)", method: "GET", body: map[string]interface{}{}, expectedStatus: http.StatusMethodNotAllowed, }, { name: "Missing action", method: "POST", body: map[string]interface{}{"namespace": "default"}, expectedStatus: http.StatusBadRequest, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { body, _ := json.Marshal(test.body) req := httptest.NewRequest(test.method, "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != test.expectedStatus { t.Errorf("Expected status %d, got %d", test.expectedStatus, w.Code) } }) } } // TestHandler_SignalWorkflow tests the SIGNAL_WORKFLOW operation func TestHandler_SignalWorkflow(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "SIGNAL_WORKFLOW", Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test_workflow_1", "run_id": "run_abc123", "signal_name": "payment_received", "input": map[string]interface{}{ "amount": 99.99, }, }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != "SIGNAL_WORKFLOW" { t.Errorf("Expected action SIGNAL_WORKFLOW") } } // TestHandler_QueryWorkflow tests the QUERY_WORKFLOW operation func TestHandler_QueryWorkflow(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "QUERY_WORKFLOW", Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test_workflow_1", "run_id": "run_abc123", "query_type": "get_status", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != "QUERY_WORKFLOW" { t.Errorf("Expected action QUERY_WORKFLOW") } } // TestHandler_TerminateWorkflow tests the TERMINATE_WORKFLOW operation func TestHandler_TerminateWorkflow(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "TERMINATE_WORKFLOW", Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test_workflow_1", "run_id": "run_abc123", "reason": "User requested cancellation", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } } // TestHandler_CancelWorkflow tests the CANCEL_WORKFLOW operation func TestHandler_CancelWorkflow(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "CANCEL_WORKFLOW", Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test_workflow_1", "run_id": "run_abc123", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected status 200, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != "CANCEL_WORKFLOW" { t.Errorf("Expected action CANCEL_WORKFLOW") } } // TestHandler_ResponseFormat tests that responses follow the standard format func TestHandler_ResponseFormat(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "DESCRIBE_NAMESPACE", Namespace: "default", } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Timestamp.IsZero() { t.Errorf("Expected timestamp to be set") } if response.Action != "DESCRIBE_NAMESPACE" { t.Errorf("Expected action to be in response") } } // TestHandler_AllWorkflowOperations tests that all workflow operations are recognized func TestHandler_AllWorkflowOperations(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") operations := []string{ "START_WORKFLOW", "DESCRIBE_WORKFLOW", "LIST_WORKFLOWS", "GET_WORKFLOW_HISTORY", "TERMINATE_WORKFLOW", "CANCEL_WORKFLOW", "SIGNAL_WORKFLOW", "QUERY_WORKFLOW", "RESET_WORKFLOW", "UPDATE_WORKFLOW", } for _, op := range operations { t.Run(op, func(t *testing.T) { reqBody := RequestPayload{ Action: op, Namespace: "default", Payload: map[string]interface{}{ "workflow_id": "test", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != op { t.Errorf("Operation %s not routed correctly", op) } }) } } // TestHandler_AllActivityOperations tests that all activity operations are recognized func TestHandler_AllActivityOperations(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") operations := []string{ "HEARTBEAT_ACTIVITY", "COMPLETE_ACTIVITY", "FAIL_ACTIVITY", } for _, op := range operations { t.Run(op, func(t *testing.T) { reqBody := RequestPayload{ Action: op, Namespace: "default", Payload: map[string]interface{}{ "task_token": "base64_encoded_token", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != op { t.Errorf("Operation %s not routed correctly", op) } }) } } // TestHandler_AllNamespaceOperations tests that all namespace operations are recognized func TestHandler_AllNamespaceOperations(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") operations := []string{ "LIST_NAMESPACES", "DESCRIBE_NAMESPACE", "CREATE_NAMESPACE", "UPDATE_NAMESPACE", "DELETE_NAMESPACE", } for _, op := range operations { t.Run(op, func(t *testing.T) { reqBody := RequestPayload{ Action: op, Namespace: "default", Payload: map[string]interface{}{}, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != op { t.Errorf("Operation %s not routed correctly", op) } }) } } // TestHandler_AllClusterOperations tests that all cluster operations are recognized func TestHandler_AllClusterOperations(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") operations := []string{ "GET_CLUSTER_INFO", "LIST_CLUSTER_MEMBERS", "GET_SYSTEM_INFO", } for _, op := range operations { t.Run(op, func(t *testing.T) { reqBody := RequestPayload{ Action: op, Namespace: "default", Payload: map[string]interface{}{}, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != op { t.Errorf("Operation %s not routed correctly", op) } }) } } // TestHandler_MissingRequiredFields tests validation of required fields func TestHandler_MissingRequiredFields(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") tests := []struct { name string operation string payload map[string]interface{} shouldFail bool }{ { name: "START_WORKFLOW missing workflow_id", operation: "START_WORKFLOW", payload: map[string]interface{}{ "workflow_type": "TestWorkflow", "task_queue": "test_queue", }, shouldFail: true, }, { name: "DESCRIBE_WORKFLOW missing workflow_id", operation: "DESCRIBE_WORKFLOW", payload: map[string]interface{}{}, shouldFail: true, }, { name: "SIGNAL_WORKFLOW missing signal_name", operation: "SIGNAL_WORKFLOW", payload: map[string]interface{}{ "workflow_id": "test", "run_id": "run", }, shouldFail: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { reqBody := RequestPayload{ Action: test.operation, Namespace: "default", Payload: test.payload, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if test.shouldFail { if w.Code == http.StatusOK { var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Success { t.Errorf("Expected request to fail for %s", test.name) } } } }) } } // TestHandler_RequestMethod tests HTTP method validation func TestHandler_RequestMethod(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") methods := []string{"GET", "PUT", "DELETE", "PATCH"} for _, method := range methods { t.Run(method, func(t *testing.T) { req := httptest.NewRequest(method, "/workflow", nil) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusMethodNotAllowed { t.Errorf("Expected 405 for %s method, got %d", method, w.Code) } }) } } // TestHandler_UnknownAction tests handling of unknown actions func TestHandler_UnknownAction(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "UNKNOWN_ACTION", Namespace: "default", Payload: map[string]interface{}{}, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusBadRequest { t.Errorf("Expected 400 for unknown action, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Error != "INVALID_ACTION" { t.Errorf("Expected INVALID_ACTION error") } } // TestHandler_HealthEndpoint tests the health check endpoint func TestHandler_HealthEndpoint(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") req := httptest.NewRequest("GET", "/workflow/health", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected 200 for health check, got %d", w.Code) } } // TestHandler_MetricsEndpoint tests the metrics endpoint func TestHandler_MetricsEndpoint(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") req := httptest.NewRequest("GET", "/workflow/metrics", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected 200 for metrics, got %d", w.Code) } } // TestHandler_NotFoundEndpoint tests 404 handling func TestHandler_NotFoundEndpoint(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") req := httptest.NewRequest("GET", "/unknown", nil) w := httptest.NewRecorder() handler.ServeHTTP(w, req) if w.Code != http.StatusNotFound { t.Errorf("Expected 404 for unknown endpoint, got %d", w.Code) } } // TestHandler_NamespaceDefaulting tests that namespace defaults to "default" func TestHandler_NamespaceDefaulting(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "DESCRIBE_WORKFLOW", Payload: map[string]interface{}{"workflow_id": "test"}, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Namespace != "default" { t.Errorf("Expected namespace to default to 'default', got %s", response.Namespace) } } // TestHandler_AllSearchAttributeOperations tests search attribute operations func TestHandler_AllSearchAttributeOperations(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") operations := []string{ "LIST_SEARCH_ATTRIBUTES", "ADD_SEARCH_ATTRIBUTES", } for _, op := range operations { t.Run(op, func(t *testing.T) { reqBody := RequestPayload{ Action: op, Namespace: "default", Payload: map[string]interface{}{}, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != op { t.Errorf("Operation %s not routed correctly", op) } }) } } // TestHandler_ListTaskQueuesOperation tests task queue operation func TestHandler_ListTaskQueuesOperation(t *testing.T) { if !isTemporalAvailable() { t.Skip("Temporal server not available"); return } handler := NewHandler("localhost:7233") reqBody := RequestPayload{ Action: "LIST_TASK_QUEUES", Namespace: "default", Payload: map[string]interface{}{ "queue_type": "WORKFLOW", }, } body, _ := json.Marshal(reqBody) req := httptest.NewRequest("POST", "/workflow", bytes.NewReader(body)) w := httptest.NewRecorder() handler.handleWorkflow(w, req) if w.Code != http.StatusOK { t.Errorf("Expected 200, got %d", w.Code) } var response ResponsePayload json.NewDecoder(w.Body).Decode(&response) if response.Action != "LIST_TASK_QUEUES" { t.Errorf("Expected LIST_TASK_QUEUES action") } }