118 lines
4.6 KiB
Python
118 lines
4.6 KiB
Python
from copy import deepcopy
|
|
from decimal import Decimal
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from ocado_grocy.receipt import ImportError, number, parse_document, parse_ocado_order
|
|
|
|
FIXTURE = Path(__file__).parent / 'fixtures' / 'history_receipt.json'
|
|
|
|
|
|
def api_data():
|
|
return json.loads(FIXTURE.read_text())
|
|
|
|
|
|
def test_synthetic_api_receipt():
|
|
receipt = parse_ocado_order(api_data())
|
|
assert receipt.order_id == '1000000000001'
|
|
assert receipt.purchased_date == '2020-01-02'
|
|
assert len(receipt.items) == 21
|
|
assert sum(i.quantity for i in receipt.items) == 23
|
|
assert sum(i.total for i in receipt.items) == Decimal('28.75')
|
|
lettuce = next(i for i in receipt.items if 'Lettuce' in i.name)
|
|
assert lettuce.best_before == '2020-01-05'
|
|
assert lettuce.metadata['storage_type'] == 'FRIDGE'
|
|
cloths = next(i for i in receipt.items if 'Cleaning Cloths' in i.name)
|
|
assert cloths.quantity == 2
|
|
assert cloths.unit_price == Decimal('1.25')
|
|
assert cloths.best_before is None
|
|
|
|
|
|
def test_order_identity_and_delivery_status():
|
|
with pytest.raises(ImportError,match='does not match'):
|
|
parse_ocado_order(api_data(),expected_order_id='123')
|
|
data=api_data()
|
|
data['entities']['order'][data['result']]['status']='CONFIRMED'
|
|
with pytest.raises(ImportError,match='not delivered'):
|
|
parse_ocado_order(data)
|
|
|
|
|
|
def test_summary_detects_partial_receipt():
|
|
data=api_data()
|
|
data['entities']['order'][data['result']]['groupedProducts']['products'].pop()
|
|
with pytest.raises(ImportError,match='quantities'):
|
|
parse_ocado_order(data)
|
|
|
|
|
|
def test_summary_detects_wrong_price():
|
|
data=api_data()
|
|
data['entities']['order'][data['result']]['groupedProducts']['products'][0]['prices']['offered']['amount']='9.99'
|
|
with pytest.raises(ImportError,match='prices'):
|
|
parse_ocado_order(data)
|
|
|
|
|
|
def test_only_accepted_substitutes_are_imported():
|
|
data=api_data()
|
|
order=data['entities']['order'][data['result']]
|
|
original=order['groupedProducts']['products'].pop(0)
|
|
accepted={**deepcopy(original),'productId':'replacement','retailerProductId':'999','name':'Replacement bananas','status':'ACCEPTED'}
|
|
rejected={**deepcopy(original),'productId':'rejected','retailerProductId':'888','name':'Rejected bananas','status':'REJECTED'}
|
|
original['substitutes']=[accepted,rejected]
|
|
order['groupedProducts']['substitutes']=[original]
|
|
receipt=parse_ocado_order(data)
|
|
assert len(receipt.items)==21
|
|
assert any(i.product_id=='999' for i in receipt.items)
|
|
assert not any(i.product_id in {'888',original['retailerProductId']} for i in receipt.items)
|
|
assert next(i for i in receipt.items if i.product_id=='999').metadata['substituted_for']==original['name']
|
|
|
|
|
|
def test_input_order_does_not_change_stable_line_order():
|
|
first=api_data()
|
|
second=deepcopy(first)
|
|
second['entities']['order'][second['result']]['groupedProducts']['products'].reverse()
|
|
assert parse_ocado_order(first)==parse_ocado_order(second)
|
|
|
|
|
|
def test_discontinued_product_does_not_need_html_link():
|
|
data=api_data()
|
|
product=data['entities']['order'][data['result']]['groupedProducts']['products'][0]
|
|
product['isInCurrentCatalog']=False
|
|
receipt=parse_ocado_order(data)
|
|
assert any(i.product_id==product['retailerProductId'] for i in receipt.items)
|
|
|
|
|
|
def test_purchase_date_is_local_delivery_date():
|
|
data=api_data()
|
|
data['entities']['order'][data['result']]['dates']['deliveryStartDate']='2026-09-01T23:30:00Z'
|
|
assert parse_ocado_order(data).purchased_date=='2026-09-02'
|
|
|
|
|
|
@pytest.mark.parametrize('value',['NaN','Infinity','-1','nonsense',None])
|
|
def test_invalid_numbers(value):
|
|
with pytest.raises(ImportError):number(value,'quantity')
|
|
|
|
|
|
def test_free_and_weighted_items():
|
|
receipt=parse_document({'order_id':'1','items':[
|
|
{'name':'Free gift','quantity':1,'total':0},
|
|
{'name':'Apples','quantity':'0.75','total':'1.50'}]})
|
|
assert receipt.items[0].unit_price==0
|
|
assert receipt.items[1].unit_price==2
|
|
|
|
|
|
def test_receipt_json_roundtrip():
|
|
receipt=parse_ocado_order(api_data())
|
|
assert parse_document(json.loads(json.dumps(receipt.export(),default=str)))==receipt
|
|
|
|
|
|
def test_live_basket_cannot_be_mistaken_for_order():
|
|
with pytest.raises(ImportError,match='structure'):
|
|
parse_ocado_order({'data':{'basket':{'items':[{'name':'Not an order'}]}}})
|
|
|
|
|
|
def test_non_gbp_order_rejected():
|
|
data=api_data()
|
|
data['entities']['order'][data['result']]['groupedProducts']['products'][0]['prices']['offered']['currency']='EUR'
|
|
with pytest.raises(ImportError,match='currency'):parse_ocado_order(data)
|