Compare commits

44 Commits

Author SHA1 Message Date
140dbb1989 Merge branch 'feat/15-Q3.4'
feat(ci): add DAST job configuration to GitLab CI

See merge request Klagarge/mse2425-grp09!13
2025-04-14 13:02:33 +00:00
355865586f refactor: removed unused code
docs: added answer for Q3.4
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-14 14:56:58 +02:00
b170eecb16 fix: label of docker image
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-14 14:56:58 +02:00
c308ffd2dd feat(ci): add Dockerfile and update CI configuration for DAST
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-14 14:56:58 +02:00
7da76417f7 feat(ci): add DAST job configuration to GitLab CI
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-14 14:56:58 +02:00
697de24eab Merge branch 'feat/8-Q2.3'
feat: Add answer for Q2.3 and configure pre-commit with flake8

See merge request Klagarge/mse2425-grp09!12
2025-04-14 12:51:04 +00:00
b3f3ce0b16 feat: Add answer for Q2.3 and configure pre-commit with flake8
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-13 13:58:20 +02:00
Alec Schmidt
5c7bbac1c5 Merge branch 'feat/Q3.3'
feat: Answered question

See merge request Klagarge/mse2425-grp09!11
2025-04-09 17:40:18 +00:00
alec.schmidt
75b8f565bc feat: Answered question 2025-04-09 19:33:18 +02:00
4c7a0bd88f Merge branch 'fix/code-coverage-pypackages'
fix: added __pypackages__ to omitted coverage. Testing on remote due to...

See merge request Klagarge/mse2425-grp09!7
2025-04-09 17:29:32 +00:00
alec.schmidt
c81193364d fix: added __pypackages__ to omitted coverage. Testing on remote due to __pypackages__ not showing locally 2025-04-09 19:23:39 +02:00
Alec Schmidt
394cc91ec6 Merge branch 'feat/Q3.2'
feat: Answered question

See merge request Klagarge/mse2425-grp09!10
2025-04-09 17:17:54 +00:00
alec.schmidt
befd66898d feat: Answered question 2025-04-09 19:12:53 +02:00
Alec Schmidt
a3b4f90135 Merge branch 'update/upstream'
Update/upstream

See merge request Klagarge/mse2425-grp09!9
2025-04-09 16:39:08 +00:00
Michael Mäder
fbea64d742 applying to 2025 2025-04-09 16:33:58 +00:00
Michael Mäder
8703d85b1f add question4 2025-04-09 16:33:58 +00:00
Alec Schmidt
a59707eb23 Merge branch 'feat/Q3.1'
feat: SAST in CI/CD

See merge request Klagarge/mse2425-grp09!8
2025-04-09 16:09:48 +00:00
alec.schmidt
c1a195f0f8 feat: SAST in CI/CD 2025-04-09 18:02:25 +02:00
Alec Schmidt
a718be6da8 Merge branch 'update/upstream'
Update/upstream

See merge request Klagarge/mse2425-grp09!6
2025-03-27 08:00:16 +00:00
Michael Mäder
ab278cc9b3 questions3 and some practical misc stuff 2025-03-26 18:34:30 +00:00
Alec Schmidt
5e9f03e0f7 Merge branch 'feat/ci-improvement'
Create custom docker for CI

See merge request Klagarge/mse2425-grp09!5
2025-03-26 17:04:59 +00:00
bcec88f930 docs(CI): answer question 2.1
Refs:  #6
Signed-off-by: Klagarge <remi@heredero.ch>
2025-03-20 16:56:01 +01:00
188a4725e6 perf(docker): slight optimisation and refector
Signed-off-by: Klagarge <remi@heredero.ch>
2025-03-20 16:41:50 +01:00
8604e3e984 docs(CI): answer question 1.4
Refs: #4
Signed-off-by: Klagarge <remi@heredero.ch>
2025-03-20 16:41:50 +01:00
4e27f8c9d9 test(docker): build docker by CI
Signed-off-by: Klagarge <remi@heredero.ch>
2025-03-20 16:41:50 +01:00
5e926d7674 test(linter): use pdm for linter installation
Refs: #5
Signed-off-by: Klagarge <remi@heredero.ch>
2025-03-19 22:09:48 +01:00
eda58ace70 feat(CI): add custom docker for CI
Refs: #4
Signed-off-by: Klagarge <remi@heredero.ch>
2025-03-19 22:09:48 +01:00
Alec Schmidt
9897526b14 Merge branch 'update/upstream'
Update/upstream

