feat: add basic "create order" feature, docs, tests

just the package API docs so far, narrative will come later
This commit is contained in:
Lance Edgar 2025-01-06 17:03:41 -06:00
parent 89265f0240
commit ef07d30a85
86 changed files with 7749 additions and 35 deletions

0
tests/__init__.py Normal file
View file

0
tests/batch/__init__.py Normal file
View file

View file

@ -0,0 +1,539 @@
# -*- coding: utf-8; -*-
import decimal
from wuttjamaican.testing import DataTestCase
from sideshow.batch import neworder as mod
class TestNewOrderBatchHandler(DataTestCase):
def make_config(self, **kwargs):
config = super().make_config(**kwargs)
config.setdefault('wutta.model_spec', 'sideshow.db.model')
config.setdefault('wutta.enum_spec', 'sideshow.enum')
return config
def make_handler(self):
return mod.NewOrderBatchHandler(self.config)
def test_set_pending_customer(self):
model = self.app.model
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user, customer_id=42)
self.assertEqual(batch.customer_id, 42)
self.assertIsNone(batch.pending_customer)
self.assertIsNone(batch.customer_name)
self.assertIsNone(batch.phone_number)
self.assertIsNone(batch.email_address)
# auto full_name
handler.set_pending_customer(batch, {
'first_name': "Fred",
'last_name': "Flintstone",
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
})
self.assertIsNone(batch.customer_id)
self.assertIsInstance(batch.pending_customer, model.PendingCustomer)
customer = batch.pending_customer
self.assertEqual(customer.full_name, "Fred Flintstone")
self.assertEqual(customer.first_name, "Fred")
self.assertEqual(customer.last_name, "Flintstone")
self.assertEqual(customer.phone_number, '555-1234')
self.assertEqual(customer.email_address, 'fred@mailinator.com')
self.assertEqual(batch.customer_name, "Fred Flintstone")
self.assertEqual(batch.phone_number, '555-1234')
self.assertEqual(batch.email_address, 'fred@mailinator.com')
# explicit full_name
batch = handler.make_batch(self.session, created_by=user, customer_id=42)
handler.set_pending_customer(batch, {
'full_name': "Freddy Flintstone",
'first_name': "Fred",
'last_name': "Flintstone",
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
})
self.assertIsNone(batch.customer_id)
self.assertIsInstance(batch.pending_customer, model.PendingCustomer)
customer = batch.pending_customer
self.assertEqual(customer.full_name, "Freddy Flintstone")
self.assertEqual(customer.first_name, "Fred")
self.assertEqual(customer.last_name, "Flintstone")
self.assertEqual(customer.phone_number, '555-1234')
self.assertEqual(customer.email_address, 'fred@mailinator.com')
self.assertEqual(batch.customer_name, "Freddy Flintstone")
self.assertEqual(batch.phone_number, '555-1234')
self.assertEqual(batch.email_address, 'fred@mailinator.com')
def test_add_pending_product(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.assertEqual(len(batch.rows), 0)
kw = dict(
scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
)
row = handler.add_pending_product(batch, kw, 1, enum.ORDER_UOM_UNIT)
self.assertEqual(len(batch.rows), 1)
self.assertIs(batch.rows[0], row)
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.product_brand, 'Bragg')
self.assertEqual(row.product_description, 'Vinegar')
self.assertEqual(row.product_size, '32oz')
self.assertEqual(row.case_size, 12)
self.assertEqual(row.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(row.unit_price_reg, decimal.Decimal('5.99'))
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
self.assertEqual(row.case_price_quoted, decimal.Decimal('71.88'))
product = row.pending_product
self.assertIsInstance(product, model.PendingProduct)
self.assertEqual(product.scancode, '07430500132')
self.assertEqual(product.brand_name, 'Bragg')
self.assertEqual(product.description, 'Vinegar')
self.assertEqual(product.size, '32oz')
self.assertEqual(product.case_size, 12)
self.assertEqual(product.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(product.unit_price_reg, decimal.Decimal('5.99'))
self.assertIs(product.created_by, user)
def test_set_pending_product(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.assertEqual(len(batch.rows), 0)
# start with mock product_id
row = handler.make_row(product_id=42, order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.flush()
self.assertEqual(row.product_id, 42)
self.assertIsNone(row.pending_product)
self.assertIsNone(row.product_scancode)
self.assertIsNone(row.product_brand)
self.assertIsNone(row.product_description)
self.assertIsNone(row.product_size)
self.assertIsNone(row.case_size)
self.assertIsNone(row.unit_cost)
self.assertIsNone(row.unit_price_reg)
self.assertIsNone(row.unit_price_quoted)
# set pending, which clears product_id
handler.set_pending_product(row, dict(
scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
))
self.session.flush()
self.assertIsNone(row.product_id)
self.assertIsInstance(row.pending_product, model.PendingProduct)
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.product_brand, 'Bragg')
self.assertEqual(row.product_description, 'Vinegar')
self.assertEqual(row.product_size, '32oz')
self.assertEqual(row.case_size, 12)
self.assertEqual(row.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(row.unit_price_reg, decimal.Decimal('5.99'))
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
self.assertEqual(row.case_price_quoted, decimal.Decimal('71.88'))
product = row.pending_product
self.assertIsInstance(product, model.PendingProduct)
self.assertEqual(product.scancode, '07430500132')
self.assertEqual(product.brand_name, 'Bragg')
self.assertEqual(product.description, 'Vinegar')
self.assertEqual(product.size, '32oz')
self.assertEqual(product.case_size, 12)
self.assertEqual(product.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(product.unit_price_reg, decimal.Decimal('5.99'))
self.assertIs(product.created_by, user)
# set again to update pending
handler.set_pending_product(row, dict(
scancode='07430500116',
size='16oz',
unit_cost=decimal.Decimal('2.19'),
unit_price_reg=decimal.Decimal('3.59'),
))
self.session.flush()
self.assertIsNone(row.product_id)
self.assertIsInstance(row.pending_product, model.PendingProduct)
self.assertEqual(row.product_scancode, '07430500116')
self.assertEqual(row.product_brand, 'Bragg')
self.assertEqual(row.product_description, 'Vinegar')
self.assertEqual(row.product_size, '16oz')
self.assertEqual(row.case_size, 12)
self.assertEqual(row.unit_cost, decimal.Decimal('2.19'))
self.assertEqual(row.unit_price_reg, decimal.Decimal('3.59'))
self.assertEqual(row.unit_price_quoted, decimal.Decimal('3.59'))
self.assertEqual(row.case_price_quoted, decimal.Decimal('43.08'))
product = row.pending_product
self.assertIsInstance(product, model.PendingProduct)
self.assertEqual(product.scancode, '07430500116')
self.assertEqual(product.brand_name, 'Bragg')
self.assertEqual(product.description, 'Vinegar')
self.assertEqual(product.size, '16oz')
self.assertEqual(product.case_size, 12)
self.assertEqual(product.unit_cost, decimal.Decimal('2.19'))
self.assertEqual(product.unit_price_reg, decimal.Decimal('3.59'))
self.assertIs(product.created_by, user)
def test_refresh_row(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.assertEqual(len(batch.rows), 0)
# missing product
row = handler.make_row(order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
self.assertIsNone(row.status_code)
handler.add_row(batch, row)
self.assertEqual(row.status_code, row.STATUS_MISSING_PRODUCT)
# missing order_qty
row = handler.make_row(product_id=42, order_uom=enum.ORDER_UOM_UNIT)
self.assertIsNone(row.status_code)
handler.add_row(batch, row)
self.assertEqual(row.status_code, row.STATUS_MISSING_ORDER_QTY)
# refreshed from pending product (null price)
product = model.PendingProduct(scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
created_by=user,
status=enum.PendingProductStatus.PENDING)
row = handler.make_row(pending_product=product, order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
self.assertIsNone(row.status_code)
handler.add_row(batch, row)
self.assertEqual(row.status_code, row.STATUS_OK)
self.assertIsNone(row.product_id)
self.assertIs(row.pending_product, product)
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.product_brand, 'Bragg')
self.assertEqual(row.product_description, 'Vinegar')
self.assertEqual(row.product_size, '32oz')
self.assertIsNone(row.case_size)
self.assertIsNone(row.unit_cost)
self.assertIsNone(row.unit_price_reg)
self.assertIsNone(row.unit_price_quoted)
self.assertIsNone(row.case_price_quoted)
self.assertIsNone(row.total_price)
# refreshed from pending product (zero price)
product = model.PendingProduct(scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
unit_price_reg=0,
created_by=user,
status=enum.PendingProductStatus.PENDING)
row = handler.make_row(pending_product=product, order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
self.assertIsNone(row.status_code)
handler.add_row(batch, row)
self.assertEqual(row.status_code, row.STATUS_OK)
self.assertIsNone(row.product_id)
self.assertIs(row.pending_product, product)
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.product_brand, 'Bragg')
self.assertEqual(row.product_description, 'Vinegar')
self.assertEqual(row.product_size, '32oz')
self.assertIsNone(row.case_size)
self.assertIsNone(row.unit_cost)
self.assertEqual(row.unit_price_reg, 0)
self.assertEqual(row.unit_price_quoted, 0)
self.assertIsNone(row.case_price_quoted)
self.assertEqual(row.total_price, 0)
# refreshed from pending product (normal, case)
product = model.PendingProduct(scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
status=enum.PendingProductStatus.PENDING)
row = handler.make_row(pending_product=product, order_qty=2, order_uom=enum.ORDER_UOM_CASE)
self.assertIsNone(row.status_code)
handler.add_row(batch, row)
self.assertEqual(row.status_code, row.STATUS_OK)
self.assertIsNone(row.product_id)
self.assertIs(row.pending_product, product)
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.product_brand, 'Bragg')
self.assertEqual(row.product_description, 'Vinegar')
self.assertEqual(row.product_size, '32oz')
self.assertEqual(row.case_size, 12)
self.assertEqual(row.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(row.unit_price_reg, decimal.Decimal('5.99'))
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
self.assertEqual(row.case_price_quoted, decimal.Decimal('71.88'))
self.assertEqual(row.total_price, decimal.Decimal('143.76'))
def test_remove_row(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.assertEqual(len(batch.rows), 0)
kw = dict(
scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
)
row = handler.add_pending_product(batch, kw, 1, enum.ORDER_UOM_CASE)
self.session.add(row)
self.session.flush()
self.assertEqual(batch.row_count, 1)
self.assertEqual(row.total_price, decimal.Decimal('71.88'))
self.assertEqual(batch.total_price, decimal.Decimal('71.88'))
handler.do_remove_row(row)
self.assertEqual(batch.row_count, 0)
self.assertEqual(batch.total_price, 0)
def test_do_delete(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
# make batch w/ pending customer
customer = model.PendingCustomer(full_name="Fred Flintstone",
status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
batch = handler.make_batch(self.session, created_by=user, pending_customer=customer)
self.session.add(batch)
self.session.commit()
# deleting batch will also delete pending customer
self.assertIn(batch, self.session)
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
handler.do_delete(batch, user)
self.session.commit()
self.assertNotIn(batch, self.session)
self.assertEqual(self.session.query(model.PendingCustomer).count(), 0)
# make new pending customer
customer = model.PendingCustomer(full_name="Fred Flintstone",
status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
# make 2 batches with same pending customer
batch1 = handler.make_batch(self.session, created_by=user, pending_customer=customer)
batch2 = handler.make_batch(self.session, created_by=user, pending_customer=customer)
self.session.add(batch1)
self.session.add(batch2)
self.session.commit()
# deleting 1 will not delete pending customer
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
handler.do_delete(batch1, user)
self.session.commit()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
self.assertIs(batch2.pending_customer, customer)
def test_get_effective_rows(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
# make batch w/ different status rows
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
# STATUS_MISSING_PRODUCT
row = handler.make_row(order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.add(row)
self.session.flush()
# STATUS_MISSING_ORDER_QTY
row = handler.make_row(product_id=42, order_qty=0, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.add(row)
self.session.flush()
# STATUS_OK
row = handler.make_row(product_id=42, order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.add(row)
self.session.commit()
# only 1 effective row
rows = handler.get_effective_rows(batch)
self.assertEqual(len(rows), 1)
row = rows[0]
self.assertEqual(row.status_code, row.STATUS_OK)
def test_why_not_execute(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.session.flush()
reason = handler.why_not_execute(batch)
self.assertEqual(reason, "Must assign the customer")
batch.customer_id = 42
reason = handler.why_not_execute(batch)
self.assertEqual(reason, "Must add at least one valid item")
kw = dict(
scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
)
row = handler.add_pending_product(batch, kw, 1, enum.ORDER_UOM_CASE)
self.session.add(row)
self.session.flush()
reason = handler.why_not_execute(batch)
self.assertIsNone(reason)
def test_make_new_order(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user,
customer_id=42, customer_name="John Doe")
self.session.add(batch)
kw = dict(
scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
)
row = handler.add_pending_product(batch, kw, 1, enum.ORDER_UOM_CASE)
self.session.add(row)
self.session.flush()
order = handler.make_new_order(batch, [row], user=user)
self.assertIsInstance(order, model.Order)
self.assertIs(order.created_by, user)
self.assertEqual(order.customer_id, 42)
self.assertEqual(order.customer_name, "John Doe")
self.assertEqual(len(order.items), 1)
item = order.items[0]
self.assertEqual(item.product_scancode, '07430500132')
self.assertEqual(item.product_brand, 'Bragg')
self.assertEqual(item.product_description, 'Vinegar')
self.assertEqual(item.product_size, '32oz')
self.assertEqual(item.case_size, 12)
self.assertEqual(item.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(item.unit_price_reg, decimal.Decimal('5.99'))
def test_execute(self):
model = self.app.model
enum = self.app.enum
handler = self.make_handler()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user,
customer_id=42, customer_name="John Doe")
self.session.add(batch)
kw = dict(
scancode='07430500132',
brand_name='Bragg',
description='Vinegar',
size='32oz',
case_size=12,
unit_cost=decimal.Decimal('3.99'),
unit_price_reg=decimal.Decimal('5.99'),
created_by=user,
)
row = handler.add_pending_product(batch, kw, 1, enum.ORDER_UOM_CASE)
self.session.add(row)
self.session.flush()
order = handler.execute(batch, user=user)
self.assertIsInstance(order, model.Order)
self.assertIs(order.created_by, user)
self.assertEqual(order.customer_id, 42)
self.assertEqual(order.customer_name, "John Doe")
self.assertEqual(len(order.items), 1)
item = order.items[0]
self.assertEqual(item.product_scancode, '07430500132')
self.assertEqual(item.product_brand, 'Bragg')
self.assertEqual(item.product_description, 'Vinegar')
self.assertEqual(item.product_size, '32oz')
self.assertEqual(item.case_size, 12)
self.assertEqual(item.unit_cost, decimal.Decimal('3.99'))
self.assertEqual(item.unit_price_reg, decimal.Decimal('5.99'))

0
tests/cli/__init__.py Normal file
View file

18
tests/cli/test_install.py Normal file
View file

@ -0,0 +1,18 @@
# -*- coding: utf-8; -*-
from unittest.mock import MagicMock, patch
from wuttjamaican.testing import ConfigTestCase
from wuttjamaican.install import InstallHandler
from sideshow.cli import install as mod
class TestInstall(ConfigTestCase):
def test_run(self):
ctx = MagicMock(params={})
ctx.parent.wutta_config = self.config
with patch.object(InstallHandler, 'run') as run:
mod.install(ctx)
run.assert_called_once_with()

0
tests/db/__init__.py Normal file
View file

View file

View file

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8; -*-
from wuttjamaican.testing import DataTestCase
from sideshow.db.model.batch import neworder as mod
from sideshow.db.model.products import PendingProduct
class TestNewOrderBatchRow(DataTestCase):
def test_str(self):
row = mod.NewOrderBatchRow()
self.assertEqual(str(row), "")
row = mod.NewOrderBatchRow(product_description="Vinegar")
self.assertEqual(str(row), "Vinegar")
product = PendingProduct(brand_name="Bragg",
description="Vinegar",
size="32oz")
row = mod.NewOrderBatchRow(pending_product=product)
self.assertEqual(str(row), "Bragg Vinegar 32oz")

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8; -*-
from wuttjamaican.testing import DataTestCase
from sideshow.db.model import customers as mod
class TestPendingCustomer(DataTestCase):
def test_str(self):
customer = mod.PendingCustomer()
self.assertEqual(str(customer), "")
customer.full_name = "Fred Flintstone"
self.assertEqual(str(customer), "Fred Flintstone")

View file

@ -0,0 +1,34 @@
# -*- coding: utf-8; -*-
from wuttjamaican.testing import DataTestCase
from sideshow.db.model import orders as mod
from sideshow.db.model.products import PendingProduct
class TestOrder(DataTestCase):
def test_str(self):
order = mod.Order()
self.assertEqual(str(order), "None")
order = mod.Order(order_id=42)
self.assertEqual(str(order), "42")
class TestOrderItem(DataTestCase):
def test_str(self):
item = mod.OrderItem()
self.assertEqual(str(item), "")
item = mod.OrderItem(product_description="Vinegar")
self.assertEqual(str(item), "Vinegar")
product = PendingProduct(brand_name="Bragg",
description="Vinegar",
size="32oz")
item = mod.OrderItem(pending_product=product)
self.assertEqual(str(item), "Bragg Vinegar 32oz")

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8; -*-
from wuttjamaican.testing import DataTestCase
from sideshow.db.model import products as mod
class TestPendingProduct(DataTestCase):
def test_str(self):
product = mod.PendingProduct()
self.assertEqual(str(product), "")
product = mod.PendingProduct(brand_name="Bragg")
self.assertEqual(str(product), "Bragg")
product = mod.PendingProduct(description="Vinegar")
self.assertEqual(str(product), "Vinegar")
product = mod.PendingProduct(size="32oz")
self.assertEqual(str(product), "32oz")
product = mod.PendingProduct(brand_name="Bragg",
description="Vinegar",
size="32oz")
self.assertEqual(str(product), "Bragg Vinegar 32oz")
def test_full_description(self):
product = mod.PendingProduct()
self.assertEqual(product.full_description, "")
product = mod.PendingProduct(brand_name="Bragg")
self.assertEqual(product.full_description, "Bragg")
product = mod.PendingProduct(description="Vinegar")
self.assertEqual(product.full_description, "Vinegar")
product = mod.PendingProduct(size="32oz")
self.assertEqual(product.full_description, "32oz")
product = mod.PendingProduct(brand_name="Bragg",
description="Vinegar",
size="32oz")
self.assertEqual(product.full_description, "Bragg Vinegar 32oz")

17
tests/test_config.py Normal file
View file

@ -0,0 +1,17 @@
# -*- coding: utf-8; -*-
from unittest import TestCase
from wuttjamaican.conf import WuttaConfig
from sideshow import config as mod
class TestSideshowConfig(TestCase):
def test_configure(self):
config = WuttaConfig(files=[])
ext = mod.SideshowConfig()
ext.configure(config)
self.assertEqual(config.get('wutta.app_title'), "Sideshow")
self.assertEqual(config.get('wutta.app_dist'), "Sideshow")

0
tests/web/example.conf Normal file
View file

View file

@ -0,0 +1,88 @@
# -*- coding: utf-8; -*-
from sqlalchemy import orm
from sideshow.testing import WebTestCase
from sideshow.web.forms import schema as mod
class TestOrderRef(WebTestCase):
def test_sort_query(self):
typ = mod.OrderRef(self.request, session=self.session)
query = typ.get_query()
self.assertIsInstance(query, orm.Query)
sorted_query = typ.sort_query(query)
self.assertIsInstance(sorted_query, orm.Query)
self.assertIsNot(sorted_query, query)
def test_get_object_url(self):
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
model = self.app.model
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
self.session.commit()
typ = mod.OrderRef(self.request, session=self.session)
url = typ.get_object_url(order)
self.assertIsNotNone(url)
self.assertIn(f'/orders/{order.uuid}', url)
class TestPendingCustomerRef(WebTestCase):
def test_sort_query(self):
typ = mod.PendingCustomerRef(self.request, session=self.session)
query = typ.get_query()
self.assertIsInstance(query, orm.Query)
sorted_query = typ.sort_query(query)
self.assertIsInstance(sorted_query, orm.Query)
self.assertIsNot(sorted_query, query)
def test_get_object_url(self):
self.pyramid_config.add_route('pending_customers.view', '/pending/customers/{uuid}')
model = self.app.model
enum = self.app.enum
user = model.User(username='barney')
self.session.add(user)
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
self.session.commit()
typ = mod.PendingCustomerRef(self.request, session=self.session)
url = typ.get_object_url(customer)
self.assertIsNotNone(url)
self.assertIn(f'/pending/customers/{customer.uuid}', url)
class TestPendingProductRef(WebTestCase):
def test_sort_query(self):
typ = mod.PendingProductRef(self.request, session=self.session)
query = typ.get_query()
self.assertIsInstance(query, orm.Query)
sorted_query = typ.sort_query(query)
self.assertIsInstance(sorted_query, orm.Query)
self.assertIsNot(sorted_query, query)
def test_get_object_url(self):
self.pyramid_config.add_route('pending_products.view', '/pending/products/{uuid}')
model = self.app.model
enum = self.app.enum
user = model.User(username='barney')
self.session.add(user)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
self.session.commit()
typ = mod.PendingProductRef(self.request, session=self.session)
url = typ.get_object_url(product)
self.assertIsNotNone(url)
self.assertIn(f'/pending/products/{product.uuid}', url)

34
tests/web/test_app.py Normal file
View file

@ -0,0 +1,34 @@
# -*- coding: utf-8; -*-
import os
from unittest import TestCase
from asgiref.wsgi import WsgiToAsgi
from pyramid.router import Router
from sideshow.web import app as mod
here = os.path.dirname(__file__)
example_conf = os.path.join(here, 'example.conf')
class TestMain(TestCase):
def test_coverage(self):
app = mod.main({}, **{'wutta.config': example_conf})
self.assertIsInstance(app, Router)
class TestMakeWsgiApp(TestCase):
def test_coverage(self):
app = mod.make_wsgi_app()
self.assertIsInstance(app, Router)
class TestMakeAsgiApp(TestCase):
def test_coverage(self):
app = mod.make_asgi_app()
self.assertIsInstance(app, WsgiToAsgi)

12
tests/web/test_menus.py Normal file
View file

@ -0,0 +1,12 @@
# -*- coding: utf-8; -*-
from sideshow.testing import WebTestCase
from sideshow.web import menus as mod
class TestSideshowMenuHandler(WebTestCase):
def test_make_menus(self):
handler = mod.SideshowMenuHandler(self.config)
menus = handler.make_menus(self.request)
self.assertEqual(len(menus), 4)

10
tests/web/test_static.py Normal file
View file

@ -0,0 +1,10 @@
# -*- coding: utf-8; -*-
from sideshow.testing import WebTestCase
from sideshow.web import static as mod
class TestIncludeme(WebTestCase):
def test_coverage(self):
mod.includeme(self.pyramid_config)

View file

@ -0,0 +1,101 @@
# -*- coding: utf-8; -*-
import datetime
from unittest.mock import patch
from wuttaweb.forms.schema import WuttaMoney
from sideshow.testing import WebTestCase
from sideshow.web.views.batch import neworder as mod
from sideshow.web.forms.schema import PendingCustomerRef
from sideshow.batch.neworder import NewOrderBatchHandler
class TestIncludeme(WebTestCase):
def test_coverage(self):
mod.includeme(self.pyramid_config)
class TestNewOrderBatchView(WebTestCase):
def make_view(self):
return mod.NewOrderBatchView(self.request)
def test_get_batch_handler(self):
view = self.make_view()
handler = view.get_batch_handler()
self.assertIsInstance(handler, NewOrderBatchHandler)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.NewOrderBatch)
self.assertNotIn('total_price', grid.renderers)
view.configure_grid(grid)
self.assertIn('total_price', grid.renderers)
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
handler = view.batch_handler
user = model.User(username='barney')
self.session.add(user)
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
batch = handler.make_batch(self.session, pending_customer=customer, created_by=user)
self.session.add(batch)
self.session.commit()
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=batch)
view.configure_form(form)
schema = form.get_schema()
self.assertIsInstance(schema['pending_customer'].typ, PendingCustomerRef)
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
def test_configure_row_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.NewOrderBatchRow)
self.assertNotIn('total_price', grid.renderers)
view.configure_row_grid(grid)
self.assertIn('total_price', grid.renderers)
def test_get_xref_buttons(self):
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
model = self.app.model
enum = self.app.enum
view = self.make_view()
handler = view.batch_handler
user = model.User(username='barney')
self.session.add(user)
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
# 1st batch has no order
batch = handler.make_batch(self.session, pending_customer=customer, created_by=user)
self.session.add(batch)
self.session.flush()
buttons = view.get_xref_buttons(batch)
self.assertEqual(len(buttons), 0)
# 2nd batch is executed; has order
batch = handler.make_batch(self.session, pending_customer=customer, created_by=user,
executed=datetime.datetime.now(), executed_by=user)
self.session.add(batch)
self.session.flush()
order = model.Order(order_id=batch.id, created_by=user)
self.session.add(order)
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
# nb. this also requires perm
with patch.object(self.request, 'is_root', new=True):
buttons = view.get_xref_buttons(batch)
self.assertEqual(len(buttons), 1)

View file

@ -0,0 +1,184 @@
# -*- coding: utf-8; -*-
import datetime
from unittest.mock import patch
from pyramid.httpexceptions import HTTPFound
from sideshow.testing import WebTestCase
from sideshow.web.views import customers as mod
from sideshow.batch.neworder import NewOrderBatchHandler
class TestIncludeme(WebTestCase):
def test_coverage(self):
mod.includeme(self.pyramid_config)
class TestPendingCustomerView(WebTestCase):
def make_view(self):
return mod.PendingCustomerView(self.request)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
# nb. mostly just getting coverage here
grid = view.make_grid(model_class=model.PendingCustomer)
view.configure_grid(grid)
self.assertIn('full_name', grid.linked_columns)
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
# creating
with patch.object(view, 'creating', new=True):
form = view.make_form(model_class=model.PendingCustomer)
view.configure_form(form)
self.assertNotIn('status', form)
self.assertNotIn('created', form)
self.assertNotIn('created_by', form)
self.assertNotIn('orders', form)
self.assertNotIn('new_order_batches', form)
user = model.User(username='barney')
self.session.add(user)
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
self.session.commit()
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=customer)
view.configure_form(form)
self.assertIn('status', form)
self.assertIn('created', form)
self.assertIn('created_by', form)
self.assertIn('orders', form)
self.assertIn('new_order_batches', form)
def test_make_orders_grid(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
order = model.Order(order_id=42, pending_customer=customer, created_by=user)
self.session.add(order)
self.session.commit()
# no view perm
grid = view.make_orders_grid(customer)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_orders_grid(customer)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_make_new_order_batches_grid(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
batch = handler.make_batch(self.session, pending_customer=customer, created_by=user)
self.session.add(batch)
self.session.commit()
# no view perm
grid = view.make_new_order_batches_grid(customer)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_new_order_batches_grid(customer)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_objectify(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
with patch.object(view, 'creating', new=True):
with patch.object(self.request, 'user', new=user):
form = view.make_model_form()
with patch.object(form, 'validated', create=True, new={
'full_name': "Fred Flinstone",
}):
customer = view.objectify(form)
self.assertIsInstance(customer, model.PendingCustomer)
self.assertIs(customer.created_by, user)
self.assertEqual(customer.status, enum.PendingCustomerStatus.PENDING)
def test_delete_instance(self):
self.pyramid_config.add_route('pending_customers.view', '/pending/customers/{uuid}')
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
# 1st customer is standalone, will be deleted
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
view.delete_instance(customer)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 0)
# 2nd customer is attached to new order batch, will not be deleted
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
batch = handler.make_batch(self.session, created_by=user, pending_customer=customer)
self.session.add(batch)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
self.assertRaises(HTTPFound, view.delete_instance, customer)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
# but after batch is executed, 2nd customer can be deleted
batch.executed = datetime.datetime.now()
batch.executed_by = user
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
view.delete_instance(customer)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 0)
# 3rd customer is attached to order, will not be deleted
customer = model.PendingCustomer(status=enum.PendingCustomerStatus.PENDING,
created_by=user)
self.session.add(customer)
order = model.Order(order_id=42, created_by=user, pending_customer=customer)
self.session.add(order)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)
self.assertRaises(HTTPFound, view.delete_instance, customer)
self.session.flush()
self.assertEqual(self.session.query(model.PendingCustomer).count(), 1)

View file

@ -0,0 +1,902 @@
# -*- coding: utf-8; -*-
import datetime
import decimal
from unittest.mock import patch
from sqlalchemy import orm
from pyramid.httpexceptions import HTTPForbidden, HTTPFound
from pyramid.response import Response
from wuttaweb.forms.schema import WuttaMoney
from sideshow.batch.neworder import NewOrderBatchHandler
from sideshow.testing import WebTestCase
from sideshow.web.views import orders as mod
from sideshow.web.forms.schema import OrderRef
class TestIncludeme(WebTestCase):
def test_coverage(self):
mod.includeme(self.pyramid_config)
class TestOrderView(WebTestCase):
def make_view(self):
return mod.OrderView(self.request)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.PendingProduct)
self.assertNotIn('order_id', grid.linked_columns)
self.assertNotIn('total_price', grid.renderers)
view.configure_grid(grid)
self.assertIn('order_id', grid.linked_columns)
self.assertIn('total_price', grid.renderers)
def test_create(self):
self.pyramid_config.include('sideshow.web.views')
model = self.app.model
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'current_route_url', return_value='/orders/new'):
# this will require some perms
with patch.multiple(self.request, create=True,
user=user, is_root=True):
# fetch page to start things off
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
response = view.create()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
batch1 = self.session.query(model.NewOrderBatch).one()
# start over; deletes current batch
with patch.multiple(self.request, create=True,
method='POST',
POST={'action': 'start_over'}):
response = view.create()
self.assertIsInstance(response, HTTPFound)
self.assertIn('/orders/new', response.location)
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
# fetch again to get new batch
response = view.create()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
batch2 = self.session.query(model.NewOrderBatch).one()
self.assertIsNot(batch2, batch1)
# set pending customer
with patch.multiple(self.request, create=True,
method='POST',
json_body={'action': 'set_pending_customer',
'first_name': 'Fred',
'last_name': 'Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com'}):
response = view.create()
self.assertIsInstance(response, Response)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json_body, {
'customer_is_known': False,
'customer_id': None,
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': 'Fred Flintstone',
'new_customer_first_name': 'Fred',
'new_customer_last_name': 'Flintstone',
'new_customer_phone': '555-1234',
'new_customer_email': 'fred@mailinator.com',
})
# invalid action
with patch.multiple(self.request, create=True,
method='POST',
POST={'action': 'bogus'},
json_body={'action': 'bogus'}):
response = view.create()
self.assertIsInstance(response, Response)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json_body, {'error': 'unknown form action'})
def test_get_current_batch(self):
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
# user is required
self.assertRaises(HTTPForbidden, view.get_current_batch)
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
# batch is auto-created
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
self.assertIs(batch.created_by, user)
# same batch is returned subsequently
batch2 = view.get_current_batch()
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
self.assertIs(batch2, batch)
def test_get_pending_product_required_fields(self):
model = self.app.model
view = self.make_view()
# only description is required by default
fields = view.get_pending_product_required_fields()
self.assertEqual(fields, ['description'])
# but config can specify otherwise
self.config.setdefault('sideshow.orders.unknown_product.fields.brand_name.required', 'true')
self.config.setdefault('sideshow.orders.unknown_product.fields.description.required', 'false')
self.config.setdefault('sideshow.orders.unknown_product.fields.size.required', 'true')
self.config.setdefault('sideshow.orders.unknown_product.fields.unit_price_reg.required', 'true')
fields = view.get_pending_product_required_fields()
self.assertEqual(fields, ['brand_name', 'size', 'unit_price_reg'])
def test_get_context_customer(self):
self.pyramid_config.add_route('orders', '/orders/')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
# with true customer
batch = handler.make_batch(self.session, created_by=user,
customer_id=42, customer_name='Fred Flintstone',
phone_number='555-1234', email_address='fred@mailinator.com')
self.session.add(batch)
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'customer_is_known': True,
'customer_id': 42,
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': None,
'new_customer_first_name': None,
'new_customer_last_name': None,
'new_customer_phone': None,
'new_customer_email': None,
})
# with pending customer
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
handler.set_pending_customer(batch, dict(
full_name="Fred Flintstone",
first_name="Fred", last_name="Flintstone",
phone_number='555-1234', email_address='fred@mailinator.com',
created_by=user,
))
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'customer_is_known': False,
'customer_id': None,
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': 'Fred Flintstone',
'new_customer_first_name': 'Fred',
'new_customer_last_name': 'Flintstone',
'new_customer_phone': '555-1234',
'new_customer_email': 'fred@mailinator.com',
})
# with no customer
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
self.session.flush()
context = view.get_context_customer(batch)
self.assertEqual(context, {
'customer_is_known': True, # nb. this is for UI default
'customer_id': None,
'customer_name': None,
'phone_number': None,
'email_address': None,
'new_customer_name': None,
'new_customer_first_name': None,
'new_customer_last_name': None,
'new_customer_phone': None,
'new_customer_email': None,
})
def test_start_over(self):
self.pyramid_config.add_route('orders.create', '/orders/new')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
result = view.start_over(batch)
self.assertIsInstance(result, HTTPFound)
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
def test_cancel_order(self):
self.pyramid_config.add_route('orders', '/orders/')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 1)
result = view.cancel_order(batch)
self.assertIsInstance(result, HTTPFound)
self.session.flush()
self.assertEqual(self.session.query(model.NewOrderBatch).count(), 0)
def test_set_pending_customer(self):
self.pyramid_config.add_route('orders.create', '/orders/new')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
data = {
'first_name': 'Fred',
'last_name': 'Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
}
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
# normal
self.assertIsNone(batch.pending_customer)
context = view.set_pending_customer(batch, data)
self.assertIsInstance(batch.pending_customer, model.PendingCustomer)
self.assertEqual(context, {
'customer_is_known': False,
'customer_id': None,
'customer_name': 'Fred Flintstone',
'phone_number': '555-1234',
'email_address': 'fred@mailinator.com',
'new_customer_name': 'Fred Flintstone',
'new_customer_first_name': 'Fred',
'new_customer_last_name': 'Flintstone',
'new_customer_phone': '555-1234',
'new_customer_email': 'fred@mailinator.com',
})
# error
with patch.object(handler, 'set_pending_customer', side_effect=RuntimeError):
context = view.set_pending_customer(batch, data)
self.assertEqual(context, {
'error': 'RuntimeError',
})
def test_add_item(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
data = {
'pending_product': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'unit_price_reg': 5.99,
},
'order_qty': 1,
'order_uom': enum.ORDER_UOM_UNIT,
}
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(len(batch.rows), 0)
# normal pending product
result = view.add_item(batch, data)
self.assertIn('batch', result)
self.assertIn('row', result)
self.session.flush()
self.assertEqual(len(batch.rows), 1)
row = batch.rows[0]
self.assertIsInstance(row.pending_product, model.PendingProduct)
# pending w/ invalid price
with patch.dict(data['pending_product'], unit_price_reg='invalid'):
result = view.add_item(batch, data)
self.assertEqual(result, {'error': "Invalid entry for field: unit_price_reg"})
self.session.flush()
self.assertEqual(len(batch.rows), 1) # still just the 1st row
# true product not yet supported
with patch.dict(data, product_is_known=True):
self.assertRaises(NotImplementedError, view.add_item, batch, data)
def test_update_item(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
data = {
'pending_product': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
},
'order_qty': 1,
'order_uom': enum.ORDER_UOM_CASE,
}
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(len(batch.rows), 0)
# add row w/ pending product
view.add_item(batch, data)
self.session.flush()
row = batch.rows[0]
self.assertIsInstance(row.pending_product, model.PendingProduct)
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
# missing row uuid
result = view.update_item(batch, data)
self.assertEqual(result, {'error': "Must specify a row UUID"})
# row not found
with patch.dict(data, uuid=self.app.make_true_uuid()):
result = view.update_item(batch, data)
self.assertEqual(result, {'error': "Row not found"})
# row for wrong batch
batch2 = handler.make_batch(self.session, created_by=user)
self.session.add(batch2)
row2 = handler.make_row(order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch2, row2)
self.session.flush()
with patch.dict(data, uuid=row2.uuid):
result = view.update_item(batch, data)
self.assertEqual(result, {'error': "Row is for wrong batch"})
# set row for remaining tests
data['uuid'] = row.uuid
# true product not yet supported
with patch.dict(data, product_is_known=True):
self.assertRaises(NotImplementedError, view.update_item, batch, data)
# update row, pending product
with patch.dict(data, order_qty=2):
with patch.dict(data['pending_product'], scancode='07430500116'):
self.assertEqual(row.product_scancode, '07430500132')
self.assertEqual(row.order_qty, 1)
result = view.update_item(batch, data)
self.assertEqual(sorted(result), ['batch', 'row'])
self.assertEqual(row.product_scancode, '07430500116')
self.assertEqual(row.order_qty, 2)
self.assertEqual(row.pending_product.scancode, '07430500116')
self.assertEqual(result['row']['product_scancode'], '07430500116')
self.assertEqual(result['row']['order_qty'], '2')
def test_delete_item(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
data = {
'pending_product': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
},
'order_qty': 1,
'order_uom': enum.ORDER_UOM_CASE,
}
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(len(batch.rows), 0)
# add row w/ pending product
view.add_item(batch, data)
self.session.flush()
row = batch.rows[0]
self.assertIsInstance(row.pending_product, model.PendingProduct)
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
# missing row uuid
result = view.delete_item(batch, data)
self.assertEqual(result, {'error': "Must specify a row UUID"})
# row not found
with patch.dict(data, uuid=self.app.make_true_uuid()):
result = view.delete_item(batch, data)
self.assertEqual(result, {'error': "Row not found"})
# row for wrong batch
batch2 = handler.make_batch(self.session, created_by=user)
self.session.add(batch2)
row2 = handler.make_row(order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch2, row2)
self.session.flush()
with patch.dict(data, uuid=row2.uuid):
result = view.delete_item(batch, data)
self.assertEqual(result, {'error': "Row is for wrong batch"})
# row is deleted
data['uuid'] = row.uuid
self.assertEqual(len(batch.rows), 1)
self.assertEqual(batch.row_count, 1)
result = view.delete_item(batch, data)
self.assertEqual(sorted(result), ['batch'])
self.session.refresh(batch)
self.assertEqual(len(batch.rows), 0)
self.assertEqual(batch.row_count, 0)
def test_submit_new_order(self):
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
self.session.commit()
data = {
'pending_product': {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
},
'order_qty': 1,
'order_uom': enum.ORDER_UOM_CASE,
}
with patch.object(view, 'batch_handler', create=True, new=handler):
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.request, 'user', new=user):
batch = view.get_current_batch()
self.session.flush()
self.assertEqual(len(batch.rows), 0)
# add row w/ pending product
view.add_item(batch, data)
self.session.flush()
row = batch.rows[0]
self.assertIsInstance(row.pending_product, model.PendingProduct)
self.assertEqual(row.unit_price_quoted, decimal.Decimal('5.99'))
# execute not allowed yet (no customer)
result = view.submit_new_order(batch, {})
self.assertEqual(result, {'error': "Must assign the customer"})
# submit/execute ok
batch.customer_id = 42
result = view.submit_new_order(batch, {})
self.assertEqual(sorted(result), ['next_url'])
self.assertIn('/orders/', result['next_url'])
# error (already executed)
result = view.submit_new_order(batch, {})
self.assertEqual(result, {
'error': f"ValueError: batch has already been executed: {batch}",
})
def test_get_default_uom_choices(self):
enum = self.app.enum
view = self.make_view()
uoms = view.get_default_uom_choices()
self.assertEqual(uoms, [{'key': key, 'value': val}
for key, val in enum.ORDER_UOM.items()])
def test_normalize_batch(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
pending = {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
'created_by': user,
}
row = handler.add_pending_product(batch, pending, 1, enum.ORDER_UOM_CASE)
self.session.commit()
data = view.normalize_batch(batch)
self.assertEqual(data, {
'uuid': batch.uuid.hex,
'total_price': '71.880',
'total_price_display': '$71.88',
'status_code': None,
'status_text': None,
})
def test_normalize_row(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
pending = {
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'unit_price_reg': 5.99,
'case_size': 12,
'created_by': user,
}
row = handler.add_pending_product(batch, pending, 2, enum.ORDER_UOM_CASE)
self.session.commit()
# normal
data = view.normalize_row(row)
self.assertIsInstance(data, dict)
self.assertEqual(data['uuid'], row.uuid.hex)
self.assertEqual(data['sequence'], 1)
self.assertEqual(data['product_scancode'], '07430500132')
self.assertEqual(data['case_size'], '12')
self.assertEqual(data['order_qty'], '2')
self.assertEqual(data['order_uom'], 'CS')
self.assertEqual(data['order_qty_display'], '2 Cases (× 12 = 24 Units)')
self.assertEqual(data['unit_price_reg'], 5.99)
self.assertEqual(data['unit_price_reg_display'], '$5.99')
self.assertNotIn('unit_price_sale', data)
self.assertNotIn('unit_price_sale_display', data)
self.assertNotIn('sale_ends', data)
self.assertNotIn('sale_ends_display', data)
self.assertEqual(data['unit_price_quoted'], 5.99)
self.assertEqual(data['unit_price_quoted_display'], '$5.99')
self.assertEqual(data['case_price_quoted'], 71.88)
self.assertEqual(data['case_price_quoted_display'], '$71.88')
self.assertEqual(data['total_price'], 143.76)
self.assertEqual(data['total_price_display'], '$143.76')
self.assertIsNone(data['special_order'])
self.assertEqual(data['status_code'], row.STATUS_OK)
self.assertEqual(data['pending_product'], {
'uuid': row.pending_product_uuid.hex,
'scancode': '07430500132',
'brand_name': 'Bragg',
'description': 'Vinegar',
'size': '32oz',
'department_id': None,
'department_name': None,
'unit_price_reg': 5.99,
'vendor_name': None,
'vendor_item_code': None,
'unit_cost': None,
'case_size': 12.0,
'notes': None,
'special_order': None,
})
# unknown case size
row.pending_product.case_size = None
handler.refresh_row(row)
self.session.flush()
data = view.normalize_row(row)
self.assertEqual(data['order_qty_display'], '2 Cases (× ?? = ?? Units)')
# order by unit
row.order_uom = enum.ORDER_UOM_UNIT
handler.refresh_row(row)
self.session.flush()
data = view.normalize_row(row)
self.assertEqual(data['order_qty_display'], '2 Units')
# item on sale
row.pending_product.case_size = 12
row.unit_price_sale = decimal.Decimal('5.19')
row.sale_ends = datetime.datetime(2025, 1, 5, 20, 32)
handler.refresh_row(row, now=datetime.datetime(2025, 1, 5, 19))
self.session.flush()
data = view.normalize_row(row)
self.assertEqual(data['unit_price_sale'], 5.19)
self.assertEqual(data['unit_price_sale_display'], '$5.19')
self.assertEqual(data['sale_ends'], '2025-01-05 20:32:00')
self.assertEqual(data['sale_ends_display'], '2025-01-05')
self.assertEqual(data['unit_price_quoted'], 5.19)
self.assertEqual(data['unit_price_quoted_display'], '$5.19')
self.assertEqual(data['case_price_quoted'], 62.28)
self.assertEqual(data['case_price_quoted_display'], '$62.28')
def test_get_instance_title(self):
model = self.app.model
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, customer_name="Fred Flintstone", created_by=user)
self.session.add(order)
self.session.flush()
title = view.get_instance_title(order)
self.assertEqual(title, "#42 for Fred Flintstone")
def test_configure_form(self):
model = self.app.model
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
self.session.commit()
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=order)
# nb. this is to avoid include/exclude ambiguity
form.remove('items')
view.configure_form(form)
schema = form.get_schema()
self.assertIsInstance(schema['total_price'].typ, WuttaMoney)
def test_get_xref_buttons(self):
self.pyramid_config.add_route('neworder_batches.view', '/batch/neworder/{uuid}')
model = self.app.model
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
# nb. this requires perm to view batch
with patch.object(self.request, 'is_root', new=True):
# order has no batch, so no buttons
buttons = view.get_xref_buttons(order)
self.assertEqual(buttons, [])
# mock up a batch to get a button
batch = handler.make_batch(self.session,
id=order.order_id,
created_by=user,
executed=datetime.datetime.now(),
executed_by=user)
self.session.add(batch)
self.session.flush()
buttons = view.get_xref_buttons(order)
self.assertEqual(len(buttons), 1)
button = buttons[0]
self.assertIn("View the Batch", button)
def test_get_row_grid_data(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
self.session.flush()
order.items.append(model.OrderItem(product_id='07430500132',
product_scancode='07430500132',
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_INITIATED))
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
query = view.get_row_grid_data(order)
self.assertIsInstance(query, orm.Query)
items = query.all()
self.assertEqual(len(items), 1)
self.assertEqual(items[0].product_scancode, '07430500132')
def test_configure_row_grid(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
self.session.flush()
order.items.append(model.OrderItem(product_id='07430500132',
product_scancode='07430500132',
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_INITIATED))
self.session.flush()
with patch.object(view, 'Session', return_value=self.session):
grid = view.make_grid(model_class=model.OrderItem, data=order.items)
self.assertNotIn('product_scancode', grid.linked_columns)
view.configure_row_grid(grid)
self.assertIn('product_scancode', grid.linked_columns)
def test_render_status_code(self):
enum = self.app.enum
view = self.make_view()
result = view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED)
self.assertEqual(result, "initiated")
self.assertEqual(result, enum.ORDER_ITEM_STATUS[enum.ORDER_ITEM_STATUS_INITIATED])
def test_get_row_action_url_view(self):
self.pyramid_config.add_route('order_items.view', '/order-items/{uuid}')
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
self.session.flush()
item = model.OrderItem(product_id='07430500132',
product_scancode='07430500132',
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
order.items.append(item)
self.session.flush()
url = view.get_row_action_url_view(item, 0)
self.assertIn(f'/order-items/{item.uuid}', url)
class TestOrderItemView(WebTestCase):
def make_view(self):
return mod.OrderItemView(self.request)
def test_get_query(self):
view = self.make_view()
query = view.get_query(session=self.session)
self.assertIsInstance(query, orm.Query)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
grid = view.make_grid(model_class=model.OrderItem)
self.assertNotIn('order_id', grid.linked_columns)
view.configure_grid(grid)
self.assertIn('order_id', grid.linked_columns)
def test_render_order_id(self):
model = self.app.model
view = self.make_view()
order = model.Order(order_id=42)
item = model.OrderItem()
order.items.append(item)
self.assertEqual(view.render_order_id(item, None, None), 42)
def test_render_status_code(self):
enum = self.app.enum
view = self.make_view()
self.assertEqual(view.render_status_code(None, None, enum.ORDER_ITEM_STATUS_INITIATED),
'initiated')
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
item = model.OrderItem(status_code=enum.ORDER_ITEM_STATUS_INITIATED)
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=item)
view.configure_form(form)
schema = form.get_schema()
self.assertIsInstance(schema['order'].typ, OrderRef)
def test_get_xref_buttons(self):
self.pyramid_config.add_route('orders.view', '/orders/{uuid}')
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, created_by=user)
self.session.add(order)
item = model.OrderItem(order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
order.items.append(item)
self.session.flush()
# nb. this requires perms
with patch.object(self.request, 'is_root', new=True):
# one button by default
buttons = view.get_xref_buttons(item)
self.assertEqual(len(buttons), 1)
self.assertIn("View the Order", buttons[0])

View file

@ -0,0 +1,163 @@
# -*- coding: utf-8; -*-
import datetime
from unittest.mock import patch
from pyramid.httpexceptions import HTTPFound
from sideshow.testing import WebTestCase
from sideshow.web.views import products as mod
from sideshow.batch.neworder import NewOrderBatchHandler
class TestIncludeme(WebTestCase):
def test_coverage(self):
mod.includeme(self.pyramid_config)
class TestPendingProductView(WebTestCase):
def make_view(self):
return mod.PendingProductView(self.request)
def test_configure_grid(self):
model = self.app.model
view = self.make_view()
# nb. mostly just getting coverage here
grid = view.make_grid(model_class=model.PendingProduct)
self.assertNotIn('scancode', grid.linked_columns)
self.assertNotIn('brand_name', grid.linked_columns)
self.assertNotIn('description', grid.linked_columns)
view.configure_grid(grid)
self.assertIn('scancode', grid.linked_columns)
self.assertIn('brand_name', grid.linked_columns)
self.assertIn('description', grid.linked_columns)
def test_configure_form(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
# creating
with patch.object(view, 'creating', new=True):
form = view.make_form(model_class=model.PendingProduct)
view.configure_form(form)
self.assertNotIn('status', form)
self.assertNotIn('created', form)
self.assertNotIn('created_by', form)
user = model.User(username='barney')
self.session.add(user)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
self.session.commit()
# viewing
with patch.object(view, 'viewing', new=True):
form = view.make_form(model_instance=product)
view.configure_form(form)
self.assertIn('status', form)
self.assertIn('created', form)
self.assertIn('created_by', form)
def test_make_orders_grid(self):
model = self.app.model
enum = self.app.enum
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
order = model.Order(order_id=42, customer_id=42, created_by=user)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
item = model.OrderItem(pending_product=product,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT,
status_code=enum.ORDER_ITEM_STATUS_INITIATED)
order.items.append(item)
self.session.add(order)
self.session.commit()
# no view perm
grid = view.make_orders_grid(product)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_orders_grid(product)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_make_new_order_batches_grid(self):
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
row = handler.make_row(pending_product=product,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.commit()
# no view perm
grid = view.make_new_order_batches_grid(product)
self.assertEqual(len(grid.actions), 0)
# with view perm
with patch.object(self.request, 'is_root', new=True):
grid = view.make_new_order_batches_grid(product)
self.assertEqual(len(grid.actions), 1)
self.assertEqual(grid.actions[0].key, 'view')
def test_delete_instance(self):
self.pyramid_config.add_route('pending_products.view', '/pending/products/{uuid}')
model = self.app.model
enum = self.app.enum
handler = NewOrderBatchHandler(self.config)
view = self.make_view()
user = model.User(username='barney')
self.session.add(user)
# 1st product is standalone, will be deleted
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
view.delete_instance(product)
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 0)
# 2nd product is attached to new order batch, will not be deleted
batch = handler.make_batch(self.session, created_by=user)
self.session.add(batch)
product = model.PendingProduct(status=enum.PendingProductStatus.PENDING,
created_by=user)
self.session.add(product)
row = handler.make_row(pending_product=product,
order_qty=1, order_uom=enum.ORDER_UOM_UNIT)
handler.add_row(batch, row)
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
self.assertRaises(HTTPFound, view.delete_instance, product)
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
# but after batch is executed, 2nd product can be deleted
batch.executed = datetime.datetime.now()
batch.executed_by = user
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 1)
view.delete_instance(product)
self.session.flush()
self.assertEqual(self.session.query(model.PendingProduct).count(), 0)