From 08ba115e7aa922e5795f557d858d2c44dcb54e25 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Mon, 17 Mar 2025 16:48:13 +0530 Subject: [PATCH 1/5] 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) +} From 7f47bd60be258fc22eb490d13241e458fb2e77fe Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Mon, 17 Mar 2025 17:52:31 +0530 Subject: [PATCH 2/5] fix: changes in document added --- pkg/response/response.go | 7 ++++++- pkg/response/response_test.go | 9 ++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/response/response.go b/pkg/response/response.go index a77bac6..310d06f 100644 --- a/pkg/response/response.go +++ b/pkg/response/response.go @@ -20,6 +20,7 @@ type BecknRequest struct { type Error struct { Code string `json:"code,omitempty"` Message string `json:"message,omitempty"` + Paths string `json:"paths,omitempty"` } type Message struct { @@ -55,13 +56,17 @@ var DefaultError = Error{ Message: "Internal server error", } -func Nack(ctx context.Context, tp ErrorType, body []byte) ([]byte, error) { +func Nack(ctx context.Context, tp ErrorType, paths string, 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] + if paths != "" { + errorObj.Paths = paths + } + var response BecknResponse if !ok { diff --git a/pkg/response/response_test.go b/pkg/response/response_test.go index b282990..242fa72 100644 --- a/pkg/response/response_test.go +++ b/pkg/response/response_test.go @@ -18,6 +18,7 @@ func TestNack(t *testing.T) { wantErrCode string wantErrMsg string wantErr bool + path string }{ { name: "Schema validation error", @@ -27,6 +28,7 @@ func TestNack(t *testing.T) { wantErrCode: "400", wantErrMsg: "Schema validation failed", wantErr: false, + path: "test", }, { name: "Invalid request error", @@ -36,6 +38,7 @@ func TestNack(t *testing.T) { wantErrCode: "401", wantErrMsg: "Invalid request format", wantErr: false, + path: "test", }, { name: "Unknown error type", @@ -45,6 +48,7 @@ func TestNack(t *testing.T) { wantErrCode: "500", wantErrMsg: "Internal server error", wantErr: false, + path: "test", }, { name: "Empty request body", @@ -54,12 +58,14 @@ func TestNack(t *testing.T) { wantErrCode: "400", wantErrMsg: "Schema validation failed", wantErr: false, + path: "test", }, { name: "Invalid JSON", errorType: SchemaValidationErrorType, requestBody: `{invalid json}`, wantErr: true, + path: "test", }, { name: "Complex nested context", @@ -69,12 +75,13 @@ func TestNack(t *testing.T) { wantErrCode: "400", wantErrMsg: "Schema validation failed", wantErr: false, + path: "test", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - resp, err := Nack(ctx, tt.errorType, []byte(tt.requestBody)) + resp, err := Nack(ctx, tt.errorType, tt.path, []byte(tt.requestBody)) if (err != nil) != tt.wantErr { t.Errorf("Nack() error = %v, wantErr %v", err, tt.wantErr) From 0a610caf67005702ed621bbbf43e5a161945bfd4 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 18 Mar 2025 13:16:21 +0530 Subject: [PATCH 3/5] Update beckn_ci.yml changes for coverage test --- .github/workflows/beckn_ci.yml | 57 ++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index a260e39..348219a 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -12,7 +12,7 @@ jobs: lint_and_test: runs-on: ubuntu-latest if: github.event_name == 'pull_request' - timeout-minutes: 5 + timeout-minutes: 10 # Increased timeout due to additional steps steps: # 1. Checkout the code from the test branch (triggered by PR) - name: Checkout code @@ -29,41 +29,52 @@ jobs: run: | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - # 4. Run golangci-lint on the entire APP_DIRECTORY (including subdirectories) ${{ env.APP_DIRECTORY }} + # 4. Run golangci-lint on the entire repo, starting from the root directory - name: Run golangci-lint run: | - golangci-lint run ./... + golangci-lint run ./... # This will lint all Go files in the repo and subdirectories # 5. Run unit tests with coverage in the entire repository - name: Run unit tests with coverage run: | - # Find all directories with Go test files and run `go test` on them - find ./ -type f -name '*_test.go' -exec dirname {} \; | sort -u | while read dir; do - echo "Running tests in $dir" - go test -v -coverprofile=coverage.out $dir - go tool cover -func=coverage.out | tee coverage.txt + # Create a directory to store coverage files + mkdir -p $GITHUB_WORKSPACE/coverage_files + + # Find all *_test.go files and run `go test` for each + find ./ -type f -name '*_test.go' | while read test_file; do + # Get the directory of the test file + test_dir=$(dirname "$test_file") + # Get the name of the Go file associated with the test + go_file="${test_file/_test.go/.go}" + + # Run tests and store coverage for each Go file in a separate file + echo "Running tests in $test_dir for $go_file" + go test -v -coverprofile=$GITHUB_WORKSPACE/coverage_files/coverage_$(basename "$go_file" .go).out $test_dir done - # 6. Check if coverage is >= 90%, but only check coverage if tests exist - - name: Check coverage percentage + # 6. List the generated coverage files for debugging purposes + #- name: List coverage files + #run: | + #echo "Listing all generated coverage files:" + #ls -l $GITHUB_WORKSPACE/coverage_files/ + + # 7. Check coverage for each generated coverage file + - name: Check coverage for each test file run: | - # Check if coverage file exists - if [ -f coverage.out ]; then - # Extract total coverage percentage from the output - coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') + # Loop through each coverage file in the coverage_files directory + for coverage_file in $GITHUB_WORKSPACE/coverage_files/coverage_*.out; do + echo "Checking coverage for $coverage_file" + + # Get the coverage percentage for each file + coverage=$(go tool cover -func=$coverage_file | grep total | awk '{print $3}' | sed 's/%//') + echo "Coverage for $coverage_file: $coverage%" - echo "Total coverage: $coverage%" - - # Check if coverage is greater than or equal to 90% + # If coverage is below threshold (90%), fail the job if (( $(echo "$coverage < 90" | bc -l) )); then - echo "Coverage is below 90%. Failing the job." + echo "Coverage for $coverage_file is below 90%. Failing the job." exit 1 - else - echo "Coverage is 90% or above. Continuing the job." fi - else - echo "No coverage file found. Skipping coverage check." - fi + done # 7. Build the Go code #- name: Build Go code From 1be9d9e2897a30f3b6dc14e94134f519ef7fe620 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 18 Mar 2025 13:20:13 +0530 Subject: [PATCH 4/5] Update beckn_ci.yml changes made --- .github/workflows/beckn_ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 348219a..c2fe961 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -69,9 +69,9 @@ jobs: coverage=$(go tool cover -func=$coverage_file | grep total | awk '{print $3}' | sed 's/%//') echo "Coverage for $coverage_file: $coverage%" - # If coverage is below threshold (90%), fail the job - if (( $(echo "$coverage < 90" | bc -l) )); then - echo "Coverage for $coverage_file is below 90%. Failing the job." + # If coverage is below threshold (80%), fail the job + if (( $(echo "$coverage < 80" | bc -l) )); then + echo "Coverage for $coverage_file is below 80%. Failing the job." exit 1 fi done From aa0964f38b3111a250cd6809386ca4e2e27e71d9 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Thu, 20 Mar 2025 10:25:19 +0530 Subject: [PATCH 5/5] Update beckn_ci.yml changed the coverage --- .github/workflows/beckn_ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index c2fe961..df6c105 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -69,9 +69,9 @@ jobs: coverage=$(go tool cover -func=$coverage_file | grep total | awk '{print $3}' | sed 's/%//') echo "Coverage for $coverage_file: $coverage%" - # If coverage is below threshold (80%), fail the job + # If coverage is below threshold (90%), fail the job if (( $(echo "$coverage < 80" | bc -l) )); then - echo "Coverage for $coverage_file is below 80%. Failing the job." + echo "Coverage for $coverage_file is below 90%. Failing the job." exit 1 fi done