not at all clear why, but c621e21cd4 has
introduced some strange bug where the "first" trainwreck query will
raise an error and yet subsequent queries work fine.
so now in the startup sequence we simply force a query, which for
unknown reasons does *not* raise any error, and will somehow magically
prevent the error when a "normal" trainwreck query happens later.
AFAIK this is a "benign" operation and causes no real side effects
other than preventing the strange error. fingers crossed, this is
fine and will not cause further problems...??!
846 lines
30 KiB
Python
846 lines
30 KiB
Python
# -*- coding: utf-8; -*-
|
|
|
|
import os
|
|
import datetime
|
|
import decimal
|
|
from functools import partial
|
|
from unittest import TestCase
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
from wuttjamaican.exc import ConfigurationError
|
|
|
|
from rattail import app as mod
|
|
from rattail.config import RattailConfig
|
|
from rattail.core import Object
|
|
from rattail.db import Session
|
|
from rattail.autocomplete import Autocompleter
|
|
from rattail.batch import BatchHandler
|
|
from rattail.importing import ImportHandler
|
|
from rattail.gpc import GPC
|
|
|
|
|
|
try:
|
|
from rattail.bouncer import BounceHandler
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
|
|
class FooBarBounceHandler(BounceHandler):
|
|
pass
|
|
|
|
|
|
class TestAppHandler(TestCase):
|
|
|
|
def setUp(self):
|
|
self.config = RattailConfig()
|
|
self.app = mod.AppHandler(self.config)
|
|
self.config.app = self.app
|
|
|
|
def test_get_setting(self):
|
|
try:
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import orm
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
Session = orm.sessionmaker()
|
|
engine = sa.create_engine('sqlite://')
|
|
session = Session(bind=engine)
|
|
session.execute(sa.text("""
|
|
create table setting (
|
|
name varchar(255) primary key,
|
|
value text
|
|
);
|
|
"""))
|
|
|
|
# value is null at first
|
|
value = self.app.get_setting(session, 'foo.date')
|
|
self.assertIsNone(value)
|
|
|
|
# but is returned as-is if present
|
|
session.execute(sa.text("insert into setting values ('foo.date', '2023-11-20 15:15:00');"))
|
|
value = self.app.get_setting(session, 'foo.date')
|
|
self.assertEqual(value, '2023-11-20 15:15:00')
|
|
|
|
# and is returned as date if requested
|
|
value = self.app.get_setting(session, 'foo.date', typ='utctime')
|
|
self.assertIsInstance(value, datetime.date)
|
|
|
|
session.close()
|
|
|
|
def test_get_title(self):
|
|
|
|
# default for unconfigured title
|
|
self.assertEqual(self.app.get_title(), "Rattail")
|
|
|
|
# unless default is provided
|
|
self.assertEqual(self.app.get_title(default="Foo"), "Foo")
|
|
|
|
# or title can be configured
|
|
self.config.setdefault('rattail', 'app_title', 'Bar')
|
|
self.assertEqual(self.app.get_title(), "Bar")
|
|
self.assertEqual(self.app.get_title(default="Foo"), "Bar")
|
|
|
|
def test_get_version(self):
|
|
from importlib.metadata import version
|
|
|
|
try:
|
|
from sqlalchemy.orm import Query
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# works with "non-native" objects
|
|
query = Query({})
|
|
ver = self.app.get_version(obj=query)
|
|
self.assertEqual(ver, version('SQLAlchemy'))
|
|
|
|
# can override dist via config
|
|
self.config.setdefault('rattail.app_dist', 'SQLAlchemy')
|
|
ver = self.app.get_version()
|
|
self.assertEqual(ver, version('SQLAlchemy'))
|
|
|
|
# but the provided object takes precedence
|
|
ver = self.app.get_version(obj=query)
|
|
self.assertEqual(ver, version('SQLAlchemy'))
|
|
|
|
# reset
|
|
del self.config.defaults['rattail.app_dist']
|
|
|
|
# can also override package via config
|
|
self.config.setdefault('rattail.app_package', 'mako')
|
|
ver = self.app.get_version()
|
|
self.assertEqual(ver, version('Mako'))
|
|
|
|
def test_get_timezone(self):
|
|
|
|
# unconfigured zone causes error
|
|
self.assertRaises(ConfigurationError, self.app.get_timezone)
|
|
|
|
# or one can be configured
|
|
self.config.setdefault('rattail', 'timezone.default', 'America/Chicago')
|
|
self.assertEqual(str(self.app.get_timezone()), 'America/Chicago')
|
|
|
|
# also can configure alternate zones
|
|
self.assertRaises(ConfigurationError, self.app.get_timezone, key='other')
|
|
self.config.setdefault('rattail', 'timezone.other', 'America/New_York')
|
|
self.assertEqual(str(self.app.get_timezone(key='other')), 'America/New_York')
|
|
|
|
def test_localtime(self):
|
|
|
|
# must define timezone first
|
|
self.config.setdefault('rattail', 'timezone.default', 'America/Chicago')
|
|
|
|
# just confirm the method works on a basic level; the
|
|
# underlying function is tested elsewhere
|
|
now = self.app.localtime()
|
|
self.assertIsNotNone(now)
|
|
|
|
def test_make_utc(self):
|
|
|
|
# just confirm the method works on a basic level; the
|
|
# underlying function is tested elsewhere
|
|
now = self.app.make_utc()
|
|
self.assertIsNotNone(now)
|
|
|
|
def test_get_active_stores(self):
|
|
try:
|
|
import sqlalchemy as sa
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
engine = sa.create_engine('sqlite://')
|
|
model = self.app.model
|
|
model.Base.metadata.create_all(bind=engine)
|
|
session = Session(bind=engine)
|
|
|
|
# no stores by default
|
|
stores = self.app.get_active_stores(session)
|
|
self.assertEqual(len(stores), 0)
|
|
|
|
# add a basic store
|
|
store001 = model.Store(id='001')
|
|
session.add(store001)
|
|
session.flush()
|
|
session.refresh(store001)
|
|
self.assertIsNone(store001.archived)
|
|
|
|
# that one store should be returned
|
|
stores = self.app.get_active_stores(session)
|
|
self.assertEqual(len(stores), 1)
|
|
self.assertIs(stores[0], store001)
|
|
|
|
# archive first store; add another
|
|
store001.archived = True
|
|
store002 = model.Store(id='002')
|
|
session.add(store002)
|
|
session.flush()
|
|
|
|
# now only store 002 should be returned
|
|
stores = self.app.get_active_stores(session)
|
|
self.assertEqual(len(stores), 1)
|
|
self.assertIs(stores[0], store002)
|
|
|
|
session.rollback()
|
|
session.close()
|
|
|
|
def test_get_autocompleter(self):
|
|
try:
|
|
import sqlalchemy
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# built-in autocompleter should be got okay
|
|
from rattail.autocomplete.products import ProductAutocompleter
|
|
autocompleter = self.app.get_autocompleter('products')
|
|
self.assertIsInstance(autocompleter, ProductAutocompleter)
|
|
|
|
# now let's invent one, but first make sure it is not yet valid
|
|
self.assertRaises(ValueError, self.app.get_autocompleter, 'foobars')
|
|
|
|
# okay now configure it and then it should be got okay
|
|
self.config.setdefault('rattail', 'autocomplete.foobars',
|
|
'tests.test_app:FooBarAutocompleter')
|
|
autocompleter = self.app.get_autocompleter('foobars')
|
|
self.assertIsInstance(autocompleter, FooBarAutocompleter)
|
|
|
|
def test_get_auth_handler(self):
|
|
try:
|
|
import sqlalchemy
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# first call gets the default handler
|
|
auth01 = self.app.get_auth_handler()
|
|
self.assertIsNotNone(auth01)
|
|
|
|
# second call gets the same handler instance
|
|
auth02 = self.app.get_auth_handler()
|
|
self.assertIs(auth02, auth01)
|
|
|
|
def test_get_batch_handler(self):
|
|
try:
|
|
import sqlalchemy
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# unknown batch type raises error by default
|
|
self.assertRaises(ValueError, self.app.get_batch_handler, 'foobar')
|
|
|
|
# or returns None if error is suppressed
|
|
bhandler = self.app.get_batch_handler('foobar', error=False)
|
|
self.assertIsNone(bhandler)
|
|
|
|
# but we can provide our own spec
|
|
bhandler = self.app.get_batch_handler(
|
|
'foobar', default='tests.test_app:FooBarBatchHandler')
|
|
self.assertIsInstance(bhandler, FooBarBatchHandler)
|
|
|
|
# we also can configure our handler
|
|
self.config.setdefault('rattail.batch', 'foobar.handler',
|
|
'tests.test_app:FooBarBatchHandler')
|
|
bhandler = self.app.get_batch_handler('foobar')
|
|
self.assertIsInstance(bhandler, FooBarBatchHandler)
|
|
|
|
# for some reason (?) the "importer" batch handler is special
|
|
# and can be returned with no config
|
|
from rattail.batch.importer import ImporterBatchHandler
|
|
bhandler = self.app.get_batch_handler('importer')
|
|
self.assertIsInstance(bhandler, ImporterBatchHandler)
|
|
|
|
def test_get_board_handler(self):
|
|
|
|
# first call gets the default handler
|
|
board01 = self.app.get_board_handler()
|
|
self.assertIsNotNone(board01)
|
|
|
|
# second call gets the same handler instance
|
|
board02 = self.app.get_board_handler()
|
|
self.assertIs(board02, board01)
|
|
|
|
def test_get_bounce_handler(self):
|
|
|
|
try:
|
|
from rattail.bouncer import BounceHandler
|
|
except ImportError:
|
|
pytest.skip("test not relevant without flufl.bounce")
|
|
|
|
# unknown type raises error by default
|
|
self.assertRaises(ValueError, self.app.get_bounce_handler, 'foobar')
|
|
|
|
# but we can configure our own too
|
|
self.config.setdefault('rattail.bouncer', 'foobar.handler',
|
|
'tests.test_app:FooBarBounceHandler')
|
|
bhandler = self.app.get_bounce_handler('foobar')
|
|
self.assertIsInstance(bhandler, FooBarBounceHandler)
|
|
|
|
# default handler is special and works out of the box
|
|
bhandler = self.app.get_bounce_handler('default')
|
|
self.assertIsInstance(bhandler, BounceHandler)
|
|
|
|
def test_get_clientele_handler(self):
|
|
|
|
# first call gets the default handler
|
|
client01 = self.app.get_clientele_handler()
|
|
self.assertIsNotNone(client01)
|
|
|
|
# second call gets the same handler instance
|
|
client02 = self.app.get_clientele_handler()
|
|
self.assertIs(client02, client01)
|
|
|
|
def test_get_custorder_handler(self):
|
|
|
|
# first call gets the default handler
|
|
custorder01 = self.app.get_custorder_handler()
|
|
self.assertIsNotNone(custorder01)
|
|
|
|
# second call gets the same handler instance
|
|
custorder02 = self.app.get_custorder_handler()
|
|
self.assertIs(custorder02, custorder01)
|
|
|
|
def test_get_employment_handler(self):
|
|
|
|
# first call gets the default handler
|
|
employ01 = self.app.get_employment_handler()
|
|
self.assertIsNotNone(employ01)
|
|
|
|
# second call gets the same handler instance
|
|
employ02 = self.app.get_employment_handler()
|
|
self.assertIs(employ02, employ01)
|
|
|
|
def test_get_feature_handler(self):
|
|
|
|
# first call gets the default handler
|
|
feature01 = self.app.get_feature_handler()
|
|
self.assertIsNotNone(feature01)
|
|
|
|
# second call gets the same handler instance
|
|
feature02 = self.app.get_feature_handler()
|
|
self.assertIs(feature02, feature01)
|
|
|
|
def test_get_email_handler(self):
|
|
|
|
# first call gets the default handler
|
|
email01 = self.app.get_email_handler()
|
|
self.assertIsNotNone(email01)
|
|
|
|
# second call gets the same handler instance
|
|
email02 = self.app.get_email_handler()
|
|
self.assertIs(email02, email01)
|
|
|
|
def test_get_all_import_handlers(self):
|
|
try:
|
|
import sqlalchemy
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# several default handlers exist, but not our custom handler
|
|
Handlers = self.app.get_all_import_handlers()
|
|
self.assertTrue(Handlers)
|
|
self.assertNotIn(FromFooToBar, Handlers)
|
|
|
|
# and by default there are no errors to be raised
|
|
Handlers = self.app.get_all_import_handlers(ignore_errors=False)
|
|
self.assertTrue(Handlers)
|
|
|
|
# and just to make sure sorting "works" (no error)
|
|
Handlers = self.app.get_all_import_handlers(sort=True)
|
|
self.assertTrue(Handlers)
|
|
|
|
# finally let's configure a custom handler, and be sure it
|
|
# comes back in the result. note that we must "override" a
|
|
# default importer here, cannot register a new type without
|
|
# creating an entry point
|
|
self.config.setdefault('rattail.importing',
|
|
'to_rattail.from_rattail.import.handler',
|
|
'tests.test_app:FromFooToBar')
|
|
Handlers = self.app.get_all_import_handlers()
|
|
self.assertTrue(Handlers)
|
|
self.assertIn(FromFooToBar, Handlers)
|
|
|
|
def test_get_designated_import_handlers(self):
|
|
try:
|
|
from rattail.importing.rattail import FromRattailToRattailImport
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# several default handlers exist, but not our custom handler
|
|
handlers = self.app.get_designated_import_handlers()
|
|
self.assertTrue(handlers)
|
|
self.assertFalse(any([isinstance(h, FromFooToBar)
|
|
for h in handlers]))
|
|
self.assertTrue(any([isinstance(h, FromRattailToRattailImport)
|
|
for h in handlers]))
|
|
|
|
# we can override a default with custom handler
|
|
self.config.setdefault('rattail.importing',
|
|
'to_rattail.from_rattail.import.handler',
|
|
'tests.test_app:FromFooToBar')
|
|
handlers = self.app.get_designated_import_handlers()
|
|
self.assertTrue(any([isinstance(h, FromFooToBar)
|
|
for h in handlers]))
|
|
self.assertFalse(any([isinstance(h, FromRattailToRattailImport)
|
|
for h in handlers]))
|
|
|
|
# but then original default is included with alternates
|
|
handlers = self.app.get_designated_import_handlers(with_alternates=True)
|
|
matches = [h for h in handlers
|
|
if isinstance(h, FromFooToBar)]
|
|
self.assertEqual(len(matches), 1)
|
|
handler = matches[0]
|
|
self.assertEqual(len(handler.alternate_handlers), 1)
|
|
alternate = handler.alternate_handlers[0]
|
|
self.assertIs(alternate, FromRattailToRattailImport)
|
|
|
|
def test_get_import_handler(self):
|
|
try:
|
|
from rattail.importing.rattail import FromRattailToRattailImport
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
# make sure a basic fetch works
|
|
handler = self.app.get_import_handler('to_rattail.from_rattail.import')
|
|
self.assertIsInstance(handler, FromRattailToRattailImport)
|
|
|
|
# and make sure custom override works
|
|
self.config.setdefault('rattail.importing',
|
|
'to_rattail.from_rattail.import.handler',
|
|
'tests.test_app:FromFooToBar')
|
|
handler = self.app.get_import_handler('to_rattail.from_rattail.import')
|
|
|
|
# unknown importer cannot be found
|
|
handler = self.app.get_import_handler('this_should_not_work')
|
|
self.assertIsNone(handler)
|
|
|
|
# and if we require it, error will raise
|
|
self.assertRaises(ValueError, self.app.get_import_handler,
|
|
'this_should_not_work', require=True)
|
|
|
|
def test_get_designated_import_handler_spec(self):
|
|
|
|
# fetch of unknown key returns none
|
|
spec = self.app.get_designated_import_handler_spec('test01')
|
|
self.assertIsNone(spec)
|
|
|
|
# unless we require it, in which case, error
|
|
self.assertRaises(ValueError, self.app.get_designated_import_handler_spec,
|
|
'test01', require=True)
|
|
|
|
# we configure one for whatever key we like
|
|
self.config.setdefault('rattail.importing',
|
|
'test02.handler',
|
|
'tests.test_app:FromFooToBar')
|
|
spec = self.app.get_designated_import_handler_spec('test02')
|
|
self.assertEqual(spec, 'tests.test_app:FromFooToBar')
|
|
|
|
# we can also define a "default" designated handler
|
|
self.config.setdefault('rattail.importing',
|
|
'test03.default_handler',
|
|
'tests.test_app:FromFooToBar')
|
|
spec = self.app.get_designated_import_handler_spec('test03')
|
|
self.assertEqual(spec, 'tests.test_app:FromFooToBar')
|
|
|
|
# we can also designate handler w/ legacy config
|
|
# TODO: this should be removed at some point, surely?
|
|
self.config.setdefault('rattail.importing',
|
|
'test04.legacy_handler_setting',
|
|
'rattail.importing, test04.custom_handler')
|
|
self.config.setdefault('rattail.importing',
|
|
'test04.custom_handler',
|
|
'tests.test_app:FromFooToBar')
|
|
spec = self.app.get_designated_import_handler_spec('test04')
|
|
self.assertEqual(spec, 'tests.test_app:FromFooToBar')
|
|
|
|
def test_get_label_handler(self):
|
|
|
|
# first call gets the default handler
|
|
labels01 = self.app.get_label_handler()
|
|
self.assertIsNotNone(labels01)
|
|
|
|
# second call gets the same handler instance
|
|
labels02 = self.app.get_label_handler()
|
|
self.assertIs(labels01, labels01)
|
|
|
|
def test_get_membership_handler(self):
|
|
|
|
# first call gets the default handler
|
|
membership01 = self.app.get_membership_handler()
|
|
self.assertIsNotNone(membership01)
|
|
|
|
# second call gets the same handler instance
|
|
membership02 = self.app.get_membership_handler()
|
|
self.assertIs(membership02, membership01)
|
|
|
|
def test_get_products_handler(self):
|
|
|
|
# first call gets the default handler
|
|
products01 = self.app.get_products_handler()
|
|
self.assertIsNotNone(products01)
|
|
|
|
# second call gets the same handler instance
|
|
products02 = self.app.get_products_handler()
|
|
self.assertIs(products02, products01)
|
|
|
|
def test_get_report_handler(self):
|
|
|
|
# first call gets the default handler
|
|
report01 = self.app.get_report_handler()
|
|
self.assertIsNotNone(report01)
|
|
|
|
# second call gets the same handler instance
|
|
report02 = self.app.get_report_handler()
|
|
self.assertIs(report02, report01)
|
|
|
|
def test_get_problem_report_handler(self):
|
|
|
|
# first call gets the default handler
|
|
problems01 = self.app.get_problem_report_handler()
|
|
self.assertIsNotNone(problems01)
|
|
|
|
# second call gets the same handler instance
|
|
problems02 = self.app.get_problem_report_handler()
|
|
self.assertIs(problems02, problems01)
|
|
|
|
def test_get_trainwreck_handler(self):
|
|
|
|
# first call gets the default handler
|
|
trainwreck01 = self.app.get_trainwreck_handler()
|
|
self.assertIsNotNone(trainwreck01)
|
|
|
|
# second call gets the same handler instance
|
|
trainwreck02 = self.app.get_trainwreck_handler()
|
|
self.assertIs(trainwreck02, trainwreck01)
|
|
|
|
def test_get_vendor_handler(self):
|
|
|
|
# first call gets the default handler
|
|
vendor01 = self.app.get_vendor_handler()
|
|
self.assertIsNotNone(vendor01)
|
|
|
|
# second call gets the same handler instance
|
|
vendor02 = self.app.get_vendor_handler()
|
|
self.assertIs(vendor02, vendor01)
|
|
|
|
def test_progress_loop(self):
|
|
from rattail.progress import ProgressBase
|
|
|
|
class NullProgress(ProgressBase):
|
|
pass
|
|
|
|
result = []
|
|
|
|
def inspect(obj, i):
|
|
result.append(obj)
|
|
|
|
# this is just a basic test to get coverage..
|
|
self.app.progress_loop(inspect, range(5), NullProgress)
|
|
self.assertEqual(result, list(range(5)))
|
|
|
|
def test_make_object(self):
|
|
|
|
# basic test
|
|
obj = self.app.make_object()
|
|
self.assertIsNotNone(obj)
|
|
|
|
# make sure attr is set
|
|
obj = self.app.make_object(answer=42)
|
|
self.assertEqual(obj.answer, 42)
|
|
|
|
def test_make_uuid(self):
|
|
uuid = self.app.make_uuid()
|
|
self.assertIsInstance(uuid, str)
|
|
self.assertEqual(len(uuid), 32)
|
|
|
|
def test_get_session(self):
|
|
try:
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import orm
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
engine = sa.create_engine('sqlite://')
|
|
model = self.app.model
|
|
model.Base.metadata.create_all(bind=engine)
|
|
session = Session(bind=engine)
|
|
|
|
# giving an unrelated object raises error
|
|
person = Object()
|
|
self.assertRaises(orm.exc.UnmappedInstanceError,
|
|
self.app.get_session, person)
|
|
|
|
# a related object still may not be in a session
|
|
person = model.Person()
|
|
result = self.app.get_session(person)
|
|
self.assertIsNone(result)
|
|
|
|
# okay then let's add to session, then should work
|
|
session.add(person)
|
|
result = self.app.get_session(person)
|
|
self.assertIs(result, session)
|
|
|
|
session.rollback()
|
|
session.close()
|
|
|
|
def test_make_session(self):
|
|
try:
|
|
import sqlalchemy as sa
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
engine = sa.create_engine('sqlite://')
|
|
model = self.app.model
|
|
model.Base.metadata.create_all(bind=engine)
|
|
|
|
# default behavior should "work" albeit with no engine bound,
|
|
# and no continuum user set
|
|
session = self.app.make_session()
|
|
self.assertIsNotNone(session)
|
|
self.assertIsNone(session.bind)
|
|
self.assertIsNone(session.continuum_user)
|
|
|
|
# okay then let's create one with engine bound, and add a user
|
|
session = self.app.make_session(bind=engine)
|
|
user = model.User(username='ferdinand')
|
|
session.add(user)
|
|
session.commit()
|
|
|
|
# now we can make a session with that user bound
|
|
session = self.app.make_session(bind=engine, user='ferdinand')
|
|
self.assertEqual(session.continuum_user.username, 'ferdinand')
|
|
|
|
# okay add another user, configure it as default, then confirm
|
|
user = model.User(username='beaufort')
|
|
session.add(user)
|
|
session.commit()
|
|
self.config.setdefault('rattail', 'runas.default', 'beaufort')
|
|
session = self.app.make_session(bind=engine)
|
|
self.assertEqual(session.continuum_user.username, 'beaufort')
|
|
|
|
def test_short_session(self):
|
|
short_session = MagicMock()
|
|
mockdb = MagicMock(short_session=short_session)
|
|
|
|
with patch.dict('sys.modules', **{'wuttjamaican.db': mockdb}):
|
|
|
|
with self.app.short_session(foo='bar') as s:
|
|
short_session.assert_called_once()
|
|
# TODO: python 3.7 mock objects do not have attrs for
|
|
# args/kwargs, but once we drop that support we can
|
|
# use those instead of treating call_args as tuple
|
|
self.assertEqual(len(short_session.call_args[1]), 2)
|
|
self.assertEqual(short_session.call_args[1]['foo'], 'bar')
|
|
self.assertIsInstance(short_session.call_args[1]['factory'], partial)
|
|
|
|
def test_cache_model(self):
|
|
try:
|
|
import sqlalchemy as sa
|
|
except ImportError:
|
|
pytest.skip("test is not relevant without sqlalchemy")
|
|
|
|
engine = sa.create_engine('sqlite://')
|
|
model = self.app.model
|
|
model.Base.metadata.create_all(bind=engine)
|
|
session = Session(bind=engine)
|
|
|
|
store001 = model.Store(id='001')
|
|
session.add(store001)
|
|
store002 = model.Store(id='002')
|
|
session.add(store002)
|
|
session.commit()
|
|
|
|
# just do a basic cache to prove the concept
|
|
stores = self.app.cache_model(session, model.Store, key='id')
|
|
self.assertEqual(len(stores), 2)
|
|
self.assertIn('001', stores)
|
|
self.assertIn('002', stores)
|
|
self.assertIs(stores['001'], store001)
|
|
self.assertIs(stores['002'], store002)
|
|
|
|
def test_make_temp_dir(self):
|
|
|
|
# things work with no args
|
|
path = self.app.make_temp_dir()
|
|
self.assertTrue(os.path.exists(path))
|
|
os.rmdir(path)
|
|
|
|
# we can specify an alternate parent dir (in this case also temp)
|
|
parent = self.app.make_temp_dir()
|
|
child = self.app.make_temp_dir(dir=parent)
|
|
self.assertTrue(os.path.exists(child))
|
|
self.assertEqual(os.path.dirname(child), parent)
|
|
os.rmdir(child)
|
|
os.rmdir(parent)
|
|
|
|
# also can configure the workdir, to be used as (indirect) parent
|
|
workdir = self.app.make_temp_dir()
|
|
self.config.setdefault('rattail', 'workdir', workdir)
|
|
child = self.app.make_temp_dir()
|
|
parent = os.path.dirname(child)
|
|
self.assertEqual(os.path.dirname(parent), workdir)
|
|
os.rmdir(child)
|
|
os.rmdir(parent)
|
|
os.rmdir(workdir)
|
|
|
|
def test_make_temp_file(self):
|
|
|
|
# things work with no args
|
|
path = self.app.make_temp_file()
|
|
self.assertTrue(os.path.exists(path))
|
|
os.remove(path)
|
|
|
|
# we can specify an alternate parent dir (in this case also temp)
|
|
parent = self.app.make_temp_dir()
|
|
path = self.app.make_temp_file(dir=parent)
|
|
self.assertTrue(os.path.exists(path))
|
|
self.assertEqual(os.path.dirname(path), parent)
|
|
os.remove(path)
|
|
os.rmdir(parent)
|
|
|
|
# also can configure the workdir, to be used as (indirect) parent
|
|
workdir = self.app.make_temp_dir()
|
|
self.config.setdefault('rattail', 'workdir', workdir)
|
|
path = self.app.make_temp_file()
|
|
self.assertTrue(os.path.exists(path))
|
|
parent = os.path.dirname(path)
|
|
self.assertEqual(os.path.dirname(parent), workdir)
|
|
os.remove(path)
|
|
os.rmdir(parent)
|
|
os.rmdir(workdir)
|
|
|
|
def normalize_phone_number(self):
|
|
|
|
# pre-normalized value is unchanged
|
|
number = '8885551234'
|
|
result = self.app.normalize_phone_number(number)
|
|
self.assertEqual(result, number)
|
|
|
|
# now a basic real-world example
|
|
number = '(888) 555-1234'
|
|
result = self.app.normalize_phone_number(number)
|
|
self.assertEqual(result, '8885551234')
|
|
|
|
# and another for good measure
|
|
number = '888.555.1234'
|
|
result = self.app.normalize_phone_number(number)
|
|
self.assertEqual(result, '8885551234')
|
|
|
|
def test_phone_number_is_invalid(self):
|
|
|
|
# basic real-world example
|
|
self.assertIsNone(self.app.phone_number_is_invalid(
|
|
'(888) 555-1234'))
|
|
|
|
# and another for good measure
|
|
self.assertIsNone(self.app.phone_number_is_invalid(
|
|
'888.555.1234'))
|
|
|
|
# 10 digits are required, so 9 or 11 digits should fail
|
|
self.assertEqual(self.app.phone_number_is_invalid('123456789'),
|
|
"Phone number must have 10 digits")
|
|
self.assertEqual(self.app.phone_number_is_invalid('12345678901'),
|
|
"Phone number must have 10 digits")
|
|
|
|
def test_format_phone_number(self):
|
|
|
|
# basic real-world example
|
|
result = self.app.format_phone_number('8885551234')
|
|
self.assertEqual(result, '(888) 555-1234')
|
|
|
|
# garbage in garbage out
|
|
result = self.app.format_phone_number('garbage')
|
|
self.assertEqual(result, 'garbage')
|
|
|
|
def test_make_gpc(self):
|
|
|
|
# basic real-world example
|
|
result = self.app.make_gpc('074305001321')
|
|
self.assertIsInstance(result, GPC)
|
|
self.assertEqual(str(result), '00074305001321')
|
|
|
|
# and let it calculate check digit
|
|
result = self.app.make_gpc('7430500132', calc_check_digit='upc')
|
|
self.assertIsInstance(result, GPC)
|
|
self.assertEqual(str(result), '00074305001321')
|
|
|
|
def test_render_gpc(self):
|
|
|
|
# basic real-world example
|
|
gpc = GPC('00074305001321')
|
|
result = self.app.render_gpc(gpc)
|
|
self.assertEqual(result, '0007430500132-1')
|
|
|
|
def test_render_currency(self):
|
|
|
|
# basic decimal example
|
|
value = decimal.Decimal('42.00')
|
|
self.assertEqual(self.app.render_currency(value), '$42.00')
|
|
|
|
# basic float example
|
|
value = 42.00
|
|
self.assertEqual(self.app.render_currency(value), '$42.00')
|
|
|
|
# decimal places will be rounded
|
|
value = decimal.Decimal('42.12345')
|
|
self.assertEqual(self.app.render_currency(value), '$42.12')
|
|
|
|
# but we can declare the scale
|
|
value = decimal.Decimal('42.12345')
|
|
self.assertEqual(self.app.render_currency(value, scale=4), '$42.1234')
|
|
|
|
# negative numbers get parens
|
|
value = decimal.Decimal('-42.42')
|
|
self.assertEqual(self.app.render_currency(value), '($42.42)')
|
|
|
|
def test_render_quantity(self):
|
|
|
|
# integer decimals become integers
|
|
value = decimal.Decimal('1.000')
|
|
self.assertEqual(self.app.render_quantity(value), '1')
|
|
|
|
# but decimal places are preserved
|
|
value = decimal.Decimal('1.234')
|
|
self.assertEqual(self.app.render_quantity(value), '1.234')
|
|
|
|
def test_render_cases_units(self):
|
|
|
|
# basic examples, note the singular noun
|
|
self.assertEqual(self.app.render_cases_units(1, None), '1 case')
|
|
self.assertEqual(self.app.render_cases_units(None, 1), '1 unit')
|
|
|
|
# mix it up a bit
|
|
self.assertEqual(self.app.render_cases_units(3, 2), '3 cases + 2 units')
|
|
|
|
# also note that zero is not hidden
|
|
self.assertEqual(self.app.render_cases_units(3, 0), '3 cases + 0 units')
|
|
|
|
def test_render_date(self):
|
|
|
|
# basic example
|
|
date = datetime.date(2021, 12, 31)
|
|
self.assertEqual(self.app.render_date(date), '2021-12-31')
|
|
|
|
def test_render_datetime(self):
|
|
|
|
# basic example
|
|
dt = datetime.datetime(2021, 12, 31, 8, 30)
|
|
self.assertEqual(self.app.render_datetime(dt), '2021-12-31 08:30:00 AM')
|
|
|
|
@patch('rattail.app.send_email')
|
|
def test_send_email(self, send_email):
|
|
|
|
# just make sure underlying function is invoked..
|
|
self.app.send_email('test')
|
|
send_email.assert_called()
|
|
|
|
|
|
class FooBarAutocompleter(Autocompleter):
|
|
autocompleter_key = 'foobars'
|
|
|
|
|
|
class FooBarBatchHandler(BatchHandler):
|
|
pass
|
|
|
|
|
|
class FromFooToBar(ImportHandler):
|
|
host_key = 'rattail'
|
|
local_key = 'rattail'
|