<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14507] CalendarServer/branches/users/cdaboo/pod2pod-migration</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14507">14507</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-03-04 18:41:22 -0800 (Wed, 04 Mar 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Implement a test that covers the complete migration cycle.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationcalendarserverpushtesttest_notifierpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/push/test/test_notifier.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationcalendarservertoolspurgepy">CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/purge.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtwistedcaldavresourcepy">CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/resource.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoreschedulingischeduledeliverypy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/ischedule/delivery.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretestcommonpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/common.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretesttest_sql_sharingpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcarddavdatastoretesttest_sqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcarddavdatastoretesttest_sql_sharingpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorefilepy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/file.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtestaccountsgroupAccountsxml">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/accounts/groupAccounts.xml</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingtestutilpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresqlpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_externalpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_notificationpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_sharingpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_utilpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoretestutilpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/test/util.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoreupgradesqlupgradestesttest_notification_upgrade_from_0_to_1py">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/upgrade/sql/upgrades/test/test_notification_upgrade_from_0_to_1.py</a></li>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_group_shareespy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_sharees.py</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_migrationpy">CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationcalendarserverpushtesttest_notifierpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/push/test/test_notifier.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/push/test/test_notifier.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/push/test/test_notifier.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -297,7 +297,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def test_notificationNotifier(self):
</span><span class="cx"> 
</span><del>-        notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
</del><ins>+        notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;, create=True)
</ins><span class="cx">         yield notifications.notifyChanged(category=ChangeCategory.default)
</span><span class="cx">         self.assertEquals(
</span><span class="cx">             set(self.notifierFactory.history),
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationcalendarservertoolspurgepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/purge.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/purge.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/calendarserver/tools/purge.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -992,7 +992,7 @@
</span><span class="cx"> 
</span><span class="cx">         if not self.dryrun:
</span><span class="cx">             yield storeCalHome.removeUnacceptedShares()
</span><del>-            notificationHome = yield txn.notificationsWithUID(storeCalHome.uid(), create=False)
</del><ins>+            notificationHome = yield txn.notificationsWithUID(storeCalHome.uid())
</ins><span class="cx">             if notificationHome is not None:
</span><span class="cx">                 yield notificationHome.remove()
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtwistedcaldavresourcepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/resource.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/resource.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/twistedcaldav/resource.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -2139,7 +2139,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def createNotificationsCollection(self):
</span><span class="cx">         txn = self._associatedTransaction
</span><del>-        notifications = yield txn.notificationsWithUID(self._newStoreHome.uid())
</del><ins>+        notifications = yield txn.notificationsWithUID(self._newStoreHome.uid(), create=True)
</ins><span class="cx"> 
</span><span class="cx">         from twistedcaldav.storebridge import StoreNotificationCollectionResource
</span><span class="cx">         similar = StoreNotificationCollectionResource(
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoreschedulingischeduledeliverypy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/ischedule/delivery.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/ischedule/delivery.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/scheduling/ischedule/delivery.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -448,15 +448,6 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _processRequest(self, ssl, host, port, path):
</span><del>-        from twisted.internet import reactor
-        f = Factory()
-        f.protocol = HTTPClientProtocol
-        if ssl:
-            ep = GAIEndpoint(reactor, host, port, _configuredClientContextFactory())
-        else:
-            ep = GAIEndpoint(reactor, host, port)
-        proto = (yield ep.connect(f))
-
</del><span class="cx">         if not self.server.podding() and config.Scheduling.iSchedule.DKIM.Enabled:
</span><span class="cx">             domain, selector, key_file, algorithm, useDNSKey, useHTTPKey, usePrivateExchangeKey, expire = DKIMUtils.getConfiguration(config)
</span><span class="cx">             request = DKIMRequest(
</span><span class="lines">@@ -481,6 +472,21 @@
</span><span class="cx">         if accountingEnabledForCategory(&quot;iSchedule&quot;):
</span><span class="cx">             self.loggedRequest = yield self.logRequest(request)
</span><span class="cx"> 
</span><ins>+        response = yield self._submitRequest(ssl, host, port, request)
+        returnValue(response)
+
+
+    @inlineCallbacks
+    def _submitRequest(self, ssl, host, port, request):
+        from twisted.internet import reactor
+        f = Factory()
+        f.protocol = HTTPClientProtocol
+        if ssl:
+            ep = GAIEndpoint(reactor, host, port, _configuredClientContextFactory())
+        else:
+            ep = GAIEndpoint(reactor, host, port)
+        proto = (yield ep.connect(f))
+
</ins><span class="cx">         response = (yield proto.submitRequest(request))
</span><span class="cx"> 
</span><span class="cx">         returnValue(response)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretestcommonpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/common.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/common.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/common.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -375,7 +375,7 @@
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def notificationUnderTest(self):
</span><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        notifications = yield txn.notificationsWithUID(&quot;home1&quot;)
</del><ins>+        notifications = yield txn.notificationsWithUID(&quot;home1&quot;, create=True)
</ins><span class="cx">         yield notifications.writeNotificationObject(
</span><span class="cx">             &quot;abc&quot;,
</span><span class="cx">             json.loads(&quot;{\&quot;notification-type\&quot;:\&quot;invite-notification\&quot;}&quot;),
</span><span class="lines">@@ -402,7 +402,7 @@
</span><span class="cx">         objects changed or deleted since
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        coll = yield txn.notificationsWithUID(&quot;home1&quot;)
</del><ins>+        coll = yield txn.notificationsWithUID(&quot;home1&quot;, create=True)
</ins><span class="cx">         yield coll.writeNotificationObject(
</span><span class="cx">             &quot;1&quot;,
</span><span class="cx">             json.loads(&quot;{\&quot;notification-type\&quot;:\&quot;invite-notification\&quot;}&quot;),
</span><span class="lines">@@ -435,7 +435,7 @@
</span><span class="cx">         overwrite the notification object.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         notifications = yield self.transactionUnderTest().notificationsWithUID(
</span><del>-            &quot;home1&quot;
</del><ins>+            &quot;home1&quot;, create=True
</ins><span class="cx">         )
</span><span class="cx">         yield notifications.writeNotificationObject(
</span><span class="cx">             &quot;abc&quot;,
</span><span class="lines">@@ -462,7 +462,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         # Prime the home collection first
</span><span class="cx">         yield self.transactionUnderTest().notificationsWithUID(
</span><del>-            &quot;home1&quot;
</del><ins>+            &quot;home1&quot;, create=True
</ins><span class="cx">         )
</span><span class="cx">         yield self.commit()
</span><span class="cx"> 
</span><span class="lines">@@ -512,7 +512,7 @@
</span><span class="cx">         overwrite the notification object.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         notifications = yield self.transactionUnderTest().notificationsWithUID(
</span><del>-            &quot;home1&quot;
</del><ins>+            &quot;home1&quot;, create=True
</ins><span class="cx">         )
</span><span class="cx">         yield notifications.writeNotificationObject(
</span><span class="cx">             &quot;abc&quot;,
</span><span class="lines">@@ -555,7 +555,7 @@
</span><span class="cx">         L{INotificationCollection} that the object was retrieved from.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         txn = self.transactionUnderTest()
</span><del>-        collection = yield txn.notificationsWithUID(&quot;home1&quot;)
</del><ins>+        collection = yield txn.notificationsWithUID(&quot;home1&quot;, create=True)
</ins><span class="cx">         notification = yield self.notificationUnderTest()
</span><span class="cx">         self.assertIdentical(collection, notification.notificationCollection())
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -767,13 +767,13 @@
</span><span class="cx">         txn2 = calendarStore.newTransaction()
</span><span class="cx"> 
</span><span class="cx">         notification_uid1_1 = yield txn1.notificationsWithUID(
</span><del>-            &quot;uid1&quot;,
</del><ins>+            &quot;uid1&quot;, create=True
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _defer_notification_uid1_2():
</span><span class="cx">             notification_uid1_2 = yield txn2.notificationsWithUID(
</span><del>-                &quot;uid1&quot;,
</del><ins>+                &quot;uid1&quot;, create=True
</ins><span class="cx">             )
</span><span class="cx">             yield txn2.commit()
</span><span class="cx">             returnValue(notification_uid1_2)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcaldavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/caldav/datastore/test/test_sql_sharing.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -464,7 +464,7 @@
</span><span class="cx">         shared = yield self.calendarUnderTest(home=&quot;user02&quot;, name=sharedName)
</span><span class="cx">         self.assertTrue(shared is not None)
</span><span class="cx"> 
</span><del>-        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user02&quot;)
</del><ins>+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(&quot;user02&quot;, create=True)
</ins><span class="cx">         notifications = yield notifyHome.listNotificationObjects()
</span><span class="cx">         self.assertEqual(len(notifications), 0)
</span><span class="cx"> 
</span><span class="lines">@@ -654,7 +654,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _check_notifications(self, uid, items):
</span><del>-        notifyHome = yield self.transactionUnderTest().notificationsWithUID(uid)
</del><ins>+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(uid, create=True)
</ins><span class="cx">         notifications = yield notifyHome.listNotificationObjects()
</span><span class="cx">         self.assertEqual(set(notifications), set(items))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcarddavdatastoretesttest_sqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -364,13 +364,13 @@
</span><span class="cx">         txn2 = addressbookStore.newTransaction()
</span><span class="cx"> 
</span><span class="cx">         notification_uid1_1 = yield txn1.notificationsWithUID(
</span><del>-            &quot;uid1&quot;,
</del><ins>+            &quot;uid1&quot;, create=True,
</ins><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         @inlineCallbacks
</span><span class="cx">         def _defer_notification_uid1_2():
</span><span class="cx">             notification_uid1_2 = yield txn2.notificationsWithUID(
</span><del>-                &quot;uid1&quot;,
</del><ins>+                &quot;uid1&quot;, create=True,
</ins><span class="cx">             )
</span><span class="cx">             yield txn2.commit()
</span><span class="cx">             returnValue(notification_uid1_2)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcarddavdatastoretesttest_sql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql_sharing.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql_sharing.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/carddav/datastore/test/test_sql_sharing.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -198,7 +198,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _check_notifications(self, home, items):
</span><del>-        notifyHome = yield self.transactionUnderTest().notificationsWithUID(home)
</del><ins>+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(home, create=True)
</ins><span class="cx">         notifications = yield notifyHome.listNotificationObjects()
</span><span class="cx">         self.assertEqual(set(notifications), set(items))
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorefilepy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/file.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/file.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/file.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -376,7 +376,7 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @memoizedKey(&quot;uid&quot;, &quot;_notificationHomes&quot;, deferredResult=False)
</span><del>-    def notificationsWithUID(self, uid, home=None):
</del><ins>+    def notificationsWithUID(self, uid, home=None, create=False):
</ins><span class="cx"> 
</span><span class="cx">         if home is None:
</span><span class="cx">             home = self.homeWithUID(self._notificationHomeType, uid, create=True)
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationhome_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/home_sync.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -85,17 +85,22 @@
</span><span class="cx"> 
</span><span class="cx">     BATCH_SIZE = 50
</span><span class="cx"> 
</span><del>-    def __init__(self, store, diruid):
</del><ins>+    def __init__(self, store, diruid, final=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param store: the data store
</span><span class="cx">         @type store: L{CommonDataStore}
</span><span class="cx">         @param diruid: directory uid of the user whose home is to be sync'd
</span><span class="cx">         @type diruid: L{str}
</span><ins>+        @param final: indicates whether this is in the final sync stage with the remote home
+            already disabled
+        @type final: L{bool}
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         self.store = store
</span><span class="cx">         self.diruid = diruid
</span><del>-        self.disabledRemote = False
</del><ins>+        self.disabledRemote = final
+        self.record = None
+        self.homeId = None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     def label(self, detail):
</span><span class="lines">@@ -133,7 +138,7 @@
</span><span class="cx">         # Step 6 - enable new home
</span><span class="cx">         yield self.enableLocalHome()
</span><span class="cx"> 
</span><del>-        # Step 7 - remote remote home
</del><ins>+        # Step 7 - remove remote home
</ins><span class="cx">         yield self.removeRemoteHome()
</span><span class="cx"> 
</span><span class="cx">         # Step 8 - say phew! TODO: Actually alert everyone else
</span><span class="lines">@@ -168,6 +173,9 @@
</span><span class="cx">         rows, recalculate quota etc.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        yield self.loadRecord()
+        yield self.prepareCalendarHome()
+
</ins><span class="cx">         # Link attachments to resources: ATTACHMENT_CALENDAR_OBJECT table
</span><span class="cx">         yield self.linkAttachments()
</span><span class="cx"> 
</span><span class="lines">@@ -198,6 +206,9 @@
</span><span class="cx">         Mark the remote home as disabled.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        yield self.loadRecord()
+        yield self.prepareCalendarHome()
+
</ins><span class="cx">         # Calendar home
</span><span class="cx">         remote_home = yield self._remoteHome(txn)
</span><span class="cx">         yield remote_home.setStatus(_HOME_STATUS_DISABLED)
</span><span class="lines">@@ -216,13 +227,16 @@
</span><span class="cx">         Mark the local home as enabled and remove any previously existing external home.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><ins>+        yield self.loadRecord()
+        yield self.prepareCalendarHome()
+
</ins><span class="cx">         # Disable any local external homes
</span><span class="cx">         oldhome = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_EXTERNAL)
</span><span class="cx">         if oldhome is not None:
</span><del>-            yield oldhome.setStatus(_HOME_STATUS_DISABLED)
</del><ins>+            yield oldhome.setLocalStatus(_HOME_STATUS_DISABLED)
</ins><span class="cx">         oldnotifications = yield txn.notificationsWithUID(self.diruid, status=_HOME_STATUS_EXTERNAL)
</span><span class="cx">         if oldnotifications:
</span><del>-            yield oldnotifications.setStatus(_HOME_STATUS_DISABLED)
</del><ins>+            yield oldnotifications.setLocalStatus(_HOME_STATUS_DISABLED)
</ins><span class="cx"> 
</span><span class="cx">         # Enable the migrating ones
</span><span class="cx">         newhome = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_MIGRATING)
</span><span class="lines">@@ -244,7 +258,8 @@
</span><span class="cx"> 
</span><span class="cx">         # TODO: implement API on CommonHome to purge the old data without
</span><span class="cx">         # any side-effects (scheduling, sharing etc).
</span><del>-        pass
</del><ins>+        yield self.loadRecord()
+        yield self.prepareCalendarHome()
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="lines">@@ -253,11 +268,12 @@
</span><span class="cx">         Initiate a sync of the home.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        self.record = yield self.store.directoryService().recordWithUID(self.diruid)
</del><span class="cx">         if self.record is None:
</span><del>-            raise DirectoryRecordNotFoundError(&quot;Cross-pod Migration Sync missing directory record for {}&quot;.format(self.diruid))
-        if self.record.thisServer():
-            raise ValueError(&quot;Cross-pod Migration Sync cannot sync with user already on this server: {}&quot;.format(self.diruid))
</del><ins>+            self.record = yield self.store.directoryService().recordWithUID(self.diruid)
+            if self.record is None:
+                raise DirectoryRecordNotFoundError(&quot;Cross-pod Migration Sync missing directory record for {}&quot;.format(self.diruid))
+            if self.record.thisServer():
+                raise ValueError(&quot;Cross-pod Migration Sync cannot sync with user already on this server: {}&quot;.format(self.diruid))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inTransactionWrapper
</span><span class="lines">@@ -267,10 +283,14 @@
</span><span class="cx">         Make sure the inactive home to migrate into is present on this pod.
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><del>-        home = yield self._localHome(txn)
-        if home is None:
-            home = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_MIGRATING, create=True)
-        self.homeId = home.id()
</del><ins>+        if self.homeId is None:
+            home = yield self._localHome(txn)
+            if home is None:
+                if self.disabledRemote:
+                    self.homeId = None
+                else:
+                    home = yield txn.calendarHomeWithUID(self.diruid, status=_HOME_STATUS_MIGRATING, create=True)
+            self.homeId = home.id() if home is not None else None
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inTransactionWrapper
</span><span class="lines">@@ -1004,6 +1024,8 @@
</span><span class="cx">         len_records = 0
</span><span class="cx">         for calendar in calendars.values():
</span><span class="cx">             records, bindUID = yield self.sharedByCollectionRecords(calendar.remoteResourceID, calendar.localResourceID)
</span><ins>+            if not records:
+                continue
</ins><span class="cx">             records = records.items()
</span><span class="cx"> 
</span><span class="cx">             # Batch setting resources for the local home
</span><span class="lines">@@ -1039,7 +1061,7 @@
</span><span class="cx">             calendarHomeResourceID=self.homeId,
</span><span class="cx">             calendarResourceID=local_id,
</span><span class="cx">         )
</span><del>-        if not local_records[0].bindUID:
</del><ins>+        if records and not local_records[0].bindUID:
</ins><span class="cx">             yield local_records[0].update(bindUID=str(uuid4()))
</span><span class="cx"> 
</span><span class="cx">         returnValue((records, local_records[0].bindUID,))
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtestaccountsgroupAccountsxml"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/accounts/groupAccounts.xml (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/accounts/groupAccounts.xml        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/accounts/groupAccounts.xml        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -106,6 +106,7 @@
</span><span class="cx">             &lt;full-name&gt;Group 01&lt;/full-name&gt;
</span><span class="cx">             &lt;email&gt;group01@example.com&lt;/email&gt;
</span><span class="cx">             &lt;member-uid&gt;user01&lt;/member-uid&gt;
</span><ins>+            &lt;member-uid&gt;puser01&lt;/member-uid&gt;
</ins><span class="cx">         &lt;/record&gt;
</span><span class="cx">         &lt;record type=&quot;group&quot;&gt;
</span><span class="cx">             &lt;short-name&gt;group02&lt;/short-name&gt;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_home_syncpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_home_sync.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -744,6 +744,7 @@
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">         home0 = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</span><ins>+        yield self.notificationCollectionUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</ins><span class="cx">         calendar0 = yield home0.childWithName(&quot;calendar&quot;)
</span><span class="cx">         object0_1 = yield calendar0.createCalendarObjectWithName(&quot;1.ics&quot;, Component.fromString(self.caldata1))
</span><span class="cx">         object0_2 = yield calendar0.createCalendarObjectWithName(&quot;2.ics&quot;, Component.fromString(self.caldata2))
</span><span class="lines">@@ -854,6 +855,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Create remote home
</span><span class="cx">         yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</span><ins>+        yield self.notificationCollectionUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</ins><span class="cx">         yield self.commitTransaction(0)
</span><span class="cx"> 
</span><span class="cx">         # Add some delegates
</span><span class="lines">@@ -941,7 +943,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Create remote home - and add some fake notifications
</span><span class="cx">         yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</span><del>-        notifications = yield self.theTransactionUnderTest(0).notificationsWithUID(&quot;user01&quot;)
</del><ins>+        notifications = yield self.theTransactionUnderTest(0).notificationsWithUID(&quot;user01&quot;, create=True)
</ins><span class="cx">         uid1 = str(uuid4())
</span><span class="cx">         obj1 = yield notifications.writeNotificationObject(uid1, &quot;type1&quot;, &quot;data1&quot;)
</span><span class="cx">         id1 = obj1.id()
</span><span class="lines">@@ -1047,6 +1049,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Create home
</span><span class="cx">         yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</span><ins>+        yield self.notificationCollectionUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</ins><span class="cx">         yield self.commitTransaction(0)
</span><span class="cx"> 
</span><span class="cx">         # Shared by migrating user
</span><span class="lines">@@ -1119,6 +1122,7 @@
</span><span class="cx"> 
</span><span class="cx">         # Create home
</span><span class="cx">         yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</span><ins>+        yield self.notificationCollectionUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
</ins><span class="cx">         yield self.commitTransaction(0)
</span><span class="cx"> 
</span><span class="cx">         # Shared by migrating user
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingmigrationtesttest_migrationpy"></a>
<div class="addfile"><h4>Added: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py (0 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py                                (rev 0)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/migration/test/test_migration.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -0,0 +1,691 @@
</span><ins>+##
+# Copyright (c) 2015 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an &quot;AS IS&quot; BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from pycalendar.datetime import DateTime
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.filepath import FilePath
+from twistedcaldav.config import config
+from twistedcaldav.ical import Component
+from txdav.common.datastore.podding.migration.home_sync import CrossPodHomeSync
+from txdav.common.datastore.podding.test.util import MultiStoreConduitTest
+from txdav.common.datastore.sql_tables import _BIND_MODE_READ, \
+    _HOME_STATUS_DISABLED, _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL, \
+    _HOME_STATUS_MIGRATING
+from txdav.common.datastore.test.util import populateCalendarsFrom
+from txdav.who.delegates import Delegates
+from txweb2.http_headers import MimeType
+from txweb2.stream import MemoryStream
+from txdav.caldav.datastore.scheduling.ischedule.delivery import IScheduleRequest
+from txdav.caldav.datastore.scheduling.ischedule.resource import IScheduleInboxResource
+from txweb2.dav.test.util import SimpleRequest
+from txdav.caldav.datastore.test.common import CaptureProtocol
+
+
+class TestCompleteMigrationCycle(MultiStoreConduitTest):
+    &quot;&quot;&quot;
+    Test that a full migration cycle using L{CrossPodHomeSync} works.
+    &quot;&quot;&quot;
+
+    def __init__(self, methodName='runTest'):
+        super(TestCompleteMigrationCycle, self).__init__(methodName)
+        self.stash = {}
+
+
+    @inlineCallbacks
+    def setUp(self):
+        @inlineCallbacks
+        def _fakeSubmitRequest(iself, ssl, host, port, request):
+            pod = (port - 8008) / 100
+            inbox = IScheduleInboxResource(self.site.resource, self.theStoreUnderTest(pod), podding=True)
+            response = yield inbox.http_POST(SimpleRequest(
+                self.site,
+                &quot;POST&quot;,
+                &quot;http://{host}:{port}/podding&quot;.format(host=host, port=port),
+                request.headers,
+                request.stream.mem,
+            ))
+            returnValue(response)
+
+
+        self.patch(IScheduleRequest, &quot;_submitRequest&quot;, _fakeSubmitRequest)
+        self.accounts = FilePath(__file__).sibling(&quot;accounts&quot;).child(&quot;groupAccounts.xml&quot;)
+        self.augments = FilePath(__file__).sibling(&quot;accounts&quot;).child(&quot;augments.xml&quot;)
+        yield super(TestCompleteMigrationCycle, self).setUp()
+        yield self.populate()
+
+
+    def configure(self):
+        super(TestCompleteMigrationCycle, self).configure()
+        config.GroupAttendees.Enabled = True
+        config.GroupAttendees.ReconciliationDelaySeconds = 0
+        config.GroupAttendees.AutoUpdateSecondsFromNow = 0
+
+
+    @inlineCallbacks
+    def populate(self):
+        yield populateCalendarsFrom(self.requirements0, self.theStoreUnderTest(0))
+        yield populateCalendarsFrom(self.requirements1, self.theStoreUnderTest(1))
+
+    requirements0 = {
+        &quot;user01&quot; : None,
+        &quot;user02&quot; : None,
+        &quot;user03&quot; : None,
+        &quot;user04&quot; : None,
+        &quot;user05&quot; : None,
+        &quot;user06&quot; : None,
+        &quot;user07&quot; : None,
+        &quot;user08&quot; : None,
+        &quot;user09&quot; : None,
+        &quot;user10&quot; : None,
+    }
+
+    requirements1 = {
+        &quot;puser01&quot; : None,
+        &quot;puser02&quot; : None,
+        &quot;puser03&quot; : None,
+        &quot;puser04&quot; : None,
+        &quot;puser05&quot; : None,
+        &quot;puser06&quot; : None,
+        &quot;puser07&quot; : None,
+        &quot;puser08&quot; : None,
+        &quot;puser09&quot; : None,
+        &quot;puser10&quot; : None,
+    }
+
+
+    @inlineCallbacks
+    def _createShare(self, shareFrom, shareTo, accept=True):
+        # Invite
+        txnindex = 1 if shareFrom[0] == &quot;p&quot; else 0
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(txnindex), name=shareFrom, create=True)
+        calendar = yield home.childWithName(&quot;calendar&quot;)
+        shareeView = yield calendar.inviteUIDToShare(shareTo, _BIND_MODE_READ, &quot;summary&quot;)
+        yield self.commitTransaction(txnindex)
+
+        # Accept
+        if accept:
+            inviteUID = shareeView.shareUID()
+            txnindex = 1 if shareTo[0] == &quot;p&quot; else 0
+            shareeHome = yield self.homeUnderTest(txn=self.theTransactionUnderTest(txnindex), name=shareTo)
+            shareeView = yield shareeHome.acceptShare(inviteUID)
+            sharedName = shareeView.name()
+            yield self.commitTransaction(txnindex)
+        else:
+            sharedName = None
+
+        returnValue(sharedName)
+
+
+    def attachmentToString(self, attachment):
+        &quot;&quot;&quot;
+        Convenience to convert an L{IAttachment} to a string.
+
+        @param attachment: an L{IAttachment} provider to convert into a string.
+
+        @return: a L{Deferred} that fires with the contents of the attachment.
+
+        @rtype: L{Deferred} firing C{bytes}
+        &quot;&quot;&quot;
+        capture = CaptureProtocol()
+        attachment.retrieve(capture)
+        return capture.deferred
+
+
+    now = {
+        &quot;now&quot;: DateTime.getToday().getYear(),
+        &quot;now1&quot;: DateTime.getToday().getYear() + 1,
+    }
+
+    data01_1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data01_1
+DTSTART:{now1:04d}0102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:data01_1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    data01_1_changed = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data01_1
+DTSTART:{now1:04d}0102T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:data01_1_changed
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    data01_2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data01_2
+DTSTART:{now1:04d}0102T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:data01_2
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:user02@example.com
+ATTENDEE:mailto:puser02@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    data01_3 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data01_3
+DTSTART:{now1:04d}0102T180000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:data01_3
+ORGANIZER:mailto:user01@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:group02@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    data02_1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data02_1
+DTSTART:{now1:04d}0103T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:data02_1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    data02_2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data02_2
+DTSTART:{now1:04d}0103T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:data02_2
+ORGANIZER:mailto:user02@example.com
+ATTENDEE:mailto:user02@example.com
+ATTENDEE:mailto:user01@example.com
+ATTENDEE:mailto:puser02@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    data02_3 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_data02_3
+DTSTART:{now1:04d}0103T180000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:data02_3
+ORGANIZER:mailto:user02@example.com
+ATTENDEE:mailto:user02@example.com
+ATTENDEE:mailto:group01@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    datap02_1 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_datap02_1
+DTSTART:{now1:04d}0103T140000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+RRULE:FREQ=WEEKLY
+SUMMARY:datap02_1
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    datap02_2 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_datap02_2
+DTSTART:{now1:04d}0103T160000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:datap02_2
+ORGANIZER:mailto:puser02@example.com
+ATTENDEE:mailto:puser02@example.com
+ATTENDEE:mailto:user01@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+    datap02_3 = &quot;&quot;&quot;BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:uid_datap02_3
+DTSTART:{now1:04d}0103T180000Z
+DURATION:PT1H
+CREATED:20060102T190000Z
+DTSTAMP:20051222T210507Z
+SUMMARY:datap02_3
+ORGANIZER:mailto:puser02@example.com
+ATTENDEE:mailto:puser02@example.com
+ATTENDEE:mailto:group01@example.com
+END:VEVENT
+END:VCALENDAR
+&quot;&quot;&quot;.replace(&quot;\n&quot;, &quot;\r\n&quot;).format(**now)
+
+
+    @inlineCallbacks
+    def preCheck(self):
+        &quot;&quot;&quot;
+        Checks prior to starting any tests
+        &quot;&quot;&quot;
+
+        for i in range(self.numberOfStores):
+            txn = self.theTransactionUnderTest(i)
+            record = yield txn.directoryService().recordWithUID(u&quot;user01&quot;)
+            self.assertEqual(record.serviceNodeUID, &quot;A&quot;)
+            self.assertEqual(record.thisServer(), i == 0)
+            record = yield txn.directoryService().recordWithUID(u&quot;user02&quot;)
+            self.assertEqual(record.serviceNodeUID, &quot;A&quot;)
+            self.assertEqual(record.thisServer(), i == 0)
+            record = yield txn.directoryService().recordWithUID(u&quot;puser02&quot;)
+            self.assertEqual(record.serviceNodeUID, &quot;B&quot;)
+            self.assertEqual(record.thisServer(), i == 1)
+            yield self.commitTransaction(i)
+
+
+    @inlineCallbacks
+    def initialState(self):
+        &quot;&quot;&quot;
+        Setup the server with an initial set of data
+
+        user01 - migrating user
+        user02 - has a calendar shared with user01
+        user03 - shared to by user01
+
+        puser01 - user on other pod
+        puser02 - has a calendar shared with user01
+        puser03 - shared to by user01
+        &quot;&quot;&quot;
+
+        # Data for user01
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user01&quot;, create=True)
+        calendar = yield home.childWithName(&quot;calendar&quot;)
+        yield calendar.createCalendarObjectWithName(&quot;01_1.ics&quot;, Component.fromString(self.data01_1))
+        yield calendar.createCalendarObjectWithName(&quot;01_2.ics&quot;, Component.fromString(self.data01_2))
+        obj3 = yield calendar.createCalendarObjectWithName(&quot;01_3.ics&quot;, Component.fromString(self.data01_3))
+        attachment, _ignore_location = yield obj3.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test.txt&quot;, MemoryStream(&quot;Here is some text #1.&quot;))
+        self.stash[&quot;user01_attachment_id&quot;] = attachment.id()
+        self.stash[&quot;user01_attachment_md5&quot;] = attachment.md5()
+        self.stash[&quot;user01_attachment_mid&quot;] = attachment.managedID()
+        yield self.commitTransaction(0)
+
+        # Data for user02
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(0), name=&quot;user02&quot;, create=True)
+        calendar = yield home.childWithName(&quot;calendar&quot;)
+        yield calendar.createCalendarObjectWithName(&quot;02_1.ics&quot;, Component.fromString(self.data02_1))
+        yield calendar.createCalendarObjectWithName(&quot;02_2.ics&quot;, Component.fromString(self.data02_2))
+        yield calendar.createCalendarObjectWithName(&quot;02_3.ics&quot;, Component.fromString(self.data02_3))
+        yield self.commitTransaction(0)
+
+        # Data for puser02
+        home = yield self.homeUnderTest(txn=self.theTransactionUnderTest(1), name=&quot;puser02&quot;, create=True)
+        calendar = yield home.childWithName(&quot;calendar&quot;)
+        yield calendar.createCalendarObjectWithName(&quot;p02_1.ics&quot;, Component.fromString(self.datap02_1))
+        yield calendar.createCalendarObjectWithName(&quot;p02_2.ics&quot;, Component.fromString(self.datap02_2))
+        yield calendar.createCalendarObjectWithName(&quot;p02_3.ics&quot;, Component.fromString(self.datap02_3))
+        yield self.commitTransaction(1)
+
+        # Share calendars
+        self.stash[&quot;sharename_user01_to_user03&quot;] = yield self._createShare(&quot;user01&quot;, &quot;user03&quot;)
+        self.stash[&quot;sharename_user01_to_puser03&quot;] = yield self._createShare(&quot;user01&quot;, &quot;puser03&quot;)
+        self.stash[&quot;sharename_user02_to_user01&quot;] = yield self._createShare(&quot;user02&quot;, &quot;user01&quot;)
+        self.stash[&quot;sharename_puser02_to_user01&quot;] = yield self._createShare(&quot;puser02&quot;, &quot;user01&quot;)
+
+        # Add some delegates
+        txn = self.theTransactionUnderTest(0)
+        record01 = yield txn.directoryService().recordWithUID(u&quot;user01&quot;)
+        record02 = yield txn.directoryService().recordWithUID(u&quot;user02&quot;)
+        record03 = yield txn.directoryService().recordWithUID(u&quot;user03&quot;)
+        precord01 = yield txn.directoryService().recordWithUID(u&quot;puser01&quot;)
+
+        group02 = yield txn.directoryService().recordWithUID(u&quot;group02&quot;)
+        group03 = yield txn.directoryService().recordWithUID(u&quot;group03&quot;)
+
+        # Add user02 and user03 as individual delegates
+        yield Delegates.addDelegate(txn, record01, record02, True)
+        yield Delegates.addDelegate(txn, record01, record03, False)
+        yield Delegates.addDelegate(txn, record01, precord01, False)
+
+        # Add group delegates
+        yield Delegates.addDelegate(txn, record01, group02, True)
+        yield Delegates.addDelegate(txn, record01, group03, False)
+
+        # Add external delegates
+        yield txn.assignExternalDelegates(u&quot;user01&quot;, None, None, u&quot;external1&quot;, u&quot;external2&quot;)
+
+        yield self.commitTransaction(0)
+
+        yield self.waitAllEmpty()
+
+
+    @inlineCallbacks
+    def secondState(self):
+        &quot;&quot;&quot;
+        Setup the server with data changes appearing after the first sync
+        &quot;&quot;&quot;
+        txn = self.theTransactionUnderTest(0)
+        obj = yield self.calendarObjectUnderTest(txn, name=&quot;01_1.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        yield obj.setComponent(self.data01_1_changed)
+
+        obj = yield self.calendarObjectUnderTest(txn, name=&quot;02_2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user02&quot;)
+        attachment, _ignore_location = yield obj.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test_02.txt&quot;, MemoryStream(&quot;Here is some text #02.&quot;))
+        self.stash[&quot;user02_attachment_id&quot;] = attachment.id()
+        self.stash[&quot;user02_attachment_md5&quot;] = attachment.md5()
+        self.stash[&quot;user02_attachment_mid&quot;] = attachment.managedID()
+
+        yield self.commitTransaction(0)
+
+        yield self.waitAllEmpty()
+
+
+    @inlineCallbacks
+    def finalState(self):
+        &quot;&quot;&quot;
+        Setup the server with data changes appearing before the final sync
+        &quot;&quot;&quot;
+        txn = self.theTransactionUnderTest(1)
+        obj = yield self.calendarObjectUnderTest(txn, name=&quot;p02_2.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;puser02&quot;)
+        attachment, _ignore_location = yield obj.addAttachment(None, MimeType.fromString(&quot;text/plain&quot;), &quot;test_p02.txt&quot;, MemoryStream(&quot;Here is some text #p02.&quot;))
+        self.stash[&quot;puser02_attachment_id&quot;] = attachment.id()
+        self.stash[&quot;puser02_attachment_mid&quot;] = attachment.managedID()
+        self.stash[&quot;puser02_attachment_md5&quot;] = attachment.md5()
+
+        yield self.commitTransaction(1)
+
+        yield self.waitAllEmpty()
+
+
+    @inlineCallbacks
+    def switchAccounts(self):
+        &quot;&quot;&quot;
+        Switch the migrated user accounts to point to the new pod
+        &quot;&quot;&quot;
+
+        for i in range(self.numberOfStores):
+            txn = self.theTransactionUnderTest(i)
+            record = yield txn.directoryService().recordWithUID(u&quot;user01&quot;)
+            yield self.changeRecord(record, txn.directoryService().fieldName.serviceNodeUID, u&quot;B&quot;, directory=txn.directoryService())
+            yield self.commitTransaction(i)
+
+        for i in range(self.numberOfStores):
+            txn = self.theTransactionUnderTest(i)
+            record = yield txn.directoryService().recordWithUID(u&quot;user01&quot;)
+            self.assertEqual(record.serviceNodeUID, &quot;B&quot;)
+            self.assertEqual(record.thisServer(), i == 1)
+            record = yield txn.directoryService().recordWithUID(u&quot;user02&quot;)
+            self.assertEqual(record.serviceNodeUID, &quot;A&quot;)
+            self.assertEqual(record.thisServer(), i == 0)
+            record = yield txn.directoryService().recordWithUID(u&quot;puser02&quot;)
+            self.assertEqual(record.serviceNodeUID, &quot;B&quot;)
+            self.assertEqual(record.thisServer(), i == 1)
+            yield self.commitTransaction(i)
+
+
+    @inlineCallbacks
+    def postCheck(self):
+        &quot;&quot;&quot;
+        Checks after migration is done
+        &quot;&quot;&quot;
+
+        # Check that the home has been moved
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;)
+        self.assertTrue(home.external())
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_NORMAL)
+        self.assertTrue(home is None)
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL)
+        self.assertTrue(home is not None)
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_DISABLED)
+        self.assertTrue(home is not None)
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_MIGRATING)
+        self.assertTrue(home is None)
+        yield self.commitTransaction(0)
+
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;)
+        self.assertTrue(home.normal())
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_NORMAL)
+        self.assertTrue(home is not None)
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL)
+        self.assertTrue(home is None)
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_DISABLED)
+        self.assertTrue(home is not None)
+        home = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_MIGRATING)
+        self.assertTrue(home is None)
+        yield self.commitTransaction(1)
+
+        # Check that the notifications have been moved
+        notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_NORMAL)
+        self.assertTrue(notifications is None)
+        notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL)
+        self.assertTrue(notifications is None)
+        notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;, status=_HOME_STATUS_DISABLED)
+        self.assertTrue(notifications is not None)
+        yield self.commitTransaction(0)
+
+        notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_NORMAL)
+        self.assertTrue(notifications is not None)
+        notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_EXTERNAL)
+        self.assertTrue(notifications is None)
+        notifications = yield self.notificationCollectionUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;, status=_HOME_STATUS_DISABLED)
+        self.assertTrue(notifications is not None)
+        yield self.commitTransaction(1)
+
+        # New pod data
+        homes = {}
+        homes[&quot;user01&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user01&quot;)
+        homes[&quot;user02&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user02&quot;)
+        self.assertTrue(homes[&quot;user02&quot;].external())
+        homes[&quot;user03&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;user03&quot;)
+        self.assertTrue(homes[&quot;user03&quot;].external())
+        homes[&quot;puser01&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;puser01&quot;)
+        self.assertTrue(homes[&quot;puser01&quot;].normal())
+        homes[&quot;puser02&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;puser02&quot;)
+        self.assertTrue(homes[&quot;puser02&quot;].normal())
+        homes[&quot;puser03&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(1), name=&quot;puser03&quot;)
+        self.assertTrue(homes[&quot;puser03&quot;].normal())
+
+        # Check calendar data on new pod
+        calendars = yield homes[&quot;user01&quot;].loadChildren()
+        calnames = dict([(calendar.name(), calendar) for calendar in calendars])
+        self.assertEqual(
+            set(calnames.keys()),
+            set((&quot;calendar&quot;, &quot;tasks&quot;, &quot;inbox&quot;, self.stash[&quot;sharename_user02_to_user01&quot;], self.stash[&quot;sharename_puser02_to_user01&quot;],))
+        )
+
+        # Check shared-by user01 on new pod
+        shared = calnames[&quot;calendar&quot;]
+        invitations = yield shared.sharingInvites()
+        by_sharee = dict([(invitation.shareeUID, invitation) for invitation in invitations])
+        self.assertEqual(len(invitations), 2)
+        self.assertEqual(set(by_sharee.keys()), set((&quot;user03&quot;, &quot;puser03&quot;,)))
+        self.assertEqual(by_sharee[&quot;user03&quot;].shareeHomeID, homes[&quot;user03&quot;].id())
+        self.assertEqual(by_sharee[&quot;puser03&quot;].shareeHomeID, homes[&quot;puser03&quot;].id())
+
+        # Check shared-to user01 on new pod
+        shared = calnames[self.stash[&quot;sharename_user02_to_user01&quot;]]
+        self.assertEqual(shared.ownerHome().uid(), &quot;user02&quot;)
+        self.assertEqual(shared.ownerHome().id(), homes[&quot;user02&quot;].id())
+
+        shared = calnames[self.stash[&quot;sharename_puser02_to_user01&quot;]]
+        self.assertEqual(shared.ownerHome().uid(), &quot;puser02&quot;)
+        self.assertEqual(shared.ownerHome().id(), homes[&quot;puser02&quot;].id())
+
+        shared = yield homes[&quot;puser02&quot;].calendarWithName(&quot;calendar&quot;)
+        invitations = yield shared.sharingInvites()
+        self.assertEqual(len(invitations), 1)
+        self.assertEqual(invitations[0].shareeHomeID, homes[&quot;user01&quot;].id())
+
+        yield self.commitTransaction(1)
+
+        # Old pod data
+        homes = {}
+        homes[&quot;user01&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user01&quot;)
+        homes[&quot;user02&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user02&quot;)
+        self.assertTrue(homes[&quot;user02&quot;].normal())
+        homes[&quot;user03&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;user03&quot;)
+        self.assertTrue(homes[&quot;user03&quot;].normal())
+        homes[&quot;puser01&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;puser01&quot;)
+        self.assertTrue(homes[&quot;puser01&quot;] is None)
+        homes[&quot;puser02&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;puser02&quot;)
+        self.assertTrue(homes[&quot;puser02&quot;].external())
+        homes[&quot;puser03&quot;] = yield self.homeUnderTest(self.theTransactionUnderTest(0), name=&quot;puser03&quot;)
+        self.assertTrue(homes[&quot;puser03&quot;].external())
+
+        # Check shared-by user01 on old pod
+        shared = yield homes[&quot;user03&quot;].calendarWithName(self.stash[&quot;sharename_user01_to_user03&quot;])
+        self.assertEqual(shared.ownerHome().uid(), &quot;user01&quot;)
+        self.assertEqual(shared.ownerHome().id(), homes[&quot;user01&quot;].id())
+
+        # Check shared-to user01 on old pod
+        shared = yield homes[&quot;user02&quot;].calendarWithName(&quot;calendar&quot;)
+        invitations = yield shared.sharingInvites()
+        self.assertEqual(len(invitations), 1)
+        self.assertEqual(invitations[0].shareeHomeID, homes[&quot;user01&quot;].id())
+
+        yield self.commitTransaction(0)
+
+        # Delegates on each pod
+        for pod in range(self.numberOfStores):
+            txn = self.theTransactionUnderTest(pod)
+            records = {}
+            for ctr in range(10):
+                uid = u&quot;user{:02d}&quot;.format(ctr + 1)
+                records[uid] = yield txn.directoryService().recordWithUID(uid)
+            for ctr in range(10):
+                uid = u&quot;puser{:02d}&quot;.format(ctr + 1)
+                records[uid] = yield txn.directoryService().recordWithUID(uid)
+            for ctr in range(10):
+                uid = u&quot;group{:02d}&quot;.format(ctr + 1)
+                records[uid] = yield txn.directoryService().recordWithUID(uid)
+
+            delegates = yield Delegates.delegatesOf(txn, records[&quot;user01&quot;], True, False)
+            self.assertTrue(records[&quot;user02&quot;] in delegates)
+            self.assertTrue(records[&quot;group02&quot;] in delegates)
+            delegates = yield Delegates.delegatesOf(txn, records[&quot;user01&quot;], True, True)
+            self.assertTrue(records[&quot;user02&quot;] in delegates)
+            self.assertTrue(records[&quot;user06&quot;] in delegates)
+            self.assertTrue(records[&quot;user07&quot;] in delegates)
+            self.assertTrue(records[&quot;user08&quot;] in delegates)
+
+            delegates = yield Delegates.delegatesOf(txn, records[&quot;user01&quot;], False, False)
+            self.assertTrue(records[&quot;user03&quot;] in delegates)
+            self.assertTrue(records[&quot;group03&quot;] in delegates)
+            self.assertTrue(records[&quot;puser01&quot;] in delegates)
+            delegates = yield Delegates.delegatesOf(txn, records[&quot;user01&quot;], False, True)
+            self.assertTrue(records[&quot;user03&quot;] in delegates)
+            self.assertTrue(records[&quot;user07&quot;] in delegates)
+            self.assertTrue(records[&quot;user08&quot;] in delegates)
+            self.assertTrue(records[&quot;user09&quot;] in delegates)
+            self.assertTrue(records[&quot;puser01&quot;] in delegates)
+
+        # Attachments
+        obj = yield self.calendarObjectUnderTest(txn=self.theTransactionUnderTest(1), name=&quot;01_3.ics&quot;, calendar_name=&quot;calendar&quot;, home=&quot;user01&quot;)
+        attachment = yield obj.attachmentWithManagedID(self.stash[&quot;user01_attachment_mid&quot;])
+        self.assertTrue(attachment is not None)
+        self.assertEqual(attachment.md5(), self.stash[&quot;user01_attachment_md5&quot;])
+        data = yield self.attachmentToString(attachment)
+        self.assertEqual(data, &quot;Here is some text #1.&quot;)
+
+
+    @inlineCallbacks
+    def test_migration(self):
+        &quot;&quot;&quot;
+        Full migration cycle.
+        &quot;&quot;&quot;
+
+        yield self.preCheck()
+
+        # Step 1. Live full sync
+        yield self.initialState()
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;)
+        yield syncer.sync()
+
+        # Step 2. Live incremental sync
+        yield self.secondState()
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;)
+        yield syncer.sync()
+
+        # Step 3. Disable home after final changes
+        yield self.finalState()
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;)
+        yield syncer.disableRemoteHome()
+
+        # Step 4. Final incremental sync
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;, final=True)
+        yield syncer.sync()
+
+        # Step 5. Final reconcile sync
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;, final=True)
+        yield syncer.finalSync()
+
+        # Step 6. Enable new home
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;, final=True)
+        yield syncer.enableLocalHome()
+
+        # Step 7. Remove old home
+        syncer = CrossPodHomeSync(self.theStoreUnderTest(1), &quot;user01&quot;, final=True)
+        yield syncer.removeRemoteHome()
+
+        yield self.switchAccounts()
+
+        yield self.postCheck()
</ins></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastorepoddingtestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/util.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/util.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/podding/test/util.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -14,6 +14,7 @@
</span><span class="cx"> # limitations under the License.
</span><span class="cx"> ##
</span><span class="cx"> 
</span><ins>+from twisted.internet import reactor
</ins><span class="cx"> from twisted.internet.defer import inlineCallbacks, returnValue
</span><span class="cx"> from twisted.internet.protocol import Protocol
</span><span class="cx"> 
</span><span class="lines">@@ -34,6 +35,7 @@
</span><span class="cx"> from txweb2.stream import ProducerStream
</span><span class="cx"> 
</span><span class="cx"> from twext.enterprise.ienterprise import AlreadyFinishedError
</span><ins>+from twext.enterprise.jobqueue import JobItem
</ins><span class="cx"> 
</span><span class="cx"> import json
</span><span class="cx"> 
</span><span class="lines">@@ -225,6 +227,12 @@
</span><span class="cx">         self.activeTransactions[count] = None
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    @inlineCallbacks
+    def waitAllEmpty(self):
+        for i in range(self.numberOfStores):
+            yield JobItem.waitEmpty(self.theStoreUnderTest(i).newTransaction, reactor, 60.0)
+
+
</ins><span class="cx">     def makeConduit(self, store):
</span><span class="cx">         conduit = PoddingConduit(store)
</span><span class="cx">         conduit.conduitRequestClass = FakeConduitRequest
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresqlpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -579,7 +579,10 @@
</span><span class="cx">                 &quot;byID&quot;: collections.defaultdict(dict),
</span><span class="cx">             },
</span><span class="cx">         }
</span><del>-        self._notificationHomes = {}
</del><ins>+        self._notificationHomes = {
+            &quot;byUID&quot;: collections.defaultdict(dict),
+            &quot;byID&quot;: collections.defaultdict(dict),
+        }
</ins><span class="cx">         self._notifierFactories = notifierFactories
</span><span class="cx">         self._notifiedAlready = set()
</span><span class="cx">         self._bumpedRevisionAlready = set()
</span><span class="lines">@@ -753,7 +756,7 @@
</span><span class="cx">             result = yield self._homeClass[storeType].homeWithResourceID(self, rid)
</span><span class="cx">             if result:
</span><span class="cx">                 self._determineMemo(storeType, &quot;byID&quot;, None)[rid] = result
</span><del>-                self._determineMemo(storeType, &quot;byUID&quot;, result._status)[result.uid()] = result
</del><ins>+                self._determineMemo(storeType, &quot;byUID&quot;, result.status())[result.uid()] = result
</ins><span class="cx">         returnValue(result)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -765,22 +768,36 @@
</span><span class="cx">         return self.homeWithResourceID(EADDRESSBOOKTYPE, rid)
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-    @memoizedKey(&quot;uid&quot;, &quot;_notificationHomes&quot;)
-    def notificationsWithUID(self, uid, status=None, create=True):
</del><ins>+    @inlineCallbacks
+    def notificationsWithUID(self, uid, status=None, create=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Implement notificationsWithUID.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return NotificationCollection.notificationsWithUID(self, uid, create=create)
</del><span class="cx"> 
</span><ins>+        result = self._notificationHomes[&quot;byUID&quot;][status].get(uid)
+        if result is None:
+            result = yield NotificationCollection.notificationsWithUID(self, uid, status=status, create=create)
+            if result:
+                self._notificationHomes[&quot;byUID&quot;][status][uid] = result
+                self._notificationHomes[&quot;byID&quot;][None][result.id()] = result
+        returnValue(result)
</ins><span class="cx"> 
</span><del>-    @memoizedKey(&quot;rid&quot;, &quot;_notificationHomes&quot;)
</del><ins>+
+    @inlineCallbacks
</ins><span class="cx">     def notificationsWithResourceID(self, rid):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Implement notificationsWithResourceID.
</span><span class="cx">         &quot;&quot;&quot;
</span><del>-        return NotificationCollection.notificationsWithResourceID(self, rid)
</del><span class="cx"> 
</span><ins>+        result = self._notificationHomes[&quot;byID&quot;][None].get(rid)
+        if result is None:
+            result = yield NotificationCollection.notificationsWithResourceID(self, rid)
+            if result:
+                self._notificationHomes[&quot;byID&quot;][None][rid] = result
+                self._notificationHomes[&quot;byUID&quot;][result.status()][result.uid()] = result
+        returnValue(result)
</ins><span class="cx"> 
</span><ins>+
</ins><span class="cx">     def preCommit(self, operation):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Run things before C{commit}.  (Note: only provided by SQL
</span><span class="lines">@@ -1968,6 +1985,10 @@
</span><span class="cx">         return self._authzUID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def status(self):
+        return self._status
+
+
</ins><span class="cx">     def normal(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Is this an normal (internal) home.
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_externalpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_external.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -89,6 +89,16 @@
</span><span class="cx">         return self._txn.store().conduit.send_home_set_status(self, newStatus)
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def setLocalStatus(self, newStatus):
+        &quot;&quot;&quot;
+        Set the status on the object in the local store not the remote one.
+
+        @param newStatus: the new status to set
+        @type newStatus: L{int}
+        &quot;&quot;&quot;
+        return super(CommonHomeExternal, self).setStatus(newStatus)
+
+
</ins><span class="cx">     def external(self):
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         Is this an external home.
</span><span class="lines">@@ -517,4 +527,14 @@
</span><span class="cx">     def setStatus(self, newStatus):
</span><span class="cx">         return self._txn.store().conduit.send_notification_set_status(self, newStatus)
</span><span class="cx"> 
</span><ins>+
+    def setLocalStatus(self, newStatus):
+        &quot;&quot;&quot;
+        Set the status on the object in the local store not the remote one.
+
+        @param newStatus: the new status to set
+        @type newStatus: L{int}
+        &quot;&quot;&quot;
+        return super(NotificationCollectionExternal, self).setStatus(newStatus)
+
</ins><span class="cx"> NotificationCollection._externalClass = NotificationCollectionExternal
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_notificationpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_notification.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -147,8 +147,8 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><del>-    def notificationsWithUID(cls, txn, uid, status=None, create=True):
-        return cls.notificationsWith(txn, None, uid, status, create=create)
</del><ins>+    def notificationsWithUID(cls, txn, uid, status=None, create=False):
+        return cls.notificationsWith(txn, None, uid, status=status, create=create)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="lines">@@ -158,7 +158,7 @@
</span><span class="cx"> 
</span><span class="cx">     @classmethod
</span><span class="cx">     @inlineCallbacks
</span><del>-    def notificationsWith(cls, txn, rid, uid, status=None, create=True):
</del><ins>+    def notificationsWith(cls, txn, rid, uid, status=None, create=False):
</ins><span class="cx">         &quot;&quot;&quot;
</span><span class="cx">         @param uid: I'm going to assume uid is utf-8 encoded bytes
</span><span class="cx">         &quot;&quot;&quot;
</span><span class="lines">@@ -318,6 +318,10 @@
</span><span class="cx">         return self._ownerUID
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+    def status(self):
+        return self._status
+
+
</ins><span class="cx">     @inlineCallbacks
</span><span class="cx">     def setStatus(self, newStatus):
</span><span class="cx">         &quot;&quot;&quot;
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_sharingpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_sharing.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -601,7 +601,7 @@
</span><span class="cx">             notificationdata[&quot;supported-components&quot;] = self.getSupportedComponents()
</span><span class="cx"> 
</span><span class="cx">         # Add to sharee's collection
</span><del>-        notifications = yield self._txn.notificationsWithUID(shareeView.viewerHome().uid())
</del><ins>+        notifications = yield self._txn.notificationsWithUID(shareeView.viewerHome().uid(), create=True)
</ins><span class="cx">         yield notifications.writeNotificationObject(shareeView.shareUID(), notificationtype, notificationdata)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -632,7 +632,7 @@
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         # Add to owner notification collection
</span><del>-        notifications = yield self._txn.notificationsWithUID(self.ownerHome().uid())
</del><ins>+        notifications = yield self._txn.notificationsWithUID(self.ownerHome().uid(), create=True)
</ins><span class="cx">         yield notifications.writeNotificationObject(notificationUID, notificationtype, notificationdata)
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoresql_utilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_util.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_util.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/sql_util.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -691,7 +691,7 @@
</span><span class="cx">         L{NotificationHome} when it has been retrieved.
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     if homeType == ENOTIFICATIONTYPE:
</span><del>-        return txn.notificationsWithUID(uid, create=False)
</del><ins>+        return txn.notificationsWithUID(uid)
</ins><span class="cx">     else:
</span><span class="cx">         return txn.homeWithUID(homeType, uid)
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoretestutilpy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/test/util.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/test/util.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/test/util.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -939,6 +939,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><ins>+    def notificationCollectionUnderTest(self, txn=None, name=&quot;home1&quot;, status=None, create=False):
+        if txn is None:
+            txn = self.transactionUnderTest()
+        returnValue((yield txn.notificationsWithUID(name, status=status, create=create)))
+
+
+    @inlineCallbacks
</ins><span class="cx">     def userRecordWithShortName(self, shortname):
</span><span class="cx">         record = yield self.directory.recordWithShortName(self.directory.recordType.user, shortname)
</span><span class="cx">         returnValue(record)
</span><span class="lines">@@ -962,11 +969,13 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><del>-    def changeRecord(self, record, fieldname, value):
</del><ins>+    def changeRecord(self, record, fieldname, value, directory=None):
+        if directory is None:
+            directory = self.directory
</ins><span class="cx">         fields = record.fields.copy()
</span><span class="cx">         fields[fieldname] = value
</span><del>-        updatedRecord = DirectoryRecord(self.directory, fields)
-        yield self.directory.updateRecords((updatedRecord,))
</del><ins>+        updatedRecord = DirectoryRecord(directory, fields)
+        yield directory.updateRecords((updatedRecord,))
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavcommondatastoreupgradesqlupgradestesttest_notification_upgrade_from_0_to_1py"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/upgrade/sql/upgrades/test/test_notification_upgrade_from_0_to_1.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/upgrade/sql/upgrades/test/test_notification_upgrade_from_0_to_1.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/common/datastore/upgrade/sql/upgrades/test/test_notification_upgrade_from_0_to_1.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -169,7 +169,7 @@
</span><span class="cx">         )
</span><span class="cx"> 
</span><span class="cx">         for uid, notificationtype, _ignore_jtype, notificationdata, _ignore_jdata in data:
</span><del>-            notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;)
</del><ins>+            notifications = yield self.transactionUnderTest().notificationsWithUID(&quot;user01&quot;, create=True)
</ins><span class="cx">             yield notifications.writeNotificationObject(uid, notificationtype, notificationdata)
</span><span class="cx"> 
</span><span class="cx">         # Force data version to previous
</span></span></pre></div>
<a id="CalendarServerbranchesuserscdaboopod2podmigrationtxdavwhotesttest_group_shareespy"></a>
<div class="modfile"><h4>Modified: CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_sharees.py (14506 => 14507)</h4>
<pre class="diff"><span>
<span class="info">--- CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_sharees.py        2015-03-04 22:48:03 UTC (rev 14506)
+++ CalendarServer/branches/users/cdaboo/pod2pod-migration/txdav/who/test/test_group_sharees.py        2015-03-05 02:41:22 UTC (rev 14507)
</span><span class="lines">@@ -84,7 +84,7 @@
</span><span class="cx"> 
</span><span class="cx">     @inlineCallbacks
</span><span class="cx">     def _check_notifications(self, uid, items):
</span><del>-        notifyHome = yield self.transactionUnderTest().notificationsWithUID(uid)
</del><ins>+        notifyHome = yield self.transactionUnderTest().notificationsWithUID(uid, create=True)
</ins><span class="cx">         notifications = yield notifyHome.listNotificationObjects()
</span><span class="cx">         self.assertEqual(set(notifications), set(items))
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>