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