Files
srab/autotest/main.py
2025-11-26 21:32:41 +03:00

256 lines
7.4 KiB
Python

#!/usr/bin/env python3
import http.client
import importlib
import json
import os
import socket
import sqlite3
import subprocess
import sys
import time
import argparse
from pathlib import Path
import traceback
ROOT = Path(__file__).resolve().parents[1]
DB_PATH = ROOT / "var" / "srab.db"
SERVER_PATH = os.environ.get("SERVER_PATH", ROOT / "build" / "target.exe")
HOST = "127.0.0.1"
PORT = 1337
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
K_FIRST_NAME = "имя"
K_LAST_NAME = "фамилия"
K_MIDDLE_NAME = "отчество"
K_EDUCATION = "образование"
K_PASSWORD = "пароль"
K_CONFIRM_PASSWORD = "повтор пароля"
K_NEW_PASSWORD = "новый пароль"
K_SNILS = "снилс"
K_PASSPORT = "паспорт"
K_CLASS_NUMBER = "номер"
K_CLASS_LETTER = "буква"
K_CLASSES = "классы"
K_IDENTIFIER = "идентификатор"
K_CREATOR = "создатель"
K_USERNAME = "имя пользователя"
MESSAGE_PASSWORD_MISMATCH = "Пароли не совпадают."
MESSAGE_LOGIN_OK = "Рады видеть вас снова :3"
CASE_PACKAGE = "autotest.cases"
CASE_MODULES = [
"case_01_users_create_teacher",
"case_02_users_create_mismatch",
"case_03_users_login_success",
"case_04_users_login_failure",
"case_05_users_change_password",
"case_06_users_login_with_new_password",
"case_07_teachers_auth_required",
"case_08_teachers_create_class",
"case_09_teachers_list_classes",
"case_10_teachers_create_extra_class",
"case_11_teachers_delete_extra_class",
"case_12_students_create_student",
"case_13_students_create_second_student",
"case_14_teachers_add_student_to_class",
"case_15_students_list",
"case_16_students_get",
"case_17_students_delete",
"case_18_lessons_teacher_add",
"case_19_lessons_list",
"case_20_lessons_teacher_delete",
"case_21_teachers_remove_student_from_class",
]
class TestContext:
def __init__(self) -> None:
self.teacher_first = "Alice"
self.teacher_last = "Smith"
self.teacher_middle = "Ann"
self.teacher_education = "Math"
self.teacher_password_initial = "Secret42"
self.teacher_password_new = "Secret99"
self.teacher_password = self.teacher_password_initial
self.teacher_username = f"{self.teacher_first}.{self.teacher_last}"
self.class_id = None
self.class_two_id = None
self.student_one_id = None
self.student_two_id = None
self.teacher_id = None
self.teacher_user_id = None
self.lesson_first_id = None
self.lesson_first_date = None
self.lesson_first_title = None
self.lesson_second_id = None
self.lesson_second_date = None
self.lesson_second_title = None
self.student_one_username = None
self.student_one_password = None
self.student_two_username = None
self.student_two_password = None
self.K_FIRST_NAME = K_FIRST_NAME
self.K_LAST_NAME = K_LAST_NAME
self.K_MIDDLE_NAME = K_MIDDLE_NAME
self.K_EDUCATION = K_EDUCATION
self.K_PASSWORD = K_PASSWORD
self.K_CONFIRM_PASSWORD = K_CONFIRM_PASSWORD
self.K_NEW_PASSWORD = K_NEW_PASSWORD
self.K_SNILS = K_SNILS
self.K_PASSPORT = K_PASSPORT
self.K_CLASS_NUMBER = K_CLASS_NUMBER
self.K_CLASS_LETTER = K_CLASS_LETTER
self.K_CLASSES = K_CLASSES
self.K_IDENTIFIER = K_IDENTIFIER
self.K_CREATOR = K_CREATOR
self.K_USERNAME = K_USERNAME
self.MESSAGE_PASSWORD_MISMATCH = MESSAGE_PASSWORD_MISMATCH
self.MESSAGE_LOGIN_OK = MESSAGE_LOGIN_OK
def expect(self, condition: bool, message: str) -> None:
expect(condition, message)
def send_request(self, method: str, path: str, *, body=None, headers=None):
return send_request(method, path, body=body, headers=headers)
def query_single_value(self, sql: str, params) -> int:
return query_single_value(sql, params)
def make_auth(self, username: str, password: str) -> dict:
return make_auth(username, password)
def remove_database() -> None:
if DB_PATH.exists():
DB_PATH.unlink()
def start_server(silent) -> subprocess.Popen:
out = subprocess.DEVNULL if silent else None
return subprocess.Popen(
[str(SERVER_PATH)],
cwd=str(ROOT),
stdout=out,
stderr=out,
)
def wait_for_server(timeout: float = 10.0) -> None:
deadline = time.time() + timeout
while time.time() < deadline:
try:
with socket.create_connection((HOST, PORT), timeout=0.25):
return
except OSError:
time.sleep(0.05)
raise RuntimeError("server did not accept connections")
def stop_server(proc: subprocess.Popen) -> None:
if proc.poll() is not None:
return
proc.terminate()
try:
proc.wait(timeout=2)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=2)
def expect(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def make_auth(username: str, password: str) -> dict:
return {"Authorization": f"Basic {username} {password}"}
def send_request(method: str, path: str, *, body=None, headers=None):
conn = http.client.HTTPConnection(HOST, PORT, timeout=5)
try:
data = None
hdrs = {} if headers is None else dict(headers)
if body is not None:
if isinstance(body, (dict, list)):
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
hdrs.setdefault(
"Content-Type",
"application/json; charset=utf-8",
)
elif isinstance(body, str):
data = body.encode("utf-8")
else:
data = body
conn.request(method, path, body=data, headers=hdrs)
response = conn.getresponse()
payload = response.read()
text = payload.decode("utf-8") if payload else ""
return response.status, text, dict(response.getheaders())
finally:
conn.close()
def query_single_value(sql: str, params) -> int:
with sqlite3.connect(DB_PATH) as conn:
conn.row_factory = sqlite3.Row
row = conn.execute(sql, params).fetchone()
expect(row is not None, f"query returned no rows: {sql!r} {params!r}")
return int(row["id"])
def load_cases():
modules = []
for name in CASE_MODULES:
module = importlib.import_module(f"{CASE_PACKAGE}.{name}")
modules.append(module)
return modules
def run_tests() -> None:
context = TestContext()
for module in load_cases():
step_name = getattr(module, "TEST_NAME", module.__name__)
print(f"[step] {step_name}")
try:
module.run(context)
except Exception:
print(f"Test '{step_name}' run failed")
print(traceback.format_exc())
sys.exit(1)
print("[OK] All steps passed")
def main() -> None:
parser = argparse.ArgumentParser(description="SRAB automatic test suit.")
parser.add_argument(
"-s",
"--suppress",
default=False,
action="store_true",
help="Suppress server output"
)
args = parser.parse_args()
remove_database()
server = start_server(args.suppress)
try:
wait_for_server()
run_tests()
finally:
stop_server(server)
if __name__ == "__main__":
main()