chromiumsync_test.py revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1#!/usr/bin/env python
2# Copyright 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Tests exercising chromiumsync and SyncDataModel."""
7
8import pickle
9import unittest
10
11import autofill_specifics_pb2
12import bookmark_specifics_pb2
13import chromiumsync
14import sync_pb2
15import theme_specifics_pb2
16
17class SyncDataModelTest(unittest.TestCase):
18  def setUp(self):
19    self.model = chromiumsync.SyncDataModel()
20    # The Synced Bookmarks folder is not created by default
21    self._expect_synced_bookmarks_folder = False
22
23  def AddToModel(self, proto):
24    self.model._entries[proto.id_string] = proto
25
26  def GetChangesFromTimestamp(self, requested_types, timestamp):
27    message = sync_pb2.GetUpdatesMessage()
28    message.from_timestamp = timestamp
29    for data_type in requested_types:
30      getattr(message.requested_types,
31              chromiumsync.SYNC_TYPE_TO_DESCRIPTOR[
32                  data_type].name).SetInParent()
33    return self.model.GetChanges(
34        chromiumsync.UpdateSieve(message, self.model.migration_history))
35
36  def FindMarkerByNumber(self, markers, datatype):
37    """Search a list of progress markers and find the one for a datatype."""
38    for marker in markers:
39      if marker.data_type_id == datatype.number:
40        return marker
41    self.fail('Required marker not found: %s' % datatype.name)
42
43  def testPermanentItemSpecs(self):
44    specs = chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS
45
46    declared_specs = set(['0'])
47    for spec in specs:
48      self.assertTrue(spec.parent_tag in declared_specs, 'parent tags must '
49                      'be declared before use')
50      declared_specs.add(spec.tag)
51
52    unique_datatypes = set([x.sync_type for x in specs])
53    self.assertEqual(unique_datatypes,
54                     set(chromiumsync.ALL_TYPES[1:]),
55                     'Every sync datatype should have a permanent folder '
56                     'associated with it')
57
58  def testSaveEntry(self):
59    proto = sync_pb2.SyncEntity()
60    proto.id_string = 'abcd'
61    proto.version = 0
62    self.assertFalse(self.model._ItemExists(proto.id_string))
63    self.model._SaveEntry(proto)
64    self.assertEqual(1, proto.version)
65    self.assertTrue(self.model._ItemExists(proto.id_string))
66    self.model._SaveEntry(proto)
67    self.assertEqual(2, proto.version)
68    proto.version = 0
69    self.assertTrue(self.model._ItemExists(proto.id_string))
70    self.assertEqual(2, self.model._entries[proto.id_string].version)
71
72  def testCreatePermanentItems(self):
73    self.model._CreateDefaultPermanentItems(chromiumsync.ALL_TYPES)
74    self.assertEqual(len(chromiumsync.ALL_TYPES) + 1,
75                     len(self.model._entries))
76
77  def ExpectedPermanentItemCount(self, sync_type):
78    if sync_type == chromiumsync.BOOKMARK:
79      if self._expect_synced_bookmarks_folder:
80        return 4
81      else:
82        return 3
83    else:
84      return 1
85
86  def testGetChangesFromTimestampZeroForEachType(self):
87    all_types = chromiumsync.ALL_TYPES[1:]
88    for sync_type in all_types:
89      self.model = chromiumsync.SyncDataModel()
90      request_types = [sync_type]
91
92      version, changes, remaining = (
93          self.GetChangesFromTimestamp(request_types, 0))
94
95      expected_count = self.ExpectedPermanentItemCount(sync_type)
96      self.assertEqual(expected_count, version)
97      self.assertEqual(expected_count, len(changes))
98      for change in changes:
99        self.assertTrue(change.HasField('server_defined_unique_tag'))
100        self.assertEqual(change.version, change.sync_timestamp)
101        self.assertTrue(change.version <= version)
102
103      # Test idempotence: another GetUpdates from ts=0 shouldn't recreate.
104      version, changes, remaining = (
105          self.GetChangesFromTimestamp(request_types, 0))
106      self.assertEqual(expected_count, version)
107      self.assertEqual(expected_count, len(changes))
108      self.assertEqual(0, remaining)
109
110      # Doing a wider GetUpdates from timestamp zero shouldn't recreate either.
111      new_version, changes, remaining = (
112          self.GetChangesFromTimestamp(all_types, 0))
113      if self._expect_synced_bookmarks_folder:
114        self.assertEqual(len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS),
115                         new_version)
116      else:
117        self.assertEqual(
118            len(chromiumsync.SyncDataModel._PERMANENT_ITEM_SPECS) -1,
119            new_version)
120      self.assertEqual(new_version, len(changes))
121      self.assertEqual(0, remaining)
122      version, changes, remaining = (
123          self.GetChangesFromTimestamp(request_types, 0))
124      self.assertEqual(new_version, version)
125      self.assertEqual(expected_count, len(changes))
126      self.assertEqual(0, remaining)
127
128  def testBatchSize(self):
129    for sync_type in chromiumsync.ALL_TYPES[1:]:
130      specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type)
131      self.model = chromiumsync.SyncDataModel()
132      request_types = [sync_type]
133
134      for i in range(self.model._BATCH_SIZE*3):
135        entry = sync_pb2.SyncEntity()
136        entry.id_string = 'batch test %d' % i
137        entry.specifics.CopyFrom(specifics)
138        self.model._SaveEntry(entry)
139      last_bit = self.ExpectedPermanentItemCount(sync_type)
140      version, changes, changes_remaining = (
141          self.GetChangesFromTimestamp(request_types, 0))
142      self.assertEqual(self.model._BATCH_SIZE, version)
143      self.assertEqual(self.model._BATCH_SIZE*2 + last_bit, changes_remaining)
144      version, changes, changes_remaining = (
145          self.GetChangesFromTimestamp(request_types, version))
146      self.assertEqual(self.model._BATCH_SIZE*2, version)
147      self.assertEqual(self.model._BATCH_SIZE + last_bit, changes_remaining)
148      version, changes, changes_remaining = (
149          self.GetChangesFromTimestamp(request_types, version))
150      self.assertEqual(self.model._BATCH_SIZE*3, version)
151      self.assertEqual(last_bit, changes_remaining)
152      version, changes, changes_remaining = (
153          self.GetChangesFromTimestamp(request_types, version))
154      self.assertEqual(self.model._BATCH_SIZE*3 + last_bit, version)
155      self.assertEqual(0, changes_remaining)
156
157      # Now delete a third of the items.
158      for i in xrange(self.model._BATCH_SIZE*3 - 1, 0, -3):
159        entry = sync_pb2.SyncEntity()
160        entry.id_string = 'batch test %d' % i
161        entry.deleted = True
162        self.model._SaveEntry(entry)
163
164      # The batch counts shouldn't change.
165      version, changes, changes_remaining = (
166          self.GetChangesFromTimestamp(request_types, 0))
167      self.assertEqual(self.model._BATCH_SIZE, len(changes))
168      self.assertEqual(self.model._BATCH_SIZE*2 + last_bit, changes_remaining)
169      version, changes, changes_remaining = (
170          self.GetChangesFromTimestamp(request_types, version))
171      self.assertEqual(self.model._BATCH_SIZE, len(changes))
172      self.assertEqual(self.model._BATCH_SIZE + last_bit, changes_remaining)
173      version, changes, changes_remaining = (
174          self.GetChangesFromTimestamp(request_types, version))
175      self.assertEqual(self.model._BATCH_SIZE, len(changes))
176      self.assertEqual(last_bit, changes_remaining)
177      version, changes, changes_remaining = (
178          self.GetChangesFromTimestamp(request_types, version))
179      self.assertEqual(last_bit, len(changes))
180      self.assertEqual(self.model._BATCH_SIZE*4 + last_bit, version)
181      self.assertEqual(0, changes_remaining)
182
183  def testCommitEachDataType(self):
184    for sync_type in chromiumsync.ALL_TYPES[1:]:
185      specifics = chromiumsync.GetDefaultEntitySpecifics(sync_type)
186      self.model = chromiumsync.SyncDataModel()
187      my_cache_guid = '112358132134'
188      parent = 'foobar'
189      commit_session = {}
190
191      # Start with a GetUpdates from timestamp 0, to populate permanent items.
192      original_version, original_changes, changes_remaining = (
193          self.GetChangesFromTimestamp([sync_type], 0))
194
195      def DoCommit(original=None, id_string='', name=None, parent=None,
196                   position=0):
197        proto = sync_pb2.SyncEntity()
198        if original is not None:
199          proto.version = original.version
200          proto.id_string = original.id_string
201          proto.parent_id_string = original.parent_id_string
202          proto.name = original.name
203        else:
204          proto.id_string = id_string
205          proto.version = 0
206        proto.specifics.CopyFrom(specifics)
207        if name is not None:
208          proto.name = name
209        if parent:
210          proto.parent_id_string = parent.id_string
211        proto.insert_after_item_id = 'please discard'
212        proto.position_in_parent = position
213        proto.folder = True
214        proto.deleted = False
215        result = self.model.CommitEntry(proto, my_cache_guid, commit_session)
216        self.assertTrue(result)
217        return (proto, result)
218
219      # Commit a new item.
220      proto1, result1 = DoCommit(name='namae', id_string='Foo',
221                                 parent=original_changes[-1], position=100)
222      # Commit an item whose parent is another item (referenced via the
223      # pre-commit ID).
224      proto2, result2 = DoCommit(name='Secondo', id_string='Bar',
225                                 parent=proto1, position=-100)
226        # Commit a sibling of the second item.
227      proto3, result3 = DoCommit(name='Third!', id_string='Baz',
228                                 parent=proto1, position=-50)
229
230      self.assertEqual(3, len(commit_session))
231      for p, r in [(proto1, result1), (proto2, result2), (proto3, result3)]:
232        self.assertNotEqual(r.id_string, p.id_string)
233        self.assertEqual(r.originator_client_item_id, p.id_string)
234        self.assertEqual(r.originator_cache_guid, my_cache_guid)
235        self.assertTrue(r is not self.model._entries[r.id_string],
236                        "Commit result didn't make a defensive copy.")
237        self.assertTrue(p is not self.model._entries[r.id_string],
238                        "Commit result didn't make a defensive copy.")
239        self.assertEqual(commit_session.get(p.id_string), r.id_string)
240        self.assertTrue(r.version > original_version)
241      self.assertEqual(result1.parent_id_string, proto1.parent_id_string)
242      self.assertEqual(result2.parent_id_string, result1.id_string)
243      version, changes, remaining = (
244          self.GetChangesFromTimestamp([sync_type], original_version))
245      self.assertEqual(3, len(changes))
246      self.assertEqual(0, remaining)
247      self.assertEqual(original_version + 3, version)
248      self.assertEqual([result1, result2, result3], changes)
249      for c in changes:
250        self.assertTrue(c is not self.model._entries[c.id_string],
251                        "GetChanges didn't make a defensive copy.")
252      self.assertTrue(result2.position_in_parent < result3.position_in_parent)
253      self.assertEqual(-100, result2.position_in_parent)
254
255      # Now update the items so that the second item is the parent of the
256      # first; with the first sandwiched between two new items (4 and 5).
257      # Do this in a new commit session, meaning we'll reference items from
258      # the first batch by their post-commit, server IDs.
259      commit_session = {}
260      old_cache_guid = my_cache_guid
261      my_cache_guid = 'A different GUID'
262      proto2b, result2b = DoCommit(original=result2,
263                                   parent=original_changes[-1])
264      proto4, result4 = DoCommit(id_string='ID4', name='Four',
265                                 parent=result2, position=-200)
266      proto1b, result1b = DoCommit(original=result1,
267                                   parent=result2, position=-150)
268      proto5, result5 = DoCommit(id_string='ID5', name='Five', parent=result2,
269                                 position=150)
270
271      self.assertEqual(2, len(commit_session), 'Only new items in second '
272                       'batch should be in the session')
273      for p, r, original in [(proto2b, result2b, proto2),
274                             (proto4, result4, proto4),
275                             (proto1b, result1b, proto1),
276                             (proto5, result5, proto5)]:
277        self.assertEqual(r.originator_client_item_id, original.id_string)
278        if original is not p:
279          self.assertEqual(r.id_string, p.id_string,
280                           'Ids should be stable after first commit')
281          self.assertEqual(r.originator_cache_guid, old_cache_guid)
282        else:
283          self.assertNotEqual(r.id_string, p.id_string)
284          self.assertEqual(r.originator_cache_guid, my_cache_guid)
285          self.assertEqual(commit_session.get(p.id_string), r.id_string)
286        self.assertTrue(r is not self.model._entries[r.id_string],
287                        "Commit result didn't make a defensive copy.")
288        self.assertTrue(p is not self.model._entries[r.id_string],
289                        "Commit didn't make a defensive copy.")
290        self.assertTrue(r.version > p.version)
291      version, changes, remaining = (
292          self.GetChangesFromTimestamp([sync_type], original_version))
293      self.assertEqual(5, len(changes))
294      self.assertEqual(0, remaining)
295      self.assertEqual(original_version + 7, version)
296      self.assertEqual([result3, result2b, result4, result1b, result5], changes)
297      for c in changes:
298        self.assertTrue(c is not self.model._entries[c.id_string],
299                        "GetChanges didn't make a defensive copy.")
300      self.assertTrue(result4.parent_id_string ==
301                      result1b.parent_id_string ==
302                      result5.parent_id_string ==
303                      result2b.id_string)
304      self.assertTrue(result4.position_in_parent <
305                      result1b.position_in_parent <
306                      result5.position_in_parent)
307
308  def testUpdateSieve(self):
309    # from_timestamp, legacy mode
310    autofill = chromiumsync.SYNC_TYPE_FIELDS['autofill']
311    theme = chromiumsync.SYNC_TYPE_FIELDS['theme']
312    msg = sync_pb2.GetUpdatesMessage()
313    msg.from_timestamp = 15412
314    msg.requested_types.autofill.SetInParent()
315    msg.requested_types.theme.SetInParent()
316
317    sieve = chromiumsync.UpdateSieve(msg)
318    self.assertEqual(sieve._state,
319        {chromiumsync.TOP_LEVEL: 15412,
320         chromiumsync.AUTOFILL: 15412,
321         chromiumsync.THEME: 15412})
322
323    response = sync_pb2.GetUpdatesResponse()
324    sieve.SaveProgress(15412, response)
325    self.assertEqual(0, len(response.new_progress_marker))
326    self.assertFalse(response.HasField('new_timestamp'))
327
328    response = sync_pb2.GetUpdatesResponse()
329    sieve.SaveProgress(15413, response)
330    self.assertEqual(0, len(response.new_progress_marker))
331    self.assertTrue(response.HasField('new_timestamp'))
332    self.assertEqual(15413, response.new_timestamp)
333
334    # Existing tokens
335    msg = sync_pb2.GetUpdatesMessage()
336    marker = msg.from_progress_marker.add()
337    marker.data_type_id = autofill.number
338    marker.token = pickle.dumps((15412, 1))
339    marker = msg.from_progress_marker.add()
340    marker.data_type_id = theme.number
341    marker.token = pickle.dumps((15413, 1))
342    sieve = chromiumsync.UpdateSieve(msg)
343    self.assertEqual(sieve._state,
344        {chromiumsync.TOP_LEVEL: 15412,
345         chromiumsync.AUTOFILL: 15412,
346         chromiumsync.THEME: 15413})
347
348    response = sync_pb2.GetUpdatesResponse()
349    sieve.SaveProgress(15413, response)
350    self.assertEqual(1, len(response.new_progress_marker))
351    self.assertFalse(response.HasField('new_timestamp'))
352    marker = response.new_progress_marker[0]
353    self.assertEqual(marker.data_type_id, autofill.number)
354    self.assertEqual(pickle.loads(marker.token), (15413, 1))
355    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
356
357    # Empty tokens indicating from timestamp = 0
358    msg = sync_pb2.GetUpdatesMessage()
359    marker = msg.from_progress_marker.add()
360    marker.data_type_id = autofill.number
361    marker.token = pickle.dumps((412, 1))
362    marker = msg.from_progress_marker.add()
363    marker.data_type_id = theme.number
364    marker.token = ''
365    sieve = chromiumsync.UpdateSieve(msg)
366    self.assertEqual(sieve._state,
367        {chromiumsync.TOP_LEVEL: 0,
368         chromiumsync.AUTOFILL: 412,
369         chromiumsync.THEME: 0})
370    response = sync_pb2.GetUpdatesResponse()
371    sieve.SaveProgress(1, response)
372    self.assertEqual(1, len(response.new_progress_marker))
373    self.assertFalse(response.HasField('new_timestamp'))
374    marker = response.new_progress_marker[0]
375    self.assertEqual(marker.data_type_id, theme.number)
376    self.assertEqual(pickle.loads(marker.token), (1, 1))
377    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
378
379    response = sync_pb2.GetUpdatesResponse()
380    sieve.SaveProgress(412, response)
381    self.assertEqual(1, len(response.new_progress_marker))
382    self.assertFalse(response.HasField('new_timestamp'))
383    marker = response.new_progress_marker[0]
384    self.assertEqual(marker.data_type_id, theme.number)
385    self.assertEqual(pickle.loads(marker.token), (412, 1))
386    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
387
388    response = sync_pb2.GetUpdatesResponse()
389    sieve.SaveProgress(413, response)
390    self.assertEqual(2, len(response.new_progress_marker))
391    self.assertFalse(response.HasField('new_timestamp'))
392    marker = self.FindMarkerByNumber(response.new_progress_marker, theme)
393    self.assertEqual(pickle.loads(marker.token), (413, 1))
394    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
395    marker = self.FindMarkerByNumber(response.new_progress_marker, autofill)
396    self.assertEqual(pickle.loads(marker.token), (413, 1))
397    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
398
399    # Migration token timestamps (client gives timestamp, server returns token)
400    # These are for migrating from the old 'timestamp' protocol to the
401    # progressmarker protocol, and have nothing to do with the MIGRATION_DONE
402    # error code.
403    msg = sync_pb2.GetUpdatesMessage()
404    marker = msg.from_progress_marker.add()
405    marker.data_type_id = autofill.number
406    marker.timestamp_token_for_migration = 15213
407    marker = msg.from_progress_marker.add()
408    marker.data_type_id = theme.number
409    marker.timestamp_token_for_migration = 15211
410    sieve = chromiumsync.UpdateSieve(msg)
411    self.assertEqual(sieve._state,
412        {chromiumsync.TOP_LEVEL: 15211,
413         chromiumsync.AUTOFILL: 15213,
414         chromiumsync.THEME: 15211})
415    response = sync_pb2.GetUpdatesResponse()
416    sieve.SaveProgress(16000, response)  # There were updates
417    self.assertEqual(2, len(response.new_progress_marker))
418    self.assertFalse(response.HasField('new_timestamp'))
419    marker = self.FindMarkerByNumber(response.new_progress_marker, theme)
420    self.assertEqual(pickle.loads(marker.token), (16000, 1))
421    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
422    marker = self.FindMarkerByNumber(response.new_progress_marker, autofill)
423    self.assertEqual(pickle.loads(marker.token), (16000, 1))
424    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
425
426    msg = sync_pb2.GetUpdatesMessage()
427    marker = msg.from_progress_marker.add()
428    marker.data_type_id = autofill.number
429    marker.timestamp_token_for_migration = 3000
430    marker = msg.from_progress_marker.add()
431    marker.data_type_id = theme.number
432    marker.timestamp_token_for_migration = 3000
433    sieve = chromiumsync.UpdateSieve(msg)
434    self.assertEqual(sieve._state,
435        {chromiumsync.TOP_LEVEL: 3000,
436         chromiumsync.AUTOFILL: 3000,
437         chromiumsync.THEME: 3000})
438    response = sync_pb2.GetUpdatesResponse()
439    sieve.SaveProgress(3000, response)  # Already up to date
440    self.assertEqual(2, len(response.new_progress_marker))
441    self.assertFalse(response.HasField('new_timestamp'))
442    marker = self.FindMarkerByNumber(response.new_progress_marker, theme)
443    self.assertEqual(pickle.loads(marker.token), (3000, 1))
444    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
445    marker = self.FindMarkerByNumber(response.new_progress_marker, autofill)
446    self.assertEqual(pickle.loads(marker.token), (3000, 1))
447    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
448
449  def testCheckRaiseTransientError(self):
450    testserver = chromiumsync.TestServer()
451    http_code, raw_respon = testserver.HandleSetTransientError()
452    self.assertEqual(http_code, 200)
453    try:
454      testserver.CheckTransientError()
455      self.fail('Should have raised transient error exception')
456    except chromiumsync.TransientError:
457      self.assertTrue(testserver.transient_error)
458
459  def testUpdateSieveStoreMigration(self):
460    autofill = chromiumsync.SYNC_TYPE_FIELDS['autofill']
461    theme = chromiumsync.SYNC_TYPE_FIELDS['theme']
462    migrator = chromiumsync.MigrationHistory()
463    msg = sync_pb2.GetUpdatesMessage()
464    marker = msg.from_progress_marker.add()
465    marker.data_type_id = autofill.number
466    marker.token = pickle.dumps((15412, 1))
467    marker = msg.from_progress_marker.add()
468    marker.data_type_id = theme.number
469    marker.token = pickle.dumps((15413, 1))
470    sieve = chromiumsync.UpdateSieve(msg, migrator)
471    sieve.CheckMigrationState()
472
473    migrator.Bump([chromiumsync.BOOKMARK, chromiumsync.PASSWORD])  # v=2
474    sieve = chromiumsync.UpdateSieve(msg, migrator)
475    sieve.CheckMigrationState()
476    self.assertEqual(sieve._state,
477        {chromiumsync.TOP_LEVEL: 15412,
478         chromiumsync.AUTOFILL: 15412,
479         chromiumsync.THEME: 15413})
480
481    migrator.Bump([chromiumsync.AUTOFILL, chromiumsync.PASSWORD])  # v=3
482    sieve = chromiumsync.UpdateSieve(msg, migrator)
483    try:
484      sieve.CheckMigrationState()
485      self.fail('Should have raised.')
486    except chromiumsync.MigrationDoneError, error:
487      # We want this to happen.
488      self.assertEqual([chromiumsync.AUTOFILL], error.datatypes)
489
490    msg = sync_pb2.GetUpdatesMessage()
491    marker = msg.from_progress_marker.add()
492    marker.data_type_id = autofill.number
493    marker.token = ''
494    marker = msg.from_progress_marker.add()
495    marker.data_type_id = theme.number
496    marker.token = pickle.dumps((15413, 1))
497    sieve = chromiumsync.UpdateSieve(msg, migrator)
498    sieve.CheckMigrationState()
499    response = sync_pb2.GetUpdatesResponse()
500    sieve.SaveProgress(15412, response)  # There were updates
501    self.assertEqual(1, len(response.new_progress_marker))
502    self.assertFalse(response.HasField('new_timestamp'))
503    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
504    marker = self.FindMarkerByNumber(response.new_progress_marker, autofill)
505    self.assertEqual(pickle.loads(marker.token), (15412, 3))
506    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
507    msg = sync_pb2.GetUpdatesMessage()
508    marker = msg.from_progress_marker.add()
509    marker.data_type_id = autofill.number
510    marker.token = pickle.dumps((15412, 3))
511    marker = msg.from_progress_marker.add()
512    marker.data_type_id = theme.number
513    marker.token = pickle.dumps((15413, 1))
514    sieve = chromiumsync.UpdateSieve(msg, migrator)
515    sieve.CheckMigrationState()
516
517    migrator.Bump([chromiumsync.THEME, chromiumsync.AUTOFILL])  # v=4
518    migrator.Bump([chromiumsync.AUTOFILL])                      # v=5
519    sieve = chromiumsync.UpdateSieve(msg, migrator)
520    try:
521      sieve.CheckMigrationState()
522      self.fail("Should have raised.")
523    except chromiumsync.MigrationDoneError, error:
524      # We want this to happen.
525      self.assertEqual(set([chromiumsync.THEME, chromiumsync.AUTOFILL]),
526                       set(error.datatypes))
527    msg = sync_pb2.GetUpdatesMessage()
528    marker = msg.from_progress_marker.add()
529    marker.data_type_id = autofill.number
530    marker.token = ''
531    marker = msg.from_progress_marker.add()
532    marker.data_type_id = theme.number
533    marker.token = pickle.dumps((15413, 1))
534    sieve = chromiumsync.UpdateSieve(msg, migrator)
535    try:
536      sieve.CheckMigrationState()
537      self.fail("Should have raised.")
538    except chromiumsync.MigrationDoneError, error:
539      # We want this to happen.
540      self.assertEqual([chromiumsync.THEME], error.datatypes)
541
542    msg = sync_pb2.GetUpdatesMessage()
543    marker = msg.from_progress_marker.add()
544    marker.data_type_id = autofill.number
545    marker.token = ''
546    marker = msg.from_progress_marker.add()
547    marker.data_type_id = theme.number
548    marker.token = ''
549    sieve = chromiumsync.UpdateSieve(msg, migrator)
550    sieve.CheckMigrationState()
551    response = sync_pb2.GetUpdatesResponse()
552    sieve.SaveProgress(15412, response)  # There were updates
553    self.assertEqual(2, len(response.new_progress_marker))
554    self.assertFalse(response.HasField('new_timestamp'))
555    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
556    marker = self.FindMarkerByNumber(response.new_progress_marker, autofill)
557    self.assertEqual(pickle.loads(marker.token), (15412, 5))
558    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
559    marker = self.FindMarkerByNumber(response.new_progress_marker, theme)
560    self.assertEqual(pickle.loads(marker.token), (15412, 4))
561    self.assertFalse(marker.HasField('timestamp_token_for_migration'))
562    msg = sync_pb2.GetUpdatesMessage()
563    marker = msg.from_progress_marker.add()
564    marker.data_type_id = autofill.number
565    marker.token = pickle.dumps((15412, 5))
566    marker = msg.from_progress_marker.add()
567    marker.data_type_id = theme.number
568    marker.token = pickle.dumps((15413, 4))
569    sieve = chromiumsync.UpdateSieve(msg, migrator)
570    sieve.CheckMigrationState()
571
572  def testCreateSyncedBookmaks(self):
573    version1, changes, remaining = (
574        self.GetChangesFromTimestamp([chromiumsync.BOOKMARK], 0))
575    id_string = self.model._MakeCurrentId(chromiumsync.BOOKMARK,
576                                          '<server tag>synced_bookmarks')
577    self.assertFalse(self.model._ItemExists(id_string))
578    self._expect_synced_bookmarks_folder = True
579    self.model.TriggerCreateSyncedBookmarks()
580    self.assertTrue(self.model._ItemExists(id_string))
581
582    # Check that the version changed when the folder was created and the only
583    # change was the folder creation.
584    version2, changes, remaining = (
585        self.GetChangesFromTimestamp([chromiumsync.BOOKMARK], version1))
586    self.assertEqual(len(changes), 1)
587    self.assertEqual(changes[0].id_string, id_string)
588    self.assertNotEqual(version1, version2)
589    self.assertEqual(
590        self.ExpectedPermanentItemCount(chromiumsync.BOOKMARK),
591        version2)
592
593    # Ensure getting from timestamp 0 includes the folder.
594    version, changes, remaining = (
595        self.GetChangesFromTimestamp([chromiumsync.BOOKMARK], 0))
596    self.assertEqual(
597        self.ExpectedPermanentItemCount(chromiumsync.BOOKMARK),
598        len(changes))
599    self.assertEqual(version2, version)
600
601  def testGetKey(self):
602    [key1] = self.model.GetKeystoreKeys()
603    [key2] = self.model.GetKeystoreKeys()
604    self.assertTrue(len(key1))
605    self.assertEqual(key1, key2)
606
607    # Trigger the rotation. A subsequent GetUpdates should return the nigori
608    # node (whose timestamp was bumped by the rotation).
609    version1, changes, remaining = (
610        self.GetChangesFromTimestamp([chromiumsync.NIGORI], 0))
611    self.model.TriggerRotateKeystoreKeys()
612    version2, changes, remaining = (
613        self.GetChangesFromTimestamp([chromiumsync.NIGORI], version1))
614    self.assertNotEqual(version1, version2)
615    self.assertEquals(len(changes), 1)
616    self.assertEquals(changes[0].name, "Nigori")
617
618    # The current keys should contain the old keys, with the new key appended.
619    [key1, key3] = self.model.GetKeystoreKeys()
620    self.assertEquals(key1, key2)
621    self.assertNotEqual(key1, key3)
622    self.assertTrue(len(key3) > 0)
623
624  def testTriggerEnableKeystoreEncryption(self):
625    version1, changes, remaining = (
626        self.GetChangesFromTimestamp([chromiumsync.EXPERIMENTS], 0))
627    keystore_encryption_id_string = (
628        self.model._ClientTagToId(
629            chromiumsync.EXPERIMENTS,
630            chromiumsync.KEYSTORE_ENCRYPTION_EXPERIMENT_TAG))
631
632    self.assertFalse(self.model._ItemExists(keystore_encryption_id_string))
633    self.model.TriggerEnableKeystoreEncryption()
634    self.assertTrue(self.model._ItemExists(keystore_encryption_id_string))
635
636    # The creation of the experiment should be downloaded on the next
637    # GetUpdates.
638    version2, changes, remaining = (
639        self.GetChangesFromTimestamp([chromiumsync.EXPERIMENTS], version1))
640    self.assertEqual(len(changes), 1)
641    self.assertEqual(changes[0].id_string, keystore_encryption_id_string)
642    self.assertNotEqual(version1, version2)
643
644    # Verify the experiment was created properly and is enabled.
645    self.assertEqual(chromiumsync.KEYSTORE_ENCRYPTION_EXPERIMENT_TAG,
646                     changes[0].client_defined_unique_tag)
647    self.assertTrue(changes[0].HasField("specifics"))
648    self.assertTrue(changes[0].specifics.HasField("experiments"))
649    self.assertTrue(
650        changes[0].specifics.experiments.HasField("keystore_encryption"))
651    self.assertTrue(
652        changes[0].specifics.experiments.keystore_encryption.enabled)
653
654if __name__ == '__main__':
655  unittest.main()
656