1
# -*- coding: utf-8 -*-
3
from datetime import datetime
5
from dateutil.tz import tzutc, tzoffset
7
from twisted.trial.unittest import TestCase
9
from txaws.server.exception import APIError
10
from txaws.server.schema import (
11
Arguments, Bool, Date, Enum, Integer, Parameter, RawStr, Schema, Unicode,
12
UnicodeLine, List, Structure, InconsistentParameterError)
15
class ArgumentsTestCase(TestCase):
17
def test_instantiate_empty(self):
18
"""Creating an L{Arguments} object."""
19
arguments = Arguments({})
20
self.assertEqual({}, arguments.__dict__)
22
def test_instantiate_non_empty(self):
23
"""Creating an L{Arguments} object with some arguments."""
24
arguments = Arguments({"foo": 123, "bar": 456})
25
self.assertEqual(123, arguments.foo)
26
self.assertEqual(456, arguments.bar)
28
def test_iterate(self):
29
"""L{Arguments} returns an iterator with both keys and values."""
30
arguments = Arguments({"foo": 123, "bar": 456})
31
self.assertEqual([("foo", 123), ("bar", 456)], list(arguments))
33
def test_getitem(self):
34
"""Values can be looked up using C{[index]} notation."""
35
arguments = Arguments({1: "a", 2: "b", "foo": "bar"})
36
self.assertEqual("b", arguments[2])
37
self.assertEqual("bar", arguments["foo"])
39
def test_getitem_error(self):
40
"""L{KeyError} is raised when the argument is not found."""
41
arguments = Arguments({})
42
self.assertRaises(KeyError, arguments.__getitem__, 1)
44
def test_contains(self):
46
The presence of a certain argument can be inspected using the 'in'
49
arguments = Arguments({"foo": 1})
50
self.assertIn("foo", arguments)
51
self.assertNotIn("bar", arguments)
54
"""C{len()} can be used with an L{Arguments} instance."""
55
self.assertEqual(0, len(Arguments({})))
56
self.assertEqual(1, len(Arguments({1: 2})))
58
def test_nested_data(self):
59
"""L{Arguments} can cope fine with nested data structures."""
60
arguments = Arguments({"foo": Arguments({"bar": "egg"})})
61
self.assertEqual("egg", arguments.foo.bar)
63
def test_nested_data_with_numbers(self):
64
"""L{Arguments} can cope fine with list items."""
65
arguments = Arguments({"foo": {1: "egg"}})
66
self.assertEqual("egg", arguments.foo[0])
69
class ParameterTestCase(TestCase):
71
def test_coerce(self):
73
L{Parameter.coerce} coerces a request argument with a single value.
75
parameter = Parameter("Test")
76
parameter.parse = lambda value: value
77
self.assertEqual("foo", parameter.coerce("foo"))
79
def test_coerce_with_optional(self):
80
"""L{Parameter.coerce} returns C{None} if the parameter is optional."""
81
parameter = Parameter("Test", optional=True)
82
self.assertEqual(None, parameter.coerce(None))
84
def test_coerce_with_required(self):
86
L{Parameter.coerce} raises an L{APIError} if the parameter is
87
required but not present in the request.
89
parameter = Parameter("Test")
90
parameter.kind = "testy kind"
91
error = self.assertRaises(APIError, parameter.coerce, None)
92
self.assertEqual(400, error.status)
93
self.assertEqual("MissingParameter", error.code)
94
self.assertEqual("The request must contain the parameter Test "
98
def test_coerce_with_default(self):
100
L{Parameter.coerce} returns F{Parameter.default} if the parameter is
101
optional and not present in the request.
103
parameter = Parameter("Test", optional=True, default=123)
104
self.assertEqual(123, parameter.coerce(None))
106
def test_coerce_with_parameter_error(self):
108
L{Parameter.coerce} raises an L{APIError} if an invalid value is
109
passed as request argument.
111
parameter = Parameter("Test")
112
parameter.parse = lambda value: int(value)
113
parameter.kind = "integer"
114
error = self.assertRaises(APIError, parameter.coerce, "foo")
115
self.assertEqual(400, error.status)
116
self.assertEqual("InvalidParameterValue", error.code)
117
self.assertEqual("Invalid integer value foo", error.message)
119
def test_coerce_with_parameter_error_unicode(self):
121
L{Parameter.coerce} raises an L{APIError} if an invalid value is
122
passed as request argument and parameter value is unicode.
124
parameter = Parameter("Test")
125
parameter.parse = lambda value: int(value)
126
parameter.kind = "integer"
127
error = self.assertRaises(APIError, parameter.coerce, "citt\xc3\xa1")
128
self.assertEqual(400, error.status)
129
self.assertEqual("InvalidParameterValue", error.code)
130
self.assertEqual(u"Invalid integer value cittá", error.message)
132
def test_coerce_with_empty_strings(self):
134
L{Parameter.coerce} returns C{None} if the value is an empty string and
135
C{allow_none} is C{True}.
137
parameter = Parameter("Test", allow_none=True)
138
self.assertEqual(None, parameter.coerce(""))
140
def test_coerce_with_empty_strings_error(self):
142
L{Parameter.coerce} raises an error if the value is an empty string and
143
C{allow_none} is not C{True}.
145
parameter = Parameter("Test")
146
error = self.assertRaises(APIError, parameter.coerce, "")
147
self.assertEqual(400, error.status)
148
self.assertEqual("MissingParameter", error.code)
149
self.assertEqual("The request must contain the parameter Test",
152
def test_coerce_with_min(self):
154
L{Parameter.coerce} raises an error if the given value is lower than
157
parameter = Parameter("Test", min=50)
158
parameter.measure = lambda value: int(value)
159
parameter.lower_than_min_template = "Please give me at least %s"
160
error = self.assertRaises(APIError, parameter.coerce, "4")
161
self.assertEqual(400, error.status)
162
self.assertEqual("InvalidParameterValue", error.code)
163
self.assertEqual("Value (4) for parameter Test is invalid. "
164
"Please give me at least 50", error.message)
166
def test_coerce_with_max(self):
168
L{Parameter.coerce} raises an error if the given value is greater than
171
parameter = Parameter("Test", max=3)
172
parameter.measure = lambda value: len(value)
173
parameter.greater_than_max_template = "%s should be enough for anybody"
174
error = self.assertRaises(APIError, parameter.coerce, "longish")
175
self.assertEqual(400, error.status)
176
self.assertEqual("InvalidParameterValue", error.code)
177
self.assertEqual("Value (longish) for parameter Test is invalid. "
178
"3 should be enough for anybody", error.message)
180
def test_validator_invalid(self):
182
L{Parameter.coerce} raises an error if the validator returns False.
184
parameter = Parameter("Test", validator=lambda _: False)
185
parameter.parse = lambda value: value
186
parameter.kind = "test_parameter"
187
error = self.assertRaises(APIError, parameter.coerce, "foo")
188
self.assertEqual(400, error.status)
189
self.assertEqual("InvalidParameterValue", error.code)
190
self.assertEqual("Invalid test_parameter value foo", error.message)
192
def test_validator_valid(self):
194
L{Parameter.coerce} returns the correct value if validator returns
197
parameter = Parameter("Test", validator=lambda _: True)
198
parameter.parse = lambda value: value
199
parameter.kind = "test_parameter"
200
self.assertEqual("foo", parameter.coerce("foo"))
202
def test_parameter_doc(self):
204
All L{Parameter} subclasses accept a 'doc' keyword argument.
211
Enum(mapping={"hey": 1}, doc="foo"),
213
List(item=Integer(), doc="foo"),
214
Structure(fields={}, doc="foo")]
215
for parameter in parameters:
216
self.assertEqual("foo", parameter.doc)
219
class UnicodeTestCase(TestCase):
221
def test_parse(self):
222
"""L{Unicode.parse} converts the given raw C{value} to C{unicode}."""
223
parameter = Unicode("Test")
224
self.assertEqual(u"foo", parameter.parse("foo"))
226
def test_parse_unicode(self):
227
"""L{Unicode.parse} works with unicode input."""
228
parameter = Unicode("Test")
229
self.assertEqual(u"cittá", parameter.parse("citt\xc3\xa1"))
231
def test_format(self):
232
"""L{Unicode.format} encodes the given C{unicode} with utf-8."""
233
parameter = Unicode("Test")
234
value = parameter.format(u"fo\N{TAGBANWA LETTER SA}")
235
self.assertEqual("fo\xe1\x9d\xb0", value)
236
self.assertTrue(isinstance(value, str))
238
def test_min_and_max(self):
239
"""The L{Unicode} parameter properly supports ranges."""
240
parameter = Unicode("Test", min=2, max=4)
242
error = self.assertRaises(APIError, parameter.coerce, "a")
243
self.assertEqual(400, error.status)
244
self.assertEqual("InvalidParameterValue", error.code)
245
self.assertIn("Length must be at least 2.", error.message)
247
error = self.assertRaises(APIError, parameter.coerce, "abcde")
248
self.assertIn("Length exceeds maximum of 4.", error.message)
249
self.assertEqual(400, error.status)
250
self.assertEqual("InvalidParameterValue", error.code)
252
def test_invalid_unicode(self):
254
The L{Unicode} parameter returns an error with invalid unicode data.
256
parameter = Unicode("Test")
257
error = self.assertRaises(APIError, parameter.coerce, "Test\x95Error")
258
self.assertIn(u"Invalid unicode value", error.message)
259
self.assertEqual(400, error.status)
260
self.assertEqual("InvalidParameterValue", error.code)
263
class UnicodeLineTestCase(TestCase):
265
def test_parse(self):
266
"""L{UnicodeLine.parse} converts the given raw C{value} to
268
parameter = UnicodeLine("Test")
269
self.assertEqual(u"foo", parameter.parse("foo"))
271
def test_newlines_in_text(self):
273
The L{UnicodeLine} parameter returns an error if text contains
276
parameter = UnicodeLine("Test")
277
error = self.assertRaises(APIError, parameter.coerce, "Test\nError")
278
self.assertIn(u"Can't contain newlines", error.message)
279
self.assertEqual(400, error.status)
282
class RawStrTestCase(TestCase):
284
def test_parse(self):
285
"""L{RawStr.parse} checks that the given raw C{value} is a string."""
286
parameter = RawStr("Test")
287
self.assertEqual("foo", parameter.parse("foo"))
289
def test_format(self):
290
"""L{RawStr.format} simply returns the given string."""
291
parameter = RawStr("Test")
292
value = parameter.format("foo")
293
self.assertEqual("foo", value)
294
self.assertTrue(isinstance(value, str))
297
class IntegerTestCase(TestCase):
299
def test_parse(self):
300
"""L{Integer.parse} converts the given raw C{value} to C{int}."""
301
parameter = Integer("Test")
302
self.assertEqual(123, parameter.parse("123"))
304
def test_parse_with_negative(self):
305
"""L{Integer.parse} converts the given raw C{value} to C{int}."""
306
parameter = Integer("Test")
307
error = self.assertRaises(APIError, parameter.coerce, "-1")
308
self.assertEqual(400, error.status)
309
self.assertEqual("InvalidParameterValue", error.code)
310
self.assertIn("Value must be at least 0.", error.message)
312
def test_format(self):
313
"""L{Integer.format} converts the given integer to a string."""
314
parameter = Integer("Test")
315
self.assertEqual("123", parameter.format(123))
317
def test_min_and_max(self):
318
"""The L{Integer} parameter properly supports ranges."""
319
parameter = Integer("Test", min=2, max=4)
321
error = self.assertRaises(APIError, parameter.coerce, "1")
322
self.assertEqual(400, error.status)
323
self.assertEqual("InvalidParameterValue", error.code)
324
self.assertIn("Value must be at least 2.", error.message)
326
error = self.assertRaises(APIError, parameter.coerce, "5")
327
self.assertIn("Value exceeds maximum of 4.", error.message)
328
self.assertEqual(400, error.status)
329
self.assertEqual("InvalidParameterValue", error.code)
331
def test_non_integer_string(self):
333
The L{Integer} parameter raises an L{APIError} when passed non-int
334
values (in this case, a string).
337
parameter = Integer("Test")
338
error = self.assertRaises(APIError, parameter.coerce, garbage)
339
self.assertEqual(400, error.status)
340
self.assertEqual("InvalidParameterValue", error.code)
341
self.assertIn("Invalid integer value %s" % garbage, error.message)
344
class BoolTestCase(TestCase):
346
def test_parse(self):
347
"""L{Bool.parse} converts 'true' to C{True}."""
348
parameter = Bool("Test")
349
self.assertEqual(True, parameter.parse("true"))
351
def test_parse_with_false(self):
352
"""L{Bool.parse} converts 'false' to C{False}."""
353
parameter = Bool("Test")
354
self.assertEqual(False, parameter.parse("false"))
356
def test_parse_with_error(self):
358
L{Bool.parse} raises C{ValueError} if the given value is neither 'true'
361
parameter = Bool("Test")
362
self.assertRaises(ValueError, parameter.parse, "0")
364
def test_format(self):
365
"""L{Bool.format} converts the given boolean to either '0' or '1'."""
366
parameter = Bool("Test")
367
self.assertEqual("true", parameter.format(True))
368
self.assertEqual("false", parameter.format(False))
371
class EnumTestCase(TestCase):
373
def test_parse(self):
374
"""L{Enum.parse} accepts a map for translating values."""
375
parameter = Enum("Test", {"foo": "bar"})
376
self.assertEqual("bar", parameter.parse("foo"))
378
def test_parse_with_error(self):
380
L{Bool.parse} raises C{ValueError} if the given value is not
381
present in the mapping.
383
parameter = Enum("Test", {})
384
self.assertRaises(ValueError, parameter.parse, "bar")
386
def test_format(self):
387
"""L{Enum.format} converts back the given value to the original map."""
388
parameter = Enum("Test", {"foo": "bar"})
389
self.assertEqual("foo", parameter.format("bar"))
392
class DateTestCase(TestCase):
394
def test_parse(self):
395
"""L{Date.parse checks that the given raw C{value} is a date/time."""
396
parameter = Date("Test")
397
date = datetime(2010, 9, 15, 23, 59, 59, tzinfo=tzutc())
398
self.assertEqual(date, parameter.parse("2010-09-15T23:59:59Z"))
400
def test_format(self):
402
L{Date.format} returns a string representation of the given datetime
405
parameter = Date("Test")
406
date = datetime(2010, 9, 15, 23, 59, 59,
407
tzinfo=tzoffset('UTC', 120 * 60))
408
self.assertEqual("2010-09-15T21:59:59Z", parameter.format(date))
411
class SchemaTestCase(TestCase):
413
def test_get_parameters(self):
415
L{Schema.get_parameters} returns the original list of parameters.
417
schema = Schema(parameters=[
419
List("scores", Integer())])
420
parameters = schema.get_parameters()
421
self.assertEqual("name", parameters[0].name)
422
self.assertEqual("scores", parameters[1].name)
424
def test_get_parameters_order_on_parameter_only_construction(self):
426
L{Schema.get_parameters} returns the original list of L{Parameter}s
427
even when they are passed as positional arguments to L{Schema}.
431
List("scores", Integer()),
432
Integer("index", Integer()))
433
self.assertEqual(["name", "scores", "index"],
434
[p.name for p in schema.get_parameters()])
436
def test_extract(self):
438
L{Schema.extract} returns an L{Argument} object whose attributes are
439
the arguments extracted from the given C{request}, as specified.
441
schema = Schema(Unicode("name"))
442
arguments, _ = schema.extract({"name": "value"})
443
self.assertEqual("value", arguments.name)
445
def test_extract_with_rest(self):
447
L{Schema.extract} stores unknown parameters in the 'rest' return
451
_, rest = schema.extract({"name": "value"})
452
self.assertEqual(rest, {"name": "value"})
454
def test_extract_with_nested_rest(self):
456
_, rest = schema.extract({"foo.1.bar": "hey", "foo.2.baz": "there"})
457
self.assertEqual({"foo.1.bar": "hey", "foo.2.baz": "there"}, rest)
459
def test_extract_with_many_arguments(self):
460
"""L{Schema.extract} can handle multiple parameters."""
461
schema = Schema(Unicode("name"), Integer("count"))
462
arguments, _ = schema.extract({"name": "value", "count": "123"})
463
self.assertEqual(u"value", arguments.name)
464
self.assertEqual(123, arguments.count)
466
def test_extract_with_optional(self):
467
"""L{Schema.extract} can handle optional parameters."""
468
schema = Schema(Unicode("name"), Integer("count", optional=True))
469
arguments, _ = schema.extract({"name": "value"})
470
self.assertEqual(u"value", arguments.name)
471
self.assertEqual(None, arguments.count)
473
def test_extract_with_optional_default(self):
475
The value of C{default} on a parameter is used as the value when it is
476
not provided as an argument and the parameter is C{optional}.
478
schema = Schema(Unicode("name"),
479
Integer("count", optional=True, default=5))
480
arguments, _ = schema.extract({"name": "value"})
481
self.assertEqual(u"value", arguments.name)
482
self.assertEqual(5, arguments.count)
484
def test_extract_structure_with_optional(self):
485
"""L{Schema.extract} can handle optional parameters."""
489
fields={"name": Unicode(optional=True, default="radix")}))
490
arguments, _ = schema.extract({"struct": {}})
491
self.assertEqual(u"radix", arguments.struct.name)
493
def test_extract_with_numbered(self):
495
L{Schema.extract} can handle parameters with numbered values.
497
schema = Schema(Unicode("name.n"))
498
arguments, _ = schema.extract({"name.0": "Joe", "name.1": "Tom"})
499
self.assertEqual("Joe", arguments.name[0])
500
self.assertEqual("Tom", arguments.name[1])
502
def test_extract_with_goofy_numbered(self):
504
L{Schema.extract} only uses the relative values of indices to determine
505
the index in the resultant list.
507
schema = Schema(Unicode("name.n"))
508
arguments, _ = schema.extract({"name.5": "Joe", "name.10": "Tom"})
509
self.assertEqual("Joe", arguments.name[0])
510
self.assertEqual("Tom", arguments.name[1])
512
def test_extract_with_single_numbered(self):
514
L{Schema.extract} can handle an un-numbered argument passed in to a
517
schema = Schema(Unicode("name.n"))
518
arguments, _ = schema.extract({"name": "Joe"})
519
self.assertEqual("Joe", arguments.name[0])
521
def test_extract_complex(self):
522
"""L{Schema} can cope with complex schemas."""
524
Unicode("GroupName"),
525
RawStr("IpPermissions.n.IpProtocol"),
526
Integer("IpPermissions.n.FromPort"),
527
Integer("IpPermissions.n.ToPort"),
528
Unicode("IpPermissions.n.Groups.m.UserId", optional=True),
529
Unicode("IpPermissions.n.Groups.m.GroupName", optional=True))
531
arguments, _ = schema.extract(
533
"IpPermissions.1.IpProtocol": "tcp",
534
"IpPermissions.1.FromPort": "1234",
535
"IpPermissions.1.ToPort": "5678",
536
"IpPermissions.1.Groups.1.GroupName": "Bar",
537
"IpPermissions.1.Groups.2.GroupName": "Egg"})
539
self.assertEqual(u"Foo", arguments.GroupName)
540
self.assertEqual(1, len(arguments.IpPermissions))
541
self.assertEqual(1234, arguments.IpPermissions[0].FromPort)
542
self.assertEqual(5678, arguments.IpPermissions[0].ToPort)
543
self.assertEqual(2, len(arguments.IpPermissions[0].Groups))
544
self.assertEqual("Bar", arguments.IpPermissions[0].Groups[0].GroupName)
545
self.assertEqual("Egg", arguments.IpPermissions[0].Groups[1].GroupName)
547
def test_extract_with_multiple_parameters_in_singular_schema(self):
549
If multiple parameters are passed in to a Schema element that is not
550
flagged as supporting multiple values then we should throw an
553
schema = Schema(Unicode("name"))
554
params = {"name.1": "value", "name.2": "value2"}
555
error = self.assertRaises(APIError, schema.extract, params)
556
self.assertEqual(400, error.status)
557
self.assertEqual("InvalidParameterCombination", error.code)
558
self.assertEqual("The parameter 'name' may only be specified once.",
561
def test_extract_with_mixed(self):
563
L{Schema.extract} stores in the rest result all numbered parameters
564
given without an index.
566
schema = Schema(Unicode("name.n"))
568
InconsistentParameterError,
569
schema.extract, {"nameFOOO": "foo", "nameFOOO.1": "bar"})
571
def test_extract_with_non_numbered_template(self):
573
L{Schema.extract} accepts a single numbered argument even if the
574
associated template is not numbered.
576
schema = Schema(Unicode("name"))
577
arguments, _ = schema.extract({"name.1": "foo"})
578
self.assertEqual("foo", arguments.name)
580
def test_extract_with_non_integer_index(self):
582
L{Schema.extract} raises an error when trying to pass a numbered
583
parameter with a non-integer index.
585
schema = Schema(Unicode("name.n"))
586
params = {"name.one": "foo"}
587
error = self.assertRaises(APIError, schema.extract, params)
588
self.assertEqual(400, error.status)
589
self.assertEqual("UnknownParameter", error.code)
590
self.assertEqual("The parameter one is not recognized",
593
def test_extract_with_negative_index(self):
595
L{Schema.extract} raises an error when trying to pass a numbered
596
parameter with a negative index.
598
schema = Schema(Unicode("name.n"))
599
params = {"name.-1": "foo"}
600
error = self.assertRaises(APIError, schema.extract, params)
601
self.assertEqual(400, error.status)
602
self.assertEqual("UnknownParameter", error.code)
603
self.assertEqual("The parameter -1 is not recognized",
606
def test_bundle(self):
608
L{Schema.bundle} returns a dictionary of raw parameters that
609
can be used for an EC2-style query.
611
schema = Schema(Unicode("name"))
612
params = schema.bundle(name="foo")
613
self.assertEqual({"name": "foo"}, params)
615
def test_bundle_with_numbered(self):
617
L{Schema.bundle} correctly handles numbered arguments.
619
schema = Schema(Unicode("name.n"))
620
params = schema.bundle(name=["foo", "bar"])
621
self.assertEqual({"name.1": "foo", "name.2": "bar"}, params)
623
def test_bundle_with_two_numbered(self):
625
L{Schema.bundle} can bundle multiple numbered lists.
627
schema = Schema(Unicode("names.n"), Unicode("things.n"))
628
params = schema.bundle(names=["foo", "bar"], things=["baz", "quux"])
629
self.assertEqual({"names.1": "foo", "names.2": "bar",
630
"things.1": "baz", "things.2": "quux"},
633
def test_bundle_with_none(self):
634
"""L{None} values are discarded in L{Schema.bundle}."""
635
schema = Schema(Unicode("name.n", optional=True))
636
params = schema.bundle(name=None)
637
self.assertEqual({}, params)
639
def test_bundle_with_empty_numbered(self):
641
L{Schema.bundle} correctly handles an empty numbered arguments list.
643
schema = Schema(Unicode("name.n"))
644
params = schema.bundle(name=[])
645
self.assertEqual({}, params)
647
def test_bundle_with_numbered_not_supplied(self):
649
L{Schema.bundle} ignores parameters that are not present.
651
schema = Schema(Unicode("name.n"))
652
params = schema.bundle()
653
self.assertEqual({}, params)
655
def test_bundle_with_multiple(self):
657
L{Schema.bundle} correctly handles multiple arguments.
659
schema = Schema(Unicode("name.n"), Integer("count"))
660
params = schema.bundle(name=["Foo", "Bar"], count=123)
661
self.assertEqual({"name.1": "Foo", "name.2": "Bar", "count": "123"},
664
def test_bundle_with_structure(self):
665
"""L{Schema.bundle} can bundle L{Structure}s."""
668
Structure("struct", fields={"field1": Unicode(),
669
"field2": Integer()})])
670
params = schema.bundle(struct={"field1": "hi", "field2": 59})
671
self.assertEqual({"struct.field1": "hi", "struct.field2": "59"},
674
def test_bundle_with_list(self):
675
"""L{Schema.bundle} can bundle L{List}s."""
676
schema = Schema(parameters=[List("things", item=Unicode())])
677
params = schema.bundle(things=["foo", "bar"])
678
self.assertEqual({"things.1": "foo", "things.2": "bar"}, params)
680
def test_bundle_with_structure_with_arguments(self):
682
L{Schema.bundle} can bundle L{Structure}s (specified as L{Arguments}).
686
Structure("struct", fields={"field1": Unicode(),
687
"field2": Integer()})])
688
params = schema.bundle(struct=Arguments({"field1": "hi",
690
self.assertEqual({"struct.field1": "hi", "struct.field2": "59"},
693
def test_bundle_with_list_with_arguments(self):
694
"""L{Schema.bundle} can bundle L{List}s (specified as L{Arguments})."""
695
schema = Schema(parameters=[List("things", item=Unicode())])
696
params = schema.bundle(things=Arguments({1: "foo", 2: "bar"}))
697
self.assertEqual({"things.1": "foo", "things.2": "bar"}, params)
699
def test_bundle_with_arguments(self):
700
"""L{Schema.bundle} can bundle L{Arguments} too."""
701
schema = Schema(Unicode("name.n"), Integer("count"))
702
arguments = Arguments({"name": Arguments({1: "Foo", 7: "Bar"}),
704
params = schema.bundle(arguments)
705
self.assertEqual({"name.1": "Foo", "name.7": "Bar", "count": "123"},
708
def test_bundle_with_arguments_and_extra(self):
710
L{Schema.bundle} can bundle L{Arguments} with keyword arguments too.
712
Keyword arguments take precedence.
714
schema = Schema(Unicode("name.n"), Integer("count"))
715
arguments = Arguments({"name": {1: "Foo", 7: "Bar"}, "count": 321})
716
params = schema.bundle(arguments, count=123)
717
self.assertEqual({"name.1": "Foo", "name.2": "Bar", "count": "123"},
720
def test_bundle_with_missing_parameter(self):
722
L{Schema.bundle} raises an exception one of the given parameters
723
doesn't exist in the schema.
725
schema = Schema(Integer("count"))
726
self.assertRaises(RuntimeError, schema.bundle, name="foo")
728
def test_add_single_extra_schema_item(self):
729
"""New Parameters can be added to the Schema."""
730
schema = Schema(Unicode("name"))
731
schema = schema.extend(Unicode("computer"))
732
arguments, _ = schema.extract({"name": "value", "computer": "testing"})
733
self.assertEqual(u"value", arguments.name)
734
self.assertEqual("testing", arguments.computer)
736
def test_add_extra_schema_items(self):
737
"""A list of new Parameters can be added to the Schema."""
738
schema = Schema(Unicode("name"))
739
schema = schema.extend(Unicode("computer"), Integer("count"))
740
arguments, _ = schema.extract({"name": "value", "computer": "testing",
742
self.assertEqual(u"value", arguments.name)
743
self.assertEqual("testing", arguments.computer)
744
self.assertEqual(5, arguments.count)
747
"""L{List}s can be extracted."""
748
schema = Schema(List("foo", Integer()))
749
arguments, _ = schema.extract({"foo.1": "1", "foo.2": "2"})
750
self.assertEqual([1, 2], arguments.foo)
752
def test_optional_list(self):
754
The default value of an optional L{List} is C{[]}.
756
schema = Schema(List("names", Unicode(), optional=True))
757
arguments, _ = schema.extract({})
758
self.assertEqual([], arguments.names)
760
def test_default_list(self):
762
The default of a L{List} can be specified as a list.
764
schema = Schema(List("names", Unicode(), optional=True,
765
default=[u"foo", u"bar"]))
766
arguments, _ = schema.extract({})
767
self.assertEqual([u"foo", u"bar"], arguments.names)
769
def test_list_of_list(self):
770
"""L{List}s can be nested."""
771
schema = Schema(List("foo", List(item=Unicode())))
772
arguments, _ = schema.extract(
773
{"foo.1.1": "first-first", "foo.1.2": "first-second",
774
"foo.2.1": "second-first", "foo.2.2": "second-second"})
775
self.assertEqual([["first-first", "first-second"],
776
["second-first", "second-second"]],
779
def test_structure(self):
781
L{Schema}s with L{Structure} parameters can have arguments extracted.
783
schema = Schema(Structure("foo", {"a": Integer(), "b": Integer()}))
784
arguments, _ = schema.extract({"foo.a": "1", "foo.b": "2"})
785
self.assertEqual(1, arguments.foo.a)
786
self.assertEqual(2, arguments.foo.b)
788
def test_structure_of_structures(self):
789
"""L{Structure}s can be nested."""
790
sub_struct = Structure(fields={"a": Unicode(), "b": Unicode()})
791
schema = Schema(Structure("foo", fields={"a": sub_struct,
793
arguments, _ = schema.extract({"foo.a.a": "a-a", "foo.a.b": "a-b",
794
"foo.b.a": "b-a", "foo.b.b": "b-b"})
795
self.assertEqual("a-a", arguments.foo.a.a)
796
self.assertEqual("a-b", arguments.foo.a.b)
797
self.assertEqual("b-a", arguments.foo.b.a)
798
self.assertEqual("b-b", arguments.foo.b.b)
800
def test_list_of_structures(self):
801
"""L{List}s of L{Structure}s are extracted properly."""
803
List("foo", Structure(fields={"a": Integer(), "b": Integer()})))
804
arguments, _ = schema.extract({"foo.1.a": "1", "foo.1.b": "2",
805
"foo.2.a": "3", "foo.2.b": "4"})
806
self.assertEqual(1, arguments.foo[0]['a'])
807
self.assertEqual(2, arguments.foo[0]['b'])
808
self.assertEqual(3, arguments.foo[1]['a'])
809
self.assertEqual(4, arguments.foo[1]['b'])
811
def test_structure_of_list(self):
812
"""L{Structure}s of L{List}s are extracted properly."""
813
schema = Schema(Structure("foo", fields={"l": List(item=Integer())}))
814
arguments, _ = schema.extract({"foo.l.1": "1", "foo.l.2": "2"})
815
self.assertEqual([1, 2], arguments.foo.l)
817
def test_new_parameters(self):
819
L{Schema} accepts a C{parameters} parameter to specify parameters in a
820
{name: field} format.
823
parameters=[Structure("foo",
824
fields={"l": List(item=Integer())})])
825
arguments, _ = schema.extract({"foo.l.1": "1", "foo.l.2": "2"})
826
self.assertEqual([1, 2], arguments.foo.l)
828
def test_schema_conversion_list(self):
830
Backwards-compatibility conversion maintains the name of lists.
832
schema = Schema(Unicode("foos.N"))
833
parameters = schema.get_parameters()
834
self.assertEqual(1, len(parameters))
835
self.assertTrue(isinstance(parameters[0], List))
836
self.assertEqual("foos", parameters[0].name)
838
def test_coerce_list(self):
840
When a L{List} coerces the value of one of its item, it uses the the
841
proper name in the C{MissingParameter} error raised.
843
parameter = List("foo", Unicode())
844
error = self.assertRaises(APIError, parameter.item.coerce, "")
845
self.assertEqual(400, error.status)
846
self.assertEqual("MissingParameter", error.code)
847
self.assertEqual("The request must contain the parameter foo "
851
def test_schema_conversion_structure_name(self):
853
Backwards-compatibility conversion maintains the names of fields in
856
schema = Schema(Unicode("foos.N.field"),
857
Unicode("foos.N.field2"))
858
parameters = schema.get_parameters()
859
self.assertEqual(1, len(parameters))
860
self.assertTrue(isinstance(parameters[0], List))
861
self.assertEqual("foos", parameters[0].name)
862
self.assertEqual("N",
863
parameters[0].item.name)
864
self.assertEqual("field",
865
parameters[0].item.fields["field"].name)
866
self.assertEqual("field2",
867
parameters[0].item.fields["field2"].name)
869
def test_schema_conversion_optional_list(self):
871
Backwards-compatibility conversions maintains optional-ness of lists.
873
schema = Schema(Unicode("foos.N", optional=True))
874
arguments, _ = schema.extract({})
875
self.assertEqual([], arguments.foos)
877
def test_schema_conversion_optional_structure_field(self):
879
Backwards-compatibility conversion maintains optional-ness of structure
882
schema = Schema(Unicode("foos.N.field"),
883
Unicode("foos.N.field2", optional=True, default=u"hi"))
884
arguments, _ = schema.extract({"foos.0.field": u"existent"})
885
self.assertEqual(u"existent", arguments.foos[0].field)
886
self.assertEqual(u"hi", arguments.foos[0].field2)
888
def test_additional_schema_attributes(self):
890
Additional data can be specified on the Schema class for specifying a
893
result = {'id': Integer(), 'name': Unicode(), 'data': RawStr()}
898
doc="""Get the stuff.""",
905
self.assertEqual("GetStuff", schema.name)
906
self.assertEqual("Get the stuff.", schema.doc)
907
self.assertEqual(result, schema.result)
908
self.assertEqual(set(errors), schema.errors)
910
def test_extend_with_additional_schema_attributes(self):
912
The additional schema attributes can be passed to L{Schema.extend}.
914
result = {'id': Integer(), 'name': Unicode(), 'data': RawStr()}
919
parameters=[Integer("id")])
921
schema2 = schema.extend(
924
parameters=[Unicode("scope")],
928
self.assertEqual("GetStuff2", schema2.name)
929
self.assertEqual("Get stuff 2", schema2.doc)
930
self.assertEqual(result, schema2.result)
931
self.assertEqual(set(errors), schema2.errors)
933
arguments, _ = schema2.extract({'id': '5', 'scope': u'foo'})
934
self.assertEqual(5, arguments.id)
935
self.assertEqual(u'foo', arguments.scope)
937
def test_extend_maintains_existing_attributes(self):
939
If additional schema attributes aren't passed to L{Schema.extend}, they
942
result = {'id': Integer(), 'name': Unicode(), 'data': RawStr()}
947
doc="""Get the stuff.""",
948
parameters=[Integer("id")],
952
schema2 = schema.extend(parameters=[Unicode("scope")])
954
self.assertEqual("GetStuff", schema2.name)
955
self.assertEqual("Get the stuff.", schema2.doc)
956
self.assertEqual(result, schema2.result)
957
self.assertEqual(set(errors), schema2.errors)
959
arguments, _ = schema2.extract({'id': '5', 'scope': u'foo'})
960
self.assertEqual(5, arguments.id)
961
self.assertEqual(u'foo', arguments.scope)
963
def test_extend_result(self):
965
Result fields can also be extended with L{Schema.extend}.
967
schema = Schema(result={'name': Unicode()})
968
schema2 = schema.extend(
969
result={'id': Integer()})
970
result_structure = Structure(fields=schema2.result)
972
{'name': u'foo', 'id': 5},
973
result_structure.coerce({'name': u'foo', 'id': '5'}))
975
def test_extend_errors(self):
977
Errors can be extended with L{Schema.extend}.
979
schema = Schema(parameters=[], errors=[APIError])
980
schema2 = schema.extend(errors=[ZeroDivisionError])
981
self.assertEqual(set([APIError, ZeroDivisionError]), schema2.errors)
983
def test_extend_maintains_parameter_order(self):
985
Extending a schema with additional parameters puts the new parameters
988
schema = Schema(parameters=[Unicode("name"), Unicode("value")])
989
schema2 = schema.extend(parameters=[Integer("foo"), Unicode("index")])
990
self.assertEqual(["name", "value", "foo", "index"],
991
[p.name for p in schema2.get_parameters()])
993
def test_schema_field_names(self):
994
structure = Structure(fields={"foo": Integer()})
995
self.assertEqual("foo", structure.fields["foo"].name)