🔑 Key points
- Deploy your tests, coverage and linting analysis using a CI pipeline.
- Protect the confidentiality of your secrets
- Create a version ID
Before you start work on this deliverable make sure you have read all of the preceding instruction topics and have completed all of the dependent exercises (topics marked with a ☑). This includes:
- Testing
- Testing variations
- Coverage
- Jest Basics
- ☑ Jest Advanced
- ☑ Test driven development (TDD)
- ☑ Service testing
- ☑ Lint
- Dependency injection
- Integration testing
- ☑ JWT Pizza Service
- JWT Pizza data
- Unit testing: JWT Pizza Service
Failing to do this will likely slow you down as you will not have the required knowledge to complete the deliverable.
Now that you have added linting and created unit tests for the jwt-pizza-service, you are ready to implement the continuous integration (CI) process that will validate the code with tests and calculate coverage on every commit. This will complete your work on this deliverable.
In your fork of the jwt-pizza-service create the file .github/workflows/ci.yml
and add the following.
name: CI Pipeline
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set_version.outputs.version }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
This workflow is fairly simple. It specifies it will output a version number, checks out the code, installs the NPM dependencies, and runs the linter.
When you commit and push this change, you can view the workflow executing on the GitHub Actions view of your repository. Assuming that you have cleaned up all the linting errors, you should see something similar to the following.
If you go and introduce a linting error to your code by doing something like declaring an unused variable, you should see an error when you commit.
Running the linter is fine, but what we really want to do is run our tests. This is complicated by the fact that we didn't mock out the database calls, and therefore your tests need a database to run against. You could go back and mock out the database calls. Doing so would make your tests faster, but would also remove a crucial integration test that represents a significant piece of what the service is doing. Additionally, mocking the database would create a lot of testing code to maintain.
So instead let's have GitHub Actions run with an instance of MySQL already running on it.
The first step is security. Your repository is public and so you want to make sure that you keep secret anything that would give an advantage to a nefarious party. This includes your JWT Factory API key, JWT authorization token secret, and database password. You can hide these secrets by creating repository secrets and then referencing the secrets in your workflow.
Using the GitHub Repository dashboard for your fork of jwt-pizza-service
, select the repository's settings and then Secrets and variables | Actions
. Chose to create a New repository secret
.
Enter the name FACTORY_API_KEY
and then enter the value you received for making calls to the factory. Press Save key
, and then it is ready to be used in your workflow with the template placeholder of ${{ secrets.FACTORY_API_KEY }}
.
Go ahead and create the following secrets.
Secret | Description | Example |
---|---|---|
JWT_SECRET | A random value that you create for signing your authentication tokens. | 343ab90294hijkfd2fdsaf4dsa3f424 |
FACTORY_API_KEY | The API Key for making calls to the Headquarters factory | 83025y7098dsf9310dc90 |
To set up MySQL you need to add the following to your workflow.
services:
mysql:
image: mysql:8.0.29
env:
MYSQL_ROOT_PASSWORD: tempdbpassword
MYSQL_DATABASE: pizza
ports:
- '3306:3306'
options: >-
--health-cmd "mysqladmin ping -ptempdbpassword"
--health-interval 10s
--health-start-period 10s
--health-timeout 5s
--health-retries 10
This tells GitHub that you require the MySQL service to be installed. We also provide the credentials that we want to use for the database. There isn't really a security problem with doing this since it is only a temporary database that will get thrown away once the tests are done. We just need to make sure we don't use any real credentials here. The options
portion of the workflow specifies to wait until the health-cmd
returns successfully. In this case we will wait ten seconds and then keep trying every ten seconds. When this completes our MySQL database is ready to use.
Next, we create the configuration file that tells the service how to connect with the database. To do this we add the following step that will write out our config to a file named src/config.js
.
- name: Write config file
run: |
echo "module.exports = {
jwtSecret: '${{ secrets.JWT_SECRET }}',
db: {
connection: {
host: '127.0.0.1',
user: 'root',
password: 'tempdbpassword',
database: 'pizza',
connectTimeout: 60000,
},
listPerPage: 10,
},
factory: {
url: 'https://pizza-factory.cs329.click',
apiKey: '${{ secrets.FACTORY_API_KEY }}',
},
};" > src/config.js
With MySQL running and our configuration all set up, you just need to add the step to actually run the tests and coverage reporting.
- name: Tests
run: npm test
That's it! Your tests are ready to run every time you commit to the repository.
Before you run your new workflow you need to add a version number and report on the code coverage. The version is stored in a file named version.json
and is represented with the current date and time.
You can report your coverage publicly by updating the coverageBadge.svg
file that is displayed in the README.md file with the latest coverage percentage. We do this with the help of an image generator found at img.shields.io
.
Here are the steps to add these two pieces.
- name: set version
id: set_version
run: |
version=$(date +'%Y%m%d.%H%M%S')
echo "version=$version" >> "$GITHUB_OUTPUT"
printf '{"version": "%s" }' "$version" > src/version.json
- name: Update coverage
run: |
coverage_pct=$(grep -o '"pct":[0-9.]*' coverage/coverage-summary.json | head -n 1 | cut -d ':' -f 2)
color=$(echo "$coverage_pct < 80" | bc -l | awk '{if ($1) print "yellow"; else print "green"}')
curl https://img.shields.io/badge/Coverage-$coverage_pct%25-$color -o coverageBadge.svg
git config user.name github-actions
git config user.email github-actions@github.com
git add .
git commit -m "generated"
git push
Now when you commit and push these changes to GitHub it should automatically do the following:
- Analyze for lint
- Run all of your tests
- Calculate and report coverage
- Create a version number
The output from successfully running the workflow should look something like this.
The README.md file should also display the current coverage and there should be a file named version.json
in the src directory of the repository.
Here is the final workflow. Make sure you completely understand everything in this file. Having a solid understanding of this file will help you as we add more complexity to our CI pipelines in later deliverables.
name: CI Pipeline
on:
push:
branches:
- main
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.set_version.outputs.version }}
services:
mysql:
image: mysql:8.0.29
env:
MYSQL_ROOT_PASSWORD: tempdbpassword
MYSQL_DATABASE: pizza
ports:
- '3306:3306'
options: >-
--health-cmd "mysqladmin ping -ptempdbpassword"
--health-interval 10s
--health-start-period 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Write config file
run: |
echo "module.exports = {
jwtSecret: '${{ secrets.JWT_SECRET }}',
db: {
connection: {
host: '127.0.0.1',
user: 'root',
password: 'tempdbpassword',
database: 'pizza',
connectTimeout: 60000,
},
listPerPage: 10,
},
factory: {
url: 'https://pizza-factory.cs329.click',
apiKey: '${{ secrets.FACTORY_API_KEY }}',
},
};" > src/config.js
- name: Tests
run: npm test
- name: set version
id: set_version
run: |
version=$(date +'%Y%m%d.%H%M%S')
echo "version=$version" >> "$GITHUB_OUTPUT"
printf '{"version": "%s" }' "$version" > src/version.json
- name: Update coverage
run: |
coverage_pct=$(grep -o '"pct":[0-9.]*' coverage/coverage-summary.json | head -n 1 | cut -d ':' -f 2)
color=$(echo "$coverage_pct < 80" | bc -l | awk '{if ($1) print "yellow"; else print "green"}')
curl https://img.shields.io/badge/Coverage-$coverage_pct%25-$color -o coverageBadge.svg
git config user.name github-actions
git config user.email github-actions@github.com
git add .
git commit -m "generated"
git push
In order to demonstrate your mastery of the concepts for this deliverable, complete the following.
- Create Jest tests for
jwt-pizza-service
that provide at least 80% coverage. - Add linting.
- Create a GitHub Actions workflow that executes the tests and linting.
- Add the configuration necessary so that the workflow fails if there is not 80% coverage or the linting fails.
- Add the reporting of the coverage to the workflow by creating a coverage badge in the README.md file.
- Add the creation of a version file named
src/version.json
.
Once this is all working, go to the AutoGrader and submit your work for the deliverable.
Percent | Item |
---|---|
10% | Successful execution of GitHub Actions to run lint on commit |
20% | Successful execution of GitHub Actions to run tests on commit |
5% | Version updated with each build |
65% | At least 80% code coverage as documented by workflow execution and README.md badge |
Time to go and celebrate. How about cake 🍰?