Skip to content
Snippets Groups Projects
Commit c3365138 authored by Ben Coleman's avatar Ben Coleman
Browse files

:white_check_mark: Adds unit tests and other refactors

parent 3c2d0eca
Branches
No related tags found
No related merge requests found
Showing with 258 additions and 515 deletions
......@@ -4,7 +4,7 @@ on:
push:
branches: [master]
paths:
- "src/**"
- 'src/**'
pull_request:
env:
......@@ -13,31 +13,45 @@ env:
jobs:
test:
name: "Tests & Linting"
name: 'Tests & Linting'
runs-on: ubuntu-latest
steps:
- name: "Checkout"
- name: 'Checkout'
uses: actions/checkout@v2
- name: "Run linting"
- name: 'Run linting'
run: make lint
- name: "Run tests"
- name: 'Run tests'
run: make test-report
- name: 'Upload test results'
uses: actions/upload-artifact@v2
# Disabled when running locally with the nektos/act tool
if: ${{ always() && !env.ACT }}
with:
name: test-results
path: ./test-results.xml
- name: Publish Unit Test Results
uses: EnricoMi/publish-unit-test-result-action@v1
if: ${{ always() && !env.ACT }}
with:
files: test-results.xml
build:
name: "Build & Push Image"
name: 'Build & Push Image'
needs: test
runs-on: ubuntu-latest
steps:
- name: "Checkout"
- name: 'Checkout'
uses: actions/checkout@v2
# Nicer than using github runid, I think, will be picked up automatically by make
- name: "Create datestamp image tag"
- name: 'Create datestamp image tag'
run: echo "IMAGE_TAG=$(date +%d-%m-%Y.%H%M)" >> $GITHUB_ENV
- name: "Docker build image"
- name: 'Docker build image'
run: make image
# Only when pushing to default branch (e.g. master or main), then push image to registry
......@@ -47,18 +61,18 @@ jobs:
echo ${{ secrets.GITHUB_TOKEN }} | docker login $IMAGE_REG -u $GITHUB_ACTOR --password-stdin
make push
- name: "Trigger AKS release pipeline"
- name: 'Trigger AKS release pipeline'
if: github.ref == 'refs/heads/master'
uses: benc-uk/workflow-dispatch@v1
with:
workflow: "CD Release - AKS"
workflow: 'CD Release - AKS'
token: ${{ secrets.GH_PAT }}
inputs: '{ "IMAGE_TAG": "${{ env.IMAGE_TAG }}" }'
- name: "Trigger Azure web app release pipeline"
- name: 'Trigger Azure web app release pipeline'
if: github.ref == 'refs/heads/master'
uses: benc-uk/workflow-dispatch@v1
with:
workflow: "CD Release - Webapp"
workflow: 'CD Release - Webapp'
token: ${{ secrets.GH_PAT }}
inputs: '{ "IMAGE_TAG": "${{ env.IMAGE_TAG }}" }'
......@@ -101,3 +101,7 @@ venv.bak/
.mypy_cache/
.secrets
.env
test-*.xml
tests/node_modules
tests/package*.json
.pytest_cache
\ No newline at end of file
......@@ -47,8 +47,8 @@ push 📤 Push container image to registry
run 🏃 Run the server locally using Python & Flask
deploy 🚀 Deploy to Azure Web App
undeploy 💀 Remove from Azure
test 🎯 Unit tests for server and frontend
test-report 🎯 Unit tests for server and frontend (with report output)
test 🎯 Unit tests for Flask app
test-report 🎯 Unit tests for Flask app (with report output)
test-api 🚦 Run integration API tests, server must be running
clean 🧹 Clean up project
```
......
......
FROM python:3.9-slim-buster
LABEL Name="Python Flask Demo App" Version=1.4.1
LABEL Name="Python Flask Demo App" Version=1.4.2
LABEL org.opencontainers.image.source = "https://github.com/benc-uk/python-demoapp"
ARG srcDir=src
......
......
......@@ -52,10 +52,13 @@ undeploy: ## 💀 Remove from Azure
@echo "### WARNING! Going to delete $(AZURE_RES_GROUP) 😲"
az group delete -n $(AZURE_RES_GROUP) -o table --no-wait
test: ## 🎯 Unit tests for server and frontend
@echo "Not implemented!"
test: venv ## 🎯 Unit tests for Flask app
. $(SRC_DIR)/.venv/bin/activate \
&& pytest -v
test-report: test ## 🎯 Unit tests for server and frontend (with report output)
test-report: venv ## 🎯 Unit tests for Flask app (with report output)
. $(SRC_DIR)/.venv/bin/activate \
&& pytest -v --junitxml=test-results.xml
test-api: .EXPORT_ALL_VARIABLES ## 🚦 Run integration API tests, server must be running
cd tests \
......@@ -66,6 +69,11 @@ clean: ## 🧹 Clean up project
rm -rf $(SRC_DIR)/.venv
rm -rf tests/node_modules
rm -rf tests/package*
rm -rf test-results.xml
rm -rf $(SRC_DIR)/app/__pycache__
rm -rf $(SRC_DIR)/app/tests/__pycache__
rm -rf .pytest_cache
rm -rf $(SRC_DIR)/.pytest_cache
# ============================================================================
......
......
from flask import Flask
app = Flask(__name__)
from app import views # noqa: E402,F401
from app import apis # noqa: E402,F401
def create_app():
app = Flask(__name__)
with app.app_context():
from . import views # noqa: E402,F401
from . import apis # noqa: E402,F401
return app
from flask import jsonify
from app import app
from flask import jsonify, current_app as app
import psutil
olddata = {}
......
......
from . import create_app
import pytest
app = create_app()
@pytest.fixture
def client():
with app.test_client() as client:
yield client
\ No newline at end of file
......@@ -63,6 +63,6 @@
></script>
<div class="container body-content">{% block content %}{% endblock %}</div>
<span style="float: right">v1.4.1 [Ben Coleman, 2018-2021] &nbsp;&nbsp;&nbsp;</span>
<span style="float: right">v1.4.2 [Ben Coleman, 2018-2021] &nbsp;&nbsp;&nbsp;</span>
</body>
</html>
import json
def test_api_process(client):
resp = client.get("/api/process")
assert resp.status_code == 200
assert resp.headers["Content-Type"] == "application/json"
resp_payload = json.loads(resp.data)
assert len(resp_payload["processes"]) > 0
assert resp_payload["processes"][0]["memory_percent"] > 0
assert len(resp_payload["processes"][0]["name"]) > 0
def test_api_monitor(client):
resp = client.get("/api/monitor")
assert resp.status_code == 200
assert resp.headers["Content-Type"] == "application/json"
resp_payload = json.loads(resp.data)
assert resp_payload["cpu"] >= 0
assert resp_payload["disk"] >= 0
assert resp_payload["disk_read"] >= 0
assert resp_payload["disk_write"] >= 0
assert resp_payload["mem"] >= 0
assert resp_payload["net_recv"] >= 0
assert resp_payload["net_sent"] >= 0
import pprint
def test_home(client):
resp = client.get("/")
assert resp.status_code == 200
assert b"Python" in resp.data
def test_page_content(client):
resp = client.get("/")
assert resp.status_code == 200
assert b"Coleman" in resp.data
def test_info(client):
resp = client.get("/info")
assert resp.status_code == 200
assert b"Hostname" in resp.data
from flask import render_template
from app import app
from flask import render_template, current_app as app
import cpuinfo
import psutil
import platform
......
......
......@@ -4,3 +4,4 @@ psutil==5.8.0
gunicorn==20.1.0
black==20.8b1
flake8==3.9.0
pytest==6.2.2
\ No newline at end of file
import os
from app import app
from app import create_app
app = create_app()
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5000))
......
......
......@@ -36,12 +36,8 @@
"url": {
"raw": "http://{{apphost}}/",
"protocol": "http",
"host": [
"{{apphost}}"
],
"path": [
""
]
"host": ["{{apphost}}"],
"path": [""]
}
},
"response": []
......@@ -77,12 +73,8 @@
"url": {
"raw": "http://{{apphost}}/info",
"protocol": "http",
"host": [
"{{apphost}}"
],
"path": [
"info"
]
"host": ["{{apphost}}"],
"path": ["info"]
}
},
"response": []
......@@ -120,15 +112,10 @@
"method": "GET",
"header": [],
"url": {
"raw": "https://{{apphost}}/api/process",
"protocol": "https",
"host": [
"{{apphost}}"
],
"path": [
"api",
"process"
]
"raw": "http://{{apphost}}/api/process",
"protocol": "http",
"host": ["{{apphost}}"],
"path": ["api", "process"]
}
},
"response": []
......@@ -139,18 +126,14 @@
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
"exec": [""]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
"exec": [""]
}
}
]
......
......
#
# Deploy to Azure Kubernetes Service using Helm
# Using Bicep for infrastructure as code
#
name: CD Release - AKS
on:
workflow_dispatch:
inputs:
IMAGE_TAG:
description: "Image tag to be deployed"
required: true
default: "latest"
# Note. Required secrets: CR_PAT & AZURE_CREDENTIALS
env:
AKS_NAME: benc
AKS_RES_GROUP: aks
HELM_RELEASE: python
HELM_NAMESPACE: demoapps
INGRESS_DNS_HOST: python-demoapp.kube.benco.io
jobs:
#
# Deploy to Kubernetes (AKS)
#
deploy-aks:
name: Deploy to AKS with Helm
runs-on: ubuntu-latest
outputs:
deployment_id: ${{ steps.deploy.outputs.deployment_id }}
steps:
- name: "Checkout"
uses: actions/checkout@v1
- name: "Start deployment"
id: deploy
uses: chrnorm/deployment-action@v1.2.0
with:
ref: ${{ github.event.ref }}
token: ${{ github.token }}
environment: AKS - ${{ env.HELM_RELEASE }}
- name: "Login to Azure"
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: "Get AKS credentials"
run: |
az aks get-credentials -n $AKS_NAME -g $AKS_RES_GROUP
- name: "Helm release"
run: |
helm repo add benc-uk https://benc-uk.github.io/helm-charts
helm upgrade ${{ env.HELM_RELEASE }} benc-uk/webapp \
--install \
--namespace ${{ env.HELM_NAMESPACE }} \
--values ./kubernetes/aks-live.yaml \
--set image.tag=${{ github.event.inputs.IMAGE_TAG }},ingress.host=${{ env.INGRESS_DNS_HOST }}
- name: "End deployment - failure"
if: ${{ failure() }}
uses: chrnorm/deployment-status@v1.0.0
with:
token: ${{ github.token }}
state: failure
deployment_id: ${{ needs.deploy-bicep.outputs.deployment_id }}
#
# Post deployment testing stage
#
validate-deployment:
name: "Run Deployment Tests"
needs: deploy-aks
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Validate site is running"
run: .github/scripts/url-check.sh -u https://${{ env.INGRESS_DNS_HOST }} -s "Python" -t 200
# - name: "Run API tests"
# run: |
# npm install newman --silent
# node_modules/newman/bin/newman.js run src/tests/postman_collection.json --global-var apphost=${{ env.INGRESS_DNS_HOST }}
- name: "End deployment - success"
if: ${{ success() }}
uses: chrnorm/deployment-status@v1.0.0
with:
token: ${{ github.token }}
state: success
deployment_id: ${{ needs.deploy-aks.outputs.deployment_id }}
environment_url: https://${{ env.INGRESS_DNS_HOST }}
- name: "End deployment - failure"
if: ${{ failure() }}
uses: chrnorm/deployment-status@v1.0.0
with:
token: ${{ github.token }}
state: failure
deployment_id: ${{ needs.deploy-aks.outputs.deployment_id }}
#
# Deploy to Azure App Service as a containerized Web App
# Using Bicep for infrastructure as code
#
name: CD Release - Webapp
on:
workflow_dispatch:
inputs:
IMAGE_TAG:
description: "Image tag to be deployed"
required: true
default: "latest"
# Note. Required secrets: CR_PAT & AZURE_CREDENTIALS
env:
IMAGE_REG: ghcr.io
IMAGE_REPO: benc-uk/python-demoapp
APP_NAME: python-demoapp
ARM_SUB_ID: 52512f28-c6ed-403e-9569-82a9fb9fec91
ARM_REGION: westeurope
ARM_RES_GROUP: apps
jobs:
#
# Deploy Azure infra (App Service) using Bicep
#
deploy-infra:
name: "Deploy Infra"
runs-on: ubuntu-latest
outputs:
deployment_id: ${{ steps.deploy.outputs.deployment_id }}
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Start deployment"
id: deploy
uses: chrnorm/deployment-action@v1.2.0
with:
ref: ${{ github.event.ref }}
token: ${{ github.token }}
environment: App Service - ${{ env.APP_NAME }}
- name: "Run Bicep compiler"
run: |
wget https://github.com/Azure/bicep/releases/download/v0.1.37-alpha/bicep-linux-x64 -qO bicep
chmod +x bicep
./bicep build webapp.bicep
working-directory: ./infra
- name: "Login to Azure"
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: "Create resource group"
run: az group create --name ${{ env.ARM_RES_GROUP }} --location ${{ env.ARM_REGION }}
- name: "Deploy resources"
uses: azure/arm-deploy@v1
with:
subscriptionId: ${{ env.ARM_SUB_ID }}
resourceGroupName: ${{ env.ARM_RES_GROUP }}
template: ./infra/webapp.json
parameters: webappName=${{ env.APP_NAME }} webappImage=${{ env.IMAGE_REG }}/${{ env.IMAGE_REPO }}:${{ github.event.inputs.IMAGE_TAG }} weatherKey=${{ secrets.WEATHER_API_KEY }}
deploymentName: webapp-deploy-${{ github.run_id }}
- name: "End deployment - failure"
if: ${{ failure() }}
uses: chrnorm/deployment-status@v1.0.0
with:
token: ${{ github.token }}
state: failure
deployment_id: ${{ needs.deploy-bicep.outputs.deployment_id }}
#
# Post deployment testing stage
#
validate-deployment:
name: "Run Deployment Tests"
needs: deploy-infra
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Validate site is running"
run: .github/scripts/url-check.sh -u https://${{ env.APP_NAME }}.azurewebsites.net/ -s "Python" -t 200
# - name: "Run API tests"
# run: |
# npm install newman --silent
# node_modules/newman/bin/newman.js run src/tests/postman_collection.json --global-var apphost=${{ env.APP_NAME }}.azurewebsites.net
- name: "End deployment - success"
if: ${{ success() }}
uses: chrnorm/deployment-status@v1.0.0
with:
token: ${{ github.token }}
state: success
deployment_id: ${{ needs.deploy-infra.outputs.deployment_id }}
environment_url: https://${{ env.APP_NAME }}.azurewebsites.net/
- name: "End deployment - failure"
if: ${{ failure() }}
uses: chrnorm/deployment-status@v1.0.0
with:
token: ${{ github.token }}
state: failure
deployment_id: ${{ needs.deploy-infra.outputs.deployment_id }}
name: CI Build App
on:
push:
branches: [master]
paths-ignore:
- ".github/**"
pull_request:
env:
IMAGE_REG: ghcr.io
IMAGE_REPO: benc-uk/python-demoapp
jobs:
test:
name: "Tests & Linting"
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Test/linting stub"
run: echo "Nothing here 😐"
# - name: "Upload test results"
# uses: actions/upload-artifact@v2
# with:
# name: test-results
# path: ./src/test-results.xml
# - name: "Report on test results"
# uses: ashley-taylor/junit-report-annotations-action@master
# if: always()
# with:
# access-token: ${{ secrets.GITHUB_TOKEN }}
# path: ./src/test-results.xml
# name: Unit test results
build:
name: "Build & Push Image"
needs: test
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Create datestamp image tag" # Nicer than using github runid, I think
run: echo "IMAGE_TAG=$(date +%d-%m-%Y.%H%M)" >> $GITHUB_ENV
- name: "Docker build image"
run: docker build . -t $IMAGE_REG/$IMAGE_REPO:$IMAGE_TAG
- name: "Login to GitHub container registry"
if: github.ref == 'refs/heads/master'
uses: docker/login-action@v1
with:
registry: ${{ env.IMAGE_REG }}
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
- name: "Docker push image to ${{ env.IMAGE_REG }}"
if: github.ref == 'refs/heads/master'
run: docker push $IMAGE_REG/$IMAGE_REPO
- name: "Trigger AKS release pipeline"
if: github.ref == 'refs/heads/master'
uses: benc-uk/workflow-dispatch@v1
with:
workflow: "CD Release - AKS"
token: ${{ secrets.CR_PAT }}
inputs: '{ "IMAGE_TAG": "${{ env.IMAGE_TAG }}" }'
- name: "Trigger Azure web app release pipeline"
if: github.ref == 'refs/heads/master'
uses: benc-uk/workflow-dispatch@v1
with:
workflow: "CD Release - Webapp"
token: ${{ secrets.CR_PAT }}
inputs: '{ "IMAGE_TAG": "${{ env.IMAGE_TAG }}" }'
name: Release Versioned Image
on:
workflow_dispatch:
release:
types: [published]
env:
IMAGE_REG: ghcr.io
IMAGE_REPO: benc-uk/python-demoapp
jobs:
publish-image:
name: "Build & Publish"
runs-on: ubuntu-latest
steps:
- name: "Checkout"
uses: actions/checkout@v2
- name: "Docker build image with version tag"
run: docker build . -t $IMAGE_REG/$IMAGE_REPO:latest -t $IMAGE_REG/$IMAGE_REPO:${{ github.event.release.tag_name }}
- name: "Login to GitHub container registry"
uses: docker/login-action@v1
with:
registry: ${{ env.IMAGE_REG }}
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
- name: "Docker push image to ${{ env.IMAGE_REG }}"
run: docker push $IMAGE_REG/$IMAGE_REPO
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment