Compare commits

..

54 Commits

Author SHA1 Message Date
Alec Schmidt
2807f7289c Merge branch 'feat/Q2.2'
feat: update pre-commit hooks and answered questions

See merge request Klagarge/mse2425-grp09!16
2025-04-16 20:11:12 +00:00
alec.schmidt
79da7a9dc9 feat: update pre-commit hooks and answered questions 2025-04-16 21:31:34 +02:00
616f2bf199 Merge branch 'feat/18-Q4.1'
Gitleaks check

See merge request Klagarge/mse2425-grp09!15
2025-04-15 20:20:45 +00:00
d7969fed3d docs: add answers for questions 4.2
Copy answer from issue discussion to the right place

Refs: #19
Signed-off-by: Alec Schmidt <alec.schmidt@master.hes-so.ch>
2025-04-15 22:11:57 +02:00
2d592123d1 docs: add answers for questions 4.1
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-15 22:01:01 +02:00
6c56c4cf3b refactor(ci): merge some jobs on same stage
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-15 22:00:26 +02:00
f6610ba524 feat(pre-commit): add Gitleaks hook for secret scanning
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-15 21:38:06 +02:00
32e2d37dd3 feat(ci): add Gitleaks job for secret scanning
Signed-off-by: Klagarge <remi@heredero.ch>
2025-04-15 21:37:55 +02:00
Alec Schmidt
b443a56524 Merge branch 'feat/Q4.2'
Dependencies check

See merge request Klagarge/mse2425-grp09!14
2025-04-15 08:41:57 +00:00
Alec Schmidt
276e4ced5c feat: added pip-audit to precommit 2025-04-15 09:55:20 +02:00
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
21 changed files with 433 additions and 50 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,50 @@
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
- lint
- test
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/
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 +53,53 @@ pages:
expire_in: 7 days
only:
- 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 .

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

@@ -0,0 +1,23 @@
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]

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,42 @@
- **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.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

@@ -8,3 +8,28 @@
- **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.

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

@@ -0,0 +1,26 @@
# 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</h1>
<h1>Hello class, TSM_Cybersec 2025</h1>

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():
x = int(request.args.get('x', 1))
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():
x = int(request.args.get('x', 1))
y = int(request.args.get('y', 1))
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():
x = int(request.args.get('x', 1))
y = int(request.args.get('y', 1))
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():
x = int(request.args.get('x', 1))
y = int(request.args.get('y', 1))
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)})

View File

@@ -42,6 +42,6 @@ def multiplication(x, y):
# returns None if the divisor is zero
# the result might be of float type
def division(x, y):
if y==0:
if y == 0:
return None
return 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