apparently passlib has not been updated in years, and the combo with latest bcrypt v5 was causing errors https://github.com/pyca/bcrypt/issues/1082 https://github.com/pyca/bcrypt/issues/1079 https://foss.heptapod.net/python-libs/passlib/-/issues/196
		
			
				
	
	
		
			852 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			852 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: utf-8; -*-
 | |
| 
 | |
| import datetime
 | |
| import decimal
 | |
| import os
 | |
| import shutil
 | |
| import sys
 | |
| import tempfile
 | |
| import warnings
 | |
| import uuid as _uuid
 | |
| from unittest import TestCase
 | |
| from unittest.mock import patch, MagicMock
 | |
| 
 | |
| import pytest
 | |
| from mako.template import Template
 | |
| 
 | |
| import wuttjamaican.enum
 | |
| from wuttjamaican import app as mod
 | |
| from wuttjamaican.progress import ProgressBase
 | |
| from wuttjamaican.conf import WuttaConfig
 | |
| from wuttjamaican.util import UNSPECIFIED
 | |
| from wuttjamaican.testing import FileTestCase, ConfigTestCase
 | |
| from wuttjamaican.batch import BatchHandler
 | |
| 
 | |
| 
 | |
| class MockBatchHandler(BatchHandler):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class AnotherBatchHandler(BatchHandler):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class TestAppHandler(FileTestCase):
 | |
| 
 | |
|     def setUp(self):
 | |
|         self.setup_files()
 | |
|         self.config = WuttaConfig(appname="wuttatest")
 | |
|         self.app = mod.AppHandler(self.config)
 | |
|         self.config.app = self.app
 | |
| 
 | |
|     def test_init(self):
 | |
|         self.assertIs(self.app.config, self.config)
 | |
|         self.assertEqual(self.app.handlers, {})
 | |
|         self.assertEqual(self.app.appname, "wuttatest")
 | |
| 
 | |
|     def test_get_enum(self):
 | |
|         self.assertIs(self.app.get_enum(), wuttjamaican.enum)
 | |
| 
 | |
|     def test_load_object(self):
 | |
| 
 | |
|         # just confirm the method works on a basic level; the
 | |
|         # underlying function is tested elsewhere
 | |
|         obj = self.app.load_object("wuttjamaican.util:UNSPECIFIED")
 | |
|         self.assertIs(obj, UNSPECIFIED)
 | |
| 
 | |
|     def test_get_appdir(self):
 | |
| 
 | |
|         mockdir = self.mkdir("mockdir")
 | |
| 
 | |
|         # default appdir
 | |
|         with patch.object(sys, "prefix", new=mockdir):
 | |
| 
 | |
|             # default is returned by default
 | |
|             appdir = self.app.get_appdir()
 | |
|             self.assertEqual(appdir, os.path.join(mockdir, "app"))
 | |
| 
 | |
|             # but not if caller wants config only
 | |
|             appdir = self.app.get_appdir(configured_only=True)
 | |
|             self.assertIsNone(appdir)
 | |
| 
 | |
|             # also, cannot create if appdir path not known
 | |
|             self.assertRaises(
 | |
|                 ValueError, self.app.get_appdir, configured_only=True, create=True
 | |
|             )
 | |
| 
 | |
|         # configured appdir
 | |
|         self.config.setdefault("wuttatest.appdir", mockdir)
 | |
|         appdir = self.app.get_appdir()
 | |
|         self.assertEqual(appdir, mockdir)
 | |
| 
 | |
|         # appdir w/ subpath
 | |
|         appdir = self.app.get_appdir("foo", "bar")
 | |
|         self.assertEqual(appdir, os.path.join(mockdir, "foo", "bar"))
 | |
| 
 | |
|         # subpath is created
 | |
|         self.assertEqual(len(os.listdir(mockdir)), 0)
 | |
|         appdir = self.app.get_appdir("foo", "bar", create=True)
 | |
|         self.assertEqual(appdir, os.path.join(mockdir, "foo", "bar"))
 | |
|         self.assertEqual(os.listdir(mockdir), ["foo"])
 | |
