From 75af4a07fb1b319867fba20e4e4c33b57ca5013f Mon Sep 17 00:00:00 2001 From: Greg Tyler Date: Mon, 4 Dec 2023 12:22:59 +0000 Subject: [PATCH] Add Pact provider tests Add a pipeline that runs on PRs, branches and on repository dispatch. Requires a JWT to be generated beforehand and included in headers for authentication. Fixes VEGA-2196 #minor --- .../workflows/pact-provider-verification.yml | 53 +++++++++++++++++++ api-test/main.go | 53 ++++++++++++------- docker-compose.yml | 16 ++++-- mock-apigw/main.go | 1 + 4 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/pact-provider-verification.yml diff --git a/.github/workflows/pact-provider-verification.yml b/.github/workflows/pact-provider-verification.yml new file mode 100644 index 00000000..bc050167 --- /dev/null +++ b/.github/workflows/pact-provider-verification.yml @@ -0,0 +1,53 @@ +name: Pact Provider Verification + +on: + repository_dispatch: + types: [provider-verification] + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + test: + name: Provider verification + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: make build up + - run: go build -o ./api-test/tester ./api-test && chmod +x ./api-test/tester + - run: cat ./docs/example-lpa.json | ./api-test/tester -jwtSecret=secret -expectedStatus=201 REQUEST PUT http://localhost:9000/lpas/M-3467-89QW-ERTY "`xargs -0`" + - run: export JWT=$(./api-test/tester -jwtSecret=secret JWT) + - name: Verify specified Pact + if: ${{ github.event_name == 'repository_dispatch' }} + run: | + docker-compose run --rm pact-verifier \ + --header="X-Jwt-Authorization: Bearer $JWT" \ + --provider-version=$(git rev-parse HEAD) \ + --provider-branch=main \ + --publish \ + --user=admin \ + --password=${{ secrets.PACT_BROKER_PASSWORD }} \ + --url=${{ github.event.client_payload.pact_url }} + - name: Verify pacts, including pending + if: ${{ github.event_name == 'push' }} + run: | + docker-compose run --rm pact-verifier \ + --header="X-Jwt-Authorization: Bearer $JWT" \ + --provider-version=$(git rev-parse HEAD) \ + --provider-branch=main \ + --publish \ + --user=admin \ + --password=${{ secrets.PACT_BROKER_PASSWORD }} \ + --consumer-version-selectors='{"mainBranch": true}' \ + --enable-pending + - name: Verify pacts are still upheld + if: ${{ github.event_name == 'pull_request' }} + run: | + docker-compose run --rm pact-verifier \ + --header="X-Jwt-Authorization=Bearer $JWT" \ + --provider-version=$(git rev-parse HEAD) \ + --provider-branch=${{ github.head_ref }} \ + --consumer-version-selectors='{"mainBranch": true}' diff --git a/api-test/main.go b/api-test/main.go index 93b3459e..cb8ee714 100644 --- a/api-test/main.go +++ b/api-test/main.go @@ -17,8 +17,9 @@ import ( ) // ./api-test/tester UID -> generate a UID +// ./api-test/tester JWT -> generate a JWT // ./api-test/tester -jwtSecret=secret -expectedStatus=200 REQUEST -// -> make a test request with a JWT generated using secret "secret" and expected status 200 +// -> make a test request with a JWT generated using secret "secret" and expected status 200 // note that the jwtSecret sends a boilerplate JWT for now with valid iat, exp, iss and sub fields func main() { expectedStatusCode := flag.Int("expectedStatus", 200, "Expected response status code") @@ -33,6 +34,11 @@ func main() { os.Exit(0) } + if args[0] == "JWT" { + fmt.Print(makeJwt([]byte(*jwtSecret))) + os.Exit(0) + } + if args[0] != "REQUEST" { panic("Unrecognised command") } @@ -49,27 +55,19 @@ func main() { req.Header.Add("Content-type", "application/json") if *jwtSecret != "" { - secretKey := []byte(*jwtSecret) - - claims := jwt.MapClaims{ - "exp": time.Now().Add(time.Hour * 24).Unix(), - "iat": time.Now().Add(time.Hour * -24).Unix(), - "iss": "opg.poas.sirius", - "sub": "someone@someplace.somewhere.com", - } + tokenString := makeJwt([]byte(*jwtSecret)) - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, _ := token.SignedString(secretKey) - - req.Header.Add("X-Jwt-Authorization", fmt.Sprintf("Bearer: %s", tokenString)) + req.Header.Add("X-Jwt-Authorization", fmt.Sprintf("Bearer %s", tokenString)) } - sess := session.Must(session.NewSession()) - signer := v4.NewSigner(sess.Config.Credentials) + sess, err := session.NewSession() + if err == nil { + signer := v4.NewSigner(sess.Config.Credentials) - _, err = signer.Sign(req, body, "execute-api", "eu-west-1", time.Now()) - if err != nil { - panic(err) + _, err = signer.Sign(req, body, "execute-api", "eu-west-1", time.Now()) + if err != nil { + panic(err) + } } client := http.Client{} @@ -88,6 +86,25 @@ func main() { log.Printf("invalid status code %d; expected: %d", resp.StatusCode, *expectedStatusCode) log.Printf("error response: %s", buf.String()) } else { + log.Print(resp.Header) log.Printf("Test passed - %s to %s - %d: %s", method, url, resp.StatusCode, buf.String()) } } + +func makeJwt(secretKey []byte) string { + claims := jwt.MapClaims{ + "exp": time.Now().Add(time.Hour * 24).Unix(), + "iat": time.Now().Add(time.Hour * -24).Unix(), + "iss": "opg.poas.sirius", + "sub": "someone@someplace.somewhere.com", + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + tokenString, err := token.SignedString(secretKey) + + if err != nil { + panic(err) + } + + return tokenString +} diff --git a/docker-compose.yml b/docker-compose.yml index bae43919..bb82a3a2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: AWS_ACCESS_KEY_ID: X AWS_SECRET_ACCESS_KEY: X DDB_TABLE_NAME_DEEDS: deeds - JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret} volumes: - "./lambda/.aws-lambda-rie:/aws-lambda" entrypoint: /aws-lambda/aws-lambda-rie /var/task/main @@ -35,7 +35,7 @@ services: AWS_ACCESS_KEY_ID: X AWS_SECRET_ACCESS_KEY: X DDB_TABLE_NAME_DEEDS: deeds - JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret} volumes: - "./lambda/.aws-lambda-rie:/aws-lambda" entrypoint: /aws-lambda/aws-lambda-rie /var/task/main @@ -53,7 +53,7 @@ services: AWS_ACCESS_KEY_ID: X AWS_SECRET_ACCESS_KEY: X DDB_TABLE_NAME_DEEDS: deeds - JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_SECRET_KEY: ${JWT_SECRET_KEY:-secret} volumes: - "./lambda/.aws-lambda-rie:/aws-lambda" entrypoint: /aws-lambda/aws-lambda-rie /var/task/main @@ -88,3 +88,13 @@ services: volumes: - .:/app command: -exclude-dir=.gocache -fmt sarif -out /app/results.sarif /app/... + + pact-verifier: + image: pactfoundation/pact-ref-verifier + entrypoint: + - pact_verifier_cli + - --hostname=apigw + - --port=8080 + - --base-path=/ + - --broker-url=https://pact-broker.api.opg.service.justice.gov.uk/ + - --provider-name=data-lpa-store diff --git a/mock-apigw/main.go b/mock-apigw/main.go index 96981fa4..6c34d805 100644 --- a/mock-apigw/main.go +++ b/mock-apigw/main.go @@ -71,6 +71,7 @@ func delegateHandler(w http.ResponseWriter, r *http.Request) { var respBody events.APIGatewayProxyResponse _ = json.Unmarshal(encodedRespBody, &respBody) + w.Header().Set("Content-Type", "application/json") w.WriteHeader(respBody.StatusCode) _, err = w.Write([]byte(respBody.Body))