From 08ba115e7aa922e5795f557d858d2c44dcb54e25 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Mon, 17 Mar 2025 16:48:13 +0530 Subject: [PATCH] add: response processing --- pkg/response/response.go | 138 ++++++++++++++++ pkg/response/response_test.go | 296 ++++++++++++++++++++++++++++++++++ 2 files changed, 434 insertions(+) create mode 100644 pkg/response/response.go create mode 100644 pkg/response/response_test.go diff --git a/pkg/response/response.go b/pkg/response/response.go new file mode 100644 index 0000000..a77bac6 --- /dev/null +++ b/pkg/response/response.go @@ -0,0 +1,138 @@ +package response + +import ( + "context" + "encoding/json" + "fmt" +) + +type ErrorType string + +const ( + SchemaValidationErrorType ErrorType = "SCHEMA_VALIDATION_ERROR" + InvalidRequestErrorType ErrorType = "INVALID_REQUEST" +) + +type BecknRequest struct { + Context map[string]interface{} `json:"context,omitempty"` +} + +type Error struct { + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +type Message struct { + Ack struct { + Status string `json:"status,omitempty"` + } `json:"ack,omitempty"` + Error *Error `json:"error,omitempty"` +} + +type BecknResponse struct { + Context map[string]interface{} `json:"context,omitempty"` + Message Message `json:"message,omitempty"` +} + +type ClientFailureBecknResponse struct { + Context map[string]interface{} `json:"context,omitempty"` + Error *Error `json:"error,omitempty"` +} + +var errorMap = map[ErrorType]Error{ + SchemaValidationErrorType: { + Code: "400", + Message: "Schema validation failed", + }, + InvalidRequestErrorType: { + Code: "401", + Message: "Invalid request format", + }, +} + +var DefaultError = Error{ + Code: "500", + Message: "Internal server error", +} + +func Nack(ctx context.Context, tp ErrorType, body []byte) ([]byte, error) { + var req BecknRequest + if err := json.Unmarshal(body, &req); err != nil { + return nil, fmt.Errorf("failed to parse request: %w", err) + } + + errorObj, ok := errorMap[tp] + var response BecknResponse + + if !ok { + response = BecknResponse{ + Context: req.Context, + Message: Message{ + Ack: struct { + Status string `json:"status,omitempty"` + }{ + Status: "NACK", + }, + Error: &DefaultError, + }, + } + } else { + response = BecknResponse{ + Context: req.Context, + Message: Message{ + Ack: struct { + Status string `json:"status,omitempty"` + }{ + Status: "NACK", + }, + Error: &errorObj, + }, + } + } + + return json.Marshal(response) +} + +func Ack(ctx context.Context, body []byte) ([]byte, error) { + var req BecknRequest + if err := json.Unmarshal(body, &req); err != nil { + return nil, fmt.Errorf("failed to parse request: %w", err) + } + + response := BecknResponse{ + Context: req.Context, + Message: Message{ + Ack: struct { + Status string `json:"status,omitempty"` + }{ + Status: "ACK", + }, + }, + } + + return json.Marshal(response) +} + +func HandleClientFailure(ctx context.Context, tp ErrorType, body []byte) ([]byte, error) { + var req BecknRequest + if err := json.Unmarshal(body, &req); err != nil { + return nil, fmt.Errorf("failed to parse request: %w", err) + } + + errorObj, ok := errorMap[tp] + var response ClientFailureBecknResponse + + if !ok { + response = ClientFailureBecknResponse{ + Context: req.Context, + Error: &DefaultError, + } + } else { + response = ClientFailureBecknResponse{ + Context: req.Context, + Error: &errorObj, + } + } + + return json.Marshal(response) +} diff --git a/pkg/response/response_test.go b/pkg/response/response_test.go new file mode 100644 index 0000000..b282990 --- /dev/null +++ b/pkg/response/response_test.go @@ -0,0 +1,296 @@ +package response + +import ( + "context" + "encoding/json" + "reflect" + "testing" +) + +func TestNack(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + errorType ErrorType + requestBody string + wantStatus string + wantErrCode string + wantErrMsg string + wantErr bool + }{ + { + name: "Schema validation error", + errorType: SchemaValidationErrorType, + requestBody: `{"context": {"domain": "test-domain", "location": "test-location"}}`, + wantStatus: "NACK", + wantErrCode: "400", + wantErrMsg: "Schema validation failed", + wantErr: false, + }, + { + name: "Invalid request error", + errorType: InvalidRequestErrorType, + requestBody: `{"context": {"domain": "test-domain"}}`, + wantStatus: "NACK", + wantErrCode: "401", + wantErrMsg: "Invalid request format", + wantErr: false, + }, + { + name: "Unknown error type", + errorType: "UNKNOWN_ERROR", + requestBody: `{"context": {"domain": "test-domain"}}`, + wantStatus: "NACK", + wantErrCode: "500", + wantErrMsg: "Internal server error", + wantErr: false, + }, + { + name: "Empty request body", + errorType: SchemaValidationErrorType, + requestBody: `{}`, + wantStatus: "NACK", + wantErrCode: "400", + wantErrMsg: "Schema validation failed", + wantErr: false, + }, + { + name: "Invalid JSON", + errorType: SchemaValidationErrorType, + requestBody: `{invalid json}`, + wantErr: true, + }, + { + name: "Complex nested context", + errorType: SchemaValidationErrorType, + requestBody: `{"context": {"domain": "test-domain", "nested": {"key1": "value1", "key2": 123}}}`, + wantStatus: "NACK", + wantErrCode: "400", + wantErrMsg: "Schema validation failed", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := Nack(ctx, tt.errorType, []byte(tt.requestBody)) + + if (err != nil) != tt.wantErr { + t.Errorf("Nack() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantErr && err != nil { + return + } + + var becknResp BecknResponse + if err := json.Unmarshal(resp, &becknResp); err != nil { + t.Errorf("Failed to unmarshal response: %v", err) + return + } + + if becknResp.Message.Ack.Status != tt.wantStatus { + t.Errorf("Nack() status = %v, want %v", becknResp.Message.Ack.Status, tt.wantStatus) + } + + if becknResp.Message.Error.Code != tt.wantErrCode { + t.Errorf("Nack() error code = %v, want %v", becknResp.Message.Error.Code, tt.wantErrCode) + } + + if becknResp.Message.Error.Message != tt.wantErrMsg { + t.Errorf("Nack() error message = %v, want %v", becknResp.Message.Error.Message, tt.wantErrMsg) + } + + var origReq BecknRequest + if err := json.Unmarshal([]byte(tt.requestBody), &origReq); err == nil { + if !compareContexts(becknResp.Context, origReq.Context) { + t.Errorf("Nack() context not preserved, got = %v, want %v", becknResp.Context, origReq.Context) + } + } + }) + } +} + +func TestAck(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + requestBody string + wantStatus string + wantErr bool + }{ + { + name: "Valid request", + requestBody: `{"context": {"domain": "test-domain", "location": "test-location"}}`, + wantStatus: "ACK", + wantErr: false, + }, + { + name: "Empty context", + requestBody: `{"context": {}}`, + wantStatus: "ACK", + wantErr: false, + }, + { + name: "Invalid JSON", + requestBody: `{invalid json}`, + wantErr: true, + }, + { + name: "Complex nested context", + requestBody: `{"context": {"domain": "test-domain", "nested": {"key1": "value1", "key2": 123, "array": [1,2,3]}}}`, + wantStatus: "ACK", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := Ack(ctx, []byte(tt.requestBody)) + + if (err != nil) != tt.wantErr { + t.Errorf("Ack() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantErr && err != nil { + return + } + + var becknResp BecknResponse + if err := json.Unmarshal(resp, &becknResp); err != nil { + t.Errorf("Failed to unmarshal response: %v", err) + return + } + + if becknResp.Message.Ack.Status != tt.wantStatus { + t.Errorf("Ack() status = %v, want %v", becknResp.Message.Ack.Status, tt.wantStatus) + } + + if becknResp.Message.Error != nil { + t.Errorf("Ack() should not have error, got %v", becknResp.Message.Error) + } + + var origReq BecknRequest + if err := json.Unmarshal([]byte(tt.requestBody), &origReq); err == nil { + if !compareContexts(becknResp.Context, origReq.Context) { + t.Errorf("Ack() context not preserved, got = %v, want %v", becknResp.Context, origReq.Context) + } + } + }) + } +} + +func TestHandleClientFailure(t *testing.T) { + ctx := context.Background() + + tests := []struct { + name string + errorType ErrorType + requestBody string + wantErrCode string + wantErrMsg string + wantErr bool + }{ + { + name: "Schema validation error", + errorType: SchemaValidationErrorType, + requestBody: `{"context": {"domain": "test-domain", "location": "test-location"}}`, + wantErrCode: "400", + wantErrMsg: "Schema validation failed", + wantErr: false, + }, + { + name: "Invalid request error", + errorType: InvalidRequestErrorType, + requestBody: `{"context": {"domain": "test-domain"}}`, + wantErrCode: "401", + wantErrMsg: "Invalid request format", + wantErr: false, + }, + { + name: "Unknown error type", + errorType: "UNKNOWN_ERROR", + requestBody: `{"context": {"domain": "test-domain"}}`, + wantErrCode: "500", + wantErrMsg: "Internal server error", + wantErr: false, + }, + { + name: "Invalid JSON", + errorType: SchemaValidationErrorType, + requestBody: `{invalid json}`, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp, err := HandleClientFailure(ctx, tt.errorType, []byte(tt.requestBody)) + + if (err != nil) != tt.wantErr { + t.Errorf("HandleClientFailure() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if tt.wantErr && err != nil { + return + } + + var failureResp ClientFailureBecknResponse + if err := json.Unmarshal(resp, &failureResp); err != nil { + t.Errorf("Failed to unmarshal response: %v", err) + return + } + + if failureResp.Error.Code != tt.wantErrCode { + t.Errorf("HandleClientFailure() error code = %v, want %v", failureResp.Error.Code, tt.wantErrCode) + } + + if failureResp.Error.Message != tt.wantErrMsg { + t.Errorf("HandleClientFailure() error message = %v, want %v", failureResp.Error.Message, tt.wantErrMsg) + } + + var origReq BecknRequest + if err := json.Unmarshal([]byte(tt.requestBody), &origReq); err == nil { + if !compareContexts(failureResp.Context, origReq.Context) { + t.Errorf("HandleClientFailure() context not preserved, got = %v, want %v", failureResp.Context, origReq.Context) + } + } + }) + } +} + +func TestErrorMap(t *testing.T) { + + expectedTypes := []ErrorType{ + SchemaValidationErrorType, + InvalidRequestErrorType, + } + + for _, tp := range expectedTypes { + if _, exists := errorMap[tp]; !exists { + t.Errorf("ErrorType %v not found in errorMap", tp) + } + } + + if DefaultError.Code != "500" || DefaultError.Message != "Internal server error" { + t.Errorf("DefaultError not set correctly, got code=%v, message=%v", DefaultError.Code, DefaultError.Message) + } +} + +func compareContexts(c1, c2 map[string]interface{}) bool { + + if c1 == nil && c2 == nil { + return true + } + + if c1 == nil && len(c2) == 0 || c2 == nil && len(c1) == 0 { + return true + } + + return reflect.DeepEqual(c1, c2) +}