diff options
Diffstat (limited to 'src/zope/schema/tests/test__bootstrapfields.py')
| -rw-r--r-- | src/zope/schema/tests/test__bootstrapfields.py | 492 |
1 files changed, 491 insertions, 1 deletions
diff --git a/src/zope/schema/tests/test__bootstrapfields.py b/src/zope/schema/tests/test__bootstrapfields.py index 419378a..733d5c9 100644 --- a/src/zope/schema/tests/test__bootstrapfields.py +++ b/src/zope/schema/tests/test__bootstrapfields.py @@ -14,7 +14,7 @@ import doctest import unittest -# pylint:disable=protected-access +# pylint:disable=protected-access,inherit-non-class,blacklisted-name class EqualityTestsMixin(object): @@ -991,6 +991,496 @@ class IntTests(IntegralTests): self.assertEqual(txt._type, integer_types) +class ObjectTests(EqualityTestsMixin, + unittest.TestCase): + + def setUp(self): + from zope.event import subscribers + self._before = subscribers[:] + + def tearDown(self): + from zope.event import subscribers + subscribers[:] = self._before + + def _getTargetClass(self): + from zope.schema._field import Object + return Object + + def _getTargetInterface(self): + from zope.schema.interfaces import IObject + return IObject + + def _makeOneFromClass(self, cls, schema=None, *args, **kw): + if schema is None: + schema = self._makeSchema() + return super(ObjectTests, self)._makeOneFromClass(cls, schema, *args, **kw) + + def _makeSchema(self, **kw): + from zope.interface import Interface + from zope.interface.interface import InterfaceClass + return InterfaceClass('ISchema', (Interface,), kw) + + def _getErrors(self, f, *args, **kw): + from zope.schema.interfaces import WrongContainedType + with self.assertRaises(WrongContainedType) as e: + f(*args, **kw) + return e.exception.args[0] + + def _makeCycles(self): + from zope.interface import Interface + from zope.interface import implementer + from zope.schema import Object + from zope.schema import List + from zope.schema._messageid import _ + + class IUnit(Interface): + """A schema that participate to a cycle""" + boss = Object( + schema=Interface, + title=_("Boss"), + description=_("Boss description"), + required=False, + ) + members = List( + value_type=Object(schema=Interface), + title=_("Member List"), + description=_("Member list description"), + required=False, + ) + + class IPerson(Interface): + """A schema that participate to a cycle""" + unit = Object( + schema=IUnit, + title=_("Unit"), + description=_("Unit description"), + required=False, + ) + + IUnit['boss'].schema = IPerson + IUnit['members'].value_type.schema = IPerson + + @implementer(IUnit) + class Unit(object): + def __init__(self, person, person_list): + self.boss = person + self.members = person_list + + @implementer(IPerson) + class Person(object): + def __init__(self, unit): + self.unit = unit + + return IUnit, Person, Unit + + def test_class_conforms_to_IObject(self): + from zope.interface.verify import verifyClass + from zope.schema.interfaces import IObject + verifyClass(IObject, self._getTargetClass()) + + def test_instance_conforms_to_IObject(self): + from zope.interface.verify import verifyObject + from zope.schema.interfaces import IObject + verifyObject(IObject, self._makeOne()) + + def test_ctor_w_bad_schema(self): + from zope.schema.interfaces import WrongType + self.assertRaises(WrongType, self._makeOne, object()) + + def test_validate_not_required(self): + schema = self._makeSchema() + objf = self._makeOne(schema, required=False) + objf.validate(None) # doesn't raise + + def test_validate_required(self): + from zope.schema.interfaces import RequiredMissing + field = self._makeOne(required=True) + self.assertRaises(RequiredMissing, field.validate, None) + + def test__validate_w_empty_schema(self): + from zope.interface import Interface + objf = self._makeOne(Interface) + objf.validate(object()) # doesn't raise + + def test__validate_w_value_not_providing_schema(self): + from zope.schema.interfaces import SchemaNotProvided + from zope.schema._bootstrapfields import Text + schema = self._makeSchema(foo=Text(), bar=Text()) + objf = self._makeOne(schema) + bad_value = object() + with self.assertRaises(SchemaNotProvided) as exc: + objf.validate(bad_value) + + not_provided = exc.exception + self.assertIs(not_provided.field, objf) + self.assertIs(not_provided.value, bad_value) + self.assertEqual(not_provided.args, (schema, bad_value), ) + + def test__validate_w_value_providing_schema_but_missing_fields(self): + from zope.interface import implementer + from zope.schema.interfaces import SchemaNotFullyImplemented + from zope.schema.interfaces import SchemaNotCorrectlyImplemented + from zope.schema._bootstrapfields import Text + schema = self._makeSchema(foo=Text(), bar=Text()) + + @implementer(schema) + class Broken(object): + pass + + objf = self._makeOne(schema) + broken = Broken() + with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: + objf.validate(broken) + + wct = exc.exception + self.assertIs(wct.field, objf) + self.assertIs(wct.value, broken) + self.assertEqual(wct.invariant_errors, []) + self.assertEqual( + sorted(wct.schema_errors), + ['bar', 'foo'] + ) + for name in ('foo', 'bar'): + error = wct.schema_errors[name] + self.assertIsInstance(error, + SchemaNotFullyImplemented) + self.assertEqual(schema[name], error.field) + self.assertIsNone(error.value) + + # The legacy arg[0] errors list + errors = self._getErrors(objf.validate, Broken()) + self.assertEqual(len(errors), 2) + errors = sorted(errors, + key=lambda x: (type(x).__name__, str(x.args[0]))) + err = errors[0] + self.assertIsInstance(err, SchemaNotFullyImplemented) + nested = err.args[0] + self.assertIsInstance(nested, AttributeError) + self.assertIn("'bar'", str(nested)) + err = errors[1] + self.assertIsInstance(err, SchemaNotFullyImplemented) + nested = err.args[0] + self.assertIsInstance(nested, AttributeError) + self.assertIn("'foo'", str(nested)) + + def test__validate_w_value_providing_schema_but_invalid_fields(self): + from zope.interface import implementer + from zope.schema.interfaces import SchemaNotCorrectlyImplemented + from zope.schema.interfaces import RequiredMissing + from zope.schema.interfaces import WrongType + from zope.schema._bootstrapfields import Text + from zope.schema._compat import text_type + schema = self._makeSchema(foo=Text(), bar=Text()) + + @implementer(schema) + class Broken(object): + foo = None + bar = 1 + + objf = self._makeOne(schema) + broken = Broken() + with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: + objf.validate(broken) + + wct = exc.exception + self.assertIs(wct.field, objf) + self.assertIs(wct.value, broken) + self.assertEqual(wct.invariant_errors, []) + self.assertEqual( + sorted(wct.schema_errors), + ['bar', 'foo'] + ) + self.assertIsInstance(wct.schema_errors['foo'], RequiredMissing) + self.assertIsInstance(wct.schema_errors['bar'], WrongType) + + # The legacy arg[0] errors list + errors = self._getErrors(objf.validate, Broken()) + self.assertEqual(len(errors), 2) + errors = sorted(errors, key=lambda x: type(x).__name__) + err = errors[0] + self.assertIsInstance(err, RequiredMissing) + self.assertEqual(err.args, ('foo',)) + err = errors[1] + self.assertIsInstance(err, WrongType) + self.assertEqual(err.args, (1, text_type, 'bar')) + + def test__validate_w_value_providing_schema(self): + from zope.interface import implementer + from zope.schema._bootstrapfields import Text + from zope.schema._field import Choice + + schema = self._makeSchema( + foo=Text(), + bar=Text(), + baz=Choice(values=[1, 2, 3]), + ) + + @implementer(schema) + class OK(object): + foo = u'Foo' + bar = u'Bar' + baz = 2 + objf = self._makeOne(schema) + objf.validate(OK()) # doesn't raise + + def test_validate_w_cycles(self): + IUnit, Person, Unit = self._makeCycles() + field = self._makeOne(schema=IUnit) + person1 = Person(None) + person2 = Person(None) + unit = Unit(person1, [person1, person2]) + person1.unit = unit + person2.unit = unit + field.validate(unit) # doesn't raise + + def test_validate_w_cycles_object_not_valid(self): + from zope.schema.interfaces import WrongContainedType + IUnit, Person, Unit = self._makeCycles() + field = self._makeOne(schema=IUnit) + person1 = Person(None) + person2 = Person(None) + person3 = Person(object()) + unit = Unit(person3, [person1, person2]) + person1.unit = unit + person2.unit = unit + self.assertRaises(WrongContainedType, field.validate, unit) + + def test_validate_w_cycles_collection_not_valid(self): + from zope.schema.interfaces import WrongContainedType + IUnit, Person, Unit = self._makeCycles() + field = self._makeOne(schema=IUnit) + person1 = Person(None) + person2 = Person(None) + person3 = Person(object()) + unit = Unit(person1, [person2, person3]) + person1.unit = unit + person2.unit = unit + self.assertRaises(WrongContainedType, field.validate, unit) + + def test_set_emits_IBOAE(self): + from zope.event import subscribers + from zope.interface import implementer + from zope.schema.interfaces import IBeforeObjectAssignedEvent + from zope.schema._bootstrapfields import Text + from zope.schema._field import Choice + + schema = self._makeSchema( + foo=Text(), + bar=Text(), + baz=Choice(values=[1, 2, 3]), + ) + + @implementer(schema) + class OK(object): + foo = u'Foo' + bar = u'Bar' + baz = 2 + log = [] + subscribers.append(log.append) + objf = self._makeOne(schema, __name__='field') + inst = DummyInst() + value = OK() + objf.set(inst, value) + self.assertIs(inst.field, value) + self.assertEqual(len(log), 5) + self.assertEqual(IBeforeObjectAssignedEvent.providedBy(log[-1]), True) + self.assertEqual(log[-1].object, value) + self.assertEqual(log[-1].name, 'field') + self.assertEqual(log[-1].context, inst) + + def test_set_allows_IBOAE_subscr_to_replace_value(self): + from zope.event import subscribers + from zope.interface import implementer + from zope.schema._bootstrapfields import Text + from zope.schema._field import Choice + + schema = self._makeSchema( + foo=Text(), + bar=Text(), + baz=Choice(values=[1, 2, 3]), + ) + + @implementer(schema) + class OK(object): + def __init__(self, foo=u'Foo', bar=u'Bar', baz=2): + self.foo = foo + self.bar = bar + self.baz = baz + ok1 = OK() + ok2 = OK(u'Foo2', u'Bar2', 3) + log = [] + subscribers.append(log.append) + + def _replace(event): + event.object = ok2 + subscribers.append(_replace) + objf = self._makeOne(schema, __name__='field') + inst = DummyInst() + self.assertEqual(len(log), 4) + objf.set(inst, ok1) + self.assertIs(inst.field, ok2) + self.assertEqual(len(log), 5) + self.assertEqual(log[-1].object, ok2) + self.assertEqual(log[-1].name, 'field') + self.assertEqual(log[-1].context, inst) + + def test_validates_invariants_by_default(self): + from zope.interface import invariant + from zope.interface import Interface + from zope.interface import implementer + from zope.interface import Invalid + from zope.schema import Text + from zope.schema import Bytes + + class ISchema(Interface): + + foo = Text() + bar = Bytes() + + @invariant + def check_foo(self): + if self.foo == u'bar': + raise Invalid("Foo is not valid") + + @invariant + def check_bar(self): + if self.bar == b'foo': + raise Invalid("Bar is not valid") + + @implementer(ISchema) + class O(object): + foo = u'' + bar = b'' + + + field = self._makeOne(ISchema) + inst = O() + + # Fine at first + field.validate(inst) + + inst.foo = u'bar' + errors = self._getErrors(field.validate, inst) + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].args[0], "Foo is not valid") + + del inst.foo + inst.bar = b'foo' + errors = self._getErrors(field.validate, inst) + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].args[0], "Bar is not valid") + + # Both invalid + inst.foo = u'bar' + errors = self._getErrors(field.validate, inst) + self.assertEqual(len(errors), 2) + errors.sort(key=lambda i: i.args) + self.assertEqual(errors[0].args[0], "Bar is not valid") + self.assertEqual(errors[1].args[0], "Foo is not valid") + + # We can specifically ask for invariants to be turned off. + field = self._makeOne(ISchema, validate_invariants=False) + field.validate(inst) + + def test_schema_defined_by_subclass(self): + from zope import interface + from zope.schema.interfaces import SchemaNotProvided + + class IValueType(interface.Interface): + "The value type schema" + + class Field(self._getTargetClass()): + schema = IValueType + + field = Field() + self.assertIs(field.schema, IValueType) + + # Non implementation is bad + self.assertRaises(SchemaNotProvided, field.validate, object()) + + # Actual implementation works + @interface.implementer(IValueType) + class ValueType(object): + "The value type" + + + field.validate(ValueType()) + + def test_bound_field_of_collection_with_choice(self): + # https://github.com/zopefoundation/zope.schema/issues/17 + from zope.interface import Interface, implementer + from zope.interface import Attribute + + from zope.schema import Choice, Object, Set + from zope.schema.fieldproperty import FieldProperty + from zope.schema.interfaces import IContextSourceBinder + from zope.schema.interfaces import WrongContainedType + from zope.schema.interfaces import SchemaNotCorrectlyImplemented + from zope.schema.vocabulary import SimpleVocabulary + + + @implementer(IContextSourceBinder) + class EnumContext(object): + def __call__(self, context): + return SimpleVocabulary.fromValues(list(context)) + + class IMultipleChoice(Interface): + choices = Set(value_type=Choice(source=EnumContext())) + # Provide a regular attribute to prove that binding doesn't + # choke. NOTE: We don't actually verify the existence of this attribute. + non_field = Attribute("An attribute") + + @implementer(IMultipleChoice) + class Choices(object): + + def __init__(self, choices): + self.choices = choices + + def __iter__(self): + # EnumContext calls this to make the vocabulary. + # Fields of the schema of the IObject are bound to the value being + # validated. + return iter(range(5)) + + class IFavorites(Interface): + fav = Object(title=u"Favorites number", schema=IMultipleChoice) + + + @implementer(IFavorites) + class Favorites(object): + fav = FieldProperty(IFavorites['fav']) + + # must not raise + good_choices = Choices({1, 3}) + IFavorites['fav'].validate(good_choices) + + # Ranges outside the context fail + bad_choices = Choices({1, 8}) + with self.assertRaises(WrongContainedType) as exc: + IFavorites['fav'].validate(bad_choices) + + e = exc.exception + self.assertEqual(IFavorites['fav'], e.field) + self.assertEqual(bad_choices, e.value) + + # Validation through field property + favorites = Favorites() + favorites.fav = good_choices + + # And validation through a field that wants IFavorites + favorites_field = Object(IFavorites) + favorites_field.validate(favorites) + + # Check the field property error + with self.assertRaises(SchemaNotCorrectlyImplemented) as exc: + favorites.fav = bad_choices + + e = exc.exception + self.assertEqual(IFavorites['fav'], e.field) + self.assertEqual(bad_choices, e.value) + self.assertEqual(['choices'], list(e.schema_errors)) + + class DummyInst(object): missing_value = object() |
