init here
This commit is contained in:
0
autotest/__init__.py
Normal file
0
autotest/__init__.py
Normal file
0
autotest/cases/__init__.py
Normal file
0
autotest/cases/__init__.py
Normal file
25
autotest/cases/case_01_users_create_teacher.py
Normal file
25
autotest/cases/case_01_users_create_teacher.py
Normal file
@@ -0,0 +1,25 @@
|
||||
TEST_NAME = "users:create teacher"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {
|
||||
context.K_FIRST_NAME: context.teacher_first,
|
||||
context.K_LAST_NAME: context.teacher_last,
|
||||
context.K_MIDDLE_NAME: context.teacher_middle,
|
||||
context.K_EDUCATION: context.teacher_education,
|
||||
context.K_PASSWORD: context.teacher_password,
|
||||
context.K_CONFIRM_PASSWORD: context.teacher_password,
|
||||
}
|
||||
status, body, _ = context.send_request("POST", "/api/users", body=payload)
|
||||
context.expect(status == 201, f"expected 201, got {status}, body={body!r}")
|
||||
user_id = context.query_single_value(
|
||||
"SELECT id FROM users WHERE username = ?",
|
||||
(context.teacher_username,),
|
||||
)
|
||||
context.teacher_user_id = user_id
|
||||
teacher_id = context.query_single_value(
|
||||
"SELECT id FROM teachers WHERE user_id = ?",
|
||||
(user_id,),
|
||||
)
|
||||
context.expect(teacher_id > 0, "teacher id must be positive")
|
||||
context.teacher_id = teacher_id
|
||||
18
autotest/cases/case_02_users_create_mismatch.py
Normal file
18
autotest/cases/case_02_users_create_mismatch.py
Normal file
@@ -0,0 +1,18 @@
|
||||
TEST_NAME = "users:create mismatch"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {
|
||||
context.K_FIRST_NAME: "Bob",
|
||||
context.K_LAST_NAME: "Doe",
|
||||
context.K_MIDDLE_NAME: "Ray",
|
||||
context.K_EDUCATION: "History",
|
||||
context.K_PASSWORD: "OnePass",
|
||||
context.K_CONFIRM_PASSWORD: "OtherPass",
|
||||
}
|
||||
status, body, _ = context.send_request("POST", "/api/users", body=payload)
|
||||
context.expect(status == 400, f"expected 400, got {status}")
|
||||
context.expect(
|
||||
context.MESSAGE_PASSWORD_MISMATCH in body,
|
||||
"expected mismatch message",
|
||||
)
|
||||
18
autotest/cases/case_03_users_login_success.py
Normal file
18
autotest/cases/case_03_users_login_success.py
Normal file
@@ -0,0 +1,18 @@
|
||||
TEST_NAME = "users:login success"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {
|
||||
context.K_USERNAME: context.teacher_username,
|
||||
context.K_PASSWORD: context.teacher_password,
|
||||
}
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/users/login",
|
||||
body=payload,
|
||||
)
|
||||
context.expect(status == 200, f"expected 200, got {status}")
|
||||
context.expect(
|
||||
body == context.MESSAGE_LOGIN_OK,
|
||||
f"unexpected login body: {body!r}",
|
||||
)
|
||||
14
autotest/cases/case_04_users_login_failure.py
Normal file
14
autotest/cases/case_04_users_login_failure.py
Normal file
@@ -0,0 +1,14 @@
|
||||
TEST_NAME = "users:login failure"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {
|
||||
context.K_USERNAME: context.teacher_username,
|
||||
context.K_PASSWORD: "WrongPass",
|
||||
}
|
||||
status, _, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/users/login",
|
||||
body=payload,
|
||||
)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
16
autotest/cases/case_05_users_change_password.py
Normal file
16
autotest/cases/case_05_users_change_password.py
Normal file
@@ -0,0 +1,16 @@
|
||||
TEST_NAME = "users:change password"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {context.K_NEW_PASSWORD: context.teacher_password_new}
|
||||
status, body, _ = context.send_request(
|
||||
"PUT",
|
||||
"/api/users/password",
|
||||
body=payload,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 204, f"expected 204, got {status}, body={body!r}")
|
||||
context.teacher_password = context.teacher_password_new
|
||||
18
autotest/cases/case_06_users_login_with_new_password.py
Normal file
18
autotest/cases/case_06_users_login_with_new_password.py
Normal file
@@ -0,0 +1,18 @@
|
||||
TEST_NAME = "users:login with new password"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {
|
||||
context.K_USERNAME: context.teacher_username,
|
||||
context.K_PASSWORD: context.teacher_password,
|
||||
}
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/users/login",
|
||||
body=payload,
|
||||
)
|
||||
context.expect(status == 200, f"expected 200, got {status}")
|
||||
context.expect(
|
||||
body == context.MESSAGE_LOGIN_OK,
|
||||
f"unexpected login body: {body!r}",
|
||||
)
|
||||
9
autotest/cases/case_07_teachers_auth_required.py
Normal file
9
autotest/cases/case_07_teachers_auth_required.py
Normal file
@@ -0,0 +1,9 @@
|
||||
TEST_NAME = "teachers:auth required"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
status, _, _ = context.send_request("GET", "/api/classes")
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
payload = {context.K_CLASS_NUMBER: 5, context.K_CLASS_LETTER: "A"}
|
||||
status, _, _ = context.send_request("POST", "/api/classes", body=payload)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
31
autotest/cases/case_08_teachers_create_class.py
Normal file
31
autotest/cases/case_08_teachers_create_class.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "teachers:create class"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {context.K_CLASS_NUMBER: 5, context.K_CLASS_LETTER: "A"}
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/classes",
|
||||
body=payload,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 201, f"expected 201, got {status}, body={body!r}")
|
||||
data = json.loads(body)
|
||||
context.class_id = int(data[context.K_IDENTIFIER])
|
||||
context.expect(
|
||||
data[context.K_CLASS_NUMBER] == payload[context.K_CLASS_NUMBER],
|
||||
"class number mismatch",
|
||||
)
|
||||
context.expect(
|
||||
data[context.K_CLASS_LETTER] == payload[context.K_CLASS_LETTER],
|
||||
"class letter mismatch",
|
||||
)
|
||||
context.expect(
|
||||
int(data[context.K_CREATOR]) > 0,
|
||||
"creator id must be positive",
|
||||
)
|
||||
24
autotest/cases/case_09_teachers_list_classes.py
Normal file
24
autotest/cases/case_09_teachers_list_classes.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "teachers:list classes"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
"/api/classes",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200, got {status}")
|
||||
data = json.loads(body)
|
||||
entries = data.get(context.K_CLASSES, [])
|
||||
context.expect(
|
||||
any(
|
||||
int(item[context.K_IDENTIFIER]) == context.class_id
|
||||
for item in entries
|
||||
),
|
||||
"class not listed",
|
||||
)
|
||||
19
autotest/cases/case_10_teachers_create_extra_class.py
Normal file
19
autotest/cases/case_10_teachers_create_extra_class.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "teachers:create extra class"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {context.K_CLASS_NUMBER: 6, context.K_CLASS_LETTER: "B"}
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/classes",
|
||||
body=payload,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 201, f"expected 201, got {status}")
|
||||
data = json.loads(body)
|
||||
context.class_two_id = int(data[context.K_IDENTIFIER])
|
||||
27
autotest/cases/case_11_teachers_delete_extra_class.py
Normal file
27
autotest/cases/case_11_teachers_delete_extra_class.py
Normal file
@@ -0,0 +1,27 @@
|
||||
TEST_NAME = "teachers:delete extra class"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/classes/{context.class_two_id}"
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 204, f"expected 204, got {status}")
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 404,
|
||||
f"expected 404 on repeated delete, got {status}",
|
||||
)
|
||||
context.class_two_id = None
|
||||
33
autotest/cases/case_12_students_create_student.py
Normal file
33
autotest/cases/case_12_students_create_student.py
Normal file
@@ -0,0 +1,33 @@
|
||||
TEST_NAME = "students:create student"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
status, _, _ = context.send_request("POST", "/api/students", body={})
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
payload = {
|
||||
context.K_FIRST_NAME: "Charlie",
|
||||
context.K_LAST_NAME: "Stone",
|
||||
context.K_MIDDLE_NAME: "Lee",
|
||||
context.K_PASSWORD: "Student42",
|
||||
context.K_CONFIRM_PASSWORD: "Student42",
|
||||
context.K_SNILS: "00011122233",
|
||||
context.K_PASSPORT: "AB1234567",
|
||||
}
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/students",
|
||||
body=payload,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 201, f"expected 201, got {status}, body={body!r}")
|
||||
context.student_one_id = context.query_single_value(
|
||||
"SELECT id FROM students WHERE snils = ?",
|
||||
(payload[context.K_SNILS],),
|
||||
)
|
||||
context.student_one_username = (
|
||||
f"{payload[context.K_FIRST_NAME]}.{payload[context.K_LAST_NAME]}"
|
||||
)
|
||||
context.student_one_password = payload[context.K_PASSWORD]
|
||||
31
autotest/cases/case_13_students_create_second_student.py
Normal file
31
autotest/cases/case_13_students_create_second_student.py
Normal file
@@ -0,0 +1,31 @@
|
||||
TEST_NAME = "students:create second student"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
payload = {
|
||||
context.K_FIRST_NAME: "Daisy",
|
||||
context.K_LAST_NAME: "King",
|
||||
context.K_MIDDLE_NAME: "May",
|
||||
context.K_PASSWORD: "Student84",
|
||||
context.K_CONFIRM_PASSWORD: "Student84",
|
||||
context.K_SNILS: "99988877766",
|
||||
context.K_PASSPORT: "CD7654321",
|
||||
}
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
"/api/students",
|
||||
body=payload,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 201, f"expected 201, got {status}, body={body!r}")
|
||||
context.student_two_id = context.query_single_value(
|
||||
"SELECT id FROM students WHERE snils = ?",
|
||||
(payload[context.K_SNILS],),
|
||||
)
|
||||
context.student_two_username = (
|
||||
f"{payload[context.K_FIRST_NAME]}.{payload[context.K_LAST_NAME]}"
|
||||
)
|
||||
context.student_two_password = payload[context.K_PASSWORD]
|
||||
32
autotest/cases/case_14_teachers_add_student_to_class.py
Normal file
32
autotest/cases/case_14_teachers_add_student_to_class.py
Normal file
@@ -0,0 +1,32 @@
|
||||
TEST_NAME = "teachers:add student to class"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/classes/{context.class_id}/students/{context.student_two_id}"
|
||||
status, _, _ = context.send_request("POST", path)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
status, _, _ = context.send_request(
|
||||
"POST",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 201, f"expected 201, got {status}")
|
||||
invalid_path = (
|
||||
f"/api/classes/{context.class_id + 999}/students/"
|
||||
f"{context.student_two_id}"
|
||||
)
|
||||
status, _, _ = context.send_request(
|
||||
"POST",
|
||||
invalid_path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 403,
|
||||
f"expected 403 for foreign class, got {status}",
|
||||
)
|
||||
31
autotest/cases/case_15_students_list.py
Normal file
31
autotest/cases/case_15_students_list.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "students:list"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
status, _, _ = context.send_request("GET", "/api/students")
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
"/api/students",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200, got {status}")
|
||||
|
||||
data = json.loads(body)
|
||||
students = data.get("ученики", [])
|
||||
ids = {int(item[context.K_IDENTIFIER]) for item in students}
|
||||
|
||||
context.expect(
|
||||
context.student_one_id in ids,
|
||||
"first student missing from list",
|
||||
)
|
||||
context.expect(
|
||||
context.student_two_id in ids,
|
||||
"second student missing from list",
|
||||
)
|
||||
48
autotest/cases/case_16_students_get.py
Normal file
48
autotest/cases/case_16_students_get.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "students:get one"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/students/{context.student_one_id}"
|
||||
status, _, _ = context.send_request("GET", path)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200, got {status}")
|
||||
|
||||
data = json.loads(body)
|
||||
context.expect(
|
||||
int(data[context.K_IDENTIFIER]) == context.student_one_id,
|
||||
"unexpected student id",
|
||||
)
|
||||
context.expect(
|
||||
data.get(context.K_FIRST_NAME),
|
||||
"missing student first name",
|
||||
)
|
||||
context.expect(
|
||||
data.get(context.K_LAST_NAME),
|
||||
"missing student last name",
|
||||
)
|
||||
context.expect(
|
||||
data.get(context.K_USERNAME),
|
||||
"missing student username",
|
||||
)
|
||||
|
||||
missing_path = f"/api/students/{context.student_two_id + 999}"
|
||||
status, _, _ = context.send_request(
|
||||
"GET",
|
||||
missing_path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 404, f"expected 404, got {status}")
|
||||
39
autotest/cases/case_17_students_delete.py
Normal file
39
autotest/cases/case_17_students_delete.py
Normal file
@@ -0,0 +1,39 @@
|
||||
TEST_NAME = "students:delete"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/students/{context.student_one_id}"
|
||||
status, _, _ = context.send_request("DELETE", path)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
f"/api/students/{context.student_one_id + 999}",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 404, f"expected 404, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 204, f"expected 204, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"GET",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 404, f"expected 404 after delete, got {status}")
|
||||
|
||||
context.student_one_id = None
|
||||
86
autotest/cases/case_18_lessons_teacher_add.py
Normal file
86
autotest/cases/case_18_lessons_teacher_add.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "lessons:create"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/classes/{context.class_id}/lessons"
|
||||
payload_primary = {
|
||||
"дата": "2025-09-01",
|
||||
"название": "Алгебра",
|
||||
"домашнее задание": "Упражнения 1-3",
|
||||
}
|
||||
|
||||
status, _, _ = context.send_request("POST", path, body=payload_primary)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"POST",
|
||||
f"/api/classes/{context.class_id + 999}/lessons",
|
||||
body=payload_primary,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 403,
|
||||
f"expected 403 for foreign class, got {status}",
|
||||
)
|
||||
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
path,
|
||||
body=payload_primary,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 201, f"expected 201, got {status}, body={body!r}")
|
||||
|
||||
data = json.loads(body)
|
||||
context.lesson_first_id = int(data[context.K_IDENTIFIER])
|
||||
context.lesson_first_date = payload_primary["дата"]
|
||||
context.lesson_first_title = payload_primary["название"]
|
||||
context.expect(
|
||||
int(data["идентификатор класса"]) == context.class_id,
|
||||
"lesson class mismatch",
|
||||
)
|
||||
context.expect(
|
||||
data["название"] == payload_primary["название"],
|
||||
"lesson title mismatch",
|
||||
)
|
||||
context.expect(
|
||||
data["дата"] == payload_primary["дата"],
|
||||
"lesson date mismatch",
|
||||
)
|
||||
|
||||
payload_secondary = {
|
||||
"дата урока": "2025-09-02",
|
||||
"тема": "Геометрия",
|
||||
"домашка": "Читать параграф 4",
|
||||
}
|
||||
|
||||
status, body, _ = context.send_request(
|
||||
"POST",
|
||||
path,
|
||||
body=payload_secondary,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 201,
|
||||
f"expected 201 for second lesson, got {status}",
|
||||
)
|
||||
|
||||
data = json.loads(body)
|
||||
context.lesson_second_id = int(data[context.K_IDENTIFIER])
|
||||
context.lesson_second_date = payload_secondary["дата урока"]
|
||||
context.lesson_second_title = payload_secondary["тема"]
|
||||
context.expect(
|
||||
data["домашнее задание"] == payload_secondary["домашка"],
|
||||
"secondary homework mismatch",
|
||||
)
|
||||
80
autotest/cases/case_19_lessons_list.py
Normal file
80
autotest/cases/case_19_lessons_list.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "lessons:list"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
base_path = f"/api/classes/{context.class_id}/lessons"
|
||||
status, _, _ = context.send_request("GET", base_path)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
base_path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200, got {status}")
|
||||
|
||||
data = json.loads(body)
|
||||
lessons = data.get("уроки", [])
|
||||
context.expect(len(lessons) >= 2, "expected at least two lessons")
|
||||
|
||||
def find_lesson(entry_id: int):
|
||||
for item in lessons:
|
||||
if int(item[context.K_IDENTIFIER]) == entry_id:
|
||||
return item
|
||||
return None
|
||||
|
||||
first_entry = find_lesson(context.lesson_first_id)
|
||||
context.expect(first_entry is not None, "first lesson missing")
|
||||
context.expect(
|
||||
first_entry.get("название") == context.lesson_first_title,
|
||||
"first lesson title mismatch",
|
||||
)
|
||||
|
||||
filter_path = (
|
||||
f"/api/classes/{context.class_id}/lessons/date/"
|
||||
f"{context.lesson_first_date}"
|
||||
)
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
filter_path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200 for filter, got {status}")
|
||||
|
||||
filtered = json.loads(body).get("уроки", [])
|
||||
context.expect(
|
||||
len(filtered) == 1,
|
||||
f"expected single lesson in filter, got {len(filtered)}",
|
||||
)
|
||||
context.expect(
|
||||
int(filtered[0][context.K_IDENTIFIER]) == context.lesson_first_id,
|
||||
"filter returned unexpected lesson",
|
||||
)
|
||||
|
||||
student_headers = context.make_auth(
|
||||
context.student_two_username,
|
||||
context.student_two_password,
|
||||
)
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
base_path,
|
||||
headers=student_headers,
|
||||
)
|
||||
context.expect(status == 200, f"expected 200 for student, got {status}")
|
||||
|
||||
student_view = json.loads(body).get("уроки", [])
|
||||
context.expect(
|
||||
any(
|
||||
int(item[context.K_IDENTIFIER]) == context.lesson_second_id
|
||||
for item in student_view
|
||||
),
|
||||
"student view missing lesson",
|
||||
)
|
||||
81
autotest/cases/case_20_lessons_teacher_delete.py
Normal file
81
autotest/cases/case_20_lessons_teacher_delete.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import json
|
||||
|
||||
TEST_NAME = "lessons:delete"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/classes/{context.class_id}/lessons/{context.lesson_first_id}"
|
||||
status, _, _ = context.send_request("DELETE", path)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
f"/api/classes/{context.class_id + 999}/lessons/"
|
||||
f"{context.lesson_first_id}",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 403,
|
||||
f"expected 403 for foreign class, got {status}",
|
||||
)
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 204, f"expected 204, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 404, f"expected 404 after delete, got {status}")
|
||||
|
||||
filter_path = (
|
||||
f"/api/classes/{context.class_id}/lessons/date/"
|
||||
f"{context.lesson_first_date}"
|
||||
)
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
filter_path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200 for filter, got {status}")
|
||||
|
||||
filtered = json.loads(body).get("уроки", [])
|
||||
context.expect(len(filtered) == 0, "filter should be empty after delete")
|
||||
|
||||
status, body, _ = context.send_request(
|
||||
"GET",
|
||||
f"/api/classes/{context.class_id}/lessons",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 200, f"expected 200 for list, got {status}")
|
||||
|
||||
remaining = json.loads(body).get("уроки", [])
|
||||
context.expect(
|
||||
any(
|
||||
int(item[context.K_IDENTIFIER]) == context.lesson_second_id
|
||||
for item in remaining
|
||||
),
|
||||
"second lesson missing after delete",
|
||||
)
|
||||
|
||||
context.lesson_first_id = None
|
||||
55
autotest/cases/case_21_teachers_remove_student_from_class.py
Normal file
55
autotest/cases/case_21_teachers_remove_student_from_class.py
Normal file
@@ -0,0 +1,55 @@
|
||||
TEST_NAME = "teachers:remove student from class"
|
||||
|
||||
|
||||
def run(context) -> None:
|
||||
path = f"/api/classes/{context.class_id}/students/{context.student_two_id}"
|
||||
status, _, _ = context.send_request("DELETE", path)
|
||||
context.expect(status == 401, f"expected 401, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
f"/api/classes/{context.class_id + 999}/students/"
|
||||
f"{context.student_two_id}",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 403,
|
||||
f"expected 403 for foreign class, got {status}",
|
||||
)
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
f"/api/classes/{context.class_id}/students/"
|
||||
f"{context.student_two_id + 999}",
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(
|
||||
status == 404,
|
||||
f"expected 404 for foreign student, got {status}",
|
||||
)
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 204, f"expected 204, got {status}")
|
||||
|
||||
status, _, _ = context.send_request(
|
||||
"DELETE",
|
||||
path,
|
||||
headers=context.make_auth(
|
||||
context.teacher_username,
|
||||
context.teacher_password,
|
||||
),
|
||||
)
|
||||
context.expect(status == 404, f"expected 404 after removal, got {status}")
|
||||
255
autotest/main.py
Normal file
255
autotest/main.py
Normal file
@@ -0,0 +1,255 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user