mirror of
https://github.com/dat515-2025/Group-8.git
synced 2026-03-22 15:12:08 +01:00
Compare commits
8 Commits
396047574a
...
cddc1d3a9f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cddc1d3a9f | ||
|
|
e78b8c2e6b | ||
|
|
aade88beb9 | ||
|
|
5305531950 | ||
|
|
6d8a6a55c0 | ||
|
|
40d07677bd | ||
|
|
76eb2cce41 | ||
|
|
391e9da0c4 |
23
.github/workflows/deploy-pr.yaml
vendored
23
.github/workflows/deploy-pr.yaml
vendored
@@ -9,6 +9,29 @@ permissions:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Run Python Tests
|
||||||
|
if: github.event.action != 'closed'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run tests with pytest
|
||||||
|
run: pytest
|
||||||
|
working-directory: ./7project/backend
|
||||||
|
|
||||||
build:
|
build:
|
||||||
if: github.event.action != 'closed'
|
if: github.event.action != 'closed'
|
||||||
name: Build and push image (reusable)
|
name: Build and push image (reusable)
|
||||||
|
|||||||
23
.github/workflows/deploy-prod.yaml
vendored
23
.github/workflows/deploy-prod.yaml
vendored
@@ -21,6 +21,29 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Run Python Tests
|
||||||
|
if: github.event.action != 'closed'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run tests with pytest
|
||||||
|
run: pytest
|
||||||
|
working-directory: ./7project/backend
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build and push image (reusable)
|
name: Build and push image (reusable)
|
||||||
uses: ./.github/workflows/build-image.yaml
|
uses: ./.github/workflows/build-image.yaml
|
||||||
|
|||||||
55
.github/workflows/run-tests.yml
vendored
Normal file
55
.github/workflows/run-tests.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: Run Python Tests
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# --- Triggers ----
|
||||||
|
# -----------------
|
||||||
|
# This section defines when the workflow will run.
|
||||||
|
on:
|
||||||
|
# Run on every push to the 'main' branch
|
||||||
|
push:
|
||||||
|
branches: [ "main", "30-create-tests-and-set-up-a-github-pipeline" ]
|
||||||
|
# Also run on every pull request that targets the 'main' branch
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# ------ Jobs -----
|
||||||
|
# -----------------
|
||||||
|
# A workflow is made up of one or more jobs that can run in parallel or sequentially.
|
||||||
|
jobs:
|
||||||
|
# A descriptive name for your job
|
||||||
|
build-and-test:
|
||||||
|
# Specifies the virtual machine to run the job on. 'ubuntu-latest' is a common and cost-effective choice.
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# ----- Steps -----
|
||||||
|
# -----------------
|
||||||
|
# A sequence of tasks that will be executed as part of the job.
|
||||||
|
steps:
|
||||||
|
# Step 1: Check out your repository's code
|
||||||
|
# This action allows the workflow to access your code.
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Step 2: Set up the Python environment
|
||||||
|
# This action installs a specific version of Python on the runner.
|
||||||
|
- name: Set up Python 3.11
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11' # Use the Python version that matches your project
|
||||||
|
|
||||||
|
# Step 3: Install project dependencies
|
||||||
|
# Runs shell commands to install the libraries listed in your requirements.txt.
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
# Step 4: Run your tests!
|
||||||
|
# Executes the pytest command to run your test suite.
|
||||||
|
- name: Run tests with pytest
|
||||||
|
run: pytest
|
||||||
|
working-directory: ./7project/backend
|
||||||
@@ -27,7 +27,9 @@ sentry_sdk.init(
|
|||||||
dsn=os.getenv("SENTRY_DSN"),
|
dsn=os.getenv("SENTRY_DSN"),
|
||||||
send_default_pii=True,
|
send_default_pii=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
fastApi = FastAPI()
|
fastApi = FastAPI()
|
||||||
|
app = fastApi
|
||||||
|
|
||||||
# CORS for frontend dev server
|
# CORS for frontend dev server
|
||||||
fastApi.add_middleware(
|
fastApi.add_middleware(
|
||||||
@@ -73,7 +75,7 @@ fastApi.include_router(
|
|||||||
auth_backend,
|
auth_backend,
|
||||||
"SECRET",
|
"SECRET",
|
||||||
associate_by_email=True,
|
associate_by_email=True,
|
||||||
redirect_url=os.getenv("FRONTEND_DOMAIN_SCHEME") + "/auth/mojeid/callback",
|
redirect_url=os.getenv("FRONTEND_DOMAIN_SCHEME", "http://localhost:3000") + "/auth/mojeid/callback",
|
||||||
),
|
),
|
||||||
prefix="/auth/mojeid",
|
prefix="/auth/mojeid",
|
||||||
tags=["auth"],
|
tags=["auth"],
|
||||||
@@ -85,7 +87,7 @@ fastApi.include_router(
|
|||||||
auth_backend,
|
auth_backend,
|
||||||
"SECRET",
|
"SECRET",
|
||||||
associate_by_email=True,
|
associate_by_email=True,
|
||||||
redirect_url=os.getenv("FRONTEND_DOMAIN_SCHEME") + "/auth/bankid/callback",
|
redirect_url=os.getenv("FRONTEND_DOMAIN_SCHEME", "http://localhost:3000") + "/auth/bankid/callback",
|
||||||
),
|
),
|
||||||
prefix="/auth/bankid",
|
prefix="/auth/bankid",
|
||||||
tags=["auth"],
|
tags=["auth"],
|
||||||
|
|||||||
2
7project/backend/pyproject.toml
Normal file
2
7project/backend/pyproject.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[tool.pytest.ini_options]
|
||||||
|
pythonpath = "."
|
||||||
22
7project/backend/tests/conftest.py
Normal file
22
7project/backend/tests/conftest.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import pytest
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
# Stub sentry_sdk to avoid optional dependency issues during import of app
|
||||||
|
stub = types.ModuleType("sentry_sdk")
|
||||||
|
stub.init = lambda *args, **kwargs: None
|
||||||
|
sys.modules.setdefault("sentry_sdk", stub)
|
||||||
|
|
||||||
|
# Import the FastAPI application
|
||||||
|
from app.app import fastApi as app # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def fastapi_app():
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def client(fastapi_app):
|
||||||
|
return TestClient(fastapi_app, raise_server_exceptions=True)
|
||||||
15
7project/backend/tests/test_e2e_auth_flow.py
Normal file
15
7project/backend/tests/test_e2e_auth_flow.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from fastapi import status
|
||||||
|
|
||||||
|
|
||||||
|
def test_e2e_minimal_auth_flow(client):
|
||||||
|
# 1) Service is alive
|
||||||
|
alive = client.get("/")
|
||||||
|
assert alive.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
# 2) Attempt to login without payload should fail fast (validation error)
|
||||||
|
login = client.post("/auth/jwt/login")
|
||||||
|
assert login.status_code in (status.HTTP_400_BAD_REQUEST, status.HTTP_422_UNPROCESSABLE_CONTENT)
|
||||||
|
|
||||||
|
# 3) Protected endpoint should not be accessible without token
|
||||||
|
me = client.get("/users/me")
|
||||||
|
assert me.status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)
|
||||||
18
7project/backend/tests/test_integration_app.py
Normal file
18
7project/backend/tests/test_integration_app.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from fastapi import status
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_ok(client):
|
||||||
|
resp = client.get("/")
|
||||||
|
assert resp.status_code == status.HTTP_200_OK
|
||||||
|
assert resp.json() == {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_authenticated_route_requires_auth(client):
|
||||||
|
resp = client.get("/authenticated-route")
|
||||||
|
assert resp.status_code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sentry_debug_raises_exception(client):
|
||||||
|
with pytest.raises(ZeroDivisionError):
|
||||||
|
client.get("/sentry-debug")
|
||||||
55
7project/backend/tests/test_unit_user_service.py
Normal file
55
7project/backend/tests/test_unit_user_service.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import types
|
||||||
|
import asyncio
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.services import user_service
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_oauth_provider_known_unknown():
|
||||||
|
# Known providers should return a provider instance
|
||||||
|
bankid = user_service.get_oauth_provider("BankID")
|
||||||
|
mojeid = user_service.get_oauth_provider("MojeID")
|
||||||
|
assert bankid is not None
|
||||||
|
assert mojeid is not None
|
||||||
|
|
||||||
|
# Unknown should return None
|
||||||
|
assert user_service.get_oauth_provider("DoesNotExist") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_jwt_strategy_lifetime():
|
||||||
|
strategy = user_service.get_jwt_strategy()
|
||||||
|
assert strategy is not None
|
||||||
|
# Basic smoke check: strategy has a lifetime set to 3600
|
||||||
|
assert getattr(strategy, "lifetime_seconds", None) in (604800,)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_on_after_request_verify_enqueues_email(monkeypatch):
|
||||||
|
calls = {}
|
||||||
|
|
||||||
|
def fake_enqueue_email(to: str, subject: str, body: str):
|
||||||
|
calls.setdefault("emails", []).append({
|
||||||
|
"to": to,
|
||||||
|
"subject": subject,
|
||||||
|
"body": body,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Patch the enqueue_email used inside user_service
|
||||||
|
monkeypatch.setattr(user_service, "enqueue_email", fake_enqueue_email)
|
||||||
|
|
||||||
|
class DummyUser:
|
||||||
|
def __init__(self, email):
|
||||||
|
self.email = email
|
||||||
|
|
||||||
|
mgr = user_service.UserManager(user_db=None) # user_db not needed for this method
|
||||||
|
user = DummyUser("test@example.com")
|
||||||
|
|
||||||
|
# Call the hook
|
||||||
|
await mgr.on_after_request_verify(user, token="abc123", request=None)
|
||||||
|
|
||||||
|
# Verify one email has been enqueued with expected content
|
||||||
|
assert len(calls.get("emails", [])) == 1
|
||||||
|
email = calls["emails"][0]
|
||||||
|
assert email["to"] == "test@example.com"
|
||||||
|
assert "ověření účtu" in email["subject"].lower()
|
||||||
|
assert "abc123" in email["body"]
|
||||||
72
requirements.txt
Normal file
72
requirements.txt
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
aio-pika==9.5.6
|
||||||
|
aiormq==6.8.1
|
||||||
|
aiosqlite==0.21.0
|
||||||
|
alembic==1.16.5
|
||||||
|
amqp==5.3.1
|
||||||
|
annotated-types==0.7.0
|
||||||
|
anyio==4.11.0
|
||||||
|
argon2-cffi==23.1.0
|
||||||
|
argon2-cffi-bindings==25.1.0
|
||||||
|
asyncmy==0.2.9
|
||||||
|
bcrypt==4.3.0
|
||||||
|
billiard==4.2.2
|
||||||
|
celery==5.5.3
|
||||||
|
certifi==2025.10.5
|
||||||
|
cffi==2.0.0
|
||||||
|
click==8.1.8
|
||||||
|
click-didyoumean==0.3.1
|
||||||
|
click-plugins==1.1.1.2
|
||||||
|
click-repl==0.3.0
|
||||||
|
cryptography==46.0.1
|
||||||
|
dnspython==2.7.0
|
||||||
|
email_validator==2.2.0
|
||||||
|
exceptiongroup==1.3.0
|
||||||
|
fastapi==0.117.1
|
||||||
|
fastapi-users==14.0.1
|
||||||
|
fastapi-users-db-sqlalchemy==7.0.0
|
||||||
|
greenlet==3.2.4
|
||||||
|
h11==0.16.0
|
||||||
|
httpcore==1.0.9
|
||||||
|
httptools==0.6.4
|
||||||
|
httpx==0.28.1
|
||||||
|
httpx-oauth==0.16.1
|
||||||
|
idna==3.10
|
||||||
|
iniconfig==2.3.0
|
||||||
|
kombu==5.5.4
|
||||||
|
makefun==1.16.0
|
||||||
|
Mako==1.3.10
|
||||||
|
MarkupSafe==3.0.2
|
||||||
|
multidict==6.6.4
|
||||||
|
packaging==25.0
|
||||||
|
pamqp==3.3.0
|
||||||
|
pluggy==1.6.0
|
||||||
|
prompt_toolkit==3.0.52
|
||||||
|
propcache==0.3.2
|
||||||
|
pwdlib==0.2.1
|
||||||
|
pycparser==2.23
|
||||||
|
pydantic==2.11.9
|
||||||
|
pydantic_core==2.33.2
|
||||||
|
Pygments==2.19.2
|
||||||
|
PyJWT==2.10.1
|
||||||
|
PyMySQL==1.1.2
|
||||||
|
pytest==8.4.2
|
||||||
|
pytest-asyncio==1.2.0
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
|
python-dotenv==1.1.1
|
||||||
|
python-multipart==0.0.20
|
||||||
|
PyYAML==6.0.2
|
||||||
|
six==1.17.0
|
||||||
|
sniffio==1.3.1
|
||||||
|
SQLAlchemy==2.0.43
|
||||||
|
starlette==0.48.0
|
||||||
|
tomli==2.2.1
|
||||||
|
typing-inspection==0.4.1
|
||||||
|
typing_extensions==4.15.0
|
||||||
|
tzdata==2025.2
|
||||||
|
uvicorn==0.37.0
|
||||||
|
uvloop==0.21.0
|
||||||
|
vine==5.1.0
|
||||||
|
watchfiles==1.1.0
|
||||||
|
wcwidth==0.2.14
|
||||||
|
websockets==15.0.1
|
||||||
|
yarl==1.20.1
|
||||||
Reference in New Issue
Block a user