3
0
Fork 0
wuttaweb/tests/forms/test_schema.py
Lance Edgar 2ccfe29553 fix: prevent error in DateTime schema type if no widget/request set
when we use this intentionally, the widget/request should be set as
expected. but apparently this gets instantiated sometimes (by
ColanderAlchemy?) without a widget.

so this adds sane fallback logic, instead of outright error
2025-12-17 17:46:31 -06:00

567 lines
19 KiB
Python

# -*- coding: utf-8; -*-
import datetime
import decimal
from unittest import TestCase
from unittest.mock import patch
import colander
from pyramid import testing
from sqlalchemy import orm
from wuttjamaican.conf import WuttaConfig
from wuttjamaican.util import get_timezone_by_name
from wuttjamaican.testing import DataTestCase
from wuttaweb.forms import schema as mod
from wuttaweb.forms import widgets
from wuttaweb.testing import WebTestCase
class TestWuttaDateTime(WebTestCase):
def test_serialize(self):
tzlocal = get_timezone_by_name("America/Los_Angeles")
with patch.object(self.app, "get_timezone", return_value=tzlocal):
typ = mod.WuttaDateTime()
node = colander.SchemaNode(
typ, widget=widgets.WuttaDateTimeWidget(self.request)
)
# null
self.assertIs(typ.serialize(node, colander.null), colander.null)
self.assertIs(typ.serialize(node, None), colander.null)
self.assertIs(typ.serialize(node, ""), colander.null)
# naive, UTC
result = typ.serialize(node, datetime.datetime(2024, 12, 11, 22, 33))
self.assertEqual(result, "2024-12-11T14:33:00-08:00")
# aware, UTC
result = typ.serialize(
node,
datetime.datetime(2024, 12, 11, 22, 33, tzinfo=datetime.timezone.utc),
)
self.assertEqual(result, "2024-12-11T14:33:00-08:00")
# aware, local
result = typ.serialize(
node,
datetime.datetime(2024, 12, 11, 14, 33, tzinfo=tzlocal),
)
self.assertEqual(result, "2024-12-11T14:33:00-08:00")
# custom format
typ = mod.WuttaDateTime(format="%Y-%m-%d %I:%M %p")
node = colander.SchemaNode(
typ, widget=widgets.WuttaDateTimeWidget(self.request)
)
result = typ.serialize(
node,
datetime.datetime(2024, 12, 11, 22, 33, tzinfo=datetime.timezone.utc),
)
self.assertEqual(result, "2024-12-11 02:33 PM")
# missing widget/request/config
typ = mod.WuttaDateTime()
node = colander.SchemaNode(typ)
result = typ.serialize(node, datetime.datetime(2024, 12, 11, 22, 33))
# nb. not possible to know which timezone is system-local
self.assertTrue(result.startswith("2024-12-"))
def test_deserialize(self):
tzlocal = get_timezone_by_name("America/Los_Angeles")
with patch.object(self.app, "get_timezone", return_value=tzlocal):
typ = mod.WuttaDateTime()
node = colander.SchemaNode(
typ, widget=widgets.WuttaDateTimeWidget(self.request)
)
# null
self.assertIs(typ.deserialize(node, colander.null), colander.null)
self.assertIs(typ.deserialize(node, None), colander.null)
self.assertIs(typ.deserialize(node, ""), colander.null)
# format #1
result = typ.deserialize(node, "2024-12-11T22:33:00")
self.assertIsInstance(result, datetime.datetime)
self.assertEqual(
result, datetime.datetime(2024, 12, 12, 6, 33, tzinfo=None)
)
# format #2
result = typ.deserialize(node, "2024-12-11T10:33 PM")
self.assertIsInstance(result, datetime.datetime)
self.assertEqual(
result, datetime.datetime(2024, 12, 12, 6, 33, tzinfo=None)
)
# invalid
self.assertRaises(colander.Invalid, typ.deserialize, node, "bogus")
class TestObjectNode(DataTestCase):
def setUp(self):
self.setup_db()
self.request = testing.DummyRequest(wutta_config=self.config)
def test_dictify(self):
model = self.app.model
person = model.Person(full_name="Betty Boop")
# unsupported type is converted to string
node = mod.ObjectNode(colander.String())
value = node.dictify(person)
self.assertEqual(value, "Betty Boop")
# but supported type can dictify
node = mod.ObjectNode(mod.PersonRef(self.request))
value = node.dictify(person)
self.assertIs(value, person)
def test_objectify(self):
model = self.app.model
person = model.Person(full_name="Betty Boop")
# unsupported type raises error
node = mod.ObjectNode(colander.String())
self.assertRaises(NotImplementedError, node.objectify, person)
# but supported type can objectify
node = mod.ObjectNode(mod.PersonRef(self.request))
value = node.objectify(person)
self.assertIs(value, person)
class TestWuttaEnum(WebTestCase):
def test_widget_maker(self):
enum = self.app.enum
typ = mod.WuttaEnum(self.request, enum.UpgradeStatus)
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.SelectWidget)
MOCK_STATUS_ONE = 1
MOCK_STATUS_TWO = 2
MOCK_STATUS = {
MOCK_STATUS_ONE: "one",
MOCK_STATUS_TWO: "two",
}
class TestWuttaDictEnum(WebTestCase):
def test_widget_maker(self):
typ = mod.WuttaDictEnum(self.request, MOCK_STATUS)
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.SelectWidget)
self.assertEqual(
widget.values,
[
(1, "one"),
(2, "two"),
],
)
class TestWuttaMoney(WebTestCase):
def test_widget_maker(self):
enum = self.app.enum
# default scale
typ = mod.WuttaMoney(self.request)
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.WuttaMoneyInputWidget)
self.assertEqual(widget.scale, 2)
# custom scale
typ = mod.WuttaMoney(self.request, scale=4)
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.WuttaMoneyInputWidget)
self.assertEqual(widget.scale, 4)
class TestWuttaQuantity(WebTestCase):
def test_serialize(self):
node = colander.SchemaNode(mod.WuttaQuantity(self.request))
typ = node.typ
# null
result = typ.serialize(node, colander.null)
self.assertIs(result, colander.null)
result = typ.serialize(node, None)
self.assertIs(result, colander.null)
# quantity
result = typ.serialize(node, 42)
self.assertEqual(result, "42")
result = typ.serialize(node, 42.00)
self.assertEqual(result, "42")
result = typ.serialize(node, decimal.Decimal("42.00"))
self.assertEqual(result, "42")
result = typ.serialize(node, 42.13)
self.assertEqual(result, "42.13")
class TestObjectRef(DataTestCase):
def setUp(self):
self.setup_db()
self.request = testing.DummyRequest(wutta_config=self.config)
def test_empty_option(self):
# null by default
typ = mod.ObjectRef(self.request)
self.assertIsNone(typ.empty_option)
# passing true yields default empty option
typ = mod.ObjectRef(self.request, empty_option=True)
self.assertEqual(typ.empty_option, ("", "(none)"))
# can set explicitly
typ = mod.ObjectRef(self.request, empty_option=("foo", "bar"))
self.assertEqual(typ.empty_option, ("foo", "bar"))
# can set just a label
typ = mod.ObjectRef(self.request, empty_option="(empty)")
self.assertEqual(typ.empty_option, ("", "(empty)"))
def test_model_class(self):
typ = mod.ObjectRef(self.request)
self.assertRaises(NotImplementedError, getattr, typ, "model_class")
def test_serialize(self):
model = self.app.model
node = colander.SchemaNode(colander.String())
# null
typ = mod.ObjectRef(self.request)
value = typ.serialize(node, colander.null)
self.assertIs(value, colander.null)
# model instance
person = model.Person(full_name="Betty Boop")
self.session.add(person)
self.session.commit()
self.assertIsNotNone(person.uuid)
typ = mod.ObjectRef(self.request)
value = typ.serialize(node, person)
self.assertEqual(value, person.uuid.hex)
# null w/ empty option
typ = mod.ObjectRef(self.request, empty_option=("bad", "BAD"))
value = typ.serialize(node, colander.null)
self.assertEqual(value, "bad")
def test_deserialize(self):
model = self.app.model
node = colander.SchemaNode(colander.String())
# null
typ = mod.ObjectRef(self.request)
value = typ.deserialize(node, colander.null)
self.assertIs(value, colander.null)
# model instance
person = model.Person(full_name="Betty Boop")
self.session.add(person)
self.session.commit()
self.assertIsNotNone(person.uuid)
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.ObjectRef(self.request)
value = typ.deserialize(node, person.uuid)
self.assertIs(value, person)
def test_dictify(self):
model = self.app.model
node = colander.SchemaNode(colander.String())
# model instance
person = model.Person(full_name="Betty Boop")
self.session.add(person)
self.session.commit()
self.assertIsNotNone(person.uuid)
typ = mod.ObjectRef(self.request)
value = typ.dictify(person)
self.assertIs(value, person)
def test_objectify(self):
model = self.app.model
node = colander.SchemaNode(colander.String())
# null
typ = mod.ObjectRef(self.request)
value = typ.objectify(None)
self.assertIsNone(value)
with patch.object(mod, "Session", return_value=self.session):
# model instance
person = model.Person(full_name="Betty Boop")
self.session.add(person)
self.session.commit()
self.assertIsNotNone(person.uuid)
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
# can specify as uuid
typ = mod.ObjectRef(self.request)
value = typ.objectify(person.uuid)
self.assertIs(value, person)
# or can specify object proper
typ = mod.ObjectRef(self.request)
value = typ.objectify(person)
self.assertIs(value, person)
# error if not found
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
typ = mod.ObjectRef(self.request)
self.assertRaises(ValueError, typ.objectify, "WRONG-UUID")
def test_get_query(self):
model = self.app.model
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.ObjectRef(self.request)
query = typ.get_query()
self.assertIsInstance(query, orm.Query)
def test_sort_query(self):
model = self.app.model
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.ObjectRef(self.request)
query = typ.get_query()
sorted_query = typ.sort_query(query)
self.assertIs(sorted_query, query)
def test_widget_maker(self):
model = self.app.model
person = model.Person(full_name="Betty Boop")
self.session.add(person)
self.session.commit()
# basic
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.ObjectRef(self.request)
widget = typ.widget_maker()
self.assertEqual(len(widget.values), 1)
self.assertEqual(widget.values[0][1], "Betty Boop")
# empty option
with patch.object(mod.ObjectRef, "model_class", new=model.Person):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.ObjectRef(self.request, empty_option=True)
widget = typ.widget_maker()
self.assertEqual(len(widget.values), 2)
self.assertEqual(widget.values[0][1], "(none)")
self.assertEqual(widget.values[1][1], "Betty Boop")
class TestPersonRef(WebTestCase):
def test_sort_query(self):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.PersonRef(self.request)
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("people.view", "/people/{uuid}")
model = self.app.model
with patch.object(mod, "Session", return_value=self.session):
typ = mod.PersonRef(self.request)
person = model.Person(full_name="Barney Rubble")
self.session.add(person)
self.session.commit()
url = typ.get_object_url(person)
self.assertIsNotNone(url)
self.assertIn(f"/people/{person.uuid}", url)
class TestRoleRef(WebTestCase):
def test_sort_query(self):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.RoleRef(self.request)
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("roles.view", "/roles/{uuid}")
model = self.app.model
with patch.object(mod, "Session", return_value=self.session):
typ = mod.RoleRef(self.request)
role = model.Role(name="Manager")
self.session.add(role)
self.session.commit()
url = typ.get_object_url(role)
self.assertIsNotNone(url)
self.assertIn(f"/roles/{role.uuid}", url)
class TestUserRef(WebTestCase):
def test_sort_query(self):
with patch.object(mod, "Session", return_value=self.session):
typ = mod.UserRef(self.request)
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("users.view", "/users/{uuid}")
model = self.app.model
with patch.object(mod, "Session", return_value=self.session):
typ = mod.UserRef(self.request)
user = model.User(username="barney")
self.session.add(user)
self.session.commit()
url = typ.get_object_url(user)
self.assertIsNotNone(url)
self.assertIn(f"/users/{user.uuid}", url)
class TestRoleRefs(DataTestCase):
def setUp(self):
self.setup_db()
self.request = testing.DummyRequest(wutta_config=self.config)
def test_widget_maker(self):
model = self.app.model
auth = self.app.get_auth_handler()
admin = auth.get_role_administrator(self.session)
authed = auth.get_role_authenticated(self.session)
anon = auth.get_role_anonymous(self.session)
blokes = model.Role(name="Blokes")
self.session.add(blokes)
self.session.commit()
with patch.object(mod, "Session", return_value=self.session):
# with root access, default values include: admin, blokes
self.request.is_root = True
typ = mod.RoleRefs(self.request)
widget = typ.widget_maker()
self.assertEqual(len(widget.values), 2)
self.assertEqual(widget.values[0][1], "Administrator")
self.assertEqual(widget.values[1][1], "Blokes")
# without root, default values include: blokes
self.request.is_root = False
typ = mod.RoleRefs(self.request)
widget = typ.widget_maker()
self.assertEqual(len(widget.values), 1)
self.assertEqual(widget.values[0][1], "Blokes")
class TestPermissions(DataTestCase):
def setUp(self):
self.setup_db()
self.request = testing.DummyRequest(wutta_config=self.config)
def test_widget_maker(self):
# no supported permissions
permissions = {}
typ = mod.Permissions(self.request, permissions)
widget = typ.widget_maker()
self.assertEqual(len(widget.values), 0)
# supported permissions are morphed to values
permissions = {
"widgets": {
"label": "Widgets",
"perms": {
"widgets.polish": {
"label": "Polish the widgets",
},
},
},
}
typ = mod.Permissions(self.request, permissions)
widget = typ.widget_maker()
self.assertEqual(len(widget.values), 1)
self.assertEqual(widget.values[0], ("widgets.polish", "Polish the widgets"))
class TestFileDownload(DataTestCase):
def setUp(self):
self.setup_db()
self.request = testing.DummyRequest(wutta_config=self.config)
def test_widget_maker(self):
# sanity / coverage check
typ = mod.FileDownload(self.request, url="/foo")
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.FileDownloadWidget)
self.assertEqual(widget.url, "/foo")
class TestEmailRecipients(TestCase):
def test_serialize(self):
typ = mod.EmailRecipients()
node = colander.SchemaNode(typ)
recips = [
"alice@example.com",
"bob@example.com",
]
recips_str = ", ".join(recips)
# values
result = typ.serialize(node, recips_str)
self.assertEqual(result, "\n".join(recips))
# null
result = typ.serialize(node, colander.null)
self.assertIs(result, colander.null)
def test_deserialize(self):
typ = mod.EmailRecipients()
node = colander.SchemaNode(typ)
recips = [
"alice@example.com",
"bob@example.com",
]
recips_str = ", ".join(recips)
# values
result = typ.deserialize(node, recips_str)
self.assertEqual(result, recips_str)
# null
result = typ.deserialize(node, colander.null)
self.assertIs(result, colander.null)
def test_widget_maker(self):
typ = mod.EmailRecipients()
widget = typ.widget_maker()
self.assertIsInstance(widget, widgets.EmailRecipientsWidget)