See merge request Klagarge/mse2425-grp09!4
2025-03-19 17:35:24 +00:00
alec.schmidt
62090041f5 update: pulled upstream [skip ci] 2025-03-19 18:31:20 +01:00
Alec Schmidt
055401ed53 Merge branch 'feat/Q1.3'
Feat/q1.3

See merge request Klagarge/mse2425-grp09!3
2025-03-18 09:45:02 +00:00
Alec.Schmidt
9c496e73bb chore: answer to questions [no ci] 2025-03-18 10:30:56 +01:00
Alec.Schmidt
df1db2fd97 fix: linter errors over test_api.py 2025-03-18 10:18:15 +01:00
Alec.Schmidt
570a0d3cea fix: changed the amount of chars in a single line to 120 2025-03-18 10:16:35 +01:00
Alec.Schmidt
89e66a8b58 fix: linter errors over conftest.py [no ci] 2025-03-18 10:14:08 +01:00
Alec.Schmidt
83d70c4fc5 fix: linter errors over operators.py [no ci] 2025-03-18 10:12:30 +01:00
Alec.Schmidt
3d6abfa9bc fix: linter errors over app.py [no ci] 2025-03-18 10:11:40 +01:00
Alec.Schmidt
528da91ed4 feat: CI/CD for flake8 linting, warning : this commit is a test for the execution of the pipeline 2025-03-18 10:03:42 +01:00
Alec.Schmidt
ea40810fa3 feat: flake8 config file 2025-03-18 09:59:08 +01:00
Alec Schmidt
0108bb25ed Merge branch 'fix/2-q1.2'
chore(security): remove hardcoded security key

See merge request Klagarge/mse2425-grp09!2
2025-03-17 19:51:05 +00:00
de8a0ab688 chore(security): remove hardcoded security key
Remove hardcoded security key for flask.
Use environment variable instead.
2025-03-16 15:19:48 +01:00
8590c7a715 Merge branch 'feat/Q1.1'
See merge request Klagarge/mse2425-grp09!1
2025-03-12 21:33:07 +00:00
Alec.Schmidt
be3db6e605 fix: API endpoints input type safety 2025-03-12 21:11:29 +01:00
Alec.Schmidt
34e24304fa test: added api input type checking tests [no ci] 2025-03-12 21:04:31 +01:00
Alec.Schmidt
ee2f80d670 chore: added test coverage html folder to gitignore 2025-03-12 21:03:18 +01:00
23 changed files with 382 additions and 49 deletions

4
.gitignore vendored
View File

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

View File

@@ -1,34 +1,52 @@
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:
- build
- build-docker-test
- build-docker-app
- lint
- test
- dast
build job:
stage: build
script:
.setup_env: &setup_env
before_script:
- cd src
- 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
- 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:
stage: test
<<: *setup_env
script:
# Set environment variables for the tests
- export FLASK_SECRET_KEY=$FLASK_SECRET_KEY
# launch tests
- export PYTHONPATH=.
- export FLASK_APP=app
- pdm run pytest tests --cov --cov-report term --cov-report html
artifacts:
paths:
- src/htmlcov/
lint job:
stage: lint
<<: *setup_env
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:
stage: build
stage: test
dependencies:
- build job
needs: ["build job"]
- test job
needs: ["test job"]
script:
- mv src/htmlcov/ public/
artifacts:
@@ -37,3 +55,45 @@ pages:
expire_in: 7 days
only:
- main
# This job runs only when Dockerfile changes
docker-build-test:
image: docker:latest
stage: build-docker-test
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-app
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: dast
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

7
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,7 @@
repos:
- repo: https://github.com/pycqa/flake8
rev: '6.1.0' # Use the latest stable version
hooks:
- id: flake8
additional_dependencies: []
args: [--config=tox.ini] # Use the same config as in CI

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
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"

BIN
docs/figures/OWASP-ZAP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -6,3 +6,15 @@
- **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.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,3 +5,10 @@
- **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.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.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.

35
docs/questions-part3.md Normal file
View File

@@ -0,0 +1,35 @@
# Questions
## 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.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.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.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.

7
docs/questions-part4.md Normal file
View File

@@ -0,0 +1,7 @@
# 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.

9
misc/docker-compose.yml Normal file
View File

@@ -0,0 +1,9 @@
services:
nginx:
ports:
- "80:80"
container_name: nginx-1.20.0
image: nginx:1.20.0
volumes:
- .:/usr/share/nginx/html:ro

View File

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

