def test_svc_level_dupe(self): ''' check that service-level duplicate activities are caught (no DB involvement) ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcA, record=recA) actB = Activity() actB.StartTime = actA.StartTime actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB, record=recB) actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) self.assertEqual(len(s._activities), 1)
def test_svc_level_dupe_tz_irregular(self): ''' check that service-level duplicate activities with irregular TZs are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = pytz.timezone("America/Edmonton").localize( datetime(1, 2, 3, 4, 5, 6, 7)) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcA, record=recA) actB = Activity() actB.StartTime = actA.StartTime.astimezone( pytz.timezone("America/Iqaluit")) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB, record=recB) actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) self.assertEqual(len(s._activities), 1)
def test_svc_level_dupe(self): ''' check that service-level duplicate activities are caught (no DB involvement) ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcA, record=recA) actB = Activity() actB.StartTime = actA.StartTime actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(recA, [actA], activities) Sync._accumulateActivities(recB, [actB], activities) self.assertEqual(len(activities), 1)
def test_activity_deduplicate_tzerror(self): ''' Test that probably-duplicate activities with starttimes like 09:12:22 and 15:12:22 (on the same day) are recognized as one ''' svcA, svcB = TestTools.create_mock_services() actA = TestTools.create_random_activity(svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 1) # Ensure that it is an exact match actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5, seconds=1) activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 2) # Ensure that overly large differences >14hr - not possible via TZ differences - are not deduplicated actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=15) activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 2)
def test_activity_coalesce(self): ''' ensure that activity data is getting coalesced by _accumulateActivities ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = TestTools.create_random_activity(svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB) actA.Name = "Not this" actA.Private = True actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(recB, [copy.deepcopy(actB)], activities) Sync._accumulateActivities(recA, [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.StartTime, actA.StartTime) self.assertEqual(act.EndTime, actA.EndTime) self.assertEqual(act.EndTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.StartTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.Waypoints, actA.Waypoints) self.assertTrue(act.Private) # Most restrictive setting self.assertEqual(act.Name, actB.Name) # The first activity takes priority. self.assertEqual(act.Type, actB.Type) # Same here. self.assertTrue(list(actB.ServiceDataCollection.keys())[0] in act.ServiceDataCollection) self.assertTrue(list(actA.ServiceDataCollection.keys())[0] in act.ServiceDataCollection) activities = [] Sync._accumulateActivities(recA, [copy.deepcopy(actA)], activities) Sync._accumulateActivities(recB, [copy.deepcopy(actB)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.StartTime, actA.StartTime) self.assertEqual(act.EndTime, actA.EndTime) self.assertEqual(act.EndTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.StartTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.Waypoints, actA.Waypoints) self.assertEqual(act.Name, actA.Name) # The first activity takes priority. self.assertEqual(act.Type, actB.Type) # Exception: ActivityType.Other does not take priority self.assertTrue(list(actB.ServiceDataCollection.keys())[0] in act.ServiceDataCollection) self.assertTrue(list(actA.ServiceDataCollection.keys())[0] in act.ServiceDataCollection) actA.Type = ActivityType.CrossCountrySkiing activities = [] Sync._accumulateActivities(recA, [copy.deepcopy(actA)], activities) Sync._accumulateActivities(recB, [copy.deepcopy(actB)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.Type, actA.Type) # Here, it will take priority.
def test_svc_supported_activity_types(self): ''' check that only activities are only sent to services which support them ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) svcA.SupportedActivities = [ActivityType.CrossCountrySkiing] svcB.SupportedActivities = [ActivityType.Cycling] actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcA, record=recA) actA.Type = svcA.SupportedActivities[0] actA.CalculateUID() actA.UIDs = set([actA.UID]) actA.Record = ActivityRecord.FromActivity(actA) actB = Activity() actB.StartTime = datetime(5, 6, 7, 8, 9, 10, 11) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB, record=recB) actB.Type = [x for x in svcB.SupportedActivities if x != actA.Type][0] actB.CalculateUID() actB.UIDs = set([actB.UID]) actB.Record = ActivityRecord.FromActivity(actB) s = SynchronizationTask(None) s._serviceConnections = [recA, recB] s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) syncToA = s._determineRecipientServices(actA) syncToB = s._determineRecipientServices(actB) self.assertEqual(len(syncToA), 0) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities syncToA = s._determineRecipientServices(actA) syncToB = s._determineRecipientServices(actB) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities = [ ActivityType.CrossCountrySkiing, ActivityType.Cycling ] syncToA = s._determineRecipientServices(actA) syncToB = s._determineRecipientServices(actB) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 1)
def test_activity_coalesce(self): ''' ensure that activity data is getting coalesced by _accumulateActivities ''' svcA, svcB = TestTools.create_mock_services() actA = TestTools.create_random_activity(svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.StartTime, actA.StartTime) self.assertEqual(act.EndTime, actA.EndTime) self.assertEqual(act.EndTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.StartTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.Waypoints, actA.Waypoints) self.assertEqual(act.Name, actB.Name) # The first activity takes priority. self.assertEqual(act.Type, actB.Type) # Same here. self.assertTrue(actB.UploadedTo[0] in act.UploadedTo) self.assertTrue(actA.UploadedTo[0] in act.UploadedTo) activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.StartTime, actA.StartTime) self.assertEqual(act.EndTime, actA.EndTime) self.assertEqual(act.EndTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.StartTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.Waypoints, actA.Waypoints) self.assertEqual(act.Name, actA.Name) # The first activity takes priority. self.assertEqual(act.Type, actB.Type) # Exception: ActivityType.Other does not take priority self.assertTrue(actB.UploadedTo[0] in act.UploadedTo) self.assertTrue(actA.UploadedTo[0] in act.UploadedTo) actA.Type = ActivityType.CrossCountrySkiing activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.Type, actA.Type) # Here, it will take priority.
def test_eligibility_excluded(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] excludedServices = [recA] eligible = Sync._determineEligibleRecipientServices(activity=act, connectedServices=recipientServices, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_activity_deduplicate_tzerror(self): ''' Test that probably-duplicate activities with starttimes like 09:12:22 and 15:12:22 (on the same day) are recognized as one ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = TestTools.create_random_activity( svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB) actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 1) # Ensure that it is deduplicated on non-exact match actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5, seconds=1) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 1) # Ensure that it is *not* deduplicated when it really doesn't match actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5, minutes=7) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 2) # Ensure that overly large differences >38hr - not possible via TZ differences & shamefully bad import/export code on the part of some services - are not deduplicated actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=50) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 2)
def test_accumulate_exclusions(self): svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) # regular s = SynchronizationTask(None) s._syncExclusions = {recA._id: {}} exc = APIExcludeActivity("Messag!e", activity_id=3.14) s._accumulateExclusions(recA, exc) exclusionstore = s._syncExclusions self.assertTrue("3_14" in exclusionstore[recA._id]) self.assertEqual(exclusionstore[recA._id]["3_14"]["Message"], "Messag!e") self.assertEqual(exclusionstore[recA._id]["3_14"]["Activity"], None) self.assertEqual( exclusionstore[recA._id]["3_14"]["ExternalActivityID"], 3.14) self.assertEqual(exclusionstore[recA._id]["3_14"]["Permanent"], True) # updating act = TestTools.create_blank_activity(svcA) act.UID = "3_14" # meh exc = APIExcludeActivity("Messag!e2", activity_id=42, permanent=False, activity=act) s = SynchronizationTask(None) s._syncExclusions = {recA._id: {}} s._accumulateExclusions(recA, exc) exclusionstore = s._syncExclusions self.assertTrue("3_14" in exclusionstore[recA._id]) self.assertEqual(exclusionstore[recA._id]["3_14"]["Message"], "Messag!e2") self.assertNotEqual( exclusionstore[recA._id]["3_14"]["Activity"], None) # Who knows what the string format will be down the road? self.assertEqual( exclusionstore[recA._id]["3_14"]["ExternalActivityID"], 42) self.assertEqual(exclusionstore[recA._id]["3_14"]["Permanent"], False) # multiple, retaining existing exc2 = APIExcludeActivity("INM", activity_id=13) exc3 = APIExcludeActivity("FNIM", activity_id=37) s._accumulateExclusions(recA, [exc2, exc3]) exclusionstore = s._syncExclusions self.assertTrue("3_14" in exclusionstore[recA._id]) self.assertTrue("37" in exclusionstore[recA._id]) self.assertTrue("13" in exclusionstore[recA._id]) # don't allow with no identifiers exc4 = APIExcludeActivity("nooooo") s = SynchronizationTask(None) s._syncExclusions = {} self.assertRaises(ValueError, s._accumulateExclusions, recA, [exc4])
def test_eligibility_flowexception_reverse(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB User.SetFlowException(user, recA, recB, flowToSource=False) recipientServices = [recA, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices(activity=act, connectedServices=recipientServices, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertFalse(recA in eligible) self.assertTrue(recB in eligible)
def test_eligibility_config(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcA.Configurable = True svcA.RequiresConfiguration = lambda x: True recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices(activity=act, connectedServices=recipientServices, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_svc_supported_activity_types(self): ''' check that only activities are only sent to services which support them ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) svcA.SupportedActivities = [ActivityType.CrossCountrySkiing] svcB.SupportedActivities = [ActivityType.Cycling] actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcA, record=recA) actA.Type = svcA.SupportedActivities[0] actA.CalculateUID() actA.UIDs = set([actA.UID]) actA.Record = ActivityRecord.FromActivity(actA) actB = Activity() actB.StartTime = datetime(5, 6, 7, 8, 9, 10, 11) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actB.Type = [x for x in svcB.SupportedActivities if x != actA.Type][0] actB.CalculateUID() actB.UIDs = set([actB.UID]) actB.Record = ActivityRecord.FromActivity(actB) s = SynchronizationTask(None) s._serviceConnections = [recA, recB] s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) syncToA = s._determineRecipientServices(actA) syncToB = s._determineRecipientServices(actB) self.assertEqual(len(syncToA), 0) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities syncToA = s._determineRecipientServices(actA) syncToB = s._determineRecipientServices(actB) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities = [ActivityType.CrossCountrySkiing, ActivityType.Cycling] syncToA = s._determineRecipientServices(actA) syncToB = s._determineRecipientServices(actB) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 1)
def test_svc_supported_activity_types(self): ''' check that only activities are only sent to services which support them ''' svcA, svcB = TestTools.create_mock_services() svcA.SupportedActivities = [ActivityType.CrossCountrySkiing] svcB.SupportedActivities = [ActivityType.Cycling] actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actA.Type = svcA.SupportedActivities[0] actB = Activity() actB.StartTime = datetime(5, 6, 7, 8, 9, 10, 11) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actB.Type = [x for x in svcB.SupportedActivities if x != actA.Type][0] actA.CalculateUID() actB.CalculateUID() allConns = [ actA.UploadedTo[0]["Connection"], actB.UploadedTo[0]["Connection"] ] activities = [] Sync._accumulateActivities(svcA, [actA], activities) Sync._accumulateActivities(svcB, [actB], activities) syncToA = Sync._determineRecipientServices(actA, allConns) syncToB = Sync._determineRecipientServices(actB, allConns) self.assertEqual(len(syncToA), 0) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities syncToA = Sync._determineRecipientServices(actA, allConns) syncToB = Sync._determineRecipientServices(actB, allConns) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities = [ ActivityType.CrossCountrySkiing, ActivityType.Cycling ] syncToA = Sync._determineRecipientServices(actA, allConns) syncToB = Sync._determineRecipientServices(actB, allConns) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 1)
def test_eligibility_excluded(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] excludedServices = [recA] eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_constant_representation(self): ''' ensures that gpx import/export is symetric ''' svcA, other = TestTools.create_mock_services() svcA.SupportsHR = svcA.SupportsCadence = svcA.SupportsTemp = True svcA.SupportsPower = svcA.SupportsCalories = False act = TestTools.create_random_activity(svcA, tz=True) mid = GPXIO.Dump(act) act2 = GPXIO.Parse(bytes(mid,"UTF-8")) act2.TZ = act.TZ # we need to fake this since local TZ isn't defined in GPX files, and TZ discovery will flail with random activities act2.AdjustTZ() act.Stats.Distance = act2.Stats.Distance = None # same here self.assertActivitiesEqual(act2, act)
def test_eligibility_excluded(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] s = SynchronizationTask(None) s._excludedServices = {recA._id: UserException(UserExceptionType.Private)} s.user = user s._serviceConnections = recipientServices act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) eligible = s._determineEligibleRecipientServices(act, recipientServices) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_eligibility_config(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcA.Configurable = True svcA.RequiresConfiguration = lambda x: True recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_eligibility_flowexception_reverse(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB User.SetFlowException(user, recA, recB, flowToSource=False) recipientServices = [recA, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertFalse(recA in eligible) self.assertTrue(recB in eligible)
def test_eligibility_flowexception_shortcircuit(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcC = TestTools.create_mock_service("mockC") recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recC = TestTools.create_mock_svc_record(svcC) act = TestTools.create_blank_activity(svcA, record=recA) User.SetFlowException(user, recA, recC, flowToTarget=False) # Behaviour with known origin and no override set act.Origin = recA recipientServices = [recC, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices(activity=act, connectedServices=[recA, recB, recC], recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible) self.assertTrue(recC not in eligible) # Enable alternate routing recB.SetConfiguration({"allow_activity_flow_exception_bypass_via_self":True}, no_save=True) self.assertTrue(recB.GetConfiguration()["allow_activity_flow_exception_bypass_via_self"]) # We should now be able to arrive at recC via recB act.Origin = recA recipientServices = [recC, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices(activity=act, connectedServices=[recA, recB, recC], recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible) self.assertTrue(recC in eligible)
def test_activity_deduplicate_tzerror(self): ''' Test that probably-duplicate activities with starttimes like 09:12:22 and 15:12:22 (on the same day) are recognized as one ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = TestTools.create_random_activity(svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB) actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 1) # Ensure that it is deduplicated on non-exact match actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5, seconds=1) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 1) # Ensure that it is *not* deduplicated when it really doesn't match actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5, minutes=7) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 2) # Ensure that overly large differences >38hr - not possible via TZ differences & shamefully bad import/export code on the part of some services - are not deduplicated actB.StartTime = actA.StartTime.replace(tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=50) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) s._accumulateActivities(recA, [copy.deepcopy(actA)]) self.assertEqual(len(s._activities), 2)
def test_svc_supported_activity_types(self): ''' check that only activities are only sent to services which support them ''' svcA, svcB = TestTools.create_mock_services() svcA.SupportedActivities = [ActivityType.CrossCountrySkiing] svcB.SupportedActivities = [ActivityType.Cycling] actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actA.Type = svcA.SupportedActivities[0] actB = Activity() actB.StartTime = datetime(5, 6, 7, 8, 9, 10, 11) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actB.Type = [x for x in svcB.SupportedActivities if x != actA.Type][0] actA.CalculateUID() actB.CalculateUID() allConns = [actA.UploadedTo[0]["Connection"], actB.UploadedTo[0]["Connection"]] activities = [] Sync._accumulateActivities(svcA, [actA], activities) Sync._accumulateActivities(svcB, [actB], activities) syncToA = Sync._determineRecipientServices(actA, allConns) syncToB = Sync._determineRecipientServices(actB, allConns) self.assertEqual(len(syncToA), 0) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities syncToA = Sync._determineRecipientServices(actA, allConns) syncToB = Sync._determineRecipientServices(actB, allConns) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 0) svcB.SupportedActivities = svcA.SupportedActivities = [ActivityType.CrossCountrySkiing, ActivityType.Cycling] syncToA = Sync._determineRecipientServices(actA, allConns) syncToB = Sync._determineRecipientServices(actB, allConns) self.assertEqual(len(syncToA), 1) self.assertEqual(len(syncToB), 1)
def test_activity_deduplicate_tzerror(self): ''' Test that probably-duplicate activities with starttimes like 09:12:22 and 15:12:22 (on the same day) are recognized as one ''' svcA, svcB = TestTools.create_mock_services() actA = TestTools.create_random_activity( svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 1) # Ensure that it is an exact match actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=5, seconds=1) activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 2) # Ensure that overly large differences >38hr - not possible via TZ differences & shamefully bad import/export code on the part of some services - are not deduplicated actB.StartTime = actA.StartTime.replace( tzinfo=pytz.timezone("America/Denver")) + timedelta(hours=50) activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 2)
def test_eligibility_flowexception_reverse(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) User.SetFlowException(user, recA, recB, flowToSource=False) recipientServices = [recA, recB] s = SynchronizationTask(None) s._excludedServices = {} s.user = user s._serviceConnections = recipientServices eligible = s._determineEligibleRecipientServices(act, recipientServices) self.assertFalse(recA in eligible) self.assertTrue(recB in eligible)
def test_eligibility_config(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcA.Configurable = True svcA.RequiresConfiguration = lambda x: True recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] s = SynchronizationTask(None) s._excludedServices = {} s.user = user s._serviceConnections = recipientServices act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) eligible = s._determineEligibleRecipientServices(act, recipientServices) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_svc_level_dupe(self): ''' check that service-level duplicate activities are caught (no DB involvement) ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actB = Activity() actB.StartTime = actA.StartTime actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertEqual(len(activities), 1)
def test_activity_deduplicate_normaltz(self): ''' ensure that we can't deduplicate activities with non-pytz timezones ''' svcA, svcB = TestTools.create_mock_services() actA = TestTools.create_random_activity(svcA, tz=UTC()) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) + timedelta(seconds=10) actB.EndTime = actA.EndTime.replace(tzinfo=None) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) self.assertRaises(ValueError, Sync._accumulateActivities, Service.FromID("mockA"), [copy.deepcopy(actA)], activities)
def test_svc_level_dupe_tz_nonuniform(self): ''' check that service-level duplicate activities with non-uniform TZs are caught ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actB = Activity() actB.StartTime = pytz.timezone("America/Denver").localize(actA.StartTime) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertEqual(len(activities), 1)
def test_svc_level_dupe_tz_irregular(self): ''' check that service-level duplicate activities with irregular TZs are caught ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = pytz.timezone("America/Edmonton").localize(datetime(1, 2, 3, 4, 5, 6, 7)) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actB = Activity() actB.StartTime = actA.StartTime.astimezone(pytz.timezone("America/Iqaluit")) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertEqual(len(activities), 1)
def test_accumulate_exclusions(self): svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) # regular s = SynchronizationTask(None) s._syncExclusions = {recA._id: {}} exc = APIExcludeActivity("Messag!e", activity_id=3.14) s._accumulateExclusions(recA, exc) exclusionstore = s._syncExclusions self.assertTrue("3_14" in exclusionstore[recA._id]) self.assertEqual(exclusionstore[recA._id]["3_14"]["Message"], "Messag!e") self.assertEqual(exclusionstore[recA._id]["3_14"]["Activity"], None) self.assertEqual(exclusionstore[recA._id]["3_14"]["ExternalActivityID"], 3.14) self.assertEqual(exclusionstore[recA._id]["3_14"]["Permanent"], True) # updating act = TestTools.create_blank_activity(svcA) act.UID = "3_14" # meh exc = APIExcludeActivity("Messag!e2", activity_id=42, permanent=False, activity=act) s = SynchronizationTask(None) s._syncExclusions = {recA._id: {}} s._accumulateExclusions(recA, exc) exclusionstore = s._syncExclusions self.assertTrue("3_14" in exclusionstore[recA._id]) self.assertEqual(exclusionstore[recA._id]["3_14"]["Message"], "Messag!e2") self.assertNotEqual(exclusionstore[recA._id]["3_14"]["Activity"], None) # Who knows what the string format will be down the road? self.assertEqual(exclusionstore[recA._id]["3_14"]["ExternalActivityID"], 42) self.assertEqual(exclusionstore[recA._id]["3_14"]["Permanent"], False) # multiple, retaining existing exc2 = APIExcludeActivity("INM", activity_id=13) exc3 = APIExcludeActivity("FNIM", activity_id=37) s._accumulateExclusions(recA, [exc2, exc3]) exclusionstore = s._syncExclusions self.assertTrue("3_14" in exclusionstore[recA._id]) self.assertTrue("37" in exclusionstore[recA._id]) self.assertTrue("13" in exclusionstore[recA._id]) # don't allow with no identifiers exc4 = APIExcludeActivity("nooooo") s = SynchronizationTask(None) s._syncExclusions = {} self.assertRaises(ValueError, s._accumulateExclusions, recA, [exc4])
def test_eligibility_flowexception_reverse(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) User.SetFlowException(user, recA, recB, flowToSource=False) recipientServices = [recA, recB] s = SynchronizationTask(None) s._excludedServices = {} s.user = user s._serviceConnections = recipientServices eligible = s._determineEligibleRecipientServices( act, recipientServices) self.assertFalse(recA in eligible) self.assertTrue(recB in eligible)
def test_svc_level_dupe_tz_nonuniform(self): ''' check that service-level duplicate activities with non-uniform TZs are caught ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actB = Activity() actB.StartTime = pytz.timezone("America/Denver").localize( actA.StartTime) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertEqual(len(activities), 1)
def test_eligibility_excluded(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] s = SynchronizationTask(None) s._excludedServices = { recA._id: UserException(UserExceptionType.Private) } s.user = user s._serviceConnections = recipientServices act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) eligible = s._determineEligibleRecipientServices( act, recipientServices) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_eligibility_config(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcA.Configurable = True svcA.RequiresConfiguration = lambda x: True recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) recipientServices = [recA, recB] s = SynchronizationTask(None) s._excludedServices = {} s.user = user s._serviceConnections = recipientServices act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) eligible = s._determineEligibleRecipientServices( act, recipientServices) self.assertTrue(recB in eligible) self.assertTrue(recA not in eligible)
def test_svc_level_dupe_tz_irregular(self): ''' check that service-level duplicate activities with irregular TZs are caught ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = pytz.timezone("America/Edmonton").localize( datetime(1, 2, 3, 4, 5, 6, 7)) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actB = Activity() actB.StartTime = actA.StartTime.astimezone( pytz.timezone("America/Iqaluit")) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertEqual(len(activities), 1)
def test_svc_level_dupe_tz_irregular(self): ''' check that service-level duplicate activities with irregular TZs are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = pytz.timezone("America/Edmonton").localize(datetime(1, 2, 3, 4, 5, 6, 7)) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcA, record=recA) actB = Activity() actB.StartTime = actA.StartTime.astimezone(pytz.timezone("America/Iqaluit")) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(recA, [actA], activities) Sync._accumulateActivities(recB, [actB], activities) self.assertEqual(len(activities), 1)
def test_svc_level_dupe_tz_nonuniform(self): ''' check that service-level duplicate activities with non-uniform TZs are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcA, record=recA) actB = Activity() actB.StartTime = pytz.timezone("America/Denver").localize(actA.StartTime) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(recA, [actA], activities) Sync._accumulateActivities(recB, [actB], activities) self.assertEqual(len(activities), 1)
def test_activity_deduplicate_normaltz(self): ''' ensure that we can't deduplicate activities with non-pytz timezones ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = TestTools.create_random_activity(svcA, tz=UTC()) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) + timedelta(seconds=10) actB.EndTime = actA.EndTime.replace(tzinfo=None) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) self.assertRaises(ValueError, s._accumulateActivities, recA, [copy.deepcopy(actA)])
def test_svc_level_dupe_tz_uniform(self): ''' check that service-level duplicate activities with the same TZs are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = pytz.timezone("America/Denver").localize(datetime(1, 2, 3, 4, 5, 6, 7)) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcA, record=recA) actB = Activity() actB.StartTime = actA.StartTime actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) self.assertEqual(len(s._activities), 1)
def test_eligibility_flowexception_none(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB User.SetFlowException(user, recA, recB, flowToSource=False, flowToTarget=False) recipientServices = [recA] excludedServices = [] eligible = Sync._determineEligibleRecipientServices( activity=act, connectedServices=[recA, recB], recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible) recipientServices = [recB] act.Origin = recA act.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcA, record=recA) eligible = Sync._determineEligibleRecipientServices( activity=act, connectedServices=[recA, recB], recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible)
def test_svc_level_dupe_time_leeway(self): ''' check that service-level duplicate activities within the defined time leeway are caught ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actA.Type = set(svcA.SupportedActivities).intersection( set(svcB.SupportedActivities)).pop() actB = Activity() actB.StartTime = datetime(1, 2, 3, 4, 6, 6, 7) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actB.Type = actA.Type actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertIn(actA.UID, actA.UIDs) self.assertIn(actB.UID, actA.UIDs) self.assertIn(actA.UID, actB.UIDs) self.assertIn(actB.UID, actB.UIDs) # we need to fake up the service records to avoid having to call the actual sync method where these values are normally preset recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recA.SynchronizedActivities = [actA.UID] recB.SynchronizedActivities = [actB.UID] recipientServicesA = Sync._determineRecipientServices( actA, [recA, recB]) recipientServicesB = Sync._determineRecipientServices( actB, [recA, recB]) self.assertEqual(len(recipientServicesA), 0) self.assertEqual(len(recipientServicesB), 0) self.assertEqual(len(activities), 1)
def test_activity_deduplicate_normaltz(self): ''' ensure that we can't deduplicate activities with non-pytz timezones ''' svcA, svcB = TestTools.create_mock_services() actA = TestTools.create_random_activity(svcA, tz=UTC()) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) + timedelta( seconds=10) actB.EndTime = actA.EndTime.replace(tzinfo=None) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) self.assertRaises(ValueError, Sync._accumulateActivities, Service.FromID("mockA"), [copy.deepcopy(actA)], activities)
def test_constant_representation(self): ''' ensures that all services' API clients are consistent through a simulated download->upload cycle ''' # runkeeper rkSvc = Service.FromID("runkeeper") act = TestTools.create_random_activity(rkSvc, rkSvc.SupportedActivities[0]) record = rkSvc._createUploadData(act) returnedAct = rkSvc._populateActivity(record) act.Name = None # RK doesn't have a "name" field, so it's fudged into the notes, but not really rkSvc._populateActivityWaypoints(record, returnedAct) self.assertActivitiesEqual(returnedAct, act) # can't test Strava well this way, the upload and download formats are entirely different # endomondo - only waypoints at this point, the activity metadata is somewhat out-of-band eSvc = Service.FromID("endomondo") act = TestTools.create_random_activity(eSvc, eSvc.SupportedActivities[0]) oldWaypoints = act.Waypoints self.assertEqual(oldWaypoints[0].Calories, None) record = eSvc._createUploadData(act) eSvc._populateActivityFromTrackData(act, record) self.assertEqual(oldWaypoints, act.Waypoints)
def test_constant_representation_rk(self): ''' ensures that all services' API clients are consistent through a simulated download->upload cycle ''' # runkeeper rkSvc = Service.FromID("runkeeper") act = TestTools.create_random_activity(rkSvc, rkSvc.SupportedActivities[0], withLaps=False) record = rkSvc._createUploadData(act) record["has_path"] = act.GPS # RK helpfully adds a "has_path" entry if we have waypoints. returnedAct = rkSvc._populateActivity(record) act.Name = None # RK doesn't have a "name" field, so it's fudged into the notes, but not really rkSvc._populateActivityWaypoints(record, returnedAct) # RK deliberately doesn't set timezone.. returnedAct.EnsureTZ() self.assertActivitiesEqual(returnedAct, act)
def test_svc_level_dupe_tz_nonuniform(self): ''' check that service-level duplicate activities with non-uniform TZs are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcA, record=recA) actB = Activity() actB.StartTime = pytz.timezone("America/Denver").localize( actA.StartTime) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB, record=recB) actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(recA, [actA], activities) Sync._accumulateActivities(recB, [actB], activities) self.assertEqual(len(activities), 1)
def test_activity_deduplicate_normaltz(self): ''' ensure that we can't deduplicate activities with non-pytz timezones ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = TestTools.create_random_activity(svcA, tz=UTC()) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) + timedelta( seconds=10) actB.EndTime = actA.EndTime.replace(tzinfo=None) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB, record=recB) actA.Name = "Not this" actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recB, [copy.deepcopy(actB)]) self.assertRaises(ValueError, s._accumulateActivities, recA, [copy.deepcopy(actA)])
def test_svc_level_dupe_time_leeway(self): ''' check that service-level duplicate activities within the defined time leeway are caught ''' svcA, svcB = TestTools.create_mock_services() actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.UploadedTo = [TestTools.create_mock_upload_record(svcA)] actA.Type = set(svcA.SupportedActivities).intersection(set(svcB.SupportedActivities)).pop() actB = Activity() actB.StartTime = datetime(1, 2, 3, 4, 6, 6, 7) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actB.Type = actA.Type actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [actA], activities) Sync._accumulateActivities(Service.FromID("mockB"), [actB], activities) self.assertIn(actA.UID, actA.UIDs) self.assertIn(actB.UID, actA.UIDs) self.assertIn(actA.UID, actB.UIDs) self.assertIn(actB.UID, actB.UIDs) # we need to fake up the service records to avoid having to call the actual sync method where these values are normally preset recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recA.SynchronizedActivities = [actA.UID] recB.SynchronizedActivities = [actB.UID] recipientServicesA = Sync._determineRecipientServices(actA, [recA, recB]) recipientServicesB = Sync._determineRecipientServices(actB, [recA, recB]) self.assertEqual(len(recipientServicesA), 0) self.assertEqual(len(recipientServicesB), 0) self.assertEqual(len(activities), 1)
def test_constant_representation_rk(self): ''' ensures that all services' API clients are consistent through a simulated download->upload cycle ''' # runkeeper rkSvc = Service.FromID("runkeeper") act = TestTools.create_random_activity(rkSvc, rkSvc.SupportedActivities[0], withLaps=False) record = rkSvc._createUploadData(act) record[ "has_path"] = act.GPS # RK helpfully adds a "has_path" entry if we have waypoints. returnedAct = rkSvc._populateActivity(record) act.Name = None # RK doesn't have a "name" field, so it's fudged into the notes, but not really rkSvc._populateActivityWaypoints(record, returnedAct) # RK deliberately doesn't set timezone.. returnedAct.EnsureTZ() self.assertActivitiesEqual(returnedAct, act)
def test_svc_level_dupe_time_leeway(self): ''' check that service-level duplicate activities within the defined time leeway are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcA, record=recA) actA.Type = set(svcA.SupportedActivities).intersection( set(svcB.SupportedActivities)).pop() actB = Activity() actB.StartTime = datetime(1, 2, 3, 4, 6, 6, 7) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection( svcB, record=recB) actB.Type = actA.Type actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) self.assertIn(actA.UID, actA.UIDs) self.assertIn(actB.UID, actA.UIDs) self.assertIn(actA.UID, actB.UIDs) self.assertIn(actB.UID, actB.UIDs) # we need to fake up the service records to avoid having to call the actual sync method where these values are normally preset recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recA.SynchronizedActivities = [actA.UID] recB.SynchronizedActivities = [actB.UID] s._serviceConnections = [recA, recB] recipientServicesA = s._determineRecipientServices(actA) recipientServicesB = s._determineRecipientServices(actB) self.assertEqual(len(recipientServicesA), 0) self.assertEqual(len(recipientServicesB), 0) self.assertEqual(len(s._activities), 1)
def test_eligibility_flowexception_shortcircuit(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcC = TestTools.create_mock_service("mockC") recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recC = TestTools.create_mock_svc_record(svcC) act = TestTools.create_blank_activity(svcA, record=recA) User.SetFlowException(user, recA, recC, flowToTarget=False) # Behaviour with known origin and no override set act.Origin = recA act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) recipientServices = [recC, recB] s = SynchronizationTask(None) s._excludedServices = {} s.user = user s._serviceConnections = [recA, recB, recC] eligible = s._determineEligibleRecipientServices( act, recipientServices) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible) self.assertTrue(recC not in eligible) # Enable alternate routing # FIXME: This setting doesn't seem to be used anywhere any more?? Test disabled at the end.. recB.SetConfiguration( {"allow_activity_flow_exception_bypass_via_self": True}, no_save=True) self.assertTrue(recB.GetConfiguration() ["allow_activity_flow_exception_bypass_via_self"]) # We should now be able to arrive at recC via recB act.Origin = recA act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) recipientServices = [recC, recB] s._excludedServices = {} s._serviceConnections = [recA, recB, recC] eligible = s._determineEligibleRecipientServices( act, recipientServices) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible)
def test_eligibility_flowexception_shortcircuit(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcC = TestTools.create_mock_service("mockC") recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recC = TestTools.create_mock_svc_record(svcC) act = TestTools.create_blank_activity(svcA, record=recA) User.SetFlowException(user, recA, recC, flowToTarget=False) # Behaviour with known origin and no override set act.Origin = recA recipientServices = [recC, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices( activity=act, connectedServices=[recA, recB, recC], recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible) self.assertTrue(recC not in eligible) # Enable alternate routing recB.SetConfiguration( {"allow_activity_flow_exception_bypass_via_self": True}, no_save=True) self.assertTrue(recB.GetConfiguration() ["allow_activity_flow_exception_bypass_via_self"]) # We should now be able to arrive at recC via recB act.Origin = recA recipientServices = [recC, recB] excludedServices = [] eligible = Sync._determineEligibleRecipientServices( activity=act, connectedServices=[recA, recB, recC], recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible) self.assertTrue(recC in eligible)
def test_svc_level_dupe_time_leeway(self): ''' check that service-level duplicate activities within the defined time leeway are caught ''' svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) actA = Activity() actA.StartTime = datetime(1, 2, 3, 4, 5, 6, 7) actA.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcA, record=recA) actA.Type = set(svcA.SupportedActivities).intersection(set(svcB.SupportedActivities)).pop() actB = Activity() actB.StartTime = datetime(1, 2, 3, 4, 6, 6, 7) actB.ServiceDataCollection = TestTools.create_mock_servicedatacollection(svcB, record=recB) actB.Type = actA.Type actA.CalculateUID() actB.CalculateUID() s = SynchronizationTask(None) s._activities = [] s._accumulateActivities(recA, [actA]) s._accumulateActivities(recB, [actB]) self.assertIn(actA.UID, actA.UIDs) self.assertIn(actB.UID, actA.UIDs) self.assertIn(actA.UID, actB.UIDs) self.assertIn(actB.UID, actB.UIDs) # we need to fake up the service records to avoid having to call the actual sync method where these values are normally preset recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recA.SynchronizedActivities = [actA.UID] recB.SynchronizedActivities = [actB.UID] s._serviceConnections = [recA, recB] recipientServicesA = s._determineRecipientServices(actA) recipientServicesB = s._determineRecipientServices(actB) self.assertEqual(len(recipientServicesA), 0) self.assertEqual(len(recipientServicesB), 0) self.assertEqual(len(s._activities), 1)
def test_eligibility_flowexception_shortcircuit(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() svcC = TestTools.create_mock_service("mockC") recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) recC = TestTools.create_mock_svc_record(svcC) act = TestTools.create_blank_activity(svcA, record=recA) User.SetFlowException(user, recA, recC, flowToTarget=False) # Behaviour with known origin and no override set act.Origin = recA act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) recipientServices = [recC, recB] s = SynchronizationTask(None) s._excludedServices = {} s.user = user s._serviceConnections = [recA, recB, recC] eligible = s._determineEligibleRecipientServices(act, recipientServices) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible) self.assertTrue(recC not in eligible) # Enable alternate routing # FIXME: This setting doesn't seem to be used anywhere any more?? Test disabled at the end.. recB.SetConfiguration({"allow_activity_flow_exception_bypass_via_self": True}, no_save=True) self.assertTrue(recB.GetConfiguration()["allow_activity_flow_exception_bypass_via_self"]) # We should now be able to arrive at recC via recB act.Origin = recA act.UIDs = set([act.UID]) act.Record = ActivityRecord.FromActivity(act) recipientServices = [recC, recB] s._excludedServices = {} s._serviceConnections = [recA, recB, recC] eligible = s._determineEligibleRecipientServices(act, recipientServices) self.assertTrue(recA not in eligible) self.assertTrue(recB in eligible)
def test_eligibility_flowexception_none(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB User.SetFlowException(user, recA, recB, flowToSource=False, flowToTarget=False) recipientServices = [recA] excludedServices = [] eligible = Sync._determineEligibleRecipientServices(activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible) recipientServices = [recB] act.Origin = recA act.UploadedTo = [TestTools.create_mock_upload_record(svcA, record=recA)] eligible = Sync._determineEligibleRecipientServices(activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible)
def test_activity_coalesce(self): ''' ensure that activity data is getting coalesced by _accumulateActivities ''' svcA, svcB = TestTools.create_mock_services() actA = TestTools.create_random_activity( svcA, tz=pytz.timezone("America/Iqaluit")) actB = Activity() actB.StartTime = actA.StartTime.replace(tzinfo=None) actB.UploadedTo = [TestTools.create_mock_upload_record(svcB)] actA.Name = "Not this" actA.Private = True actB.Name = "Heya" actB.Type = ActivityType.Walking actA.CalculateUID() actB.CalculateUID() activities = [] Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.StartTime, actA.StartTime) self.assertEqual(act.EndTime, actA.EndTime) self.assertEqual(act.EndTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.StartTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.Waypoints, actA.Waypoints) self.assertTrue(act.Private) # Most restrictive setting self.assertEqual(act.Name, actB.Name) # The first activity takes priority. self.assertEqual(act.Type, actB.Type) # Same here. self.assertTrue(actB.UploadedTo[0] in act.UploadedTo) self.assertTrue(actA.UploadedTo[0] in act.UploadedTo) activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.StartTime, actA.StartTime) self.assertEqual(act.EndTime, actA.EndTime) self.assertEqual(act.EndTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.StartTime.tzinfo, actA.StartTime.tzinfo) self.assertEqual(act.Waypoints, actA.Waypoints) self.assertEqual(act.Name, actA.Name) # The first activity takes priority. self.assertEqual( act.Type, actB.Type) # Exception: ActivityType.Other does not take priority self.assertTrue(actB.UploadedTo[0] in act.UploadedTo) self.assertTrue(actA.UploadedTo[0] in act.UploadedTo) actA.Type = ActivityType.CrossCountrySkiing activities = [] Sync._accumulateActivities(Service.FromID("mockA"), [copy.deepcopy(actA)], activities) Sync._accumulateActivities(Service.FromID("mockB"), [copy.deepcopy(actB)], activities) self.assertEqual(len(activities), 1) act = activities[0] self.assertEqual(act.Type, actA.Type) # Here, it will take priority.
def test_duration_calculation(self): ''' ensures that true-duration calculation is being reasonable ''' act = TestTools.create_blank_activity() act.StartTime = datetime.now() act.EndTime = act.StartTime + timedelta(hours=3) # No waypoints self.assertRaises(ValueError, act.GetTimerTime) # Too few waypoints act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.EndTime)] self.assertRaises(ValueError, act.GetTimerTime) # straight-up calculation act.EndTime = act.StartTime + timedelta(seconds=14) act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.StartTime + timedelta(seconds=2)), Waypoint(timestamp=act.StartTime + timedelta(seconds=6)), Waypoint(timestamp=act.StartTime + timedelta(seconds=10)), Waypoint(timestamp=act.StartTime + timedelta(seconds=14))] self.assertEqual(act.GetTimerTime(), timedelta(seconds=14)) # pauses act.EndTime = act.StartTime + timedelta(seconds=14) act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.StartTime + timedelta(seconds=2)), Waypoint(timestamp=act.StartTime + timedelta(seconds=6), ptType=WaypointType.Pause), Waypoint(timestamp=act.StartTime + timedelta(seconds=9), ptType=WaypointType.Pause), Waypoint(timestamp=act.StartTime + timedelta(seconds=10), ptType=WaypointType.Resume), Waypoint(timestamp=act.StartTime + timedelta(seconds=14))] self.assertEqual(act.GetTimerTime(), timedelta(seconds=10)) # laps - NO effect act.EndTime = act.StartTime + timedelta(seconds=14) act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.StartTime + timedelta(seconds=2)), Waypoint(timestamp=act.StartTime + timedelta(seconds=6), ptType=WaypointType.Lap), Waypoint(timestamp=act.StartTime + timedelta(seconds=9)), Waypoint(timestamp=act.StartTime + timedelta(seconds=10), ptType=WaypointType.Lap), Waypoint(timestamp=act.StartTime + timedelta(seconds=14))] self.assertEqual(act.GetTimerTime(), timedelta(seconds=14)) # multiple pauses + ending after pause act.EndTime = act.StartTime + timedelta(seconds=20) act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.StartTime + timedelta(seconds=2)), Waypoint(timestamp=act.StartTime + timedelta(seconds=6), ptType=WaypointType.Pause), Waypoint(timestamp=act.StartTime + timedelta(seconds=9), ptType=WaypointType.Pause), Waypoint(timestamp=act.StartTime + timedelta(seconds=10), ptType=WaypointType.Resume), Waypoint(timestamp=act.StartTime + timedelta(seconds=12)), Waypoint(timestamp=act.StartTime + timedelta(seconds=16)), Waypoint(timestamp=act.StartTime + timedelta(seconds=17), ptType=WaypointType.Pause), Waypoint(timestamp=act.StartTime + timedelta(seconds=20), ptType=WaypointType.End)] self.assertEqual(act.GetTimerTime(), timedelta(seconds=13)) # implicit pauses (>1m5s) act.EndTime = act.StartTime + timedelta(seconds=20) act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.StartTime + timedelta(seconds=2)), Waypoint(timestamp=act.StartTime + timedelta(seconds=6)), Waypoint(timestamp=act.StartTime + timedelta(seconds=120)), Waypoint(timestamp=act.StartTime + timedelta(seconds=124)), Waypoint(timestamp=act.StartTime + timedelta(seconds=130))] self.assertEqual(act.GetTimerTime(), timedelta(seconds=16)) # mixed pauses - would this ever happen?? Either way, the explicit pause should override the implicit one and cause otherwise-ignored time to be counted act.EndTime = act.StartTime + timedelta(seconds=23) act.Waypoints = [Waypoint(timestamp=act.StartTime), Waypoint(timestamp=act.StartTime + timedelta(seconds=2)), Waypoint(timestamp=act.StartTime + timedelta(seconds=6)), Waypoint(timestamp=act.StartTime + timedelta(seconds=20), ptType=WaypointType.Pause), Waypoint(timestamp=act.StartTime + timedelta(seconds=24), ptType=WaypointType.Resume), Waypoint(timestamp=act.StartTime + timedelta(seconds=30))] self.assertEqual(act.GetTimerTime(), timedelta(seconds=26))
def test_eligibility_flowexception_change(self): user = TestTools.create_mock_user() svcA, svcB = TestTools.create_mock_services() recA = TestTools.create_mock_svc_record(svcA) recB = TestTools.create_mock_svc_record(svcB) act = TestTools.create_blank_activity(svcA, record=recB) act.Origin = recB recipientServices = [recA] excludedServices = [] User.SetFlowException(user, recA, recB, flowToSource=False, flowToTarget=True) eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible) recipientServices = [recB] act.Origin = recA act.UploadedTo = [ TestTools.create_mock_upload_record(svcA, record=recA) ] User.SetFlowException(user, recA, recB, flowToSource=True, flowToTarget=False) eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible) User.SetFlowException(user, recA, recB, flowToSource=False, flowToTarget=False) eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA not in eligible) self.assertTrue(recB not in eligible) recipientServices = [recA, recB] User.SetFlowException(user, recA, recB, flowToSource=True, flowToTarget=True) eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA in eligible) self.assertTrue(recB in eligible) eligible = Sync._determineEligibleRecipientServices( activity=act, recipientServices=recipientServices, excludedServices=excludedServices, user=user) self.assertTrue(recA in eligible) self.assertTrue(recB in eligible)