|         self.assertEqual(os.listdir(os.path.join(mockdir, "foo")), ["bar"])
 | |
| 
 | |
|     def test_make_appdir(self):
 | |
| 
 | |
|         # appdir is created, and 3 subfolders added by default
 | |
|         tempdir = tempfile.mkdtemp()
 | |
|         appdir = os.path.join(tempdir, "app")
 | |
|         self.assertFalse(os.path.exists(appdir))
 | |
|         self.app.make_appdir(appdir)
 | |
|         self.assertTrue(os.path.exists(appdir))
 | |
|         self.assertEqual(len(os.listdir(appdir)), 4)
 | |
|         shutil.rmtree(tempdir)
 | |
| 
 | |
|         # subfolders still added if appdir already exists
 | |
|         tempdir = tempfile.mkdtemp()
 | |
|         self.assertTrue(os.path.exists(tempdir))
 | |
|         self.assertEqual(len(os.listdir(tempdir)), 0)
 | |
|         self.app.make_appdir(tempdir)
 | |
|         self.assertEqual(len(os.listdir(tempdir)), 4)
 | |
|         shutil.rmtree(tempdir)
 | |
| 
 | |
|     def test_render_mako_template(self):
 | |
|         output_conf = self.write_file("output.conf", "")
 | |
|         template = Template(
 | |
|             """\
 | |
| [wutta]
 | |
| app_title = WuttaTest
 | |
| """
 | |
|         )
 | |
|         output = self.app.render_mako_template(template, {}, output_path=output_conf)
 | |
|         self.assertEqual(
 | |
|             output,
 | |
|             """\
 | |
| [wutta]
 | |
| app_title = WuttaTest
 | |
| """,
 | |
|         )
 | |
| 
 | |
|         with open(output_conf, "rt") as f:
 | |
|             self.assertEqual(f.read(), output)
 | |
| 
 | |
|     def test_resource_path(self):
 | |
|         result = self.app.resource_path("wuttjamaican:templates")
 | |
|         self.assertEqual(
 | |
|             result, os.path.join(os.path.dirname(mod.__file__), "templates")
 | |
|         )
 | |
| 
 | |
|     def test_make_session(self):
 | |
|         try:
 | |
|             from wuttjamaican import db
 | |
|         except ImportError:
 | |
|             pytest.skip("test is not relevant without sqlalchemy")
 | |
| 
 | |
|         session = self.app.make_session()
 | |
|         self.assertIsInstance(session, db.Session.class_)
 | |
| 
 | |
|     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_with(
 | |
|                     foo="bar", factory=self.app.make_session
 | |
|                 )
 | |
| 
 | |
|     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
 | |
|         );
 | |
|         """
 | |
|             )
 | |
|         )
 | |
|         session.commit()
 | |
| 
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|         session.execute(sa.text("insert into setting values ('foo', 'bar');"))
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertEqual(value, "bar")
 | |
| 
 | |
|     def test_save_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
 | |
|         );
 | |
|         """
 | |
|             )
 | |
|         )
 | |
|         session.commit()
 | |
| 
 | |
|         # value null by default
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|         # unless we save a value
 | |
|         self.app.save_setting(session, "foo", "1")
 | |
|         session.commit()
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertEqual(value, "1")
 | |
| 
 | |
|     def test_delete_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
 | |
|         );
 | |
