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