1# Copyright 2008 Canonical Ltd.
2
3# This file is part of launchpadlib.
4#
5# launchpadlib is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as
7# published by the Free Software Foundation, either version 3 of the
8# License, or (at your option) any later version.
9#
10# launchpadlib is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13# Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with launchpadlib.  If not, see
17# <http://www.gnu.org/licenses/>.
18
19from datetime import datetime
20
21from testresources import ResourcedTestCase
22
23from launchpadlib.testing.launchpad import (
24    FakeLaunchpad,
25    FakeResource,
26    FakeRoot,
27    IntegrityError,
28    )
29from launchpadlib.testing.resources import (
30    FakeLaunchpadResource, get_application)
31
32
33class FakeRootTest(ResourcedTestCase):
34
35    def test_create_root_resource(self):
36        root_resource = FakeRoot(get_application())
37        self.assertTrue(isinstance(root_resource, FakeResource))
38
39
40class FakeResourceTest(ResourcedTestCase):
41
42    resources = [("launchpad", FakeLaunchpadResource())]
43
44    def test_repr(self):
45        """A custom C{__repr__} is provided for L{FakeResource}s."""
46        branches = dict(total_size="test-branch")
47        self.launchpad.me = dict(getBranches=lambda statuses: branches)
48        branches = self.launchpad.me.getBranches([])
49        obj_id = hex(id(branches))
50        self.assertEqual(
51            "<FakeResource branch-page-resource object at %s>" % obj_id,
52            repr(branches))
53
54    def test_repr_with_name(self):
55        """
56        If the fake has a C{name} property it's included in the repr string to
57        make it easier to figure out what it is.
58        """
59        self.launchpad.me = dict(name="foo")
60        person = self.launchpad.me
61        self.assertEqual("<FakeEntry person foo at %s>" % hex(id(person)),
62                         repr(person))
63
64    def test_repr_with_id(self):
65        """
66        If the fake has an C{id} property it's included in the repr string to
67        make it easier to figure out what it is.
68        """
69        bug = dict(id="1", title="Bug #1")
70        self.launchpad.bugs = dict(entries=[bug])
71        [bug] = list(self.launchpad.bugs)
72        self.assertEqual("<FakeResource bug 1 at %s>" % hex(id(bug)), repr(bug))
73
74
75class FakeLaunchpadTest(ResourcedTestCase):
76
77    resources = [("launchpad", FakeLaunchpadResource())]
78
79    def test_wb_instantiate_without_application(self):
80        """
81        The builtin WADL definition is used if the C{application} is not
82        provided during instantiation.
83        """
84        credentials = object()
85        launchpad = FakeLaunchpad(credentials)
86        self.assertEqual(credentials, launchpad.credentials)
87        self.assertEqual(get_application(), launchpad._application)
88
89    def test_instantiate_with_everything(self):
90        """
91        L{FakeLaunchpad} takes the same parameters as L{Launchpad} during
92        instantiation, with the addition of an C{application} parameter.  The
93        optional parameters are discarded when the object is instantiated.
94        """
95        credentials = object()
96        launchpad = FakeLaunchpad(credentials, service_root=None, cache=None,
97                                  timeout=None, proxy_info=None,
98                                  application=get_application())
99        self.assertEqual(credentials, launchpad.credentials)
100
101    def test_instantiate_with_credentials(self):
102        """A L{FakeLaunchpad} can be instantiated with credentials."""
103        credentials = object()
104        launchpad = FakeLaunchpad(credentials, application=get_application())
105        self.assertEqual(credentials, launchpad.credentials)
106
107    def test_instantiate_without_credentials(self):
108        """
109        A L{FakeLaunchpad} instantiated without credentials has its
110        C{credentials} attribute set to C{None}.
111        """
112        self.assertIsNone(self.launchpad.credentials)
113
114    def test_set_undefined_property(self):
115        """
116        An L{IntegrityError} is raised if an attribute is set on a
117        L{FakeLaunchpad} instance that isn't present in the WADL definition.
118        """
119        self.assertRaises(IntegrityError, setattr, self.launchpad, "foo", "bar")
120
121    def test_get_undefined_resource(self):
122        """
123        An L{AttributeError} is raised if an attribute is accessed on a
124        L{FakeLaunchpad} instance that doesn't exist.
125        """
126        self.launchpad.me = dict(display_name="Foo")
127        self.assertRaises(AttributeError, getattr, self.launchpad.me, "name")
128
129    def test_string_property(self):
130        """
131        Sample data can be created by setting L{FakeLaunchpad} attributes with
132        dicts that represent objects.  Plain string values can be represented
133        as C{str} values.
134        """
135        self.launchpad.me = dict(name="foo")
136        self.assertEqual("foo", self.launchpad.me.name)
137
138    def test_unicode_property(self):
139        """
140        Sample data can be created by setting L{FakeLaunchpad} attributes with
141        dicts that represent objects.  Plain string values can be represented
142        as C{unicode} strings.
143        """
144        self.launchpad.me = dict(name=u"foo")
145        self.assertEqual(u"foo", self.launchpad.me.name)
146
147    def test_datetime_property(self):
148        """
149        Attributes that represent dates are set with C{datetime} instances.
150        """
151        now = datetime.utcnow()
152        self.launchpad.me = dict(date_created=now)
153        self.assertEqual(now, self.launchpad.me.date_created)
154
155    def test_invalid_datetime_property(self):
156        """
157        Only C{datetime} values can be set on L{FakeLaunchpad} instances for
158        attributes that represent dates.
159        """
160        self.assertRaises(IntegrityError, setattr, self.launchpad, "me",
161                          dict(date_created="now"))
162
163    def test_multiple_string_properties(self):
164        """
165        Sample data can be created by setting L{FakeLaunchpad} attributes with
166        dicts that represent objects.
167        """
168        self.launchpad.me = dict(name="foo", display_name="Foo")
169        self.assertEqual("foo", self.launchpad.me.name)
170        self.assertEqual("Foo", self.launchpad.me.display_name)
171
172    def test_invalid_property_name(self):
173        """
174        Sample data set on a L{FakeLaunchpad} instance is validated against
175        the WADL definition.  If a key is defined on a resource that doesn't
176        match a related parameter, an L{IntegrityError} is raised.
177        """
178        self.assertRaises(IntegrityError, setattr, self.launchpad, "me",
179                          dict(foo="bar"))
180
181    def test_invalid_property_value(self):
182        """
183        The types of sample data values set on L{FakeLaunchpad} instances are
184        validated against types defined in the WADL definition.
185        """
186        self.assertRaises(IntegrityError, setattr, self.launchpad, "me",
187                          dict(name=102))
188
189    def test_callable(self):
190        """
191        A callable set on a L{FakeLaunchpad} instance is validated against the
192        WADL definition, to make sure a matching method exists.
193        """
194        branches = dict(total_size="test-branch")
195        self.launchpad.me = dict(getBranches=lambda statuses: branches)
196        self.assertNotEqual(None, self.launchpad.me.getBranches([]))
197
198    def test_invalid_callable_name(self):
199        """
200        An L{IntegrityError} is raised if a method is defined on a resource
201        that doesn't match a method defined in the WADL definition.
202        """
203        self.assertRaises(IntegrityError, setattr, self.launchpad, "me",
204                          dict(foo=lambda: None))
205
206    def test_callable_object_return_type(self):
207        """
208        The result of a fake method is a L{FakeResource}, automatically
209        created from the object used to define the return object.
210        """
211        branches = dict(total_size="8")
212        self.launchpad.me = dict(getBranches=lambda statuses: branches)
213        branches = self.launchpad.me.getBranches([])
214        self.assertTrue(isinstance(branches, FakeResource))
215        self.assertEqual("8", branches.total_size)
216
217    def test_invalid_callable_object_return_type(self):
218        """
219        An L{IntegrityError} is raised if a method returns an invalid result.
220        """
221        branches = dict(total_size=8)
222        self.launchpad.me = dict(getBranches=lambda statuses: branches)
223        self.assertRaises(IntegrityError, self.launchpad.me.getBranches, [])
224
225    def test_collection_property(self):
226        """
227        Sample collections can be set on L{FakeLaunchpad} instances.  They are
228        validated the same way other sample data is validated.
229        """
230        branch = dict(name="foo")
231        self.launchpad.branches = dict(getByUniqueName=lambda name: branch)
232        branch = self.launchpad.branches.getByUniqueName("foo")
233        self.assertEqual("foo", branch.name)
234
235    def test_iterate_collection(self):
236        """
237        Data for a sample collection set on a L{FakeLaunchpad} instance can be
238        iterated over if an C{entries} key is defined.
239        """
240        bug = dict(id="1", title="Bug #1")
241        self.launchpad.bugs = dict(entries=[bug])
242        bugs = list(self.launchpad.bugs)
243        self.assertEqual(1, len(bugs))
244        bug = bugs[0]
245        self.assertEqual("1", bug.id)
246        self.assertEqual("Bug #1", bug.title)
247
248    def test_collection_with_invalid_entries(self):
249        """
250        Sample data for each entry in a collection is validated when it's set
251        on a L{FakeLaunchpad} instance.
252        """
253        bug = dict(foo="bar")
254        self.assertRaises(IntegrityError, setattr, self.launchpad, "bugs",
255                          dict(entries=[bug]))
256
257    def test_slice_collection(self):
258        """
259        Data for a sample collection set on a L{FakeLaunchpad} instance can be
260        sliced if an C{entries} key is defined.
261        """
262        bug1 = dict(id="1", title="Bug #1")
263        bug2 = dict(id="2", title="Bug #2")
264        bug3 = dict(id="3", title="Bug #3")
265        self.launchpad.bugs = dict(entries=[bug1, bug2, bug3])
266        bugs = self.launchpad.bugs[1:3]
267        self.assertEqual(2, len(bugs))
268        self.assertEqual("2", bugs[0].id)
269        self.assertEqual("3", bugs[1].id)
270
271    def test_slice_collection_with_negative_start(self):
272        """
273        A C{ValueError} is raised if a negative start value is used when
274        slicing a sample collection set on a L{FakeLaunchpad} instance.
275        """
276        bug1 = dict(id="1", title="Bug #1")
277        bug2 = dict(id="2", title="Bug #2")
278        self.launchpad.bugs = dict(entries=[bug1, bug2])
279        self.assertRaises(ValueError, lambda: self.launchpad.bugs[-1:])
280        self.assertRaises(ValueError, lambda: self.launchpad.bugs[-1:2])
281
282    def test_slice_collection_with_negative_stop(self):
283        """
284        A C{ValueError} is raised if a negative stop value is used when
285        slicing a sample collection set on a L{FakeLaunchpad} instance.
286        """
287        bug1 = dict(id="1", title="Bug #1")
288        bug2 = dict(id="2", title="Bug #2")
289        self.launchpad.bugs = dict(entries=[bug1, bug2])
290        self.assertRaises(ValueError, lambda: self.launchpad.bugs[:-1])
291        self.assertRaises(ValueError, lambda: self.launchpad.bugs[0:-1])
292
293    def test_subscript_operator_out_of_range(self):
294        """
295        An C{IndexError} is raised if an invalid index is used when retrieving
296        data from a sample collection.
297        """
298        bug1 = dict(id="1", title="Bug #1")
299        self.launchpad.bugs = dict(entries=[bug1])
300        self.assertRaises(IndexError, lambda: self.launchpad.bugs[2])
301
302    def test_replace_property(self):
303        """Values already set on fake resource objects can be replaced."""
304        self.launchpad.me = dict(name="foo")
305        person = self.launchpad.me
306        self.assertEqual("foo", person.name)
307        person.name = "bar"
308        self.assertEqual("bar", person.name)
309        self.assertEqual("bar", self.launchpad.me.name)
310
311    def test_replace_method(self):
312        """Methods already set on fake resource objects can be replaced."""
313        branch1 = dict(name="foo", bzr_identity="lp:~user/project/branch1")
314        branch2 = dict(name="foo", bzr_identity="lp:~user/project/branch2")
315        self.launchpad.branches = dict(getByUniqueName=lambda name: branch1)
316        self.launchpad.branches.getByUniqueName = lambda name: branch2
317        branch = self.launchpad.branches.getByUniqueName("foo")
318        self.assertEqual("lp:~user/project/branch2", branch.bzr_identity)
319
320    def test_replace_property_with_invalid_value(self):
321        """Values set on fake resource objects are validated."""
322        self.launchpad.me = dict(name="foo")
323        person = self.launchpad.me
324        self.assertRaises(IntegrityError, setattr, person, "name", 1)
325
326    def test_replace_resource(self):
327        """Resources already set on L{FakeLaunchpad} can be replaced."""
328        self.launchpad.me = dict(name="foo")
329        self.assertEqual("foo", self.launchpad.me.name)
330        self.launchpad.me = dict(name="bar")
331        self.assertEqual("bar", self.launchpad.me.name)
332
333    def test_add_property(self):
334        """Sample data set on a L{FakeLaunchpad} instance can be added to."""
335        self.launchpad.me = dict(name="foo")
336        person = self.launchpad.me
337        person.display_name = "Foo"
338        self.assertEqual("foo", person.name)
339        self.assertEqual("Foo", person.display_name)
340        self.assertEqual("foo", self.launchpad.me.name)
341        self.assertEqual("Foo", self.launchpad.me.display_name)
342
343    def test_add_property_to_empty_object(self):
344        """An empty object can be used when creating sample data."""
345        self.launchpad.me = dict()
346        self.assertRaises(AttributeError, getattr, self.launchpad.me, "name")
347        self.launchpad.me.name = "foo"
348        self.assertEqual("foo", self.launchpad.me.name)
349
350    def test_login(self):
351        """
352        L{FakeLaunchpad.login} ignores all parameters and returns a new
353        instance using the builtin WADL definition.
354        """
355        launchpad = FakeLaunchpad.login("name", "token", "secret")
356        self.assertTrue(isinstance(launchpad, FakeLaunchpad))
357
358    def test_get_token_and_login(self):
359        """
360        L{FakeLaunchpad.get_token_and_login} ignores all parameters and
361        returns a new instance using the builtin WADL definition.
362        """
363        launchpad = FakeLaunchpad.get_token_and_login("name")
364        self.assertTrue(isinstance(launchpad, FakeLaunchpad))
365
366    def test_login_with(self):
367        """
368        L{FakeLaunchpad.login_with} ignores all parameters and returns a new
369        instance using the builtin WADL definition.
370        """
371        launchpad = FakeLaunchpad.login_with("name")
372        self.assertTrue(isinstance(launchpad, FakeLaunchpad))
373
374    def test_lp_save(self):
375        """
376        Sample object have an C{lp_save} method that is a no-op by default.
377        """
378        self.launchpad.me = dict(name="foo")
379        self.assertTrue(self.launchpad.me.lp_save())
380
381    def test_custom_lp_save(self):
382        """A custom C{lp_save} method can be set on a L{FakeResource}."""
383        self.launchpad.me = dict(name="foo", lp_save=lambda: "custom")
384        self.assertEqual("custom", self.launchpad.me.lp_save())
385
386    def test_set_custom_lp_save(self):
387        """
388        A custom C{lp_save} method can be set on a L{FakeResource} after its
389        been created.
390        """
391        self.launchpad.me = dict(name="foo")
392        self.launchpad.me.lp_save = lambda: "custom"
393        self.assertEqual("custom", self.launchpad.me.lp_save())
394