|         """
 | |
|             )
 | |
|         )
 | |
|         session.commit()
 | |
| 
 | |
|         # value null by default
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|         # unless we save a value
 | |
|         self.app.save_setting(session, "foo", "1")
 | |
|         session.commit()
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertEqual(value, "1")
 | |
| 
 | |
|         # but then if we delete it, should be null again
 | |
|         self.app.delete_setting(session, "foo")
 | |
|         session.commit()
 | |
|         value = self.app.get_setting(session, "foo")
 | |
|         self.assertIsNone(value)
 | |
| 
 | |
|     def test_continuum_is_enabled(self):
 | |
| 
 | |
|         # false by default
 | |
|         with patch.object(self.app, "providers", new={}):
 | |
|             self.assertFalse(self.app.continuum_is_enabled())
 | |
| 
 | |
|         # but "any" provider technically could enable it...
 | |
|         class MockProvider:
 | |
|             def continuum_is_enabled(self):
 | |
|                 return True
 | |
| 
 | |
|         with patch.object(self.app, "providers", new={"mock": MockProvider()}):
 | |
|             self.assertTrue(self.app.continuum_is_enabled())
 | |
| 
 | |
|     def test_model(self):
 | |
|         try:
 | |
|             from wuttjamaican.db import model
 | |
|         except ImportError:
 | |
|             pytest.skip("test not relevant without sqlalchemy")
 | |
| 
 | |
|         self.assertNotIn("model", self.app.__dict__)
 | |
|         self.assertIs(self.app.model, model)
 | |
| 
 | |
|     def test_get_model(self):
 | |
|         try:
 | |
|             from wuttjamaican.db import model
 | |
|         except ImportError:
 | |
|             pytest.skip("test not relevant without sqlalchemy")
 | |
| 
 | |
|         self.assertIs(self.app.get_model(), model)
 | |
| 
 | |
|     def test_get_title(self):
 | |
|         self.assertEqual(self.app.get_title(), "WuttJamaican")
 | |
| 
 | |
|     def test_get_node_title(self):
 | |
| 
 | |
|         # default
 | |
|         self.assertEqual(self.app.get_node_title(), "WuttJamaican")
 | |
| 
 | |
|         # will fallback to app title
 | |
|         self.config.setdefault("wuttatest.app_title", "WuttaTest")
 | |
|         self.assertEqual(self.app.get_node_title(), "WuttaTest")
 | |
| 
 | |
|         # will read from config
 | |
|         self.config.setdefault("wuttatest.node_title", "WuttaNode")
 | |
|         self.assertEqual(self.app.get_node_title(), "WuttaNode")
 | |
| 
 | |
|     def test_get_node_type(self):
 | |
| 
 | |
|         # default
 | |
|         self.assertIsNone(self.app.get_node_type())
 | |
| 
 | |
|         # will read from config
 | |
|         self.config.setdefault("wuttatest.node_type", "warehouse")
 | |
|         self.assertEqual(self.app.get_node_type(), "warehouse")
 | |
| 
 | |
|     def test_get_distribution(self):
 | |
| 
 | |
|         try:
 | |
|             from sqlalchemy.orm import Query
 | |
|         except ImportError:
 | |
|             pytest.skip("test is not relevant without sqlalchemy")
 | |
| 
 | |
|         # works with "non-native" objects
 | |
|         query = Query({})
 | |
|         dist = self.app.get_distribution(query)
 | |
|         self.assertEqual(dist, "SQLAlchemy")
 | |
| 
 | |
|         # can override dist via config
 | |
|         self.config.setdefault("wuttatest.app_dist", "importlib_metadata")
 | |
|         dist = self.app.get_distribution()
 | |
|         self.assertEqual(dist, "importlib_metadata")
 | |
| 
 | |
|         # but the provided object takes precedence
 | |
|         dist = self.app.get_distribution(query)
 | |
|         self.assertEqual(dist, "SQLAlchemy")
 | |
| 
 | |
|     def test_get_distribution_pre_python_3_10(self):
 | |
| 
 | |
|         # the goal here is to get coverage for code which would only
 | |
|         # run on python 3,9 and older, but we only need that coverage
 | |
|         # if we are currently testing python 3.10+
 | |
|         if sys.version_info.major == 3 and sys.version_info.minor < 10:
 | |
|             pytest.skip("this test is not relevant before python 3.10")
 | |
| 
 | |
|         importlib_metadata = MagicMock()
 | |
|         importlib_metadata.packages_distributions = MagicMock(
 | |
|             return_value={
 | |
|                 "wuttjamaican": ["WuttJamaican"],
 | |
|                 "config": ["python-configuration"],
 | |
|             }
 | |
|         )
 | |
| 
 | |
|         orig_import = __import__
 | |
| 
 | |
|         def mock_import(name, *args, **kwargs):
 | |
|             if name == "importlib.metadata":
 | |
|                 raise ImportError
 | |
|             if name == "importlib_metadata":
 | |
|                 return importlib_metadata
 | |
|             return orig_import(name, *args, **kwargs)
 | |
| 
 | |
|         with patch("builtins.__import__", side_effect=mock_import):
 | |
| 
 | |
|             # default should always be WuttJamaican (right..?)
 | |
|             dist = self.app.get_distribution()
 | |
|             self.assertEqual(dist, "WuttJamaican")
 | |
| 
 | |
|             # also works with "non-native" objects
 | |
|             from config import Configuration
 | |
| 
 | |
|             config = Configuration({})
 | |
|             dist = self.app.get_distribution(config)
 | |
|             self.assertEqual(dist, "python-configuration")
 | |
| 
 | |
|             # hacky sort of test, just in case we can't deduce the
 | |
|             # package dist based on the obj - easy enough since we
 | |
|             # have limited the packages_distributions() above
 | |
|             dist = self.app.get_distribution(42)
 | |
|             self.assertIsNone(dist)
 | |
| 
 | |
|             # can override dist via config
 | |
|             self.config.setdefault("wuttatest.app_dist", "importlib_metadata")
 | |
|             dist = self.app.get_distribution()
 | |
|             self.assertEqual(dist, "importlib_metadata")
 | |
| 
 | |
|             # but the provided object takes precedence
 | |
|             dist = self.app.get_distribution(config)
 | |
|             self.assertEqual(dist, "python-configuration")
 | |
| 
 | |
|             # hacky test again, this time config override should win
 | |
|             dist = self.app.get_distribution(42)
 | |
|             self.assertEqual(dist, "importlib_metadata")
 | |
| 
 | |
|     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"))
 | |
| 
 | |
|         # random object will not yield a dist nor version
 | |
|         ver = self.app.get_version(obj=42)
 | |
|         self.assertIsNone(ver)
 | |
| 
 | |
|         # can override dist via config
 | |
|         self.config.setdefault("wuttatest.app_dist", "python-configuration")
 | |
|         ver = self.app.get_version()
 | |
|         self.assertEqual(ver, version("python-configuration"))
 | |
| 
 | |
|         # but the provided object takes precedence
 | |
|         ver = self.app.get_version(obj=query)
 | |
|         self.assertEqual(ver, version("SQLAlchemy"))
 | |
| 
 | |
|         # can also specify the dist
 | |
|         ver = self.app.get_version(dist="progress")
 | |
|         self.assertEqual(ver, version("progress"))
 | |
| 
 | |
|     def test_make_title(self):
 | |
|         text = self.app.make_title("foo_bar")
 | |
|         self.assertEqual(text, "Foo Bar")
 | |
| 
 | |
|     def test_make_full_name(self):
 | |
|         name = self.app.make_full_name("Fred", "", "Flintstone", "")
 | |
|         self.assertEqual(name, "Fred Flintstone")
 | |
| 
 | |
|     def test_make_uuid(self):
 | |
|         uuid = self.app.make_uuid()
 | |
|         self.assertEqual(len(uuid), 32)
 | |
| 
 | |
|     def test_make_true_uuid(self):
 | |
|         uuid = self.app.make_true_uuid()
 | |
|         self.assertIsInstance(uuid, _uuid.UUID)
 | |
| 
 | |
|     def test_progress_loop(self):
 | |
| 
 | |
|         def act(obj, i):
 | |
|             pass
 | |
| 
 | |
|         # with progress
 | |
|         self.app.progress_loop(act, [1, 2, 3], ProgressBase, message="whatever")
 | |
| 
 | |
|         # without progress
 | |
|         self.app.progress_loop(act, [1, 2, 3], None, message="whatever")
 | |
| 
 | |
|     def test_get_session(self):
 | |
|         try:
 | |
|             import sqlalchemy as sa
 | |
|             from sqlalchemy import orm
 | |
|         except ImportError:
 | |
|             pytest.skip("test not relevant without sqlalchemy")
 | |
| 
 | |
|         model = self.app.model
 | |
|         user = model.User()
 | |
|         self.assertIsNone(self.app.get_session(user))
 | |
| 
 | |
|         Session = orm.sessionmaker()
 | |
|         engine = sa.create_engine("sqlite://")
 | |
|         mysession = Session(bind=engine)
 | |
|         mysession.add(user)
 | |
|         session = self.app.get_session(user)
 | |
|         self.assertIs(session, mysession)
 | |
| 
 | |
|     def test_render_boolean(self):
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(self.app.render_boolean(None), "")
 | |
| 
 | |
|         # true
 | |
|         self.assertEqual(self.app.render_boolean(True), "Yes")
 | |
| 
 | |
|         # false
 | |
|         self.assertEqual(self.app.render_boolean(False), "No")
 | |
| 
 | |
|     def test_render_currency(self):
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(self.app.render_currency(None), "")
 | |
| 
 | |
|         # 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_date(self):
 | |
|         self.assertEqual(self.app.render_date(None), "")
 | |
| 
 | |
|         dt = datetime.date(2024, 12, 11)
 | |
|         self.assertEqual(self.app.render_date(dt), "2024-12-11")
 | |
| 
 | |
|     def test_render_datetime(self):
 | |
|         self.assertEqual(self.app.render_datetime(None), "")
 | |
| 
 | |
|         dt = datetime.datetime(2024, 12, 11, 8, 30, tzinfo=datetime.timezone.utc)
 | |
|         self.assertEqual(self.app.render_datetime(dt), "2024-12-11 08:30+0000")
 | |
| 
 | |
|     def test_render_error(self):
 | |
| 
 | |
|         # with description
 | |
|         try:
 | |
|             raise RuntimeError("just testin")
 | |
|         except Exception as error:
 | |
|             result = self.app.render_error(error)
 | |
|         self.assertEqual(result, "RuntimeError: just testin")
 | |
| 
 | |
|         # without description
 | |
|         try:
 | |
|             raise RuntimeError
 | |
|         except Exception as error:
 | |
|             result = self.app.render_error(error)
 | |
|         self.assertEqual(result, "RuntimeError")
 | |
| 
 | |
|     def test_render_percent(self):
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(self.app.render_percent(None), "")
 | |
| 
 | |
|         # typical
 | |
|         self.assertEqual(self.app.render_percent(12.3419), "12.34 %")
 | |
| 
 | |
|         # more decimal places
 | |
|         self.assertEqual(self.app.render_percent(12.3419, decimals=3), "12.342 %")
 | |
|         self.assertEqual(self.app.render_percent(12.3419, decimals=4), "12.3419 %")
 | |
| 
 | |
|         # negative
 | |
|         self.assertEqual(self.app.render_percent(-12.3419), "(12.34 %)")
 | |
|         self.assertEqual(self.app.render_percent(-12.3419, decimals=3), "(12.342 %)")
 | |
| 
 | |
|     def test_render_quantity(self):
 | |
| 
 | |
|         # null
 | |
|         self.assertEqual(self.app.render_quantity(None), "")
 | |
| 
 | |
|         # 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")
 | |
| 
 | |
|         # zero can be empty string
 | |
|         self.assertEqual(self.app.render_quantity(0), "0")
 | |
|         self.assertEqual(self.app.render_quantity(0, empty_zero=True), "")
 | |
| 
 | |
|     def test_render_time_ago(self):
 | |
|         with patch.object(mod, "humanize") as humanize:
 | |
|             humanize.naturaltime.return_value = "now"
 | |
|             now = datetime.datetime.now()
 | |
|             result = self.app.render_time_ago(now)
 | |
|             self.assertEqual(result, "now")
 | |
|             humanize.naturaltime.assert_called_once_with(now)
 | |
| 
 | |
|     def test_get_person(self):
 | |
|         people = self.app.get_people_handler()
 | |
|         with patch.object(people, "get_person") as get_person:
 | |
|             get_person.return_value = "foo"
 | |
|             person = self.app.get_person("bar")
 | |
|             get_person.assert_called_once_with("bar")
 | |
|             self.assertEqual(person, "foo")
 | |
| 
 | |
|     def test_get_auth_handler(self):
 | |
|         from wuttjamaican.auth import AuthHandler
 | |
| 
 | |
|         auth = self.app.get_auth_handler()
 | |
|         self.assertIsInstance(auth, AuthHandler)
 | |
| 
 | |
|     def test_get_batch_handler(self):
 | |
| 
 | |
|         # error if handler not found
 | |
|         self.assertRaises(KeyError, self.app.get_batch_handler, "CannotFindMe!")
 | |
| 
 | |
|         # caller can specify default
 | |
|         handler = self.app.get_batch_handler(
 | |
|             "foo", default="wuttjamaican.batch:BatchHandler"
 | |
|         )
 | |
|         self.assertIsInstance(handler, BatchHandler)
 | |
| 
 | |
|         # default can be configured
 | |
|         self.config.setdefault(
 | |
|             "wuttatest.batch.foo.handler.default_spec",
 | |
|             "wuttjamaican.batch:BatchHandler",
 | |
|         )
 | |
|         handler = self.app.get_batch_handler("foo")
 | |
|         self.assertIsInstance(handler, BatchHandler)
 | |
| 
 | |
|         # preference can be configured
 | |
|         self.config.setdefault(
 | |
|             "wuttatest.batch.foo.handler.spec", "tests.test_app:MockBatchHandler"
 | |
|         )
 | |
|         handler = self.app.get_batch_handler("foo")
 | |
|         self.assertIsInstance(handler, MockBatchHandler)
 | |
| 
 | |
|     def test_get_batch_handler_specs(self):
 | |
| 
 | |
|         # empty by default
 | |
|         specs = self.app.get_batch_handler_specs("foo")
 | |
|         self.assertEqual(specs, [])
 | |
| 
 | |
|         # caller can specify default as string
 | |
|         specs = self.app.get_batch_handler_specs(
 | |
|             "foo", default="wuttjamaican.batch:BatchHandler"
 | |
|         )
 | |
|         self.assertEqual(specs, ["wuttjamaican.batch:BatchHandler"])
 | |
| 
 | |
|         # caller can specify default as list
 | |
|         specs = self.app.get_batch_handler_specs(
 | |
|             "foo",
 | |
|             default=[
 | |
|                 "wuttjamaican.batch:BatchHandler",
 | |
|                 "tests.test_app:MockBatchHandler",
 | |
|             ],
 | |
|         )
 | |
|         self.assertEqual(
 | |
|             specs,
 | |
|             ["wuttjamaican.batch:BatchHandler", "tests.test_app:MockBatchHandler"],
 | |
|         )
 | |
| 
 | |
|         # default can be configured
 | |
|         self.config.setdefault(
 | |
|             "wuttatest.batch.foo.handler.default_spec",
 | |
|             "wuttjamaican.batch:BatchHandler",
 | |
|         )
 | |
|         specs = self.app.get_batch_handler_specs("foo")
 | |
|         self.assertEqual(specs, ["wuttjamaican.batch:BatchHandler"])
 | |
| 
 | |
|         # the rest come from entry points
 | |
|         with patch.object(
 | |
|             mod,
 | |
|             "load_entry_points",
 | |
|             return_value={
 | |
|                 "mock": MockBatchHandler,
 | |
|                 "another": AnotherBatchHandler,
 | |
|             },
 | |
|         ):
 | |
|             specs = self.app.get_batch_handler_specs("foo")
 | |
|             self.assertEqual(
 | |
|                 specs,
 | |
|                 [
 | |
|                     "wuttjamaican.batch:BatchHandler",
 | |
|                     "tests.test_app:AnotherBatchHandler",
 | |
|                     "tests.test_app:MockBatchHandler",
 | |
|                 ],
 | |
|             )
 | |
| 
 | |
|     def test_get_db_handler(self):
 | |
|         try:
 | |
|             from wuttjamaican.db.handler import DatabaseHandler
 | |
|         except ImportError:
 | |
|             pytest.skip("test not relevant without sqlalchemy")
 | |
| 
 | |
|         db = self.app.get_db_handler()
 | |
|         self.assertIsInstance(db, DatabaseHandler)
 | |
| 
 | |
|     def test_get_email_handler(self):
 | |
|         from wuttjamaican.email import EmailHandler
 | |
| 
 | |
|         mail = self.app.get_email_handler()
 | |
|         self.assertIsInstance(mail, EmailHandler)
 | |
| 
 | |
|     def test_get_install_handler(self):
 | |
|         from wuttjamaican.install import InstallHandler
 | |
| 
 | |
|         install = self.app.get_install_handler()
 | |
|         self.assertIsInstance(install, InstallHandler)
 | |
| 
 | |
|     def test_get_people_handler(self):
 | |
|         from wuttjamaican.people import PeopleHandler
 | |
| 
 | |
|         people = self.app.get_people_handler()
 | |
|         self.assertIsInstance(people, PeopleHandler)
 | |
| 
 | |
|     def test_get_problem_handler(self):
 | |
|         from wuttjamaican.problems import ProblemHandler
 | |
| 
 | |
|         handler = self.app.get_problem_handler()
 | |
|         self.assertIsInstance(handler, ProblemHandler)
 | |
| 
 | |
|     def test_get_report_handler(self):
 | |
|         from wuttjamaican.reports import ReportHandler
 | |
| 
 | |
|         handler = self.app.get_report_handler()
 | |
|         self.assertIsInstance(handler, ReportHandler)
 | |
| 
 | |
|     def test_send_email(self):
 | |
|         from wuttjamaican.email import EmailHandler
 | |
| 
 | |
|         with patch.object(EmailHandler, "send_email") as send_email:
 | |
|             self.app.send_email("foo")
 | |
|             send_email.assert_called_once_with("foo")
 | |
| 
 | |
| 
 | |
| class TestAppProvider(TestCase):
 | |
| 
 | |
|     def setUp(self):
 | |
|         self.config = WuttaConfig(appname="wuttatest")
 | |
|         self.app = mod.AppHandler(self.config)
 | |
|         self.config._app = self.app
 | |
| 
 | |
|     def test_constructor(self):
 | |
| 
 | |
|         # config object is expected
 | |
|         provider = mod.AppProvider(self.config)
 | |
|         self.assertIs(provider.config, self.config)
 | |
|         self.assertIs(provider.app, self.app)
 | |
|         self.assertEqual(provider.appname, "wuttatest")
 | |
| 
 | |
|         # but can pass app handler instead
 | |
|         with warnings.catch_warnings():
 | |
|             warnings.filterwarnings("ignore", category=DeprecationWarning)
 | |
|             provider = mod.AppProvider(self.app)
 | |
|         self.assertIs(provider.config, self.config)
 | |
|         self.assertIs(provider.app, self.app)
 | |
| 
 | |
|     def test_get_all_providers(self):
 | |
| 
 | |
|         class FakeProvider(mod.AppProvider):
 | |
|             pass
 | |
| 
 | |
|         # nb. we specify *classes* here
 | |
|         fake_providers = {"fake": FakeProvider}
 | |
| 
 | |
|         with patch("wuttjamaican.app.load_entry_points") as load_entry_points:
 | |
|             load_entry_points.return_value = fake_providers
 | |
| 
 | |
|             # sanity check, we get *instances* back from this
 | |
|             providers = self.app.get_all_providers()
 | |
|             load_entry_points.assert_called_once_with("wutta.app.providers")
 | |
|             self.assertEqual(len(providers), 1)
 | |
|             self.assertIn("fake", providers)
 | |
|             self.assertIsInstance(providers["fake"], FakeProvider)
 | |
| 
 | |
|     def test_hasattr(self):
 | |
| 
 | |
|         class FakeProvider(mod.AppProvider):
 | |
|             def fake_foo(self):
 | |
|                 pass
 | |
| 
 | |
|         self.app.providers = {"fake": FakeProvider(self.config)}
 | |
| 
 | |
|         self.assertTrue(hasattr(self.app, "fake_foo"))
 | |
|         self.assertFalse(hasattr(self.app, "fake_method_does_not_exist"))
 | |
| 
 | |
|     def test_getattr(self):
 | |
| 
 | |
|         # enum
 | |
|         self.assertNotIn("enum", self.app.__dict__)
 | |
|         self.assertIs(self.app.enum, wuttjamaican.enum)
 | |
| 
 | |
|         # now we test that providers are loaded...
 | |
| 
 | |
|         class FakeProvider(mod.AppProvider):
 | |
|             def fake_foo(self):
 | |
|                 return 42
 | |
| 
 | |
|         # nb. using instances here
 | |
|         fake_providers = {"fake": FakeProvider(self.config)}
 | |
| 
 | |
|         with patch.object(self.app, "get_all_providers") as get_all_providers:
 | |
|             get_all_providers.return_value = fake_providers
 | |
| 
 | |
|             self.assertNotIn("providers", self.app.__dict__)
 | |
|             self.assertIs(self.app.providers, fake_providers)
 | |
|             get_all_providers.assert_called_once_with()
 | |
| 
 | |
|     def test_getattr_model(self):
 | |
|         try:
 | |
|             import wuttjamaican.db.model
 | |
|         except ImportError:
 | |
|             pytest.skip("test not relevant without sqlalchemy")
 | |
| 
 | |
|         # model
 | |
|         self.assertNotIn("model", self.app.__dict__)
 | |
|         self.assertIs(self.app.model, wuttjamaican.db.model)
 | |
| 
 | |
|     def test_getattr_providers(self):
 | |
| 
 | |
|         # collection of providers is loaded on demand
 | |
|         self.assertNotIn("providers", self.app.__dict__)
 | |
|         self.assertIsNotNone(self.app.providers)
 | |
| 
 | |
|         # custom attr does not exist yet
 | |
|         self.assertRaises(AttributeError, getattr, self.app, "foo_value")
 | |
| 
 | |
|         # but provider can supply the attr
 | |
|         self.app.providers["mytest"] = MagicMock(foo_value="bar")
 | |
|         self.assertEqual(self.app.foo_value, "bar")
 | |
| 
 | |
| 
 | |
| class TestGenericHandler(ConfigTestCase):
 | |
| 
 | |
|     def make_config(self, **kw):
 | |
|         kw.setdefault("appname", "wuttatest")
 | |
|         return super().make_config(**kw)
 | |
| 
 | |
|     def make_handler(self, **kwargs):
 | |
|         return mod.GenericHandler(self.config, **kwargs)
 | |
| 
 | |
|     def test_constructor(self):
 | |
|         handler = mod.GenericHandler(self.config)
 | |
|         self.assertIs(handler.config, self.config)
 | |
|         self.assertIs(handler.app, self.app)
 | |
|         self.assertEqual(handler.appname, "wuttatest")
 | |
| 
 | |
|     def test_get_spec(self):
 | |
|         self.assertEqual(
 | |
|             mod.GenericHandler.get_spec(), "wuttjamaican.app:GenericHandler"
 | |
|         )
 | |
| 
 | |
|     def test_get_provider_modules(self):
 | |
| 
 | |
|         # no providers, no email modules
 | |
|         with patch.object(self.app, "providers", new={}):
 | |
|             handler = self.make_handler()
 | |
|             self.assertEqual(handler.get_provider_modules("email"), [])
 | |
| 
 | |
|         # provider may specify modules as list
 | |
|         providers = {
 | |
|             "wuttatest": MagicMock(email_modules=["wuttjamaican.app"]),
 | |
|         }
 | |
|         with patch.object(self.app, "providers", new=providers):
 | |
|             handler = self.make_handler()
 | |
|             modules = handler.get_provider_modules("email")
 | |
|             self.assertEqual(len(modules), 1)
 | |
|             self.assertIs(modules[0], mod)
 | |
| 
 | |
|         # provider may specify modules as string
 | |
|         providers = {
 | |
|             "wuttatest": MagicMock(email_modules="wuttjamaican.app"),
 | |
|         }
 | |
|         with patch.object(self.app, "providers", new=providers):
 | |
|             handler = self.make_handler()
 | |
|             modules = handler.get_provider_modules("email")
 | |
|             self.assertEqual(len(modules), 1)
 | |
|             self.assertIs(modules[0], mod)
 |