Skip to content

Commit

Permalink
Add Pact provider tests
Browse files Browse the repository at this point in the history
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
  • Loading branch information
gregtyler committed Dec 4, 2023
1 parent cc50fcc commit 340efeb
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 16 deletions.
53 changes: 53 additions & 0 deletions .github/workflows/pact-provider-verification.yml
Original file line number Diff line number Diff line change
@@ -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: echo "JWT=$(./api-test/tester -jwtSecret=secret JWT)" >> "$GITHUB_ENV"
- 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='{"branch": "VEGA2174_lpastore_service"}'
# --consumer-version-selectors='{"mainBranch": true}'
41 changes: 28 additions & 13 deletions api-test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <METHOD> <URL> <REQUEST BODY>
// -> 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")
Expand All @@ -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")
}
Expand All @@ -49,19 +55,9 @@ 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": "[email protected]",
}
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())
Expand All @@ -88,6 +84,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": "[email protected]",
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(secretKey)

if err != nil {
panic(err)
}

return tokenString
}
17 changes: 14 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -88,3 +88,14 @@ 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
- --state-change-url=http://apigw:8080/_pact_state
73 changes: 73 additions & 0 deletions mock-apigw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ func delegateHandler(w http.ResponseWriter, r *http.Request) {
lambdaName := ""
uid := ""

if r.URL.Path == "/_pact_state" {
err := handlePactState(r)
if err != nil {
log.Printf("Error setting up state: %s", err.Error())
http.Error(w, err.Error(), 500)
} else {
w.WriteHeader(200)
}

return
}

if LPAPath.MatchString(r.URL.Path) && r.Method == http.MethodPut {
uid = LPAPath.FindStringSubmatch(r.URL.Path)[1]
lambdaName = "create"
Expand Down Expand Up @@ -71,6 +83,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))

Expand All @@ -79,6 +92,66 @@ func delegateHandler(w http.ResponseWriter, r *http.Request) {
}
}

func handlePactState(r *http.Request) error {
var state struct {
State string `json:"state"`
}

if err := json.NewDecoder(r.Body).Decode(&state); err != nil {
return err
}

re := regexp.MustCompile(`^An LPA with UID (M-[A-Z0-9-]+) exists$`)
if match := re.FindStringSubmatch(state.State); len(match) > 0 {
url := fmt.Sprintf("http://localhost:8080/lpas/%s", match[1])
body := `{
"donor": {
"firstNames": "Homer",
"surname": "Zoller",
"dateOfBirth": "1960-04-06",
"address": {
"line1": "79 Bury Rd",
"town": "Hampton Lovett",
"postcode": "WR9 2PF",
"country": "GB"
}
},
"attorneys": [
{
"firstNames": "Jake",
"surname": "Vallar",
"dateOfBirth": "2001-01-17",
"status": "active",
"address": {
"line1": "71 South Western Terrace",
"town": "Milton",
"country": "AU"
}
}
]
}`

req, err := http.NewRequest("PUT", url, strings.NewReader(body))
if err != nil {
return err
}

req.Header = r.Header.Clone()

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}

if resp.StatusCode >= 400 {
return fmt.Errorf("request failed with status code %d", resp.StatusCode)
}
}

return nil
}

func main() {
http.HandleFunc("/", delegateHandler)

Expand Down

0 comments on commit 340efeb

Please sign in to comment.