UPDATE
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
|||||||
|
FROM docker.iranserver.com/python:3.10
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# APT MIRRORS (runflare)
|
||||||
|
# ------------------------
|
||||||
|
RUN rm -f /etc/apt/sources.list /etc/apt/sources.list.d/* && \
|
||||||
|
printf '%s\n' \
|
||||||
|
'deb https://mirror-linux.runflare.com/debian/ bookworm main contrib non-free non-free-firmware' \
|
||||||
|
'deb https://mirror-linux.runflare.com/debian/ bookworm-updates main contrib non-free non-free-firmware' \
|
||||||
|
'deb https://mirror-linux.runflare.com/debian-security/ bookworm-security main contrib non-free non-free-firmware' \
|
||||||
|
'' \
|
||||||
|
> /etc/apt/sources.list
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
curl \
|
||||||
|
jq \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Copy requirements
|
||||||
|
# ------------------------
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# PIP MIRROR
|
||||||
|
# ------------------------
|
||||||
|
ENV PIP_INDEX_URL=https://mirror-pypi.runflare.com/simple
|
||||||
|
ENV PIP_TRUSTED_HOST=mirror-pypi.runflare.com
|
||||||
|
|
||||||
|
RUN pip install --upgrade pip && \
|
||||||
|
pip install --prefer-binary -r requirements.txt
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# Copy test code
|
||||||
|
# ------------------------
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose is optional for test container
|
||||||
|
# EXPOSE 8000
|
||||||
|
|
||||||
|
CMD bash -c "pytest tests -v --maxfail=1 | tee /logs/test.log"
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
base_url: http://backend:8000/api/auth
|
||||||
|
|
||||||
|
flows:
|
||||||
|
|
||||||
|
register_login:
|
||||||
|
|
||||||
|
register:
|
||||||
|
method: POST
|
||||||
|
path: /register/
|
||||||
|
body:
|
||||||
|
username: "{random_username}"
|
||||||
|
email: "{random_username}@example.com"
|
||||||
|
phone_number: "09120000000"
|
||||||
|
password: "test123456"
|
||||||
|
first_name: "test"
|
||||||
|
last_name: "user"
|
||||||
|
|
||||||
|
expected_status: 201
|
||||||
|
expected_json:
|
||||||
|
msg: success
|
||||||
|
|
||||||
|
login:
|
||||||
|
method: POST
|
||||||
|
path: /login/
|
||||||
|
body:
|
||||||
|
identifier: "{random_username}"
|
||||||
|
password: "test123456"
|
||||||
|
|
||||||
|
expected_status: 200
|
||||||
|
|
||||||
|
extract:
|
||||||
|
token: token
|
||||||
|
|
||||||
|
store_redis:
|
||||||
|
key: "test_token:{random_username}"
|
||||||
|
ttl: 3600
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
services:
|
||||||
|
integration-tests:
|
||||||
|
image: integration-tests
|
||||||
|
build: .
|
||||||
|
container_name: integration_tests
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
environment:
|
||||||
|
REDIS_HOST: redis
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- ./logs:/logs
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- crop_network
|
||||||
|
|
||||||
|
tty: true
|
||||||
|
stdin_open: true
|
||||||
|
|
||||||
|
networks:
|
||||||
|
crop_network:
|
||||||
|
external: true
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
============================= test session starts ==============================
|
||||||
|
platform linux -- Python 3.10.20, pytest-9.0.3, pluggy-1.6.0 -- /usr/local/bin/python3.10
|
||||||
|
cachedir: .pytest_cache
|
||||||
|
rootdir: /app
|
||||||
|
configfile: pytest.ini
|
||||||
|
collecting ... collected 0 items / 1 error
|
||||||
|
|
||||||
|
==================================== ERRORS ====================================
|
||||||
|
________________ ERROR collecting tests/test_authentication.py _________________
|
||||||
|
ImportError while importing test module '/app/tests/test_authentication.py'.
|
||||||
|
Hint: make sure your test modules/packages have valid Python names.
|
||||||
|
Traceback:
|
||||||
|
/usr/local/lib/python3.10/site-packages/_pytest/python.py:507: in importtestmodule
|
||||||
|
mod = import_path(
|
||||||
|
/usr/local/lib/python3.10/site-packages/_pytest/pathlib.py:587: in import_path
|
||||||
|
importlib.import_module(module_name)
|
||||||
|
/usr/local/lib/python3.10/importlib/__init__.py:126: in import_module
|
||||||
|
return _bootstrap._gcd_import(name[level:], package, level)
|
||||||
|
<frozen importlib._bootstrap>:1050: in _gcd_import
|
||||||
|
???
|
||||||
|
<frozen importlib._bootstrap>:1027: in _find_and_load
|
||||||
|
???
|
||||||
|
<frozen importlib._bootstrap>:1006: in _find_and_load_unlocked
|
||||||
|
???
|
||||||
|
<frozen importlib._bootstrap>:688: in _load_unlocked
|
||||||
|
???
|
||||||
|
/usr/local/lib/python3.10/site-packages/_pytest/assertion/rewrite.py:197: in exec_module
|
||||||
|
exec(co, module.__dict__)
|
||||||
|
tests/test_authentication.py:5: in <module>
|
||||||
|
from utils.yaml_loader import load_config
|
||||||
|
E ImportError: cannot import name 'load_config' from 'utils.yaml_loader' (/app/utils/yaml_loader.py)
|
||||||
|
=========================== short test summary info ============================
|
||||||
|
ERROR tests/test_authentication.py
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
|
||||||
|
=============================== 1 error in 0.52s ===============================
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[pytest]
|
||||||
|
pythonpath = .
|
||||||
|
testpaths = tests
|
||||||
|
python_files = test_*.py
|
||||||
|
addopts = -v
|
||||||
|
markers =
|
||||||
|
ai: tests that use AI validation
|
||||||
|
slow: slow integration tests
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
pytest
|
||||||
|
requests
|
||||||
|
redis
|
||||||
|
python-dotenv
|
||||||
|
pyyaml
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import uuid
|
||||||
|
import redis
|
||||||
|
|
||||||
|
from utils.http_client import http_request
|
||||||
|
from utils.yaml_loader import load_config
|
||||||
|
from utils.template import render
|
||||||
|
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
BASE_URL = config["base_url"]
|
||||||
|
flow = config["flows"]["register_login"]
|
||||||
|
|
||||||
|
redis_client = redis.Redis(
|
||||||
|
host="redis",
|
||||||
|
port=6379,
|
||||||
|
decode_responses=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_auth_flow():
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
|
||||||
|
context["random_username"] = f"user_{uuid.uuid4().hex[:6]}"
|
||||||
|
|
||||||
|
# -------- register --------
|
||||||
|
|
||||||
|
register = flow["register"]
|
||||||
|
|
||||||
|
body = render(register["body"], context)
|
||||||
|
|
||||||
|
res = http_request(
|
||||||
|
register["method"],
|
||||||
|
BASE_URL + register["path"],
|
||||||
|
json=body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res["status_code"] == register["expected_status"]
|
||||||
|
|
||||||
|
assert res["json"]["msg"] == register["expected_json"]["msg"]
|
||||||
|
|
||||||
|
# -------- login --------
|
||||||
|
|
||||||
|
login = flow["login"]
|
||||||
|
|
||||||
|
body = render(login["body"], context)
|
||||||
|
|
||||||
|
res = http_request(
|
||||||
|
login["method"],
|
||||||
|
BASE_URL + login["path"],
|
||||||
|
json=body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert res["status_code"] == login["expected_status"]
|
||||||
|
|
||||||
|
token_field = login["extract"]["token"]
|
||||||
|
|
||||||
|
token = res["json"][token_field]
|
||||||
|
|
||||||
|
assert token is not None
|
||||||
|
|
||||||
|
context["token"] = token
|
||||||
|
|
||||||
|
# -------- redis store --------
|
||||||
|
|
||||||
|
redis_cfg = login["store_redis"]
|
||||||
|
|
||||||
|
key = render(redis_cfg["key"], context)
|
||||||
|
|
||||||
|
redis_client.set(
|
||||||
|
key,
|
||||||
|
token,
|
||||||
|
ex=redis_cfg["ttl"]
|
||||||
|
)
|
||||||
|
|
||||||
|
saved_token = redis_client.get(key)
|
||||||
|
|
||||||
|
assert saved_token == token
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
def http_request(method, url, **kwargs):
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
response = requests.request(method, url, **kwargs)
|
||||||
|
|
||||||
|
latency = time.time() - start
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
except:
|
||||||
|
data = response.text
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": response.status_code,
|
||||||
|
"data": data,
|
||||||
|
"latency": latency
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
def render(obj, context):
|
||||||
|
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
return {k: render(v, context) for k, v in obj.items()}
|
||||||
|
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [render(i, context) for i in obj]
|
||||||
|
|
||||||
|
if isinstance(obj, str):
|
||||||
|
return obj.format(**context)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
def load_apis():
|
||||||
|
with open("apis.yaml", "r") as f:
|
||||||
|
return yaml.safe_load(f)
|
||||||
|
|
||||||
Reference in New Issue
Block a user