From 0d4cee69f677121cd1b8485805b54ca9fbf7c977 Mon Sep 17 00:00:00 2001 From: Faiz Mohammed Date: Thu, 13 Feb 2025 12:43:03 +0530 Subject: [PATCH 01/21] Create ci.yml created ci.yml file for configuring the ci workflow --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cef2efd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: Go CI +on: + pull_request: + branches: + - beck-onix-v1.0-develop + - beck-onix-v1.0 + push: + branches: + - beck-onix-v1.0-develop + - beck-onix-v1.0 +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: '1.20' + - name: Install dependencies + run: go mod tidy + - name: Run tests + run: go test -coverprofile=coverage.out ./... + - name: Check coverage + run: | + coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') + if (( $(echo "$coverage < 90" | bc -l) )); then + echo "Coverage is below 90%" + exit 1 + fi + - name: Run golangci-lint + run: golangci-lint run + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: ./coverage.out From d035d4c7c209ff79c37e4661604e4daf4dec911a Mon Sep 17 00:00:00 2001 From: Faiz Mohammed Date: Thu, 13 Feb 2025 12:44:13 +0530 Subject: [PATCH 02/21] Create CODEOWNERS code owners file created and configured --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..a35bfd6 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ashishkgGoogle @poojajoshi2 @Deepa-Mulchandani @tanyamadaan From ecc4fed517d0bcc3e3b9771a9964f38cae5c1e04 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Wed, 5 Mar 2025 12:12:39 +0530 Subject: [PATCH 03/21] Create beckn CI Testing of CI with Test App --- .github/workflows/beckn CI | 112 +++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 .github/workflows/beckn CI diff --git a/.github/workflows/beckn CI b/.github/workflows/beckn CI new file mode 100644 index 0000000..7955754 --- /dev/null +++ b/.github/workflows/beckn CI @@ -0,0 +1,112 @@ +name: CI/CD Pipeline + +on: + pull_request: + branches: + - beck-onix-v1.0-develop # Trigger when a PR is raised for merging into `main` + +env: + # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" + APP_DIRECTORY: "go_app" + REF_BRANCH: "Feature/Test_CICD" + +jobs: + lint_and_test: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + timeout-minutes: 5 + steps: + # 1. Checkout the code from the test branch (triggered by PR) + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ${{ env.REF_BRANCH }} # Make sure we're on the 'test' branch for checking out code + fetch-depth: 1 + + # 2. Debugging: List files in the repository root after checkout + - name: List files in the root directory + run: | + echo "Current working directory is: $(pwd)" + echo "Listing files in the root directory:" + ls -al + + # 3. Set up Go environment + - name: Set up Go 1.24.0 + uses: actions/setup-go@v4 + with: + go-version: '1.24.0' + + # 4. Navigate to the app directory + - name: Navigate to app directory + run: | + echo "Changing directory to ${{ env.APP_DIRECTORY }}" + cd ${{ env.APP_DIRECTORY }} + echo "Listing files in ${{ env.APP_DIRECTORY }} directory:" + ls -al # List files in the ${{ env.APP_DIRECTORY }} directory + + # 5. Install golangci-lint + - name: Install golangci-lint + run: | + echo "Installing golangci-lint..." + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + echo "golangci-lint installed to: $(go env GOPATH)/bin/" + ls -l $(go env GOPATH)/bin/ # Debugging: List the contents of the bin directory + + # 6. Verify golangci-lint binary location + - name: Verify golangci-lint binary + run: | + echo "Checking if golangci-lint is installed..." + ls $(go env GOPATH)/bin/ # Debugging: Check if golangci-lint is present + if [ ! -f $(go env GOPATH)/bin/golangci-lint ]; then + echo "golangci-lint was not found, exiting." + exit 1 + fi + + # 7. Run Linter + - name: Run golangci-lint + run: | + echo "Running golangci-lint..." + cd ${{ env.APP_DIRECTORY }} + golangci-lint run + + # 8. Run unit tests with coverage + - name: Run unit tests with coverage + run: | + cd ${{ env.APP_DIRECTORY }} + echo "Running unit tests..." + go test -v -coverprofile=coverage.out + go tool cover -func=coverage.out | tee coverage.txt + + # 9. Check if coverage is >= 90% + - name: Check coverage percentage + run: | + cd ${{ env.APP_DIRECTORY }} + # Extract total coverage percentage from the output + coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') + + echo "Total coverage: $coverage%" + + # Check if coverage is greater than or equal to 90% + if (( $(echo "$coverage < 80" | bc -l) )); then + echo "Coverage is below 80%. Failing the job." + exit 1 + else + echo "Coverage is 80% or above. Continuing the job." + fi + + + # 10. Build the Go code + - name: Build Go code + run: | + echo "Building the Go application..." + cd ${{ env.APP_DIRECTORY }} + go build -o myapp + + # Verify if the build was successful + if [ ! -f myapp ]; then + echo "Build failed: myapp executable was not created." + exit 1 + else + echo "Build succeeded: myapp executable created." + fi + From 6a78249dd99069cda0f701e89d7a1801373d5e15 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Wed, 5 Mar 2025 12:29:23 +0530 Subject: [PATCH 04/21] Update and rename beckn CI to beckn_ci.yml --- .github/workflows/{beckn CI => beckn_ci.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{beckn CI => beckn_ci.yml} (99%) diff --git a/.github/workflows/beckn CI b/.github/workflows/beckn_ci.yml similarity index 99% rename from .github/workflows/beckn CI rename to .github/workflows/beckn_ci.yml index 7955754..2a37324 100644 --- a/.github/workflows/beckn CI +++ b/.github/workflows/beckn_ci.yml @@ -8,7 +8,7 @@ on: env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" APP_DIRECTORY: "go_app" - REF_BRANCH: "Feature/Test_CICD" + REF_BRANCH: "feature/test_go_app" jobs: lint_and_test: From 4126b47d965647fcc39f2072987db5e9601146a6 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Wed, 5 Mar 2025 12:33:46 +0530 Subject: [PATCH 05/21] Update beckn_ci.yml --- .github/workflows/beckn_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 2a37324..30364be 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -3,7 +3,7 @@ name: CI/CD Pipeline on: pull_request: branches: - - beck-onix-v1.0-develop # Trigger when a PR is raised for merging into `main` + - beck-onix-v1.0-develop # Trigger when a PR is raised for merging into `beck-onix-v1.0-develop` env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" From 7f707bfd523575c8bc77966b3dc5154dd1957a9f Mon Sep 17 00:00:00 2001 From: AbhishekHS220 Date: Wed, 5 Mar 2025 14:12:16 +0530 Subject: [PATCH 06/21] Update beckn_ci.yml --- .github/workflows/beckn_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 30364be..78f6861 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -3,7 +3,7 @@ name: CI/CD Pipeline on: pull_request: branches: - - beck-onix-v1.0-develop # Trigger when a PR is raised for merging into `beck-onix-v1.0-develop` + - beckn-onix-v1.0-develop # Trigger when a PR is raised for merging into `beck-onix-v1.0-develop` env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" From f59b2666460eb84cf72b883aa2ba7bb764144477 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Thu, 6 Mar 2025 16:24:27 +0530 Subject: [PATCH 07/21] Update beckn_ci.yml --- .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 78f6861..311ad26 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -87,11 +87,11 @@ jobs: echo "Total coverage: $coverage%" # Check if coverage is greater than or equal to 90% - if (( $(echo "$coverage < 80" | bc -l) )); then - echo "Coverage is below 80%. Failing the job." + if (( $(echo "$coverage < 90" | bc -l) )); then + echo "Coverage is below 90%. Failing the job." exit 1 else - echo "Coverage is 80% or above. Continuing the job." + echo "Coverage is 90% or above. Continuing the job." fi From b2e27f2415c961de8406a19bfe0a0031c5f2237b Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Mon, 10 Mar 2025 13:02:34 +0530 Subject: [PATCH 08/21] Update beckn_ci.yml Testing for Signer --- .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 311ad26..7172286 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -7,8 +7,8 @@ on: env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" - APP_DIRECTORY: "go_app" - REF_BRANCH: "feature/test_go_app" + APP_DIRECTORY: "shared/plugin/implementations/signer" + REF_BRANCH: "feature/*" jobs: lint_and_test: From 2a1e28121fdf199e71f1989d98bcd098340cc959 Mon Sep 17 00:00:00 2001 From: AbhishekHS220 Date: Mon, 10 Mar 2025 13:14:39 +0530 Subject: [PATCH 09/21] Update beckn_ci.yml --- .github/workflows/beckn_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 7172286..4a72830 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -8,7 +8,7 @@ on: env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" APP_DIRECTORY: "shared/plugin/implementations/signer" - REF_BRANCH: "feature/*" + REF_BRANCH: "feature/signing_plugin" jobs: lint_and_test: From bb93707bec71b6e880752a8e3f58e073e6c4b00f Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 11 Mar 2025 11:14:32 +0530 Subject: [PATCH 10/21] Update beckn_ci.yml testing specific to Mohit's feature branch --- .github/workflows/beckn_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 4a72830..ecad3b2 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -8,7 +8,7 @@ on: env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" APP_DIRECTORY: "shared/plugin/implementations/signer" - REF_BRANCH: "feature/signing_plugin" + # REF_BRANCH: "feature/signing_plugin" jobs: lint_and_test: From 4f1ba58ceb865ed31121aecd9911b052a7cc3e97 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 11 Mar 2025 12:20:25 +0530 Subject: [PATCH 11/21] Update beckn_ci.yml testing for multiple files --- .github/workflows/beckn_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index ecad3b2..17db47f 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -7,7 +7,7 @@ on: env: # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" - APP_DIRECTORY: "shared/plugin/implementations/signer" + APP_DIRECTORY: "shared/plugin" # REF_BRANCH: "feature/signing_plugin" jobs: From 3fab82bba38587bcc11f6a5ce2e71b0eafa7fd24 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 11 Mar 2025 14:04:57 +0530 Subject: [PATCH 12/21] Update beckn_ci.yml testing for root directory and if test file not present --- .github/workflows/beckn_ci.yml | 89 +++++++++++----------------------- 1 file changed, 29 insertions(+), 60 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 17db47f..64d4ff0 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -3,12 +3,10 @@ name: CI/CD Pipeline on: pull_request: branches: - - beckn-onix-v1.0-develop # Trigger when a PR is raised for merging into `beck-onix-v1.0-develop` + - beckn-onix-v1.0-develop env: - # AR_LOCATION: "${{ secrets.GCP_PROJECT_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/${{ secrets.GCP_AR_REPO }}" - APP_DIRECTORY: "shared/plugin" - # REF_BRANCH: "feature/signing_plugin" + APP_DIRECTORY: "shared/plugin" # Root directory to start searching from jobs: lint_and_test: @@ -20,89 +18,59 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ${{ env.REF_BRANCH }} # Make sure we're on the 'test' branch for checking out code + ref: ${{ env.REF_BRANCH }} fetch-depth: 1 - # 2. Debugging: List files in the repository root after checkout - - name: List files in the root directory - run: | - echo "Current working directory is: $(pwd)" - echo "Listing files in the root directory:" - ls -al - - # 3. Set up Go environment + # 2. Set up Go environment - name: Set up Go 1.24.0 uses: actions/setup-go@v4 with: go-version: '1.24.0' - # 4. Navigate to the app directory - - name: Navigate to app directory - run: | - echo "Changing directory to ${{ env.APP_DIRECTORY }}" - cd ${{ env.APP_DIRECTORY }} - echo "Listing files in ${{ env.APP_DIRECTORY }} directory:" - ls -al # List files in the ${{ env.APP_DIRECTORY }} directory - - # 5. Install golangci-lint + # 3. Install golangci-lint - name: Install golangci-lint run: | - echo "Installing golangci-lint..." go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - echo "golangci-lint installed to: $(go env GOPATH)/bin/" - ls -l $(go env GOPATH)/bin/ # Debugging: List the contents of the bin directory - # 6. Verify golangci-lint binary location - - name: Verify golangci-lint binary - run: | - echo "Checking if golangci-lint is installed..." - ls $(go env GOPATH)/bin/ # Debugging: Check if golangci-lint is present - if [ ! -f $(go env GOPATH)/bin/golangci-lint ]; then - echo "golangci-lint was not found, exiting." - exit 1 - fi - - # 7. Run Linter + # 4. Run golangci-lint on the entire APP_DIRECTORY (including subdirectories) - name: Run golangci-lint run: | - echo "Running golangci-lint..." - cd ${{ env.APP_DIRECTORY }} - golangci-lint run + golangci-lint run ${{ env.APP_DIRECTORY }} - # 8. Run unit tests with coverage + # 5. Run unit tests with coverage recursively for all Go files - name: Run unit tests with coverage run: | - cd ${{ env.APP_DIRECTORY }} - echo "Running unit tests..." - go test -v -coverprofile=coverage.out + # Run tests recursively for all Go files + go test -v -coverprofile=coverage.out ${{ env.APP_DIRECTORY }}/... + + # Generate coverage report go tool cover -func=coverage.out | tee coverage.txt - # 9. Check if coverage is >= 90% + # 6. Check if coverage is >= 90%, but only check coverage if tests exist - name: Check coverage percentage run: | - cd ${{ env.APP_DIRECTORY }} - # Extract total coverage percentage from the output - coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') + # 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/%//') - echo "Total coverage: $coverage%" + echo "Total coverage: $coverage%" - # Check if coverage is greater than or equal to 90% - if (( $(echo "$coverage < 90" | bc -l) )); then - echo "Coverage is below 90%. Failing the job." - exit 1 + # Check if coverage is greater than or equal to 90% + if (( $(echo "$coverage < 90" | bc -l) )); then + echo "Coverage is below 90%. Failing the job." + exit 1 + else + echo "Coverage is 90% or above. Continuing the job." + fi else - echo "Coverage is 90% or above. Continuing the job." + echo "No coverage file found. Skipping coverage check." fi - - # 10. Build the Go code + # 7. Build the Go code - name: Build Go code run: | - echo "Building the Go application..." - cd ${{ env.APP_DIRECTORY }} - go build -o myapp - - # Verify if the build was successful + go build -o myapp ${{ env.APP_DIRECTORY }}/... if [ ! -f myapp ]; then echo "Build failed: myapp executable was not created." exit 1 @@ -110,3 +78,4 @@ jobs: echo "Build succeeded: myapp executable created." fi +4 From 23132c22ab9f275d07b887d5264a88ddd9927215 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 11 Mar 2025 14:10:16 +0530 Subject: [PATCH 13/21] Update beckn_ci.yml removed the syntax error --- .github/workflows/beckn_ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 64d4ff0..f92af57 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -77,5 +77,3 @@ jobs: else echo "Build succeeded: myapp executable created." fi - -4 From 783712f642606e2424416a17f1c0bc9c84aa4bad Mon Sep 17 00:00:00 2001 From: AbhishekHS220 Date: Tue, 11 Mar 2025 14:22:38 +0530 Subject: [PATCH 14/21] Update beckn_ci.yml --- .github/workflows/beckn_ci.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index f92af57..41fe38c 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -32,16 +32,16 @@ jobs: run: | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - # 4. Run golangci-lint on the entire APP_DIRECTORY (including subdirectories) + # 4. Run golangci-lint on the entire APP_DIRECTORY (including subdirectories) ${{ env.APP_DIRECTORY }} - name: Run golangci-lint run: | - golangci-lint run ${{ env.APP_DIRECTORY }} + golangci-lint run ./... - # 5. Run unit tests with coverage recursively for all Go files + # 5. Run unit tests with coverage recursively for all Go files ${{ env.APP_DIRECTORY }} - name: Run unit tests with coverage run: | # Run tests recursively for all Go files - go test -v -coverprofile=coverage.out ${{ env.APP_DIRECTORY }}/... + go test -v -coverprofile=coverage.out ./... # Generate coverage report go tool cover -func=coverage.out | tee coverage.txt @@ -68,12 +68,12 @@ jobs: fi # 7. Build the Go code - - name: Build Go code - run: | - go build -o myapp ${{ env.APP_DIRECTORY }}/... - if [ ! -f myapp ]; then - echo "Build failed: myapp executable was not created." - exit 1 - else - echo "Build succeeded: myapp executable created." - fi + #- name: Build Go code + # run: | + # go build -o myapp ${{ env.APP_DIRECTORY }}/... + # if [ ! -f myapp ]; then + # echo "Build failed: myapp executable was not created." + # exit 1 + # else + # echo "Build succeeded: myapp executable created." + # fi From 51f8548e01044eac5ab7482a891b2205208d798b Mon Sep 17 00:00:00 2001 From: AbhishekHS220 Date: Tue, 11 Mar 2025 14:28:17 +0530 Subject: [PATCH 15/21] Update beckn_ci.yml --- .github/workflows/beckn_ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 41fe38c..3b0e2e4 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -17,9 +17,6 @@ jobs: # 1. Checkout the code from the test branch (triggered by PR) - name: Checkout code uses: actions/checkout@v4 - with: - ref: ${{ env.REF_BRANCH }} - fetch-depth: 1 # 2. Set up Go environment - name: Set up Go 1.24.0 From b0c827fbd4d7e39e2b2f4f133759481e4530e955 Mon Sep 17 00:00:00 2001 From: BushraS-Protean Date: Tue, 11 Mar 2025 14:57:17 +0530 Subject: [PATCH 16/21] Update beckn_ci.yml checking entire directory --- .github/workflows/beckn_ci.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/beckn_ci.yml b/.github/workflows/beckn_ci.yml index 3b0e2e4..a260e39 100644 --- a/.github/workflows/beckn_ci.yml +++ b/.github/workflows/beckn_ci.yml @@ -34,14 +34,15 @@ jobs: run: | golangci-lint run ./... - # 5. Run unit tests with coverage recursively for all Go files ${{ env.APP_DIRECTORY }} + # 5. Run unit tests with coverage in the entire repository - name: Run unit tests with coverage run: | - # Run tests recursively for all Go files - go test -v -coverprofile=coverage.out ./... - - # Generate coverage report - go tool cover -func=coverage.out | tee coverage.txt + # 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 + done # 6. Check if coverage is >= 90%, but only check coverage if tests exist - name: Check coverage percentage From 9722c3bf6869c2546da339ce49f7c57979d3ab84 Mon Sep 17 00:00:00 2001 From: MohitKatare-protean <144896981+MohitKatare-protean@users.noreply.github.com> Date: Thu, 13 Mar 2025 17:12:49 +0530 Subject: [PATCH 17/21] Feature/signing plugin (#402) * test commit * delete test commit * signing plugin - changes for review * Initial commit : beckn Signing Plugin Module * Added verification plugin post review discussion with leads below changes are made 1. Commented out the signature expiration validation code for both the signing and verification plugins. will update it based on the confirmation. * Create signing_plugin_test.go Added Test Cases for Signing Plugin * Signing and Verification Plugin Added signing plugin and verification plugin with the unit test cases, achieving the following code coverage Signing Plugin : 91.9% Verification Plugin : 92.3% * Added plugin.go to handle the dynamic loading and usage of the plugin implementation * Update the code to meet the linting standards * Added Test Cases for plugin.go 1.Added test cases for plugin.go for both signer and verifier. 2.Added new Function Close to release the resources (mock implementation) 3.Fixed camelCase Issue. * Updated the code coverage for signing plugin Raised code coverage from 85 to 92 for signing plugin * Changes for review Comments 1. updated directory names from Signer to signer 2. Updated Verifier plugin to take header value 3. Updated the config to use a pointer in the signing plugin * Updated directory name for signer and verifier * Removed the Duplicate directories Signer and Verifier * Updated the code to pass the timestamp as a parameter for the signing plugin * Updates on the review comments * Update on the Review Comments * Test commit for code coverage * Update on the review Comments 1. Renaming of NewSigner to New 2. Removed of .so files. 3. Removed external libraries. * Test commit for code coverage * udpate as per the golint standards * update on the code review comments 1. Rename of Validator to Verifier 2. Removed as a pointer for plugins 3. comment updated for Signer * Test Commit for the code coverage * test commit for code coverage * test commit for code coverage * test commit for code coverage * updated code on review comments * update on review comments * update on review comments --------- Co-authored-by: mohit3367 --- go.mod | 12 ++ go.sum | 8 + shared/plugin/definition/signVerifier.go | 22 +++ shared/plugin/definition/signer.go | 24 +++ .../implementation/signVerifier/cmd/plugin.go | 26 +++ .../signVerifier/cmd/plugin_test.go | 89 ++++++++++ .../signVerifier/signVerifier.go | 120 ++++++++++++++ .../signVerifier/signVerifier_test.go | 153 ++++++++++++++++++ .../implementation/signer/cmd/plugin.go | 24 +++ .../implementation/signer/cmd/plugin_test.go | 101 ++++++++++++ shared/plugin/implementation/signer/signer.go | 77 +++++++++ .../implementation/signer/signer_test.go | 104 ++++++++++++ shared/plugin/manager.go | 108 +++++++++++++ 13 files changed, 868 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 shared/plugin/definition/signVerifier.go create mode 100644 shared/plugin/definition/signer.go create mode 100644 shared/plugin/implementation/signVerifier/cmd/plugin.go create mode 100644 shared/plugin/implementation/signVerifier/cmd/plugin_test.go create mode 100644 shared/plugin/implementation/signVerifier/signVerifier.go create mode 100644 shared/plugin/implementation/signVerifier/signVerifier_test.go create mode 100644 shared/plugin/implementation/signer/cmd/plugin.go create mode 100644 shared/plugin/implementation/signer/cmd/plugin_test.go create mode 100644 shared/plugin/implementation/signer/signer.go create mode 100644 shared/plugin/implementation/signer/signer_test.go create mode 100644 shared/plugin/manager.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..67f3590 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/beckn/beckn-onix + +go 1.23.0 + +toolchain go1.23.7 + +require golang.org/x/crypto v0.36.0 + +require ( + golang.org/x/sys v0.31.0 // indirect + gopkg.in/yaml.v2 v2.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d05e730 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/shared/plugin/definition/signVerifier.go b/shared/plugin/definition/signVerifier.go new file mode 100644 index 0000000..fe36358 --- /dev/null +++ b/shared/plugin/definition/signVerifier.go @@ -0,0 +1,22 @@ +package definition + +import "context" + +// Verifier defines the method for verifying signatures. +type Verifier interface { + // Verify checks the validity of the signature for the given body. + Verify(ctx context.Context, body []byte, header []byte, publicKeyBase64 string) (bool, error) + Close() error // Close for releasing resources +} + +// VerifierProvider initializes a new Verifier instance with the given config. +type VerifierProvider interface { + // New creates a new Verifier instance based on the provided config. + New(ctx context.Context, config map[string]string) (Verifier, func() error, error) +} + +// PublicKeyManager is the interface for key management plugin. +type PublicKeyManager interface { + // PublicKey retrieves the public key for the given subscriberID and keyID. + PublicKey(ctx context.Context, subscriberID string, keyID string) (string, error) +} diff --git a/shared/plugin/definition/signer.go b/shared/plugin/definition/signer.go new file mode 100644 index 0000000..84db5f5 --- /dev/null +++ b/shared/plugin/definition/signer.go @@ -0,0 +1,24 @@ +package definition + +import "context" + +// Signer defines the method for signing. +type Signer interface { + // Sign generates a signature for the given body and privateKeyBase64. + // The signature is created with the given timestamps: createdAt (signature creation time) + // and expiresAt (signature expiration time). + Sign(ctx context.Context, body []byte, privateKeyBase64 string, createdAt, expiresAt int64) (string, error) + Close() error // Close for releasing resources +} + +// SignerProvider initializes a new signer instance with the given config. +type SignerProvider interface { + // New creates a new signer instance based on the provided config. + New(ctx context.Context, config map[string]string) (Signer, func() error, error) +} + +// PrivateKeyManager is the interface for key management plugin. +type PrivateKeyManager interface { + // PrivateKey retrieves the private key for the given subscriberID and keyID. + PrivateKey(ctx context.Context, subscriberID string, keyID string) (string, error) +} diff --git a/shared/plugin/implementation/signVerifier/cmd/plugin.go b/shared/plugin/implementation/signVerifier/cmd/plugin.go new file mode 100644 index 0000000..1e4fb06 --- /dev/null +++ b/shared/plugin/implementation/signVerifier/cmd/plugin.go @@ -0,0 +1,26 @@ +package main + +import ( + "context" + "errors" + + "github.com/beckn/beckn-onix/shared/plugin/definition" + + plugin "github.com/beckn/beckn-onix/shared/plugin/definition" + verifier "github.com/beckn/beckn-onix/shared/plugin/implementation/signVerifier" +) + +// VerifierProvider provides instances of Verifier. +type VerifierProvider struct{} + +// New initializes a new Verifier instance. +func (vp VerifierProvider) New(ctx context.Context, config map[string]string) (plugin.Verifier, func() error, error) { + if ctx == nil { + return nil, nil, errors.New("context cannot be nil") + } + + return verifier.New(ctx, &verifier.Config{}) +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider definition.VerifierProvider = VerifierProvider{} diff --git a/shared/plugin/implementation/signVerifier/cmd/plugin_test.go b/shared/plugin/implementation/signVerifier/cmd/plugin_test.go new file mode 100644 index 0000000..85caee5 --- /dev/null +++ b/shared/plugin/implementation/signVerifier/cmd/plugin_test.go @@ -0,0 +1,89 @@ +package main + +import ( + "context" + "testing" +) + +// TestVerifierProviderSuccess tests successful creation of a verifier. +func TestVerifierProviderSuccess(t *testing.T) { + provider := VerifierProvider{} + + tests := []struct { + name string + ctx context.Context + config map[string]string + }{ + { + name: "Successful creation", + ctx: context.Background(), + config: map[string]string{}, + }, + { + name: "Nil context", + ctx: context.TODO(), + config: map[string]string{}, + }, + { + name: "Empty config", + ctx: context.Background(), + config: map[string]string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifier, close, err := provider.New(tt.ctx, tt.config) + + if err != nil { + t.Fatalf("Expected no error, but got: %v", err) + } + if verifier == nil { + t.Fatal("Expected verifier instance to be non-nil") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + }) + } +} + +// TestVerifierProviderFailure tests cases where verifier creation should fail. +func TestVerifierProviderFailure(t *testing.T) { + provider := VerifierProvider{} + + tests := []struct { + name string + ctx context.Context + config map[string]string + wantErr bool + }{ + { + name: "Nil context failure", + ctx: nil, + config: map[string]string{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifierInstance, close, err := provider.New(tt.ctx, tt.config) + + if (err != nil) != tt.wantErr { + t.Fatalf("Expected error: %v, but got: %v", tt.wantErr, err) + } + if verifierInstance != nil { + t.Fatal("Expected verifier instance to be nil") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + + }) + } +} diff --git a/shared/plugin/implementation/signVerifier/signVerifier.go b/shared/plugin/implementation/signVerifier/signVerifier.go new file mode 100644 index 0000000..963d137 --- /dev/null +++ b/shared/plugin/implementation/signVerifier/signVerifier.go @@ -0,0 +1,120 @@ +package verifier + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/blake2b" +) + +// Config struct for Verifier. +type Config struct { +} + +// Verifier implements the Verifier interface. +type Verifier struct { + config *Config +} + +// New creates a new Verifier instance. +func New(ctx context.Context, config *Config) (*Verifier, func() error, error) { + v := &Verifier{config: config} + + return v, v.Close, nil +} + +// Verify checks the signature for the given payload and public key. +func (v *Verifier) Verify(ctx context.Context, body []byte, header []byte, publicKeyBase64 string) (bool, error) { + createdTimestamp, expiredTimestamp, signature, err := parseAuthHeader(string(header)) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return false, fmt.Errorf("error parsing header: %w", err) + } + + signatureBytes, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return false, fmt.Errorf("error decoding signature: %w", err) + } + + currentTime := time.Now().Unix() + if createdTimestamp > currentTime || currentTime > expiredTimestamp { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return false, fmt.Errorf("signature is expired or not yet valid") + } + + createdTime := time.Unix(createdTimestamp, 0) + expiredTime := time.Unix(expiredTimestamp, 0) + + signingString := hash(body, createdTime.Unix(), expiredTime.Unix()) + + decodedPublicKey, err := base64.StdEncoding.DecodeString(publicKeyBase64) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return false, fmt.Errorf("error decoding public key: %w", err) + } + + if !ed25519.Verify(ed25519.PublicKey(decodedPublicKey), []byte(signingString), signatureBytes) { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return false, fmt.Errorf("signature verification failed") + } + + return true, nil +} + +// parseAuthHeader extracts signature values from the Authorization header. +func parseAuthHeader(header string) (int64, int64, string, error) { + header = strings.TrimPrefix(header, "Signature ") + + parts := strings.Split(header, ",") + signatureMap := make(map[string]string) + + for _, part := range parts { + keyValue := strings.SplitN(strings.TrimSpace(part), "=", 2) + if len(keyValue) == 2 { + key := strings.TrimSpace(keyValue[0]) + value := strings.Trim(keyValue[1], "\"") + signatureMap[key] = value + } + } + + createdTimestamp, err := strconv.ParseInt(signatureMap["created"], 10, 64) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return 0, 0, "", fmt.Errorf("invalid created timestamp: %w", err) + } + + expiredTimestamp, err := strconv.ParseInt(signatureMap["expires"], 10, 64) + if err != nil { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return 0, 0, "", fmt.Errorf("invalid expires timestamp: %w", err) + } + + signature := signatureMap["signature"] + if signature == "" { + // TODO: Return appropriate error code when Error Code Handling Module is ready + return 0, 0, "", fmt.Errorf("signature missing in header") + } + + return createdTimestamp, expiredTimestamp, signature, nil +} + +// hash constructs a signing string for verification. +func hash(payload []byte, createdTimestamp, expiredTimestamp int64) string { + hasher, _ := blake2b.New512(nil) + hasher.Write(payload) + hashSum := hasher.Sum(nil) + digestB64 := base64.StdEncoding.EncodeToString(hashSum) + + return fmt.Sprintf("(created): %d\n(expires): %d\ndigest: BLAKE-512=%s", createdTimestamp, expiredTimestamp, digestB64) +} + +// Close releases resources (mock implementation returning nil). +func (v *Verifier) Close() error { + return nil +} diff --git a/shared/plugin/implementation/signVerifier/signVerifier_test.go b/shared/plugin/implementation/signVerifier/signVerifier_test.go new file mode 100644 index 0000000..36da03a --- /dev/null +++ b/shared/plugin/implementation/signVerifier/signVerifier_test.go @@ -0,0 +1,153 @@ +package verifier + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "strconv" + "testing" + "time" +) + +// generateTestKeyPair generates a new ED25519 key pair for testing. +func generateTestKeyPair() (string, string) { + publicKey, privateKey, _ := ed25519.GenerateKey(nil) + return base64.StdEncoding.EncodeToString(privateKey), base64.StdEncoding.EncodeToString(publicKey) +} + +// signTestData creates a valid signature for test cases. +func signTestData(privateKeyBase64 string, body []byte, createdAt, expiresAt int64) string { + privateKeyBytes, _ := base64.StdEncoding.DecodeString(privateKeyBase64) + privateKey := ed25519.PrivateKey(privateKeyBytes) + + signingString := hash(body, createdAt, expiresAt) + signature := ed25519.Sign(privateKey, []byte(signingString)) + + return base64.StdEncoding.EncodeToString(signature) +} + +// TestVerifySuccessCases tests all valid signature verification cases. +func TestVerifySuccess(t *testing.T) { + privateKeyBase64, publicKeyBase64 := generateTestKeyPair() + + tests := []struct { + name string + body []byte + createdAt int64 + expiresAt int64 + }{ + { + name: "Valid Signature", + body: []byte("Test Payload"), + createdAt: time.Now().Unix(), + expiresAt: time.Now().Unix() + 3600, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + signature := signTestData(privateKeyBase64, tt.body, tt.createdAt, tt.expiresAt) + header := `Signature created="` + strconv.FormatInt(tt.createdAt, 10) + + `", expires="` + strconv.FormatInt(tt.expiresAt, 10) + + `", signature="` + signature + `"` + + verifier, close, _ := New(context.Background(), &Config{}) + valid, err := verifier.Verify(context.Background(), tt.body, []byte(header), publicKeyBase64) + + if err != nil { + t.Fatalf("Expected no error, but got: %v", err) + } + if !valid { + t.Fatal("Expected signature verification to succeed") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + }) + } +} + +// TestVerifyFailureCases tests all invalid signature verification cases. +func TestVerifyFailure(t *testing.T) { + privateKeyBase64, publicKeyBase64 := generateTestKeyPair() + _, wrongPublicKeyBase64 := generateTestKeyPair() + + tests := []struct { + name string + body []byte + header string + pubKey string + }{ + { + name: "Missing Authorization Header", + body: []byte("Test Payload"), + header: "", + pubKey: publicKeyBase64, + }, + { + name: "Malformed Header", + body: []byte("Test Payload"), + header: `InvalidSignature created="wrong"`, + pubKey: publicKeyBase64, + }, + { + name: "Invalid Base64 Signature", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + + `", signature="!!INVALIDBASE64!!"`, + pubKey: publicKeyBase64, + }, + { + name: "Expired Signature", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix()-7200, 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()-3600, 10) + + `", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix()-7200, time.Now().Unix()-3600) + `"`, + pubKey: publicKeyBase64, + }, + { + name: "Invalid Public Key", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + + `", signature="` + signTestData(privateKeyBase64, []byte("Test Payload"), time.Now().Unix(), time.Now().Unix()+3600) + `"`, + pubKey: wrongPublicKeyBase64, + }, + { + name: "Invalid Expires Timestamp", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="invalid_timestamp"`, + pubKey: publicKeyBase64, + }, + { + name: "Signature Missing in Headers", + body: []byte("Test Payload"), + header: `Signature created="` + strconv.FormatInt(time.Now().Unix(), 10) + + `", expires="` + strconv.FormatInt(time.Now().Unix()+3600, 10) + `"`, + pubKey: publicKeyBase64, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + verifier, close, _ := New(context.Background(), &Config{}) + valid, err := verifier.Verify(context.Background(), tt.body, []byte(tt.header), tt.pubKey) + + if err == nil { + t.Fatal("Expected an error but got none") + } + if valid { + t.Fatal("Expected verification to fail") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Test %q failed: cleanup function returned an error: %v", tt.name, err) + } + } + }) + } +} diff --git a/shared/plugin/implementation/signer/cmd/plugin.go b/shared/plugin/implementation/signer/cmd/plugin.go new file mode 100644 index 0000000..854ecbe --- /dev/null +++ b/shared/plugin/implementation/signer/cmd/plugin.go @@ -0,0 +1,24 @@ +package main + +import ( + "context" + "errors" + + "github.com/beckn/beckn-onix/shared/plugin/definition" + "github.com/beckn/beckn-onix/shared/plugin/implementation/signer" +) + +// SignerProvider implements the definition.SignerProvider interface. +type SignerProvider struct{} + +// New creates a new Signer instance using the provided configuration. +func (p SignerProvider) New(ctx context.Context, config map[string]string) (definition.Signer, func() error, error) { + if ctx == nil { + return nil, nil, errors.New("context cannot be nil") + } + + return signer.New(ctx, &signer.Config{}) +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider definition.SignerProvider = SignerProvider{} diff --git a/shared/plugin/implementation/signer/cmd/plugin_test.go b/shared/plugin/implementation/signer/cmd/plugin_test.go new file mode 100644 index 0000000..e4730d5 --- /dev/null +++ b/shared/plugin/implementation/signer/cmd/plugin_test.go @@ -0,0 +1,101 @@ +package main + +import ( + "context" + "testing" +) + +// TestSignerProviderSuccess verifies successful scenarios for SignerProvider. +func TestSignerProviderSuccess(t *testing.T) { + provider := SignerProvider{} + + successTests := []struct { + name string + ctx context.Context + config map[string]string + }{ + { + name: "Valid Config", + ctx: context.Background(), + config: map[string]string{}, + }, + { + name: "Unexpected Config Key", + ctx: context.Background(), + config: map[string]string{"unexpected_key": "some_value"}, + }, + { + name: "Empty Config", + ctx: context.Background(), + config: map[string]string{}, + }, + { + name: "Config with empty TTL", + ctx: context.Background(), + config: map[string]string{"ttl": ""}, + }, + { + name: "Config with negative TTL", + ctx: context.Background(), + config: map[string]string{"ttl": "-100"}, + }, + { + name: "Config with non-numeric TTL", + ctx: context.Background(), + config: map[string]string{"ttl": "not_a_number"}, + }, + } + + for _, tt := range successTests { + t.Run(tt.name, func(t *testing.T) { + signer, close, err := provider.New(tt.ctx, tt.config) + + if err != nil { + t.Fatalf("Test %q failed: expected no error, but got: %v", tt.name, err) + } + if signer == nil { + t.Fatalf("Test %q failed: signer instance should not be nil", tt.name) + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Cleanup function returned an error: %v", err) + } + } + }) + } +} + +// TestSignerProviderFailure verifies failure scenarios for SignerProvider. +func TestSignerProviderFailure(t *testing.T) { + provider := SignerProvider{} + + failureTests := []struct { + name string + ctx context.Context + config map[string]string + wantErr bool + }{ + { + name: "Nil Context", + ctx: nil, + config: map[string]string{}, + wantErr: true, + }, + } + + for _, tt := range failureTests { + t.Run(tt.name, func(t *testing.T) { + signerInstance, close, err := provider.New(tt.ctx, tt.config) + + if (err != nil) != tt.wantErr { + t.Fatalf("Test %q failed: expected error: %v, got: %v", tt.name, tt.wantErr, err) + } + if signerInstance != nil { + t.Fatalf("Test %q failed: expected signer instance to be nil", tt.name) + } + if close != nil { + t.Fatalf("Test %q failed: expected cleanup function to be nil", tt.name) + } + }) + } +} diff --git a/shared/plugin/implementation/signer/signer.go b/shared/plugin/implementation/signer/signer.go new file mode 100644 index 0000000..c1f2af9 --- /dev/null +++ b/shared/plugin/implementation/signer/signer.go @@ -0,0 +1,77 @@ +package signer + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "errors" + "fmt" + + "golang.org/x/crypto/blake2b" +) + +// Config holds the configuration for the signing process. +type Config struct { +} + +// Signer implements the Signer interface and handles the signing process. +type Signer struct { + config *Config +} + +// New creates a new Signer instance with the given configuration. +func New(ctx context.Context, config *Config) (*Signer, func() error, error) { + s := &Signer{config: config} + + return s, s.Close, nil +} + +// hash generates a signing string using BLAKE-512 hashing. +func hash(payload []byte, createdAt, expiresAt int64) (string, error) { + hasher, _ := blake2b.New512(nil) + + _, err := hasher.Write(payload) + if err != nil { + return "", fmt.Errorf("failed to hash payload: %w", err) + } + + hashSum := hasher.Sum(nil) + digestB64 := base64.StdEncoding.EncodeToString(hashSum) + + return fmt.Sprintf("(created): %d\n(expires): %d\ndigest: BLAKE-512=%s", createdAt, expiresAt, digestB64), nil +} + +// generateSignature signs the given signing string using the provided private key. +func generateSignature(signingString []byte, privateKeyBase64 string) ([]byte, error) { + privateKeyBytes, err := base64.StdEncoding.DecodeString(privateKeyBase64) + if err != nil { + return nil, fmt.Errorf("error decoding private key: %w", err) + } + + if len(privateKeyBytes) != ed25519.PrivateKeySize { + return nil, errors.New("invalid private key length") + } + + privateKey := ed25519.PrivateKey(privateKeyBytes) + return ed25519.Sign(privateKey, signingString), nil +} + +// Sign generates a digital signature for the provided payload. +func (s *Signer) Sign(ctx context.Context, body []byte, privateKeyBase64 string, createdAt, expiresAt int64) (string, error) { + signingString, err := hash(body, createdAt, expiresAt) + if err != nil { + return "", err + } + + signature, err := generateSignature([]byte(signingString), privateKeyBase64) + if err != nil { + return "", err + } + + return base64.StdEncoding.EncodeToString(signature), nil +} + +// Close releases resources (mock implementation returning nil). +func (s *Signer) Close() error { + return nil +} diff --git a/shared/plugin/implementation/signer/signer_test.go b/shared/plugin/implementation/signer/signer_test.go new file mode 100644 index 0000000..6a25da1 --- /dev/null +++ b/shared/plugin/implementation/signer/signer_test.go @@ -0,0 +1,104 @@ +package signer + +import ( + "context" + "crypto/ed25519" + "encoding/base64" + "strings" + "testing" + "time" +) + +// generateTestKeys generates a test private and public key pair in base64 encoding. +func generateTestKeys() (string, string) { + publicKey, privateKey, _ := ed25519.GenerateKey(nil) + return base64.StdEncoding.EncodeToString(privateKey), base64.StdEncoding.EncodeToString(publicKey) +} + +// TestSignSuccess tests the Sign method with valid inputs to ensure it produces a valid signature. +func TestSignSuccess(t *testing.T) { + privateKey, _ := generateTestKeys() + config := Config{} + signer, close, _ := New(context.Background(), &config) + + successTests := []struct { + name string + payload []byte + privateKey string + createdAt int64 + expiresAt int64 + }{ + { + name: "Valid Signing", + payload: []byte("test payload"), + privateKey: privateKey, + createdAt: time.Now().Unix(), + expiresAt: time.Now().Unix() + 3600, + }, + } + + for _, tt := range successTests { + t.Run(tt.name, func(t *testing.T) { + signature, err := signer.Sign(context.Background(), tt.payload, tt.privateKey, tt.createdAt, tt.expiresAt) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(signature) == 0 { + t.Errorf("expected a non-empty signature, but got empty") + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Cleanup function returned an error: %v", err) + } + } + }) + } +} + +// TestSignFailure tests the Sign method with invalid inputs to ensure proper error handling. +func TestSignFailure(t *testing.T) { + config := Config{} + signer, close, _ := New(context.Background(), &config) + + failureTests := []struct { + name string + payload []byte + privateKey string + createdAt int64 + expiresAt int64 + expectErrString string + }{ + { + name: "Invalid Private Key", + payload: []byte("test payload"), + privateKey: "invalid_key", + createdAt: time.Now().Unix(), + expiresAt: time.Now().Unix() + 3600, + expectErrString: "error decoding private key", + }, + { + name: "Short Private Key", + payload: []byte("test payload"), + privateKey: base64.StdEncoding.EncodeToString([]byte("short_key")), + createdAt: time.Now().Unix(), + expiresAt: time.Now().Unix() + 3600, + expectErrString: "invalid private key length", + }, + } + + for _, tt := range failureTests { + t.Run(tt.name, func(t *testing.T) { + _, err := signer.Sign(context.Background(), tt.payload, tt.privateKey, tt.createdAt, tt.expiresAt) + if err == nil { + t.Errorf("expected error but got none") + } else if !strings.Contains(err.Error(), tt.expectErrString) { + t.Errorf("expected error message to contain %q, got %v", tt.expectErrString, err) + } + if close != nil { + if err := close(); err != nil { + t.Fatalf("Cleanup function returned an error: %v", err) + } + } + }) + } +} diff --git a/shared/plugin/manager.go b/shared/plugin/manager.go new file mode 100644 index 0000000..e31fc98 --- /dev/null +++ b/shared/plugin/manager.go @@ -0,0 +1,108 @@ +package plugin + +import ( + "context" + "fmt" + "path/filepath" + "plugin" + "strings" + + "github.com/beckn/beckn-onix/shared/plugin/definition" +) + +// Config represents the plugin manager configuration. +type Config struct { + Root string `yaml:"root"` + Signer PluginConfig `yaml:"signer"` + Verifier PluginConfig `yaml:"verifier"` +} + +// PluginConfig represents configuration details for a plugin. +type PluginConfig struct { + ID string `yaml:"id"` + Config map[string]string `yaml:"config"` +} + +// Manager handles dynamic plugin loading and management. +type Manager struct { + sp definition.SignerProvider + vp definition.VerifierProvider + cfg *Config +} + +// NewManager initializes a new Manager with the given configuration file. +func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { + if cfg == nil { + return nil, fmt.Errorf("configuration cannot be nil") + } + + // Load signer plugin + sp, err := provider[definition.SignerProvider](cfg.Root, cfg.Signer.ID) + if err != nil { + return nil, fmt.Errorf("failed to load signer plugin: %w", err) + } + + // Load verifier plugin + vp, err := provider[definition.VerifierProvider](cfg.Root, cfg.Verifier.ID) + if err != nil { + return nil, fmt.Errorf("failed to load Verifier plugin: %w", err) + } + + return &Manager{sp: sp, vp: vp, cfg: cfg}, nil +} + +// provider loads a plugin dynamically and retrieves its provider instance. +func provider[T any](root, id string) (T, error) { + var zero T + if len(strings.TrimSpace(id)) == 0 { + return zero, nil + } + + p, err := plugin.Open(pluginPath(root, id)) + if err != nil { + return zero, fmt.Errorf("failed to open plugin %s: %w", id, err) + } + + symbol, err := p.Lookup("Provider") + if err != nil { + return zero, fmt.Errorf("failed to find Provider symbol in plugin %s: %w", id, err) + } + + prov, ok := symbol.(*T) + if !ok { + return zero, fmt.Errorf("failed to cast Provider for %s", id) + } + + return *prov, nil +} + +// pluginPath constructs the path to the plugin shared object file. +func pluginPath(root, id string) string { + return filepath.Join(root, id+".so") +} + +// Signer retrieves the signing plugin instance. +func (m *Manager) Signer(ctx context.Context) (definition.Signer, func() error, error) { + if m.sp == nil { + return nil, nil, fmt.Errorf("signing plugin provider not loaded") + } + + signer, close, err := m.sp.New(ctx, m.cfg.Signer.Config) + if err != nil { + return nil, nil, fmt.Errorf("failed to initialize signer: %w", err) + } + return signer, close, nil +} + +// Verifier retrieves the verification plugin instance. +func (m *Manager) Verifier(ctx context.Context) (definition.Verifier, func() error, error) { + if m.vp == nil { + return nil, nil, fmt.Errorf("Verifier plugin provider not loaded") + } + + Verifier, close, err := m.vp.New(ctx, m.cfg.Verifier.Config) + if err != nil { + return nil, nil, fmt.Errorf("failed to initialize Verifier: %w", err) + } + return Verifier, close, nil +} From 08ba115e7aa922e5795f557d858d2c44dcb54e25 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Mon, 17 Mar 2025 16:48:13 +0530 Subject: [PATCH 18/21] 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 19/21] 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 20/21] 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 21/21] 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