diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 927ef66..6d85000 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,9 @@ jobs: node-version: 18 - name: Install lstk - run: npm install -g @localstack/lstk + run: | + npm install -g @localstack/lstk + lstk setup aws --force --non-interactive - name: Start LocalStack env: @@ -63,6 +65,9 @@ jobs: command: | yarn cdklocal deploy + - name: Run tests + run: make test + - name: Send a Slack notification if: failure() || github.event_name != 'pull_request' uses: ravsamhq/notify-slack-action@v2 diff --git a/Makefile b/Makefile index 62ef22a..d402b5d 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,10 @@ bootstrap-frontend: deploy-frontend: yarn cdklocal deploy --app="node dist/aws-sdk-js-notes-app-frontend.js"; +## Create, read, update and delete a note through the API +test: + ./test.sh + ## Stop the Running LocalStack container stop: @echo @@ -63,4 +67,4 @@ setup-challenge: make prepare-frontend-local make hotreload -.PHONY: usage install run start stop logs +.PHONY: usage install run start stop logs test diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..328e99d --- /dev/null +++ b/test.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Walk a note through the REST API: create, read, list, update, delete. +# Each route is its own Lambda function backed by the same DynamoDB table. +set -euo pipefail + +STACK=aws-sdk-js-notes-app +CONTENT="note from CI $(date +%s)" + +API_URL=$(lstk aws cloudformation describe-stacks --stack-name "$STACK" \ + --query "Stacks[0].Outputs[?starts_with(OutputKey, 'endpointEndpoint')].OutputValue | [0]" --output text) +NOTES="${API_URL%/}/notes" +echo "API: $NOTES" + +# Print the body, or fail with the status and body on a non-2xx response +request() { + local body status + body=$(curl -s -m 30 -w '\n%{http_code}' "$@") + status=${body##*$'\n'} + body=${body%$'\n'*} + if [ "${status:0:1}" != "2" ]; then + echo "Test failed: $* returned HTTP $status: $body" >&2 + return 1 + fi + echo "$body" +} + +created=$(request -X POST "$NOTES" -H 'Content-Type: application/json' -d "{\"content\":\"$CONTENT\"}") +ID=$(echo "$created" | jq -r '.noteId.S') +if [ -z "$ID" ] || [ "$ID" = "null" ]; then + echo "Test failed: create returned $created" + exit 1 +fi +echo "PASS: created note $ID" + +note=$(request "$NOTES/$ID") +if ! echo "$note" | jq -e --arg c "$CONTENT" '.content == $c' > /dev/null; then + echo "Test failed: get returned $note" + exit 1 +fi +echo "PASS: read note back" + +if ! request "$NOTES" | jq -e --arg id "$ID" 'any(.[]; .noteId == $id)' > /dev/null; then + echo "Test failed: note $ID missing from the list" + exit 1 +fi +echo "PASS: note appears in the list" + +request -X PUT "$NOTES/$ID" -H 'Content-Type: application/json' -d '{"content":"updated by CI"}' > /dev/null +note=$(request "$NOTES/$ID") +if ! echo "$note" | jq -e '.content == "updated by CI"' > /dev/null; then + echo "Test failed: update not applied, got $note" + exit 1 +fi +echo "PASS: updated note" + +request -X DELETE "$NOTES/$ID" > /dev/null +# getNote answers 500 "Item not found." once the note is gone +status=$(curl -s -m 30 -o /dev/null -w '%{http_code}' "$NOTES/$ID") +if [ "$status" != "500" ]; then + echo "Test failed: note still readable after delete (HTTP $status)" + exit 1 +fi +echo "PASS: deleted note" + +echo "Test passed: notes API handled the full CRUD cycle"