4
misc/start_juiceshop.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
docker run --rm -p 3000:3000 bkimminich/juice-shop

View File

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

1
src/.env.template Normal file
View File

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

31
src/Dockerfile Normal file
View File

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

92
src/pdm.lock generated
View File

@@ -2,10 +2,10 @@
# It is not intended for manual editing.
[metadata]
groups = ["default"]
groups = ["default", "lint", "test"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:5a2be8939d6734b2295f420aee17c34be5958903eb13eba88b45213f3c4c0333"
content_hash = "sha256:f5c4e58e167316cb2440e9205a1e15474a3599463aa47c3c259b4009038166b0"
[[metadata.targets]]
requires_python = ">3.11"
@@ -41,7 +41,7 @@ name = "colorama"
version = "0.4.6"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
summary = "Cross-platform colored terminal text."
groups = ["default"]
groups = ["default", "test"]
marker = "sys_platform == \"win32\" or platform_system == \"Windows\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
@@ -53,7 +53,7 @@ name = "coverage"
version = "7.6.12"
requires_python = ">=3.9"
summary = "Code coverage measurement for Python"
groups = ["default"]
groups = ["default", "test"]
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_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879"},
@@ -116,7 +116,7 @@ version = "7.6.12"
extras = ["toml"]
requires_python = ">=3.9"
summary = "Code coverage measurement for Python"
groups = ["default"]
groups = ["default", "test"]
dependencies = [
"coverage==7.6.12",
"tomli; python_full_version <= \"3.11.0a6\"",
@@ -177,6 +177,34 @@ files = [
{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]]
name = "flask"
version = "3.1.0"
@@ -217,7 +245,7 @@ name = "iniconfig"
version = "2.0.0"
requires_python = ">=3.7"
summary = "brain-dead simple config-ini parsing"
groups = ["default"]
groups = ["default", "test"]
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -308,12 +336,23 @@ files = [
{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]]
name = "packaging"
version = "24.2"
requires_python = ">=3.8"
summary = "Core utilities for Python packages"
groups = ["default"]
groups = ["default", "test"]
files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
@@ -324,18 +363,40 @@ name = "pluggy"
version = "1.5.0"
requires_python = ">=3.8"
summary = "plugin and hook calling mechanisms for python"
groups = ["default"]
groups = ["default", "test"]
files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{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]]
name = "pytest"
version = "8.3.5"
requires_python = ">=3.8"
summary = "pytest: simple powerful testing with Python"
groups = ["default"]
groups = ["default", "test"]
dependencies = [
"colorama; sys_platform == \"win32\"",
"exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
@@ -354,7 +415,7 @@ name = "pytest-cov"
version = "6.0.0"
requires_python = ">=3.9"
summary = "Pytest plugin for measuring coverage."
groups = ["default"]
groups = ["default", "test"]
dependencies = [
"coverage[toml]>=7.5",
"pytest>=4.6",
@@ -364,6 +425,17 @@ files = [
{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]]
name = "werkzeug"
version = "3.1.3"

View File

@@ -10,6 +10,7 @@ dependencies = [
"pytest-cov>=4.1.0",
"Flask>=3.0.2",
"flask-wtf>=1.2.1",
"dotenv>=0.9.9",
]
requires-python = ">3.11"
readme = "README.md"
@@ -23,3 +24,11 @@ distribution = false
flask.cmd = "flask run -p 5000 --debug"
flask.env = {FLASK_ENV = "development"}
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
# see https://flask.palletsprojects.com/en/2.0.x/testing/#sending-requests-with-the-test-client
# see https://flask.palletsprojects.com/en/2.0.x/testing/#sending-requests-with-the-test-client # noqa: E501
@pytest.fixture
def client():
app.config['TESTING'] = True

View File

@@ -39,27 +39,59 @@ def call(client, path, params):
response = client.get(url)
return json.loads(response.data.decode('utf-8'))
# increment test 1
def test_plus_one1(client):
result = call(client, '/inc', {'x': 2})
assert result['x'] == 3
# increment test 1
def test_plus_one2(client):
result = call(client, '/inc', {'x': -2})
assert result['x'] == -1
# adding test with negative value
def test_plus_y(client):
result = call(client, '/add', {'x': -2, 'y': 7})
assert result['result'] == 5
# multiplication test
def test_multiply(client):
result = call(client, '/mul', {'x': -2, 'y': 7})
assert result['result'] == -14
# division test
def test_division(client):
result = call(client, '/div', {'x': 35, 'y': 7})
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"

5
tox.ini Normal file
View File

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