Проверка на данном этапе

This commit is contained in:
vladslav064 committed 2026-11-12 08:02:52 +03:00
commit 626ac21b41
9 files changed
+1986

No files matched your search

+118
View File
@@ -0,0 +1,118 @@
import csv
import pytest
from src.extract import CSVFileError, FIELDS, read_records
from src.transform import MAX_INTEGER, clean_orders
def order(**changes):
return dict(order_id="1", order_date="2024-02-29", category="Книги",
quantity="3", unit_price="0,07") | changes
def process(tmp_path, *rows, header=FIELDS):
path = tmp_path / "orders.csv"
with path.open("w", encoding="utf-8-sig", newline="") as output:
writer = csv.writer(output)
writer.writerow(header)
for row in rows:
writer.writerow([row[name] for name in header] if isinstance(row, dict) else row)
before = path.read_bytes()
accepted, rejected = clean_orders(read_records(path))
assert path.read_bytes() == before
numbers = [item.record_number for item in accepted]
numbers.extend(item.source.record_number for item in rejected)
assert sorted(numbers) == list(range(1, len(rows) + 1))
return accepted, rejected
@pytest.mark.parametrize("value", ["2024-02-29", " 29.02.2024 "])
def test_normalization_and_exact_money(tmp_path, value):
accepted, rejected = process(tmp_path, order(order_date=value, category=" "), header=FIELDS[::-1])
assert not rejected
item, = accepted
assert (item.order_date, item.category) == ("2024-02-29", "Не указана")
assert (item.unit_price_kopecks, item.revenue_kopecks) == (7, 21)
@pytest.mark.parametrize(("name", "value"), [
("order_date", "2023-02-29"), ("order_date", "1.2.2024"), ("order_date", "20240229"),
("quantity", "1.2"), ("quantity", "0"), ("quantity", "-2"), ("order_id", "0"),
("unit_price", "NaN"), ("unit_price", "Infinity"), ("unit_price", "-Infinity"),
("unit_price", "0"), ("unit_price", "-1"), ("unit_price", "1,230"),
("unit_price", "1 000.20"), ("unit_price", "1e2"),
("unit_price", "92233720368547758.08"), ("quantity", str(MAX_INTEGER + 1)),
("order_id", "9" * 5000),
])
def test_invalid_field(tmp_path, name, value):
accepted, rejected = process(tmp_path, order(**{name: value}))
assert not accepted
reason, = rejected[0].reasons
assert reason.startswith(name + ":")
def test_sqlite_boundary_and_product_overflow(tmp_path):
price = "92233720368547758.07"
accepted, rejected = process(tmp_path, order(quantity="1", unit_price=price),
order(quantity="2", unit_price=price))
assert accepted[0].revenue_kopecks == MAX_INTEGER
assert rejected[0].reasons == ["revenue_kopecks: превышен предел INTEGER SQLite"]
def test_multiple_missing_fields_and_exact_repeat(tmp_path):
bad = order(order_id=" ", order_date="", quantity="", unit_price="")
_, rejected = process(tmp_path, bad, bad)
assert rejected[0].source.values == tuple(bad.values())
assert [s.split(":")[0] for s in rejected[0].reasons] == [
"order_id", "order_date", "quantity", "unit_price"
]
assert rejected[1].reasons == ["Точный дубль"]
def test_duplicate_stages_and_conflict(tmp_path):
first = order(category=" ТоВаРы\n ДЛЯ ДоМа ")
equivalent = first | {"order_date": "29.02.2024", "order_id": "001",
"category": "товары для дома", "unit_price": "0.07"}
conflict = order(order_id="2")
accepted, rejected = process(
tmp_path, first, first, equivalent, conflict, conflict | {"quantity": "03"},
conflict | {"quantity": "4"}, order(quantity="много")
)
assert [(item.record_number, item.category) for item in accepted] == [(1, "Товары для дома")]
assert [(r.source.record_number, r.reasons) for r in rejected] == [
(2, ["Точный дубль"]), (3, ["Нормализованный дубль"]),
(4, ["Конфликт order_id"]), (5, ["Конфликт order_id"]), (6, ["Конфликт order_id"]),
(7, ["quantity: ожидается положительное целое число"]),
]
def test_empty_short_and_long_records(tmp_path):
accepted, rejected = process(tmp_path, [], ["1"], list(order().values()) + ["лишнее"], order())
assert accepted[0].record_number == 4
assert [r.reasons for r in rejected] == [
["Пустая запись CSV"], ["Число полей: ожидалось 5, получено 1"],
["Число полей: ожидалось 5, получено 6"],
]
def test_header_only(tmp_path):
assert process(tmp_path) == ([], [])
@pytest.mark.parametrize("header", ["", "order_id,quantity", ",".join((*FIELDS, "extra")),
",".join((*FIELDS[:-1], "quantity"))])
def test_invalid_header(tmp_path, header):
path = tmp_path / "orders.csv"
path.write_text(header, encoding="utf-8")
with pytest.raises(CSVFileError, match="неверный заголовок"):
clean_orders(read_records(path))
def test_missing_or_damaged_file(tmp_path):
path = tmp_path / "orders.csv"
with pytest.raises(CSVFileError, match="файл не найден"):
clean_orders(read_records(path))
path.write_text(",".join(FIELDS) + '\n1,2024-02-29,"незакрытое поле', encoding="utf-8")
with pytest.raises(CSVFileError, match="ошибка структуры CSV"):
clean_orders(read_records(path))
+20
View File
@@ -0,0 +1,20 @@
import csv
from src.generate_data import generate_data
def test_record_count_and_header(tmp_path):
path = tmp_path / "orders.csv"
generate_data(path)
with path.open(encoding="utf-8", newline="") as source:
records = csv.reader(source)
assert next(records) == ["order_id", "order_date", "category", "quantity", "unit_price"]
assert sum(1 for _ in records) == 1500
def test_repeated_generation_is_byte_identical(tmp_path):
path = tmp_path / "orders.csv"
generate_data(path)
first_run = path.read_bytes()
generate_data(path)
assert path.read_bytes() == first_run