1 Commits
main ... v3.0

Author SHA1 Message Date
Michael Mäder
1552c7da4b questions3 and some practical misc stuff 2025-03-26 16:03:43 +01:00
21 changed files with 49 additions and 432 deletions

4
.gitignore vendored
View File

@@ -5,7 +5,3 @@ __pycache__/
.devcontainer/ .devcontainer/
src/.pdm-python src/.pdm-python
src/htmlcov
src/.env
.env

View File

@@ -1,50 +1,34 @@
variables:
DOCKER_IMAGE_TEST: registry.forge.hefr.ch/klagarge/mse2425-grp09/python-pdm:latest
DOCKER_IMAGE_APP: registry.forge.hefr.ch/klagarge/mse2425-grp09/devsecops-app:latest
default:
image: $DOCKER_IMAGE_TEST
stages: stages:
- build-docker - build
- lint
- test
.setup_env: &setup_env
before_script:
- cd src
- cp -r /app/__pypackages__ .
- export "PYTHONPATH=/builds/Klagarge/mse2425-grp09/src:/builds/Klagarge/mse2425-grp09/src/__pypackages__/3.9/lib"
- export "PATH=/builds/Klagarge/mse2425-grp09/src/__pypackages__/3.9/bin:$PATH"
- export "FLASK_APP=app"
test job: build job:
stage: test stage: build
<<: *setup_env
script: script:
# Set environment variables for the tests - cd src
- export FLASK_SECRET_KEY=$FLASK_SECRET_KEY - apt-get update -qy
- apt-get install -y python3-dev python3-pip python3.12-venv curl
- python3 -V
# - pip3 install --break-system-packages -r requirements.txt
- curl -sSL https://pdm-project.org/install-pdm.py | python3 -
- export PATH=/root/.local/bin:$PATH
- pdm install
# launch tests # launch tests
- export PYTHONPATH=.
- export FLASK_APP=app
- pdm run pytest tests --cov --cov-report term --cov-report html - pdm run pytest tests --cov --cov-report term --cov-report html
artifacts:
paths:
- src/htmlcov/
lint job: artifacts:
stage: lint paths:
<<: *setup_env - src/htmlcov/
dependencies: []
script:
- pdm run flake8 --config=../tox.ini
allow_failure: true # Linter can fail, fixing it is for now outside of the projects scope
pages: pages:
stage: test stage: build
dependencies: dependencies:
- test job - build job
needs: ["test job"] needs: ["build job"]
script: script:
- mv src/htmlcov/ public/ - mv src/htmlcov/ public/
artifacts: artifacts:
@@ -53,53 +37,3 @@ pages:
expire_in: 7 days expire_in: 7 days
only: only:
- main - main
# This job runs only when Dockerfile changes
docker-build-test:
image: docker:latest
stage: build-docker
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE_TEST -f Dockerfile .
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
- docker push $DOCKER_IMAGE_TEST
rules:
- if: $GITLAB_CI == 'false' # Only run in GitLab CI
when: never
- changes:
- Dockerfile
- src/pyproject.toml
- src/pdm.lock
docker-build-app:
image: docker:latest
stage: build-docker
services:
- docker:dind
script:
- docker build -t $DOCKER_IMAGE_APP -f src/Dockerfile .
- echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
- docker push $DOCKER_IMAGE_APP
include:
- template: Jobs/SAST.gitlab-ci.yml
dast:
stage: test
image: ghcr.io/zaproxy/zaproxy:stable
services:
- name: $DOCKER_IMAGE_APP
alias: app
script:
- echo "Waiting for the app to start on http://app:5000"
- timeout 60 bash -c 'until curl -s http://app:5000; do echo "Waiting..."; sleep 3; done'
- zap-full-scan.py -t http://app:5000 -I
gitleaks:
stage: test
image:
name: zricethezav/gitleaks:latest
entrypoint: [""]
script:
- gitleaks dir -v --redact=75 .

View File

@@ -1,23 +0,0 @@
repos:
- repo: https://github.com/pycqa/flake8
rev: '7.2.0' # Use the latest stable version
hooks:
- id: flake8
additional_dependencies: []
args: [--config=tox.ini] # Use the same config as in CI
- repo: https://github.com/pypa/pip-audit
rev: v2.9.0
hooks:
- id: pip-audit
args: ["./src"]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.3
hooks:
- id: gitleaks
ci:
# Leave pip-audit to only run locally and not in CI
# pre-commit.ci does not allow network calls
skip: [pip-audit]

