Проверка на данном этапе
This commit is contained in:
commit
626ac21b41
9 files changed
+1986
No files matched your search
@@ -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))
|
||||
@@ -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
|
||||
Reference in new issue
Block a user