OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library db_test; |
| 6 |
| 7 /// NOTE: In order to run these tests, the following datastore indices must |
| 8 /// exist: |
| 9 /// $ cat index.yaml |
| 10 /// indexes: |
| 11 /// - kind: User |
| 12 /// ancestor: no |
| 13 /// properties: |
| 14 /// - name: name |
| 15 /// direction: asc |
| 16 /// - name: nickname |
| 17 /// direction: desc |
| 18 /// |
| 19 /// - kind: User |
| 20 /// ancestor: no |
| 21 /// properties: |
| 22 /// - name: name |
| 23 /// direction: desc |
| 24 /// - name: nickname |
| 25 /// direction: desc |
| 26 /// |
| 27 /// - kind: User |
| 28 /// ancestor: no |
| 29 /// properties: |
| 30 /// - name: name |
| 31 /// direction: desc |
| 32 /// - name: nickname |
| 33 /// direction: asc |
| 34 /// |
| 35 /// - kind: User |
| 36 /// ancestor: no |
| 37 /// properties: |
| 38 /// - name: language |
| 39 /// direction: asc |
| 40 /// - name: name |
| 41 /// direction: asc |
| 42 /// |
| 43 /// $ gcloud preview datastore create-indexes . |
| 44 /// 02:19 PM Host: appengine.google.com |
| 45 /// 02:19 PM Uploading index definitions. |
| 46 |
| 47 import 'dart:async'; |
| 48 |
| 49 import 'package:unittest/unittest.dart'; |
| 50 import 'package:gcloud/db.dart' as db; |
| 51 import 'package:gcloud/src/datastore_impl.dart' as datastore_impl; |
| 52 |
| 53 import '../../datastore/e2e/datastore_test_impl.dart' as datastore_test; |
| 54 import '../../common_e2e.dart'; |
| 55 |
| 56 @db.Kind() |
| 57 class Person extends db.Model { |
| 58 @db.StringProperty() |
| 59 String name; |
| 60 |
| 61 @db.IntProperty() |
| 62 int age; |
| 63 |
| 64 @db.ModelKeyProperty() |
| 65 db.Key wife; |
| 66 |
| 67 operator==(Object other) => sameAs(other); |
| 68 |
| 69 sameAs(Object other) { |
| 70 return other is Person && |
| 71 id == other.id && |
| 72 parentKey == other.parentKey && |
| 73 name == other.name && |
| 74 age == other.age && |
| 75 wife == other.wife; |
| 76 } |
| 77 |
| 78 String toString() => 'Person(id: $id, name: $name, age: $age)'; |
| 79 } |
| 80 |
| 81 |
| 82 @db.Kind() |
| 83 class User extends Person { |
| 84 @db.StringProperty() |
| 85 String nickname; |
| 86 |
| 87 @db.StringListProperty(propertyName: 'language') |
| 88 List<String> languages = const []; |
| 89 |
| 90 sameAs(Object other) { |
| 91 if (!(super.sameAs(other) && other is User && nickname == other.nickname)) |
| 92 return false; |
| 93 |
| 94 User user = other; |
| 95 if (languages == null) { |
| 96 if (user.languages == null) return true; |
| 97 return false; |
| 98 } |
| 99 if (languages.length != user.languages.length) { |
| 100 return false; |
| 101 } |
| 102 |
| 103 for (int i = 0; i < languages.length; i++) { |
| 104 if (languages[i] != user.languages[i]) { |
| 105 return false; |
| 106 } |
| 107 } |
| 108 return true; |
| 109 } |
| 110 |
| 111 String toString() => |
| 112 'User(${super.toString()}, nickname: $nickname, languages: $languages'; |
| 113 } |
| 114 |
| 115 |
| 116 @db.Kind() |
| 117 class ExpandoPerson extends db.ExpandoModel { |
| 118 @db.StringProperty() |
| 119 String name; |
| 120 |
| 121 @db.StringProperty(propertyName: 'NN') |
| 122 String nickname; |
| 123 |
| 124 operator==(Object other) { |
| 125 if (other is ExpandoPerson && id == other.id && name == other.name) { |
| 126 if (additionalProperties.length != other.additionalProperties.length) { |
| 127 return false; |
| 128 } |
| 129 for (var key in additionalProperties.keys) { |
| 130 if (additionalProperties[key] != other.additionalProperties[key]) { |
| 131 return false; |
| 132 } |
| 133 } |
| 134 return true; |
| 135 } |
| 136 return false; |
| 137 } |
| 138 } |
| 139 |
| 140 |
| 141 Future sleep(Duration duration) { |
| 142 var completer = new Completer(); |
| 143 new Timer(duration, completer.complete); |
| 144 return completer.future; |
| 145 } |
| 146 |
| 147 runTests(db.DatastoreDB store) { |
| 148 void compareModels(List<db.Model> expectedModels, |
| 149 List<db.Model> models, |
| 150 {bool anyOrder: false}) { |
| 151 expect(models.length, equals(expectedModels.length)); |
| 152 if (anyOrder) { |
| 153 // Do expensive O(n^2) search. |
| 154 for (var searchModel in expectedModels) { |
| 155 bool found = false; |
| 156 for (var m in models) { |
| 157 if (m == searchModel) { |
| 158 found = true; |
| 159 break; |
| 160 } |
| 161 } |
| 162 expect(found, isTrue); |
| 163 } |
| 164 } else { |
| 165 for (var i = 0; i < expectedModels.length; i++) { |
| 166 expect(models[i], equals(expectedModels[i])); |
| 167 } |
| 168 } |
| 169 } |
| 170 |
| 171 Future testInsertLookupDelete( |
| 172 List<db.Model> objects, {bool transactional: false}) { |
| 173 var keys = objects.map((db.Model obj) => obj.key).toList(); |
| 174 |
| 175 if (transactional) { |
| 176 return store.withTransaction((db.Transaction commitTransaction) { |
| 177 commitTransaction.queueMutations(inserts: objects); |
| 178 return commitTransaction.commit(); |
| 179 }).then((_) { |
| 180 return store.withTransaction((db.Transaction deleteTransaction) { |
| 181 return deleteTransaction.lookup(keys).then((List<db.Model> models) { |
| 182 compareModels(objects, models); |
| 183 deleteTransaction.queueMutations(deletes: keys); |
| 184 return deleteTransaction.commit(); |
| 185 }); |
| 186 }); |
| 187 }); |
| 188 } else { |
| 189 return store.commit(inserts: objects).then(expectAsync((_) { |
| 190 return store.lookup(keys).then(expectAsync((List<db.Model> models) { |
| 191 compareModels(objects, models); |
| 192 return store.commit(deletes: keys).then(expectAsync((_) { |
| 193 return store.lookup(keys).then(expectAsync((List<db.Model> models) { |
| 194 for (var i = 0; i < models.length; i++) { |
| 195 expect(models[i], isNull); |
| 196 } |
| 197 })); |
| 198 })); |
| 199 })); |
| 200 })); |
| 201 } |
| 202 } |
| 203 |
| 204 group('key', () { |
| 205 test('equal_and_hashcode', () { |
| 206 var k1 = store.emptyKey.append(User, id: 10).append(Person, id: 12); |
| 207 var k2 = store.newPartition(null) |
| 208 .emptyKey.append(User, id: 10).append(Person, id: 12); |
| 209 expect(k1, equals(k2)); |
| 210 expect(k1.hashCode, equals(k2.hashCode)); |
| 211 }); |
| 212 }); |
| 213 |
| 214 group('e2e_db', () { |
| 215 group('insert_lookup_delete', () { |
| 216 test('persons', () { |
| 217 var root = store.emptyKey; |
| 218 var persons = []; |
| 219 for (var i = 1; i <= 10; i++) { |
| 220 persons.add(new Person() |
| 221 ..id = i |
| 222 ..parentKey = root |
| 223 ..age = 42 + i |
| 224 ..name = 'user$i'); |
| 225 } |
| 226 persons.first.wife = persons.last.key; |
| 227 return testInsertLookupDelete(persons); |
| 228 }); |
| 229 test('users', () { |
| 230 var root = store.emptyKey; |
| 231 var users = []; |
| 232 for (var i = 1; i <= 10; i++) { |
| 233 users.add(new User() |
| 234 ..id = i |
| 235 ..parentKey = root |
| 236 ..age = 42 + i |
| 237 ..name = 'user$i' |
| 238 ..nickname = 'nickname${i%3}'); |
| 239 } |
| 240 return testInsertLookupDelete(users); |
| 241 }); |
| 242 test('expando_insert', () { |
| 243 var root = store.emptyKey; |
| 244 var expandoPersons = []; |
| 245 for (var i = 1; i <= 10; i++) { |
| 246 var expandoPerson = new ExpandoPerson() |
| 247 ..parentKey = root |
| 248 ..id = i |
| 249 ..name = 'user$i'; |
| 250 expandoPerson.foo = 'foo$i'; |
| 251 expandoPerson.bar = i; |
| 252 expect(expandoPerson.additionalProperties['foo'], equals('foo$i')); |
| 253 expect(expandoPerson.additionalProperties['bar'], equals(i)); |
| 254 expandoPersons.add(expandoPerson); |
| 255 } |
| 256 return testInsertLookupDelete(expandoPersons); |
| 257 }); |
| 258 test('transactional_insert', () { |
| 259 var root = store.emptyKey; |
| 260 var models = []; |
| 261 |
| 262 models.add(new Person() |
| 263 ..id = 1 |
| 264 ..parentKey = root |
| 265 ..age = 1 |
| 266 ..name = 'user1'); |
| 267 models.add(new User() |
| 268 ..id = 2 |
| 269 ..parentKey = root |
| 270 ..age = 2 |
| 271 ..name = 'user2' |
| 272 ..nickname = 'nickname2'); |
| 273 var expandoPerson = new ExpandoPerson() |
| 274 ..parentKey = root |
| 275 ..id = 3 |
| 276 ..name = 'user1'; |
| 277 expandoPerson.foo = 'foo1'; |
| 278 expandoPerson.bar = 2; |
| 279 |
| 280 return testInsertLookupDelete(models, transactional: true); |
| 281 }); |
| 282 |
| 283 test('parent_key', () { |
| 284 var root = store.emptyKey; |
| 285 var users = []; |
| 286 for (var i = 333; i <= 334; i++) { |
| 287 users.add(new User() |
| 288 ..id = i |
| 289 ..parentKey = root |
| 290 ..age = 42 + i |
| 291 ..name = 'user$i' |
| 292 ..nickname = 'nickname${i%3}'); |
| 293 } |
| 294 var persons = []; |
| 295 for (var i = 335; i <= 336; i++) { |
| 296 persons.add(new Person() |
| 297 ..id = i |
| 298 ..parentKey = root |
| 299 ..age = 42 + i |
| 300 ..name = 'person$i'); |
| 301 } |
| 302 |
| 303 // We test that we can insert + lookup |
| 304 // users[0], (persons[0] + users[0] as parent) |
| 305 // persons[1], (users[1] + persons[0] as parent) |
| 306 persons[0].parentKey = users[0].key; |
| 307 users[1].parentKey = persons[1].key; |
| 308 |
| 309 return testInsertLookupDelete([]..addAll(users)..addAll(persons)); |
| 310 }); |
| 311 |
| 312 test('auto_ids', () { |
| 313 var root = store.emptyKey; |
| 314 var persons = []; |
| 315 persons.add(new Person() |
| 316 ..id = 42 |
| 317 ..parentKey = root |
| 318 ..age = 80 |
| 319 ..name = 'user80'); |
| 320 // Auto id person with parentKey |
| 321 persons.add(new Person() |
| 322 ..parentKey = root |
| 323 ..age = 81 |
| 324 ..name = 'user81'); |
| 325 // Auto id person without parentKey |
| 326 persons.add(new Person() |
| 327 ..age = 82 |
| 328 ..name = 'user82'); |
| 329 // Auto id person with non-root parentKey |
| 330 var fatherKey = persons.first.parentKey; |
| 331 persons.add(new Person() |
| 332 ..parentKey = fatherKey |
| 333 ..age = 83 |
| 334 ..name = 'user83'); |
| 335 persons.add(new Person() |
| 336 ..id = 43 |
| 337 ..parentKey = root |
| 338 ..age = 84 |
| 339 ..name = 'user84'); |
| 340 return store.commit(inserts: persons).then(expectAsync((_) { |
| 341 // At this point, autoIds are allocated and are relfected in the |
| 342 // models (as well as parentKey if it was empty). |
| 343 |
| 344 var keys = persons.map((db.Model obj) => obj.key).toList(); |
| 345 |
| 346 for (var i = 0; i < persons.length; i++) { |
| 347 expect(persons[i].age, equals(80 + i)); |
| 348 expect(persons[i].name, equals('user${80 + i}')); |
| 349 } |
| 350 |
| 351 expect(persons[0].id, equals(42)); |
| 352 expect(persons[0].parentKey, equals(root)); |
| 353 |
| 354 expect(persons[1].id, isNotNull); |
| 355 expect(persons[1].id is int, isTrue); |
| 356 expect(persons[1].parentKey, equals(root)); |
| 357 |
| 358 expect(persons[2].id, isNotNull); |
| 359 expect(persons[2].id is int, isTrue); |
| 360 expect(persons[2].parentKey, equals(root)); |
| 361 |
| 362 expect(persons[3].id, isNotNull); |
| 363 expect(persons[3].id is int, isTrue); |
| 364 expect(persons[3].parentKey, equals(fatherKey)); |
| 365 |
| 366 expect(persons[4].id, equals(43)); |
| 367 expect(persons[4].parentKey, equals(root)); |
| 368 |
| 369 expect(persons[1].id != persons[2].id, isTrue); |
| 370 // NOTE: We can't make assumptions about the id of persons[3], |
| 371 // because an id doesn't need to be globally unique, only under |
| 372 // entities with the same parent. |
| 373 |
| 374 return store.lookup(keys).then(expectAsync((List<Person> models) { |
| 375 // Since the id/parentKey fields are set after commit and a lookup |
| 376 // returns new model instances, we can do full model comparision |
| 377 // here. |
| 378 compareModels(persons, models); |
| 379 return store.commit(deletes: keys).then(expectAsync((_) { |
| 380 return store.lookup(keys).then(expectAsync((List models) { |
| 381 for (var i = 0; i < models.length; i++) { |
| 382 expect(models[i], isNull); |
| 383 } |
| 384 })); |
| 385 })); |
| 386 })); |
| 387 })); |
| 388 }); |
| 389 }); |
| 390 |
| 391 test('query', () { |
| 392 var root = store.emptyKey; |
| 393 var users = []; |
| 394 for (var i = 1; i <= 10; i++) { |
| 395 var languages = []; |
| 396 if (i == 9) { |
| 397 languages = ['foo']; |
| 398 } else if (i == 10) { |
| 399 languages = ['foo', 'bar']; |
| 400 } |
| 401 users.add(new User() |
| 402 ..id = i |
| 403 ..parentKey = root |
| 404 ..age = 42 + i |
| 405 ..name = 'user$i' |
| 406 ..nickname = 'nickname${i%3}' |
| 407 ..languages = languages); |
| 408 } |
| 409 |
| 410 var expandoPersons = []; |
| 411 for (var i = 1; i <= 3; i++) { |
| 412 var expandoPerson = new ExpandoPerson() |
| 413 ..parentKey = root |
| 414 ..id = i |
| 415 ..name = 'user$i' |
| 416 ..nickname = 'nickuser$i'; |
| 417 expandoPerson.foo = 'foo$i'; |
| 418 expandoPerson.bar = i; |
| 419 expect(expandoPerson.additionalProperties['foo'], equals('foo$i')); |
| 420 expect(expandoPerson.additionalProperties['bar'], equals(i)); |
| 421 expandoPersons.add(expandoPerson); |
| 422 } |
| 423 |
| 424 var LOWER_BOUND = 'user2'; |
| 425 |
| 426 var usersSortedNameDescNicknameAsc = new List.from(users); |
| 427 usersSortedNameDescNicknameAsc.sort((User a, User b) { |
| 428 var result = b.name.compareTo(a.name); |
| 429 if (result == 0) return a.nickname.compareTo(b.nickname); |
| 430 return result; |
| 431 }); |
| 432 |
| 433 var usersSortedNameDescNicknameDesc = new List.from(users); |
| 434 usersSortedNameDescNicknameDesc.sort((User a, User b) { |
| 435 var result = b.name.compareTo(a.name); |
| 436 if (result == 0) return b.nickname.compareTo(a.nickname); |
| 437 return result; |
| 438 }); |
| 439 |
| 440 var usersSortedAndFilteredNameDescNicknameAsc = |
| 441 usersSortedNameDescNicknameAsc.where((User u) { |
| 442 return LOWER_BOUND.compareTo(u.name) <= 0; |
| 443 }).toList(); |
| 444 |
| 445 var usersSortedAndFilteredNameDescNicknameDesc = |
| 446 usersSortedNameDescNicknameDesc.where((User u) { |
| 447 return LOWER_BOUND.compareTo(u.name) <= 0; |
| 448 }).toList(); |
| 449 |
| 450 var fooUsers = users.where( |
| 451 (User u) => u.languages.contains('foo')).toList(); |
| 452 var barUsers = users.where( |
| 453 (User u) => u.languages.contains('bar')).toList(); |
| 454 |
| 455 var allInserts = [] |
| 456 ..addAll(users) |
| 457 ..addAll(expandoPersons); |
| 458 var allKeys = allInserts.map((db.Model model) => model.key).toList(); |
| 459 return store.commit(inserts: allInserts).then((_) { |
| 460 return waitUntilEntitiesReady(store, allKeys).then((_) { |
| 461 var tests = [ |
| 462 // Queries for [Person] return no results, we only have [User] |
| 463 // objects. |
| 464 () { |
| 465 return store.query(Person).run().toList() |
| 466 .then((List<db.Model> models) { |
| 467 compareModels([], models); |
| 468 }); |
| 469 }, |
| 470 |
| 471 // All users query |
| 472 () { |
| 473 return store.query(User).run().toList() |
| 474 .then((List<db.Model> models) { |
| 475 compareModels(users, models, anyOrder: true); |
| 476 }); |
| 477 }, |
| 478 |
| 479 // Sorted query |
| 480 () { |
| 481 return store.query(User) |
| 482 ..order('-name') |
| 483 ..order('nickname') |
| 484 ..run().toList().then((List<db.Model> models) { |
| 485 compareModels( |
| 486 usersSortedNameDescNicknameAsc, models); |
| 487 }); |
| 488 }, |
| 489 () { |
| 490 return store.query(User) |
| 491 ..order('-name') |
| 492 ..order('-nickname') |
| 493 ..run().toList().then((List<db.Model> models) { |
| 494 compareModels( |
| 495 usersSortedNameDescNicknameDesc, models); |
| 496 }); |
| 497 }, |
| 498 |
| 499 // Sorted query with filter |
| 500 () { |
| 501 return store.query(User) |
| 502 ..filter('name >=', LOWER_BOUND) |
| 503 ..order('-name') |
| 504 ..order('nickname') |
| 505 ..run().toList().then((List<db.Model> models) { |
| 506 compareModels(usersSortedAndFilteredNameDescNicknameAsc, |
| 507 models); |
| 508 }); |
| 509 }, |
| 510 () { |
| 511 return store.query(User) |
| 512 ..filter('name >=', LOWER_BOUND) |
| 513 ..order('-name') |
| 514 ..order('-nickname') |
| 515 ..run().toList().then((List<db.Model> models) { |
| 516 compareModels(usersSortedAndFilteredNameDescNicknameDesc, |
| 517 models); |
| 518 }); |
| 519 }, |
| 520 |
| 521 // Filter lists |
| 522 /* FIXME: TODO: FIXME: "IN" not supported in public proto/apiary */ |
| 523 () { |
| 524 return store.query(User) |
| 525 ..filter('languages IN', ['foo']) |
| 526 ..order('name') |
| 527 ..run().toList().then((List<db.Model> models) { |
| 528 compareModels(fooUsers, models, anyOrder: true); |
| 529 }); |
| 530 }, |
| 531 () { |
| 532 return store.query(User) |
| 533 ..filter('languages IN', ['bar']) |
| 534 ..order('name') |
| 535 ..run().toList().then((List<db.Model> models) { |
| 536 compareModels(barUsers, models, anyOrder: true); |
| 537 }); |
| 538 }, |
| 539 |
| 540 // Simple limit/offset test. |
| 541 () { |
| 542 return store.query(User) |
| 543 ..order('-name') |
| 544 ..order('nickname') |
| 545 ..offset(3) |
| 546 ..limit(4) |
| 547 ..run().toList().then((List<db.Model> models) { |
| 548 var expectedModels = |
| 549 usersSortedAndFilteredNameDescNicknameAsc.sublist(3, 7); |
| 550 compareModels(expectedModels, models); |
| 551 }); |
| 552 }, |
| 553 |
| 554 // Expando queries: Filter on normal property. |
| 555 () { |
| 556 return store.query(ExpandoPerson) |
| 557 ..filter('name =', expandoPersons.last.name) |
| 558 ..run().toList().then((List<db.Model> models) { |
| 559 compareModels([expandoPersons.last], models); |
| 560 }); |
| 561 }, |
| 562 // Expando queries: Filter on expanded String property |
| 563 () { |
| 564 return store.query(ExpandoPerson) |
| 565 ..filter('foo =', expandoPersons.last.foo) |
| 566 ..run().toList().then((List<db.Model> models) { |
| 567 compareModels([expandoPersons.last], models); |
| 568 }); |
| 569 }, |
| 570 // Expando queries: Filter on expanded int property |
| 571 () { |
| 572 return store.query(ExpandoPerson) |
| 573 ..filter('bar =', expandoPersons.last.bar) |
| 574 ..run().toList().then((List<db.Model> models) { |
| 575 compareModels([expandoPersons.last], models); |
| 576 }); |
| 577 }, |
| 578 // Expando queries: Filter normal property with different |
| 579 // propertyName (datastore name is 'NN'). |
| 580 () { |
| 581 return store.query(ExpandoPerson) |
| 582 ..filter('nickname =', expandoPersons.last.nickname) |
| 583 ..run().toList().then((List<db.Model> models) { |
| 584 compareModels([expandoPersons.last], models); |
| 585 }); |
| 586 }, |
| 587 |
| 588 // Delete results |
| 589 () => store.commit(deletes: allKeys), |
| 590 |
| 591 // Wait until the entity deletes are reflected in the indices. |
| 592 () => waitUntilEntitiesGone(store, allKeys), |
| 593 |
| 594 // Make sure queries don't return results |
| 595 () => store.lookup(allKeys).then((List<db.Model> models) { |
| 596 expect(models.length, equals(allKeys.length)); |
| 597 for (var model in models) { |
| 598 expect(model, isNull); |
| 599 } |
| 600 }), |
| 601 ]; |
| 602 return Future.forEach(tests, (f) => f()); |
| 603 }); |
| 604 }); |
| 605 }); |
| 606 }); |
| 607 } |
| 608 |
| 609 Future waitUntilEntitiesReady(db.DatastoreDB mdb, List<db.Key> keys) { |
| 610 return waitUntilEntitiesHelper(mdb, keys, true); |
| 611 } |
| 612 |
| 613 Future waitUntilEntitiesGone(db.DatastoreDB mdb, List<db.Key> keys) { |
| 614 return waitUntilEntitiesHelper(mdb, keys, false); |
| 615 } |
| 616 |
| 617 Future waitUntilEntitiesHelper(db.DatastoreDB mdb, |
| 618 List<db.Key> keys, |
| 619 bool positive) { |
| 620 var keysByKind = {}; |
| 621 for (var key in keys) { |
| 622 keysByKind.putIfAbsent(key.type, () => []).add(key); |
| 623 } |
| 624 |
| 625 Future waitForKeys(Type kind, List<db.Key> keys) { |
| 626 return mdb.query(kind).run().toList().then((List<db.Model> models) { |
| 627 for (var key in keys) { |
| 628 bool found = false; |
| 629 for (var model in models) { |
| 630 if (key == model.key) found = true; |
| 631 } |
| 632 if (positive) { |
| 633 if (!found) return waitForKeys(kind, keys); |
| 634 } else { |
| 635 if (found) return waitForKeys(kind, keys); |
| 636 } |
| 637 } |
| 638 return null; |
| 639 }); |
| 640 } |
| 641 |
| 642 return Future.forEach(keysByKind.keys.toList(), (Type kind) { |
| 643 return waitForKeys(kind, keysByKind[kind]); |
| 644 }); |
| 645 } |
| 646 |
| 647 main() { |
| 648 var scopes = datastore_impl.DatastoreImpl.SCOPES; |
| 649 |
| 650 withAuthClient(scopes, (String project, httpClient) { |
| 651 var datastore = new datastore_impl.DatastoreImpl(httpClient, 's~$project'); |
| 652 return datastore_test.cleanupDB(datastore).then((_) { |
| 653 return runE2EUnittest(() => runTests(new db.DatastoreDB(datastore))); |
| 654 }); |
| 655 }); |
| 656 } |
OLD | NEW |