הערה (קובץ קוד)
קובץ זה הוא קובץ קוד מסוג PYTHON.
"""
Black-box test suite for HW5 - MatamazonSystem (matamazon.py).
Built ONLY from:
* hw5.pdf (the assignment specification)
* the example files under tests/ (matamazon_log*.txt, matamazon_system*.txt,
out_*.txt) that ship with the assignment
This file does NOT look at any student's matamazon.py implementation, so it
should be a fair, independent check of spec compliance.
HOW TO RUN
----------
Put this file in the same directory as your matamazon.py (or adjust
MATAMAZON_PATH below) and run:
pip install pytest --break-system-packages # if needed
pytest test_matamazon.py -v
Some tests are grouped in classes; run a single class with e.g.:
pytest test_matamazon.py::TestSearchProducts -v
NOTES ON SPEC AMBIGUITIES
--------------------------
A few points in the spec are genuinely ambiguous or under-specified. Where
that happens the test is written to match the *most literal* reading of the
spec text, and a comment explains the ambiguity so you can judge for
yourself whether your design choice is still defensible:
1. place_order(): the spec lists "The product does not exist in the
system" as a *return string*, but also separately says invalid ids
must raise InvalidIdException. We treat a missing PRODUCT as the
return-string case (explicitly listed) and a missing CUSTOMER as the
exception case (not given its own return string).
2. remove_object() for an Order: the spec says the ordered quantity must
be "returned to the product" (i.e. stock must be restored). It does
NOT say the method's return value must be used for this - the method
itself should mutate state. We test the state change (stock goes back
up), not any particular return value.
3. The "general error" message ("The matamazon script has encountered an
error") - the spec doesn't explicitly state stdout vs stderr, but
tests/test2.txt (a captured-stdout comparison file, per section 4.6 of
the spec) contains exactly that message, implying it belongs on
stdout. The usage-error message IS explicitly specified to go to
stderr. We test accordingly.
4. Multi-word log fields use "_" as a separator (spec section 4.4) and
must be converted to spaces in the constructed objects - confirmed by
tests/out_matamazon_system0.txt ("Kelly Kell" vs. log's "Kelly_Kell").
"""
import io
import json
import os
import subprocess
import sys
import tempfile
import pytest
# --------------------------------------------------------------------------
# Locate and import the student's matamazon.py
# --------------------------------------------------------------------------
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
MATAMAZON_PATH = os.path.join(THIS_DIR, "matamazon.py")
if THIS_DIR not in sys.path:
sys.path.insert(0, THIS_DIR)
try:
import matamazon
except ImportError as e: # pragma: no cover
raise ImportError(
"Could not import matamazon.py. Place test_matamazon.py in the same "
"directory as matamazon.py, or edit MATAMAZON_PATH / sys.path above."
) from e
# --------------------------------------------------------------------------
# Small helpers
# --------------------------------------------------------------------------
def run_script(args, cwd=None):
"""Run `python3 matamazon.py <args>` as a subprocess and capture output."""
cmd = [sys.executable, MATAMAZON_PATH] + args
return subprocess.run(cmd, capture_output=True, text=True, cwd=cwd, timeout=30)
def write(path, content):
with open(path, "w") as f:
f.write(content)
# ==========================================================================
# 4.1.1 / 4.1.2 Customer & Supplier
# ==========================================================================
class TestCustomer:
def test_valid_creation_and_str(self):
c = matamazon.Customer(42, "Daniel Elgarici", "Karmiel", "123 Main Street")
assert str(c) == "Customer(id=42, name='Daniel Elgarici', city='Karmiel', address='123 Main Street')"
def test_zero_id_is_valid_boundary(self):
c = matamazon.Customer(0, "A", "B", "C")
assert c.id == 0
def test_negative_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Customer(-1, "A", "B", "C")
class TestSupplier:
def test_valid_creation_and_str(self):
s = matamazon.Supplier(42, "Yinon Goldshtein", "Haifa", "32 David Rose Street")
assert str(s) == "Supplier(id=42, name='Yinon Goldshtein', city='Haifa', address='32 David Rose Street')"
def test_negative_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Supplier(-5, "A", "B", "C")
# ==========================================================================
# 4.1.3 Product
# ==========================================================================
class TestProduct:
def test_valid_creation_and_str(self):
p = matamazon.Product(101, "Harry Potter Cushion", 29.99, 42, 555)
assert str(p) == "Product(id=101, name='Harry Potter Cushion', price=29.99, supplier_id=42, quantity=555)"
def test_negative_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Product(-1, "A", 1.0, 1, 1)
def test_negative_supplier_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Product(1, "A", 1.0, -1, 1)
def test_negative_price_raises(self):
with pytest.raises(matamazon.InvalidPriceException):
matamazon.Product(1, "A", -0.01, 1, 1)
def test_zero_price_is_valid_boundary(self):
p = matamazon.Product(1, "A", 0.0, 1, 1)
assert p.price == 0.0
def test_zero_quantity_is_valid_boundary(self):
p = matamazon.Product(1, "A", 1.0, 1, 0)
assert p.quantity == 0
# ==========================================================================
# 4.1.4 Order
# ==========================================================================
class TestOrder:
def test_valid_creation_and_str(self):
o = matamazon.Order(1, 42, 101, 10, 299.9)
assert str(o) == "Order(id=1, customer_id=42, product_id=101, quantity=10, total_price=299.9)"
def test_negative_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Order(-1, 1, 1, 1, 1.0)
def test_negative_customer_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Order(1, -1, 1, 1, 1.0)
def test_negative_product_id_raises(self):
with pytest.raises(matamazon.InvalidIdException):
matamazon.Order(1, 1, -1, 1, 1.0)
def test_negative_total_price_raises(self):
with pytest.raises(matamazon.InvalidPriceException):
matamazon.Order(1, 1, 1, 1, -0.01)
# ==========================================================================
# 4.2 MatamazonSystem - construction & register_entity
# ==========================================================================
class TestConstructorAndRegister:
def test_parameterless_constructor(self):
system = matamazon.MatamazonSystem()
assert system is not None
def test_register_customer_and_supplier(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "A", "B", "C"), True)
system.register_entity(matamazon.Supplier(2, "D", "E", "F"), False)
# sanity check via export
buf = _export_system_lines(system)
assert any(line.startswith("Customer(id=1") for line in buf)
assert any(line.startswith("Supplier(id=2") for line in buf)
def test_duplicate_customer_id_raises(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "A", "B", "C"), True)
with pytest.raises(matamazon.InvalidIdException):
system.register_entity(matamazon.Customer(1, "X", "Y", "Z"), True)
def test_duplicate_supplier_id_raises(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Supplier(1, "A", "B", "C"), False)
with pytest.raises(matamazon.InvalidIdException):
system.register_entity(matamazon.Supplier(1, "X", "Y", "Z"), False)
def test_customer_and_supplier_id_namespaces_are_independent(self):
"""Spec explicitly states customer ids and supplier ids are
independent - i.e. a customer and a supplier MAY share the same id."""
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(42, "A", "B", "C"), True)
# Must NOT raise:
system.register_entity(matamazon.Supplier(42, "D", "E", "F"), False)
def _export_system_lines(system):
with tempfile.TemporaryDirectory() as d:
path = os.path.join(d, "out.txt")
system.export_system_to_file(path)
with open(path) as f:
return [line.strip("\n") for line in f if line.strip("\n")]
# ==========================================================================
# 4.2 add_or_update_product
# ==========================================================================
class TestAddOrUpdateProduct:
def _system_with_supplier(self, supplier_id=34):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Supplier(supplier_id, "S", "City", "Addr"), False)
return system
def test_add_new_product_with_existing_supplier_succeeds(self):
system = self._system_with_supplier()
system.add_or_update_product(matamazon.Product(1, "paper", 1.0, 34, 10))
lines = _export_system_lines(system)
assert any(line.startswith("Product(id=1") for line in lines)
def test_add_product_with_nonexistent_supplier_raises(self):
system = matamazon.MatamazonSystem()
with pytest.raises(matamazon.InvalidIdException):
system.add_or_update_product(matamazon.Product(1, "paper", 1.0, 999, 10))
def test_update_existing_product_same_supplier_updates_fields(self):
system = self._system_with_supplier()
system.add_or_update_product(matamazon.Product(1, "paper", 1.0, 34, 10))
system.add_or_update_product(matamazon.Product(1, "new paper", 2.0, 34, 20))
lines = _export_system_lines(system)
product_lines = [l for l in lines if l.startswith("Product(id=1")]
assert len(product_lines) == 1
assert "new paper" in product_lines[0]
assert "price=2.0" in product_lines[0]
assert "quantity=20" in product_lines[0]
def test_update_existing_product_different_supplier_raises_and_leaves_original(self):
system = self._system_with_supplier(34)
system.register_entity(matamazon.Supplier(99, "S2", "City2", "Addr2"), False)
system.add_or_update_product(matamazon.Product(1, "paper", 1.0, 34, 10))
with pytest.raises(matamazon.InvalidIdException):
system.add_or_update_product(matamazon.Product(1, "paper2", 5.0, 99, 999))
lines = _export_system_lines(system)
product_lines = [l for l in lines if l.startswith("Product(id=1")]
assert len(product_lines) == 1
# original untouched
assert "supplier_id=34" in product_lines[0]
assert "quantity=10" in product_lines[0]
# ==========================================================================
# 4.2 place_order
# ==========================================================================
class TestPlaceOrder:
def _system(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "Cust", "City", "Addr"), True)
system.register_entity(matamazon.Supplier(1, "Sup", "OriginCity", "Addr"), False)
system.add_or_update_product(matamazon.Product(1, "widget", 10.0, 1, 5))
return system
def test_successful_order_default_quantity_is_one(self):
system = self._system()
msg = system.place_order(1, 1)
assert msg == "The order has been accepted in the system"
lines = _export_system_lines(system)
product_line = [l for l in lines if l.startswith("Product(id=1")][0]
assert "quantity=4" in product_line # 5 - 1
def test_successful_order_explicit_quantity(self):
system = self._system()
msg = system.place_order(1, 1, 3)
assert msg == "The order has been accepted in the system"
lines = _export_system_lines(system)
product_line = [l for l in lines if l.startswith("Product(id=1")][0]
assert "quantity=2" in product_line # 5 - 3
def test_order_id_starts_at_one_and_increments(self):
system = self._system()
system.add_or_update_product(matamazon.Product(1, "widget", 10.0, 1, 100))
buf = io.StringIO()
system.place_order(1, 1, 1)
system.place_order(1, 1, 1)
system.export_orders(buf)
data = json.loads(buf.getvalue())
all_orders = sum(data.values(), [])
assert any("Order(id=1," in o for o in all_orders)
assert any("Order(id=2," in o for o in all_orders)
def test_order_nonexistent_product_returns_message_not_exception(self):
system = self._system()
msg = system.place_order(1, 999)
assert msg == "The product does not exist in the system"
def test_order_quantity_exceeds_stock_returns_message_and_stock_unchanged(self):
system = self._system()
msg = system.place_order(1, 1, 999)
assert msg == "The quantity requested for this product is greater than the quantity in stock"
lines = _export_system_lines(system)
product_line = [l for l in lines if l.startswith("Product(id=1")][0]
assert "quantity=5" in product_line # unchanged
def test_order_quantity_equal_to_stock_succeeds(self):
"""Spec: 'quantity greater OR EQUAL' to requested is a success case."""
system = self._system()
msg = system.place_order(1, 1, 5)
assert msg == "The order has been accepted in the system"
def test_order_nonexistent_customer_raises(self):
system = self._system()
with pytest.raises(matamazon.InvalidIdException):
system.place_order(999, 1, 1)
# ==========================================================================
# 4.2 remove_object
# ==========================================================================
class TestRemoveObject:
def _system_with_order(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "Cust", "City", "Addr"), True)
system.register_entity(matamazon.Supplier(1, "Sup", "OriginCity", "Addr"), False)
system.add_or_update_product(matamazon.Product(1, "widget", 10.0, 1, 5))
system.place_order(1, 1, 3) # stock now 2, order id 1
return system
def test_negative_id_raises(self):
system = matamazon.MatamazonSystem()
with pytest.raises(matamazon.InvalidIdException):
system.remove_object(-1, "Customer")
def test_remove_nonexistent_id_raises(self):
system = matamazon.MatamazonSystem()
with pytest.raises(matamazon.InvalidIdException):
system.remove_object(1, "Customer")
@pytest.mark.parametrize("class_type", ["Customer", "customer", "CUSTOMER", " Customer ", "CuStOmEr"])
def test_class_type_case_and_whitespace_insensitive(self, class_type):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "A", "B", "C"), True)
system.remove_object(1, class_type) # should not raise
lines = _export_system_lines(system)
assert not any(l.startswith("Customer(id=1") for l in lines)
def test_remove_customer_with_dependent_order_raises_and_nothing_removed(self):
system = self._system_with_order()
with pytest.raises(matamazon.InvalidIdException):
system.remove_object(1, "Customer")
lines = _export_system_lines(system)
assert any(l.startswith("Customer(id=1") for l in lines)
def test_remove_supplier_with_dependent_order_raises(self):
system = self._system_with_order()
with pytest.raises(matamazon.InvalidIdException):
system.remove_object(1, "Supplier")
def test_remove_product_with_dependent_order_raises(self):
system = self._system_with_order()
with pytest.raises(matamazon.InvalidIdException):
system.remove_object(1, "Product")
def test_remove_order_restores_product_stock(self):
system = self._system_with_order() # stock is 5-3=2
system.remove_object(1, "Order")
lines = _export_system_lines(system)
product_line = [l for l in lines if l.startswith("Product(id=1")][0]
assert "quantity=5" in product_line # stock restored
def test_remove_order_then_customer_succeeds(self):
system = self._system_with_order()
system.remove_object(1, "Order")
system.remove_object(1, "Customer") # should not raise now
lines = _export_system_lines(system)
assert not any(l.startswith("Customer(id=1") for l in lines)
def test_remove_product_without_dependent_orders_succeeds(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Supplier(1, "S", "C", "A"), False)
system.add_or_update_product(matamazon.Product(1, "widget", 1.0, 1, 1))
system.remove_object(1, "Product")
lines = _export_system_lines(system)
assert not any(l.startswith("Product(id=1") for l in lines)
# ==========================================================================
# 4.2 search_products
# ==========================================================================
class TestSearchProducts:
def _system(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Supplier(1, "S", "C", "A"), False)
system.add_or_update_product(matamazon.Product(1, "plane paper", 0.1, 1, 4500))
system.add_or_update_product(matamazon.Product(2, "green paper", 0.5, 1, 499))
system.add_or_update_product(matamazon.Product(3, "red paper", 0.5, 1, 50))
system.add_or_update_product(matamazon.Product(4, "sold out paper", 0.05, 1, 0))
return system
def test_no_match_returns_empty_list(self):
system = self._system()
assert system.search_products("nonexistent") == []
def test_substring_match(self):
system = self._system()
results = system.search_products("paper")
ids = [p.id for p in results]
assert set(ids) == {1, 2, 3} # id 4 excluded (quantity 0)
def test_results_sorted_ascending_by_price(self):
system = self._system()
results = system.search_products("paper")
prices = [p.price for p in results]
assert prices == sorted(prices)
assert results[0].id == 1 # cheapest, price 0.1
def test_zero_quantity_products_excluded(self):
system = self._system()
results = system.search_products("sold")
assert results == []
def test_max_price_filter_is_inclusive(self):
system = self._system()
results = system.search_products("paper", 0.5)
assert {p.id for p in results} == {1, 2, 3}
results_strict = system.search_products("paper", 0.1)
assert {p.id for p in results_strict} == {1}
def test_max_price_none_returns_all_matches(self):
system = self._system()
results = system.search_products("paper", None)
assert {p.id for p in results} == {1, 2, 3}
# ==========================================================================
# 4.2 export_system_to_file
# ==========================================================================
class TestExportSystemToFile:
def test_exports_customers_suppliers_products_excludes_orders(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "Cust", "City", "Addr"), True)
system.register_entity(matamazon.Supplier(1, "Sup", "OCity", "OAddr"), False)
system.add_or_update_product(matamazon.Product(1, "widget", 1.0, 1, 10))
system.place_order(1, 1, 1)
lines = _export_system_lines(system)
assert any(l.startswith("Customer(id=1") for l in lines)
assert any(l.startswith("Supplier(id=1") for l in lines)
assert any(l.startswith("Product(id=1") for l in lines)
assert not any(l.startswith("Order(") for l in lines)
def test_propagates_error_on_bad_path(self):
system = matamazon.MatamazonSystem()
with pytest.raises(OSError):
system.export_system_to_file("/this/path/does/not/exist/out.txt")
# ==========================================================================
# 4.2 export_orders
# ==========================================================================
class TestExportOrders:
def _system(self):
system = matamazon.MatamazonSystem()
system.register_entity(matamazon.Customer(1, "Cust", "City", "Addr"), True)
system.register_entity(matamazon.Supplier(1, "Sup", "Sde Boker", "Addr"), False)
system.register_entity(matamazon.Supplier(2, "Sup2", "Haifa", "Addr2"), False)
system.add_or_update_product(matamazon.Product(1, "widget", 10.0, 1, 100))
system.add_or_update_product(matamazon.Product(2, "gadget", 5.0, 2, 100))
return system
def test_orders_grouped_by_origin_city(self):
system = self._system()
system.place_order(1, 1, 1)
system.place_order(1, 2, 1)
buf = io.StringIO()
system.export_orders(buf)
data = json.loads(buf.getvalue())
assert "Sde Boker" in data
assert "Haifa" in data
assert len(data["Sde Boker"]) == 1
assert len(data["Haifa"]) == 1
def test_multiple_orders_same_city_grouped_in_one_list(self):
system = self._system()
system.place_order(1, 1, 1)
system.place_order(1, 1, 1)
buf = io.StringIO()
system.export_orders(buf)
data = json.loads(buf.getvalue())
assert len(data["Sde Boker"]) == 2
def test_no_orders_produces_valid_json(self):
system = self._system()
buf = io.StringIO()
system.export_orders(buf)
data = json.loads(buf.getvalue())
assert isinstance(data, dict)
# ==========================================================================
# 4.3 load_system_from_file
# ==========================================================================
class TestLoadSystemFromFile:
def test_loads_customers_suppliers_products_any_order(self, tmp_path):
path = tmp_path / "system.txt"
# Product line appears BEFORE its supplier - spec explicitly allows this.
content = (
"Product(id=1, name='widget', price=1.0, supplier_id=1, quantity=10)\n"
"Customer(id=1, name='Cust', city='City', address='Addr')\n"
"Supplier(id=1, name='Sup', city='OCity', address='OAddr')\n"
)
path.write_text(content)
system = matamazon.load_system_from_file(str(path))
lines = _export_system_lines(system)
assert any(l.startswith("Customer(id=1") for l in lines)
assert any(l.startswith("Supplier(id=1") for l in lines)
assert any(l.startswith("Product(id=1") for l in lines)
def test_invalid_lines_are_ignored(self, tmp_path):
path = tmp_path / "system.txt"
content = (
"this is not a valid line at all\n"
"Customer(id=1, name='Cust', city='City', address='Addr')\n"
"\n"
)
path.write_text(content)
system = matamazon.load_system_from_file(str(path))
lines = _export_system_lines(system)
assert any(l.startswith("Customer(id=1") for l in lines)
def test_nonexistent_file_propagates_error(self, tmp_path):
with pytest.raises(OSError):
matamazon.load_system_from_file(str(tmp_path / "does_not_exist.txt"))
def test_returns_system_object_usable_afterwards(self, tmp_path):
path = tmp_path / "system.txt"
path.write_text("Customer(id=1, name='Cust', city='City', address='Addr')\n")
system = matamazon.load_system_from_file(str(path))
# further mutation should work normally
system.register_entity(matamazon.Supplier(2, "S", "C", "A"), False)
lines = _export_system_lines(system)
assert any(l.startswith("Supplier(id=2") for l in lines)
# ==========================================================================
# 4.4 Script behavior (subprocess, black box)
# ==========================================================================
class TestScriptFlags:
def test_missing_required_log_flag_prints_usage_to_stderr(self, tmp_path):
result = run_script(["-o", str(tmp_path / "out.txt")])
assert "Usage: python3 matamazon.py -l < matamazon_log > -s < matamazon_system > -o <output_file> -os <out_matamazon_system>" in result.stderr
assert result.returncode != 0
def test_unknown_flag_prints_usage_to_stderr(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text("")
result = run_script(["-l", str(log), "--bogus", "value"])
assert "Usage: python3 matamazon.py -l < matamazon_log > -s < matamazon_system > -o <output_file> -os <out_matamazon_system>" in result.stderr
assert result.returncode != 0
def test_flags_can_appear_in_any_order(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text("register customer 1 Alice City Addr\n")
sysout = tmp_path / "sysout.txt"
result = run_script(["-os", str(sysout), "-l", str(log)])
assert result.returncode == 0
assert sysout.exists()
assert "Alice" in sysout.read_text()
class TestScriptExecution:
def test_basic_log_produces_expected_system_output(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register customer 55 Kelly_Kell Michmoret Harmzor_22\n"
"register supplier 34 Albert_Almost_Einstein Sde_Boker Stam_Ktovet\n"
"add 123 plane_paper 0.1 34 4500\n"
)
sysout = tmp_path / "sysout.txt"
result = run_script(["-l", str(log), "-os", str(sysout)])
assert result.returncode == 0
content = sysout.read_text()
# underscores in log fields must become spaces in the object fields
assert "Kelly Kell" in content
assert "Michmoret" in content
assert "Albert Almost Einstein" in content
assert "Sde Boker" in content
assert "plane paper" in content
assert "Kelly_Kell" not in content
def test_search_command_prints_to_stdout(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register supplier 34 Sup City Addr\n"
"add 1 plane_paper 0.1 34 10\n"
"search paper\n"
)
result = run_script(["-l", str(log)])
assert result.returncode == 0
assert "plane paper" in result.stdout
def test_order_command_default_quantity(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register customer 1 Cust City Addr\n"
"register supplier 1 Sup City2 Addr2\n"
"add 1 widget 1.0 1 10\n"
"order 1 1\n"
)
sysout = tmp_path / "sysout.txt"
result = run_script(["-l", str(log), "-os", str(sysout)])
assert result.returncode == 0
assert "quantity=9" in sysout.read_text()
def test_output_file_receives_orders_json(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register customer 1 Cust City Addr\n"
"register supplier 1 Sup Origin_City Addr2\n"
"add 1 widget 1.0 1 10\n"
"order 1 1 2\n"
)
out = tmp_path / "out.txt"
result = run_script(["-l", str(log), "-o", str(out)])
assert result.returncode == 0
data = json.loads(out.read_text())
assert "Origin City" in data
assert len(data["Origin City"]) == 1
def test_no_output_file_prints_orders_to_stdout(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register customer 1 Cust City Addr\n"
"register supplier 1 Sup Origin_City Addr2\n"
"add 1 widget 1.0 1 10\n"
"order 1 1 2\n"
)
result = run_script(["-l", str(log)])
assert result.returncode == 0
assert "Origin City" in result.stdout
def test_preloaded_system_file_is_combined_with_log(self, tmp_path):
system_file = tmp_path / "system.txt"
system_file.write_text("Customer(id=1, name='Old Cust', city='City', address='Addr')\n")
log = tmp_path / "log.txt"
log.write_text("register customer 2 New_Cust City2 Addr2\n")
sysout = tmp_path / "sysout.txt"
result = run_script(["-l", str(log), "-s", str(system_file), "-os", str(sysout)])
assert result.returncode == 0
content = sysout.read_text()
assert "Old Cust" in content
assert "New Cust" in content
def test_remove_command_via_log(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register customer 1 Cust City Addr\n"
"remove customer 1\n"
)
sysout = tmp_path / "sysout.txt"
result = run_script(["-l", str(log), "-os", str(sysout)])
assert result.returncode == 0
assert "Cust" not in sysout.read_text()
def test_update_command_via_log(self, tmp_path):
log = tmp_path / "log.txt"
log.write_text(
"register supplier 1 Sup City Addr\n"
"add 1 widget 1.0 1 10\n"
"update 1 new_widget 2.0 1 20\n"
)
sysout = tmp_path / "sysout.txt"
result = run_script(["-l", str(log), "-os", str(sysout)])
assert result.returncode == 0
content = sysout.read_text()
assert "new widget" in content
assert "price=2.0" in content
assert "quantity=20" in content
def test_generic_runtime_error_prints_expected_message(self, tmp_path):
"""
Force a runtime error (nonexistent -s system file) and check the
exact required message is emitted. Per the spec's own test file
tests/test2.txt (captured via stdout redirection, see section 4.6),
this message is expected on stdout; we assert it appears in
stdout+stderr combined to stay lenient about the exact stream.
"""
log = tmp_path / "log.txt"
log.write_text("register customer 1 Cust City Addr\n")
result = run_script(["-l", str(log), "-s", str(tmp_path / "missing_system.txt")])
combined = result.stdout + result.stderr
assert "The matamazon script has encountered an error" in combined
# ==========================================================================
# Optional: cross-check against the actual tests/ fixtures shipped with the
# assignment, if this file is run from within the assignment repo.
# ==========================================================================
TESTS_DIR = os.path.join(THIS_DIR, "tests")
def _has(*names):
return all(os.path.exists(os.path.join(TESTS_DIR, n)) for n in names)
class TestAgainstShippedFixtures:
"""
These reuse the log/system/expected-output files that come with the
assignment (tests/ folder). They're skipped automatically if those
files aren't present next to this test file.
NOTE: the tests/ folder in this repo contains two differently-named,
inconsistent expected-system files for index 0
(out_matamazon_system0.txt uses spaces, out_system0.txt uses
underscores and even a differing quantity for product 125). We check
against out_matamazon_system0.txt, since converting '_' to ' ' is what
section 4.4 of the spec explicitly requires - but be aware the other
file exists too and you may want to look into the discrepancy yourself.
"""
@pytest.mark.skipif(not _has("matamazon_log0.txt", "matamazon_system0.txt"),
reason="tests/ fixtures not found next to this file")
def test_log0_against_out_matamazon_system0(self, tmp_path):
sysout = tmp_path / "sysout.txt"
result = run_script([
"-l", os.path.join(TESTS_DIR, "matamazon_log0.txt"),
"-s", os.path.join(TESTS_DIR, "matamazon_system0.txt"),
"-os", str(sysout),
])
assert result.returncode == 0
expected_path = os.path.join(TESTS_DIR, "out_matamazon_system0.txt")
if os.path.exists(expected_path):
with open(expected_path) as f:
expected_lines = {l.strip() for l in f if l.strip()}
actual_lines = {l.strip() for l in sysout.read_text().splitlines() if l.strip()}
assert actual_lines == expected_lines
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-v"]))