View File

@@ -1,20 +0,0 @@
FROM python:3.10-slim
LABEL org.opencontainers.image.authors="remi.heredero@hevs.ch"
RUN apt-get update && \
pip install --no-cache-dir -U pdm && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.local/bin:$PATH" \
PDM_USE_VENV=false
WORKDIR /app
COPY src/pyproject.toml src/pdm.lock ./
RUN pdm config python.use_venv false && \
pdm install -G:all
ENV PYTHONPATH="/app/__pypackages__/3.9/lib" \
PATH="/app/__pypackages__/3.9/bin:$PATH"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -6,15 +6,3 @@
- **Q1.2**: The secret key for flask is hard coded. Is this good practice? What are the dangers? How could this be fixed? - **Q1.2**: The secret key for flask is hard coded. Is this good practice? What are the dangers? How could this be fixed?
- **Q1.3**: Give a short description of *Linter*. Integrate a basic linter like [Flake8](https://flake8.pycqa.org/en/latest/) or [Ruff](https://github.com/astral-sh/ruff) in the existing CI/CD pipeline - **Q1.3**: Give a short description of *Linter*. Integrate a basic linter like [Flake8](https://flake8.pycqa.org/en/latest/) or [Ruff](https://github.com/astral-sh/ruff) in the existing CI/CD pipeline
- **Q1.4 (optional)**: The run of the current CI/CD pipeline takes some time. Especially the time to setup the docker with the update and installation of all the dependencies is quite time consuming compared to the real testing time. Do you see any alternatives to speed up this process? Describe and try to implement it in your pipeline. - **Q1.4 (optional)**: The run of the current CI/CD pipeline takes some time. Especially the time to setup the docker with the update and installation of all the dependencies is quite time consuming compared to the real testing time. Do you see any alternatives to speed up this process? Describe and try to implement it in your pipeline.
# Answers - Part 1
## Q1.2
- It's a very bad practice. The secret key will be exposed in the codebase and can be easily accessed by anyone who has access to the codebase. This can lead to security vulnerabilities and compromise the integrity of the application.
- To fix this, you can use environment variables to store the secret key.
## Q1.3
- A linter is a tool to statically analyse code for readability and improving code quality. It is usually executed from a standalone tool.
- It is used to check for errors, vulns, code smells or general issues but also to enforce a coding style over the whole project.
## Q1.4
The minimum is to change the image on the CI to put an Alpine or a basic python image. Another option (which we implemented) is to create a custom Docker image that includes all the necessary dependencies for the application. This can significantly reduce the time required to set up the environment and speed up the CI/CD pipeline.

View File

@@ -5,42 +5,3 @@
- **Q2.1**: Every commit triggers the CI/CD pipeline. Find out a way to trigger the pipeline only if specific commits (e.g. commit in a development branch) are made. Where can this be configured. Describe your solution and implement it in your pipeline. - **Q2.1**: Every commit triggers the CI/CD pipeline. Find out a way to trigger the pipeline only if specific commits (e.g. commit in a development branch) are made. Where can this be configured. Describe your solution and implement it in your pipeline.
- **Q2.2**: Take the [CIS controls](./CIS_Controls_v8_Online.22.02.pdf) and give some examples (minimum 5) of controls from this standard that are not or not enough implemented in the calculator app. Provide a short description and a possible remediation. Implement at least two of the controls in the app / pipeline. - **Q2.2**: Take the [CIS controls](./CIS_Controls_v8_Online.22.02.pdf) and give some examples (minimum 5) of controls from this standard that are not or not enough implemented in the calculator app. Provide a short description and a possible remediation. Implement at least two of the controls in the app / pipeline.
- **Q2.3 (optional)**: The linter from question 1.3 is a good start. It is only executed in your pipeline. But what if you would also integrate it directly in your local development environment (e.g. IDE)? Can you do the linting before you commit? Describe your solution and implement it in your (local) pipeline. Describe the advantages and disadvantages of this approach. - **Q2.3 (optional)**: The linter from question 1.3 is a good start. It is only executed in your pipeline. But what if you would also integrate it directly in your local development environment (e.g. IDE)? Can you do the linting before you commit? Describe your solution and implement it in your (local) pipeline. Describe the advantages and disadvantages of this approach.
# Answers - Part 2
## Q2.1
Solution is to add a `rule` section to add condition to trigger the pipeline. It's what is implemented for the `docker-build` job. Another option is to use an `only` section to trigger the pipeline only if the change is made in a specific branch. It's what is implemented for the `pages` job.
## Q2.2
### Example 1 - 3.6 Encrypt Data on End-User Devices
**description**
Sensible data is everywhere. It is also on the end user's device. It is primordial to keep it secure.
**mitigation**
There is multiple ways to secure data. CIS suggests the following : Windows BitLocker®, Apple FileVault®, Linux® dm-crypt.
### Example 2 - 4.3 Configure Automatic Session Locking on Enterprise Assets
**description**
A logged in computer doesn't check permanantly for the user's identity. A user could by mistake leave his computer open and logged in, give way for anyone ill-intentionned with physical access to use the computer with it's permissions.
**mitigation**
Forcing an auto-logout after a few minutes.
### Example 3 - 5.2 Use Unique Passwords
**description**
If a user uses the same password everywhere, it only needs one to get compromised and everything is equally compromised.
**mitigation**
Usage of unique passwords and for users use 2FA at least.
### Example 4 & 5 with implementation
Unfortunately, due to the amount of work we both had, from work and from school, we didn't have enough time to do those last two points.
We did the rest (except for optionals) though.
## Q2.3
We can use a pre-commit that runs the linter before committing. This ensures that the code is linted before it is committed, which can help catch errors and improve code quality. However, this approach can be time-consuming and may require additional setup.

View File

@@ -2,34 +2,9 @@
## Part 3 ## Part 3
- **Q3.1**: Setup your CI/CD pipeline with an additional SAST solution. I propose that you use `semgrep` for this task. Get your inspiration here: https://semgrep.dev/for/gitlab and https://docs.gitlab.com/ee/user/application_security/sast/ - **Q3.1**: Setup your CI/CD pipeline with an additional SAST solution. I propose that you use `semgrep` for this task. Get your inspiration here: https://semgrep.dev/for/gitlab and https://docs.gitlab.com/ee/user/application_security/sast/
- **Q3.2**: Describe the found problems (alerts) in the `calculator app` (in the original code, git tag `v3.0`) - **Q3.2**: Describe the found problems (alerts) in the `calculator app` (in the original code, git tag `v3.0`)
- **Q3.3**: Install DAST OWASP ZAP on your host or in a Docker. Play with OWASP ZAP, analyze the calculator code - **Q3.3**: Install DAST OWASP ZAP on your host or in a Docker. Play with OWASP ZAP, analyze the calculator code
- **Q3.4**: Implement a DAST solution in your pipeline. Get some inspiration here https://docs.gitlab.com/ee/user/application_security/dast/ . Describe what you have integrated in your pipeline. *Note: you must ensure that your application is running while you are testing!* - **Q3.4**: Implement a DAST solution in your pipeline. Get some inspiration here https://docs.gitlab.com/ee/user/application_security/dast/ . Describe what you have integrated in your pipeline. *Note: you must ensure that your application is running while you are testing!*
- **Q3.5 (optional)**: Normally, the provided code has some bugs, which are discovered by SAST solution. Describe the found bugs (in the original code, git tag `v3.0`) and provide solution to remediate the problems. Indicate which commit/tag contains the corrected code - **Q3.5 (optional)**: Normally, the provided code has some bugs, which are discovered by SAST solution. Describe the found bugs (in the original code, git tag `v3.0`) and provide solution to remediate the problems. Indicate which commit/tag contains the corrected code
- **Q3.6 (optional)**: Describe the found bugs (in the original code, git tag `v3.0`) with DAST and provide solution to remediate the problems. Indicate which commit/tag contains the corrected code. Do corrections only in the provided code (no libraries) - **Q3.6 (optional)**: Describe the found bugs (in the original code, git tag `v3.0`) with DAST and provide solution to remediate the problems. Indicate which commit/tag contains the corrected code. Do corrections only in the provided code (no libraries)
# Answers - Part 3
## Q3.2
For some reasons, semgrep works locally, but not on GitLab. Here is the report when runned locally.
![SAST-report](figures/SAST-report.png)
## Q3.3
After performing a scan, we can see a few alerts as seen on this screenshot :
![alt text](figures/OWASP-ZAP.png)
## Q3.4
The integrate DAST in Github doesn't work on our version, we need the _Ultimate_ version of GitLab selfhosted.
We create a new Docker image for the application. This image auto launch the flask app when the container is started.
We used this image as a service for the DAST stage on our CI.
The stage use zaproxy to test the application. Warning do not return wailure, so the stage pass if no error is found by the OWASP ZAP.
We don't understand why the stage fail when we try to provide the html report as artifact. So if the stage fail, we can see the error in the logs.

View File

@@ -1,26 +0,0 @@
# Questions
## Part 4
- **Q4.1**: Often secrets are committed in a repository. Different research tools exist and help to detect this kind of dangerous forgotten credentials. Integrate a check in your pipeline for these kinds of problems. Have a look at <https://github.com/zricethezav/gitleaks>. What kind of leaked secrets can you find in the git repo? Did the tool not find something that it should have found? Why? What possibilities exist to prevent this kind of leakage?
- **Q4.2**: Try to find any possible problems in our used libraries (e.g. flask). The `pyproject.toml` describes all the additional libraries used by the application. You can use a dependency scanning (have a look here: <https://docs.gitlab.com/ee/user/application_security/dependency_scanning/>) to see if all imported libraries are safe. Do you find any problems? Integrate the scanning in your pipeline.
- **Q4.3 (optional)**: API Fuzzing (and other kinds of DAST) is described at this page: <https://docs.gitlab.com/ee/user/application_security/api_fuzzing/>. Choose one of the different description possibilities for your *calculator* API. Integrate it in your pipeline.
# Answers - Part 4
## Q4.1
GitLeaks can find strings like API keys, passwords, and other sensitive information that might be accidentally committed to a repository. This tool can only recognize them if they look like sensitive information.
The scan of the git repository didn't detect the previous flask key because it was not in the format that GitLeaks recognizes.
Usually, the best practice is to use environment variables to store sensitive information. This way, the information is not exposed in the code.
## Q4.2
The Dependency scanning tool from GitLab linked by the teacher in the exercise cannot be used as it is limited to Gitlab Ultimate. I am looking into using an open-source solution
After using three different scanning tools, no known vulnerabilities were found.
Tools used :
- pyscan
- safety
- pip-audit

View File

@@ -1 +1 @@
<h1>Hello class, TSM_Cybersec 2025</h1> <h1>Hello class, TSM_Cybersec</h1>

View File

@@ -1,5 +1,2 @@
[run] [run]
omit= omit=/usr/lib/python3/dist-packages/*
/usr/lib/python3/dist-packages/*
./__pypackages__
./src/__pypackages__

View File

@@ -1 +0,0 @@
FLASK_SECRET_KEY=

View File

@@ -1,31 +0,0 @@
FROM python:3.10-slim
LABEL org.opencontainers.image.authors="remi.heredero@hevs.ch"
RUN apt-get update && \
pip install --no-cache-dir -U pdm && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.local/bin:$PATH" \
PDM_USE_VENV=false
WORKDIR /app
COPY src/pyproject.toml src/pdm.lock ./
RUN pdm config python.use_venv false && \
pdm install
ENV PYTHONPATH="/app/__pypackages__/3.9/lib" \
PATH="/app/__pypackages__/3.9/bin:$PATH"
############################################################
# Everything above should be imported from the test image, #
# but GitLab can't pull it, so I copy-paste the content #
############################################################
COPY src ./
ENV FLASK_RUN_HOST=0.0.0.0
CMD ["pdm", "run", "flask"]

View File

@@ -18,7 +18,7 @@
from flask import request, Flask, url_for, render_template, redirect from flask import request, Flask, url_for, render_template, redirect
import operators import operators
import json import json
import os
__author__ = 'Michael Mäder' __author__ = 'Michael Mäder'
__date__ = "2025-03-10" __date__ = "2025-03-10"
@@ -36,8 +36,7 @@ A little web application that offers API calls for arithmetic operations
# creation of the Flask application # creation of the Flask application
app = Flask(__name__) app = Flask(__name__)
# super secure key against CSRF attacks app.config['SECRET_KEY'] = 'the-best-secret-ever' # super secure key against CSRF attacks
app.config['SECRET_KEY'] = os.environ.get('FLASK_SECRET_KEY')
# global variable containing the name of the login user # global variable containing the name of the login user
global_data = {'username': 'no_user'} global_data = {'username': 'no_user'}
@@ -46,44 +45,30 @@ global_data = {'username': 'no_user'}
# incrementation route # incrementation route
@app.route('/inc') @app.route('/inc')
def plus_one(): def plus_one():
try: x = int(request.args.get('x', 1))
x = int(request.args.get('x', 1))
except ValueError:
return json.dumps({"error": "Type error"})
return json.dumps({'x': operators.addition(x, 1)}) return json.dumps({'x': operators.addition(x, 1)})
# addition route, the parameters will be passed with 'x' and 'y' # addition route, the parameters will be passed with 'x' and 'y'
@app.route('/add') @app.route('/add')
def plus_y(): def plus_y():
try: x = int(request.args.get('x', 1))
x = int(request.args.get('x', 1)) y = int(request.args.get('y', 1))
y = int(request.args.get('y', 1))
except ValueError:
return json.dumps({"error": "Type error"})
return json.dumps({'result': operators.addition(x, y)}) return json.dumps({'result': operators.addition(x, y)})
# multiplication route, the parameters will be passed with 'x' and 'y' # multiplication route, the parameters will be passed with 'x' and 'y'
@app.route('/mul') @app.route('/mul')
def multiply_y(): def multiply_y():
try: x = int(request.args.get('x', 1))
x = int(request.args.get('x', 1)) y = int(request.args.get('y', 1))
y = int(request.args.get('y', 1))
except ValueError:
return json.dumps({"error": "Type error"})
return json.dumps({'result': operators.multiplication(x, y)}) return json.dumps({'result': operators.multiplication(x, y)})
# division route, the parameters will be passed with 'x' and 'y' # division route, the parameters will be passed with 'x' and 'y'
@app.route('/div') @app.route('/div')
def division_y(): def division_y():
try: x = int(request.args.get('x', 1))
x = int(request.args.get('x', 1)) y = int(request.args.get('y', 1))
y = int(request.args.get('y', 1))
except ValueError:
return json.dumps({"error": "Type error"})
return json.dumps({'result': operators.division(x, y)}) return json.dumps({'result': operators.division(x, y)})

View File

@@ -23,17 +23,17 @@ __email__ = "michael.maeder@hefr.ch"
# file containing the operations for the calculator # file containing the operations for the calculator
# addition: returns x+y # addition: returns x+y
def addition(x, y): def addition(x, y):
return x+y return x+y
# subtraction: returns x-y # subtraction: returns x-y
def subtraction(x, y): def subtraction(x, y):
return x-y return x-y
# multiplication: returns x*y # multiplication: returns x*y
def multiplication(x, y): def multiplication(x, y):
return x*y return x*y
@@ -42,6 +42,6 @@ def multiplication(x, y):
# returns None if the divisor is zero # returns None if the divisor is zero
# the result might be of float type # the result might be of float type
def division(x, y): def division(x, y):
if y == 0: if y==0:
return None return None
return x/y return x/y

92
src/pdm.lock generated
View File

@@ -2,10 +2,10 @@
# It is not intended for manual editing. # It is not intended for manual editing.
[metadata] [metadata]
groups = ["default", "lint", "test"] groups = ["default"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:f5c4e58e167316cb2440e9205a1e15474a3599463aa47c3c259b4009038166b0" content_hash = "sha256:5a2be8939d6734b2295f420aee17c34be5958903eb13eba88b45213f3c4c0333"
[[metadata.targets]] [[metadata.targets]]
requires_python = ">3.11" requires_python = ">3.11"
@@ -41,7 +41,7 @@ name = "colorama"
version = "0.4.6" version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text." summary = "Cross-platform colored terminal text."
groups = ["default", "test"] groups = ["default"]
marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
@@ -53,7 +53,7 @@ name = "coverage"
version = "7.6.12" version = "7.6.12"
requires_python = ">=3.9" requires_python = ">=3.9"
summary = "Code coverage measurement for Python" summary = "Code coverage measurement for Python"
groups = ["default", "test"] groups = ["default"]
files = [ files = [
{file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"}, {file = "coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8"},
{file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"}, {file = "coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
@@ -116,7 +116,7 @@ version = "7.6.12"
extras = ["toml"] extras = ["toml"]
requires_python = ">=3.9" requires_python = ">=3.9"
summary = "Code coverage measurement for Python" summary = "Code coverage measurement for Python"
groups = ["default", "test"] groups = ["default"]
dependencies = [ dependencies = [
"coverage==7.6.12", "coverage==7.6.12",
"tomli; python_full_version <= \"3.11.0a6\"", "tomli; python_full_version <= \"3.11.0a6\"",
@@ -177,34 +177,6 @@ files = [
{file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"}, {file = "coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2"},
] ]
[[package]]
name = "dotenv"
version = "0.9.9"
summary = "Deprecated package"
groups = ["default"]
dependencies = [
"python-dotenv",
]
files = [
{file = "dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9"},
]
[[package]]
name = "flake8"
version = "7.1.2"
requires_python = ">=3.8.1"
summary = "the modular source code checker: pep8 pyflakes and co"
groups = ["lint"]
dependencies = [
"mccabe<0.8.0,>=0.7.0",
"pycodestyle<2.13.0,>=2.12.0",
"pyflakes<3.3.0,>=3.2.0",
]
files = [
{file = "flake8-7.1.2-py2.py3-none-any.whl", hash = "sha256:1cbc62e65536f65e6d754dfe6f1bada7f5cf392d6f5db3c2b85892466c3e7c1a"},
{file = "flake8-7.1.2.tar.gz", hash = "sha256:c586ffd0b41540951ae41af572e6790dbd49fc12b3aa2541685d253d9bd504bd"},
]
[[package]] [[package]]
name = "flask" name = "flask"
version = "3.1.0" version = "3.1.0"
@@ -245,7 +217,7 @@ name = "iniconfig"
version = "2.0.0" version = "2.0.0"
requires_python = ">=3.7" requires_python = ">=3.7"
summary = "brain-dead simple config-ini parsing" summary = "brain-dead simple config-ini parsing"
groups = ["default", "test"] groups = ["default"]
files = [ files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -336,23 +308,12 @@ files = [
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
] ]
[[package]]
name = "mccabe"
version = "0.7.0"
requires_python = ">=3.6"
summary = "McCabe checker, plugin for flake8"
groups = ["lint"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.2" version = "24.2"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "Core utilities for Python packages" summary = "Core utilities for Python packages"
groups = ["default", "test"] groups = ["default"]
files = [ files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
@@ -363,40 +324,18 @@ name = "pluggy"
version = "1.5.0" version = "1.5.0"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "plugin and hook calling mechanisms for python" summary = "plugin and hook calling mechanisms for python"
groups = ["default", "test"] groups = ["default"]
files = [ files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
] ]
[[package]]
name = "pycodestyle"
version = "2.12.1"
requires_python = ">=3.8"
summary = "Python style guide checker"
groups = ["lint"]
files = [
{file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"},
{file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"},
]
[[package]]
name = "pyflakes"
version = "3.2.0"
requires_python = ">=3.8"
summary = "passive checker of Python programs"
groups = ["lint"]
files = [
{file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"},
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "8.3.5" version = "8.3.5"
requires_python = ">=3.8" requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python" summary = "pytest: simple powerful testing with Python"
groups = ["default", "test"] groups = ["default"]
dependencies = [ dependencies = [
"colorama; sys_platform == \"win32\"", "colorama; sys_platform == \"win32\"",
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"", "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
@@ -415,7 +354,7 @@ name = "pytest-cov"
version = "6.0.0" version = "6.0.0"
requires_python = ">=3.9" requires_python = ">=3.9"
summary = "Pytest plugin for measuring coverage." summary = "Pytest plugin for measuring coverage."
groups = ["default", "test"] groups = ["default"]
dependencies = [ dependencies = [
"coverage[toml]>=7.5", "coverage[toml]>=7.5",
"pytest>=4.6", "pytest>=4.6",
@@ -425,17 +364,6 @@ files = [
{file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"},
] ]
[[package]]
name = "python-dotenv"
version = "1.0.1"
requires_python = ">=3.8"
summary = "Read key-value pairs from a .env file and set them as environment variables"
groups = ["default"]
files = [
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[[package]] [[package]]
name = "werkzeug" name = "werkzeug"
version = "3.1.3" version = "3.1.3"

View File

@@ -10,7 +10,6 @@ dependencies = [
"pytest-cov>=4.1.0", "pytest-cov>=4.1.0",
"Flask>=3.0.2", "Flask>=3.0.2",
"flask-wtf>=1.2.1", "flask-wtf>=1.2.1",
"dotenv>=0.9.9",
] ]
requires-python = ">3.11" requires-python = ">3.11"
readme = "README.md" readme = "README.md"
@@ -23,12 +22,4 @@ distribution = false
[tool.pdm.scripts] [tool.pdm.scripts]
flask.cmd = "flask run -p 5000 --debug" flask.cmd = "flask run -p 5000 --debug"
flask.env = {FLASK_ENV = "development"} flask.env = {FLASK_ENV = "development"}
shell.cmd = "sh" shell.cmd = "sh"
[dependency-groups]
test = [
"pytest>=8.3.5",
"pytest-cov>=6.0.0",
]
lint = [
"flake8>=7.1.2",
]

View File

@@ -5,7 +5,7 @@ from app import app
# the client here allow to use the app without running in live server # the client here allow to use the app without running in live server
# see https://flask.palletsprojects.com/en/2.0.x/testing/#sending-requests-with-the-test-client # noqa: E501 # see https://flask.palletsprojects.com/en/2.0.x/testing/#sending-requests-with-the-test-client
@pytest.fixture @pytest.fixture
def client(): def client():
app.config['TESTING'] = True app.config['TESTING'] = True

View File

@@ -26,12 +26,12 @@ __email__ = "michael.maeder@hefr.ch"
def call(client, path, params): def call(client, path, params):
"""calling function that simulates an API webcall of a specific function/route """calling function that simulates an API webcall of a specific function/route
Args: Args:
client: this is the client object used by pytest to 'simulate' the API without running the webserver client: this is the client object used by pytest to 'simulate' the API without running the webserver
path: route of the API to use path: route of the API to use
params: GET parameter that are passed to the function params: GET parameter that are passed to the function
Returns: Returns:
json: the result of the client call json: the result of the client call
""" """
@@ -39,59 +39,27 @@ def call(client, path, params):
response = client.get(url) response = client.get(url)
return json.loads(response.data.decode('utf-8')) return json.loads(response.data.decode('utf-8'))
# increment test 1 # increment test 1
def test_plus_one1(client): def test_plus_one1(client):
result = call(client, '/inc', {'x': 2}) result = call(client, '/inc', {'x': 2})
assert result['x'] == 3 assert result['x'] == 3
# increment test 1 # increment test 1
def test_plus_one2(client): def test_plus_one2(client):
result = call(client, '/inc', {'x': -2}) result = call(client, '/inc', {'x': -2})
assert result['x'] == -1 assert result['x'] == -1
# adding test with negative value # adding test with negative value
def test_plus_y(client): def test_plus_y(client):
result = call(client, '/add', {'x': -2, 'y': 7}) result = call(client, '/add', {'x': -2, 'y': 7})
assert result['result'] == 5 assert result['result'] == 5
# multiplication test # multiplication test
def test_multiply(client): def test_multiply(client):
result = call(client, '/mul', {'x': -2, 'y': 7}) result = call(client, '/mul', {'x': -2, 'y': 7})
assert result['result'] == -14 assert result['result'] == -14
# division test # division test
def test_division(client): def test_division(client):
result = call(client, '/div', {'x': 35, 'y': 7}) result = call(client, '/div', {'x': 35, 'y': 7})
assert result['result'] == 5 assert result['result'] == 5
# type tests
def test_type_inc(client):
result = call(client, '/inc', {'x': 'a'})
assert result["error"] == "Type error"
def test_type_add(client):
result = call(client, '/add', {'x': 'a', 'y': 1})
assert result["error"] == "Type error"
result = call(client, '/add', {'x': 1, 'y': 'a'})
assert result["error"] == "Type error"
def test_type_mul(client):
result = call(client, '/mul', {'x': 'a', 'y': 1})
assert result["error"] == "Type error"
result = call(client, '/mul', {'x': 1, 'y': 'a'})
assert result["error"] == "Type error"
def test_type_div(client):
result = call(client, '/div', {'x': 'a', 'y': 1})
assert result["error"] == "Type error"
result = call(client, '/div', {'x': 1, 'y': 'a'})
assert result["error"] == "Type error"

View File

@@ -1,5 +0,0 @@
[flake8]
extend-ignore = E203
exclude = .git,__pycache__,.venv,__pypackages__
max-complexity = 10
max-line-length = 120