Chromium Code Reviews

Side by Side Diff: pkg/appengine/test/integration/db/db_impl_test.dart

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

Powered by Google App Engine