def test_deleted_src_tasks(): src_tasks = [] dst = random_task() dst_tasks = [dst] ss = MockTaskService(src_tasks) ds = MockTaskService(dst_tasks) map = TaskMap() # we need to create a mapping between a src task and dst, but leave # the source task out of the source service src = random_task() map.map(src, dst) sync = TaskSync(ss, ds, map) # preconditions assert len(ss.tasks) == 0 assert len(ds.tasks) == 1 assert dst.status == SyncStatus.unchanged sync.synchronise() # the task list lengths should not be changed assert len(ss.tasks) == 0 assert len(ds.tasks) == 1 # dst should now be flagged as deleted assert dst.status == SyncStatus.deleted
def test_missing_mapped_destination_tasks_src_not_complete(): """ Tests expected behaviours on mapped tasks that are missing in the destination. """ src = random_task() src.completed = False src_tasks = [src] src_svc = MockTaskService(src_tasks) dst = random_task() dst_tasks = [] dst_svc = MockTaskService(dst_tasks) map = TaskMap() # create the pre-existing mapping map.map(src, dst) # preconditions assert len(map.get_all_src_keys()) == 1 assert map.get_dst_id(src.id) == dst.id assert map.get_src_id(dst.id) == src.id TaskSync(src_svc, dst_svc, map).synchronise() assert dst.id != dst_svc.tasks[0].id assert len(map.get_all_src_keys()) == 1, "should still be just one mapping" assert not map.try_get_src_id(dst.id), "old dst should be unmapped" assert map.get_dst_id(src.id) != dst.id, "src should be mapped to something else" assert dst_svc.tasks[0].status == SyncStatus.new, "should be flagged as a new task" assert len(dst_svc.tasks) == 1
def test_get_all_dst_keys(self): src_tasks = [MockTask(_id=i+20) for i in range(4)] dst_keys = (1,2,3,4) dst_tasks = [MockTask(_id=i) for i in dst_keys] map = TaskMap() for s,d in zip(src_tasks, dst_tasks): map.map(s,d) for actual in map.get_all_dst_keys(): assert actual in dst_keys
def test_get_all_dst_keys(self): src_tasks = [MockTask(_id=i + 20) for i in range(4)] dst_keys = (1, 2, 3, 4) dst_tasks = [MockTask(_id=i) for i in dst_keys] map = TaskMap() for s, d in zip(src_tasks, dst_tasks): map.map(s, d) for actual in map.get_all_dst_keys(): assert actual in dst_keys
def test_persist_task_mapping(self): expected = TaskMap() tasks = [MockTask(_id=i) for i in range(4)] expected.map(tasks[0], tasks[1]) expected.map(tasks[2], tasks[3]) filename = NamedTemporaryFile(suffix='.pickle') expected.persist(filename.name) actual = TaskMap(filename.name) assert actual.get_dst_id(tasks[0].id) == tasks[1].id assert actual.get_dst_id(tasks[2].id) == tasks[3].id assert actual.get_src_id(tasks[1].id) == tasks[0].id assert actual.get_src_id(tasks[3].id) == tasks[2].id
def test_missing_mapped_destination_tasks_src_complete(): """ Tests expected behaviours on mapped tasks that are missing in the destination. """ src = random_task() src.completed = True src_tasks = [src] src_svc = MockTaskService(src_tasks) dst = random_task() dst_tasks = [] dst_svc = MockTaskService(dst_tasks) map = TaskMap() # create the pre-existing mapping map.map(src, dst) # preconditions assert len(map.get_all_src_keys()) == 1 assert map.get_dst_id(src.id) == dst.id assert map.get_src_id(dst.id) == src.id TaskSync(src_svc, dst_svc, map).synchronise() assert len(dst_svc.tasks) == 0
def test_persist_task_mapping(self): expected = TaskMap() tasks = [ MockTask(_id='aaa'), MockTask(_id='bbb'), MockTask(_id='ccc'), MockTask(_id='ddd') ] expected.map(tasks[0], tasks[1]) expected.map(tasks[2], tasks[3]) filename = NamedTemporaryFile(suffix='.tm') expected.persist(filename.name) actual = TaskMap(filename.name) assert actual.get_dst_id(tasks[0].id) == tasks[1].id assert actual.get_dst_id(tasks[2].id) == tasks[3].id assert actual.get_src_id(tasks[1].id) == tasks[0].id assert actual.get_src_id(tasks[3].id) == tasks[2].id
def test_new_completed_tasks_are_updated_when_last_mod_newer_than_last_sync(): last_sync = datetime(2016, 8, 15, tzinfo=pytz.utc) src_mod_date = last_sync + timedelta(hours=2) src = random_task(completed=True, last_modified=src_mod_date) src_tasks = [src] src_svc = MockTaskService(src_tasks) dst_svc = MockTaskService([]) TaskSync(src_svc, dst_svc, TaskMap(), last_sync=last_sync).synchronise() assert len(dst_svc.persisted_tasks) == 1 assert dst_svc.tasks[0].status == SyncStatus.new
def test_new_completed_tasks(): src = random_task(completed=True) src_tasks = [src] src_svc = MockTaskService(src_tasks) dst_tasks = [] dst_svc = MockTaskService(dst_tasks) map = TaskMap() TaskSync(src_svc, dst_svc, map).synchronise() assert len(dst_svc.tasks) == 1 assert dst_svc.tasks[0].completed assert dst_svc.tasks[0].status == SyncStatus.new
def test_new_completed_tasks_are_not_updated_when_last_mod_older_than_last_sync(): last_sync = datetime(2016, 8, 15, tzinfo=pytz.utc) src = random_task( completed=True, last_modified=last_sync - timedelta(days=2)) src_tasks = [src] src_svc = MockTaskService(src_tasks) dst_svc = MockTaskService([]) TaskSync(src_svc, dst_svc, TaskMap(), last_sync=last_sync).synchronise() assert len(dst_svc.persisted_tasks) == 0
def test_completion_of_existing_mapped_tasks(): src = random_task(completed=True) src_tasks = [src] src_svc = MockTaskService(src_tasks) dst = random_task() # make dst the same in all but the completed flag dst.copy_fields(dst) dst.completed = False dst_tasks = [dst] dst_svc = MockTaskService(dst_tasks) map = TaskMap() map.map(src, dst) assert not dst_svc.tasks[0].completed TaskSync(src_svc, dst_svc, map).synchronise() assert len(dst_svc.tasks) == 1 assert dst_svc.tasks[0].completed assert dst_svc.tasks[0].status == SyncStatus.updated
def test_existing_tasks_are_updated(): src = random_task() src.difficulty = Difficulty.hard src.attribute = CharacterAttribute.strength src_tasks = [src] src_svc = MockTaskService(src_tasks) dst = random_task() dst.description = 'something different' dst.difficulty = Difficulty.medium dst.attribute = CharacterAttribute.constitution dst_tasks = [dst] dst_svc = MockTaskService(dst_tasks) # precondition tests assert src.id != dst.id assert src.status == SyncStatus.unchanged assert dst.name != src.name assert dst.attribute != src.attribute assert dst.difficulty != src.difficulty assert dst.status == SyncStatus.unchanged assert dst.description != src.description map = TaskMap() map.map(src, dst) sync = TaskSync(src_svc, dst_svc, map) sync.synchronise() assert len(dst_svc.persisted_tasks) == 1 actual = dst_svc.persisted_tasks[0] assert actual.id == dst.id, "id not changed" assert actual.id != src.id, "id not changed" assert actual.name == src.name assert actual.attribute == src.attribute assert actual.difficulty == src.difficulty assert actual.completed == src.completed assert actual.status == SyncStatus.updated assert actual.description == src.description assert actual.completed == src.completed
def test_new_existing_tasks_are_updated(): last_sync = datetime(2016, 8, 15, tzinfo=pytz.utc) # make the src modified date newer than the last sync src_mod_date = last_sync + timedelta(minutes=2) dst_mod_date = last_sync + timedelta(minutes=1) src = random_task(last_modified=src_mod_date) src_tasks = [src] src_svc = MockTaskService(src_tasks) dst = random_task(last_modified=dst_mod_date) dst_tasks = [dst] dst_svc = MockTaskService(dst_tasks) map = TaskMap() map.map(src, dst) # preconditions assert dst.status == SyncStatus.unchanged TaskSync(src_svc, dst_svc, map).synchronise() assert len(dst_svc.persisted_tasks) == 1 assert dst.status == SyncStatus.updated
def test_persist_task_mapping(self): expected = TaskMap() tasks = [ MockTask(_id='aaa'), MockTask(_id='bbb'), MockTask(_id='ccc'), MockTask(_id='ddd')] expected.map(tasks[0], tasks[1]) expected.map(tasks[2], tasks[3]) filename = NamedTemporaryFile(suffix='.tm') expected.persist(filename.name) actual = TaskMap(filename.name) assert actual.get_dst_id(tasks[0].id) == tasks[1].id assert actual.get_dst_id(tasks[2].id) == tasks[3].id assert actual.get_src_id(tasks[1].id) == tasks[0].id assert actual.get_src_id(tasks[3].id) == tasks[2].id
def test_new_tasks(): src_tasks = [random_task() for x in range(3)] dst_tasks = [] src = MockTaskService(src_tasks) dst = MockTaskService(dst_tasks) map = TaskMap() sync = TaskSync(src, dst, map) sync.synchronise() assert len(dst.persisted_tasks) == len(src_tasks) for d in dst.persisted_tasks: assert d.status == SyncStatus.new assert d in dst_tasks assert map.try_get_src_id(d.id) for s in src.get_all_tasks(): dst_id = map.try_get_dst_id(s.id) assert dst_id d = dst.get_task(dst_id) assert s.name == d.name assert s.description == d.description assert s.completed == d.completed assert s.difficulty == d.difficulty assert s.attribute == d.attribute
def test_remove_orphan_mappings(): src_tasks = [random_task()] dst_tasks = [] ss = MockTaskService(src_tasks) ds = MockTaskService(dst_tasks) map = TaskMap() # add a few task mappings that won't exist in either source or destination map.map(random_task(), random_task()) map.map(random_task(), random_task()) map.map(random_task(), random_task()) TaskSync(ss, ds, map).synchronise(clean_orphans=True) # We now expect just one mapping for the new src task all_mappings = map.get_all_src_keys() assert len(all_mappings) == 1 assert map.get_dst_id(src_tasks[0].id)
def test_new_tasks_are_mapped(): src_tasks = [random_task()] dst_tasks = [] src = MockTaskService(src_tasks) dst = MockTaskService(dst_tasks) map = TaskMap() sync = TaskSync(src, dst, map) # preconditions assert len(map.get_all_src_keys()) == 0 sync.synchronise() assert len(map.get_all_src_keys()) == 1 assert src_tasks[0].id in map.get_all_src_keys() assert map.get_dst_id(src_tasks[0].id) == dst_tasks[0].id
def test_create_when_file_doesnt_exist(self): tmpfile = NamedTemporaryFile(suffix='.pickle') name = tmpfile.name tmpfile.close() tm = TaskMap(name) assert tm
def setup(self): self.tm = TaskMap() self.src = MockTask(_id='1') self.dst = MockTask(_id='a') self.missing = MockTask(_id='blah')
class TestTaskMap(object): def setup(self): self.tm = TaskMap() self.src = MockTask(_id='1') self.dst = MockTask(_id='a') self.missing = MockTask(_id='blah') def test_create_when_file_doesnt_exist(self): tmpfile = NamedTemporaryFile(suffix='.pickle') name = tmpfile.name tmpfile.close() tm = TaskMap(name) assert tm def test_persist_task_mapping(self): expected = TaskMap() tasks = [ MockTask(_id='aaa'), MockTask(_id='bbb'), MockTask(_id='ccc'), MockTask(_id='ddd') ] expected.map(tasks[0], tasks[1]) expected.map(tasks[2], tasks[3]) filename = NamedTemporaryFile(suffix='.tm') expected.persist(filename.name) actual = TaskMap(filename.name) assert actual.get_dst_id(tasks[0].id) == tasks[1].id assert actual.get_dst_id(tasks[2].id) == tasks[3].id assert actual.get_src_id(tasks[1].id) == tasks[0].id assert actual.get_src_id(tasks[3].id) == tasks[2].id def test_duplicate_src(self): s = MockTask('1') d = MockTask('a') dd = MockTask('aa') self.tm.map(s, d) with pytest.raises(KeyDuplicationError): self.tm.map(s, dd) def test_duplicate_dst(self): src2 = MockTask(_id='9') self.tm.map(self.src, self.dst) with pytest.raises(ValueDuplicationError): self.tm.map(src2, self.dst) def test_duplicate_src_dst(self): tasks = [MockTask(_id=i) for i in range(4)] self.tm.map(tasks[0], tasks[1]) self.tm.map(tasks[2], tasks[3]) with pytest.raises(KeyAndValueDuplicationError): # both src and dst are already mapped to something self.tm.map(tasks[0], tasks[3]) def test_valid_forward(self): self.tm.map(self.src, self.dst) assert self.tm.get_dst_id(self.src.id) == self.dst.id def test_invalid_forward(self): with pytest.raises(KeyError): self.tm.get_dst_id(self.missing) def test_valid_reverse(self): self.tm.map(self.src, self.dst) assert self.tm.get_src_id(self.dst.id) == self.src.id def test_invalid_reverse(self): with pytest.raises(KeyError): self.tm.get_src_id(self.missing.id) def test_valid_forward_try_get(self): self.tm.map(self.src, self.dst) assert self.tm.try_get_dst_id(self.src.id) == self.dst.id def test_invalid_forward_try_get(self): assert not self.tm.try_get_dst_id(self.missing.id) def test_valid_reverse_try_get(self): self.tm.map(self.src, self.dst) assert self.tm.try_get_src_id(self.dst.id) == self.src.id def test_invalid_reverse_try_get(self): assert not self.tm.try_get_src_id(self.missing.id) def test_get_all_src_keys(self): src_keys = (1, 2, 3, 4) src_tasks = [MockTask(_id=i) for i in src_keys] dst_tasks = [MockTask(_id=i + 20) for i in range(4)] map = TaskMap() for s, d in zip(src_tasks, dst_tasks): map.map(s, d) for actual in map.get_all_src_keys(): assert actual in src_keys def test_get_all_dst_keys(self): src_tasks = [MockTask(_id=i + 20) for i in range(4)] dst_keys = (1, 2, 3, 4) dst_tasks = [MockTask(_id=i) for i in dst_keys] map = TaskMap() for s, d in zip(src_tasks, dst_tasks): map.map(s, d) for actual in map.get_all_dst_keys(): assert actual in dst_keys def test_delete_mapping(self): self.tm.map(self.src, self.dst) self.tm.unmap(self.src.id) assert not self.tm.try_get_dst_id(self.src.id) assert not self.tm.try_get_src_id(self.dst.id)
class TestTaskMap(object): def setup(self): self.tm = TaskMap() self.src = MockTask(_id='1') self.dst = MockTask(_id='a') self.missing = MockTask(_id='blah') def test_create_when_file_doesnt_exist(self): tmpfile = NamedTemporaryFile(suffix='.pickle') name = tmpfile.name tmpfile.close() tm = TaskMap(name) assert tm def test_persist_task_mapping(self): expected = TaskMap() tasks = [ MockTask(_id='aaa'), MockTask(_id='bbb'), MockTask(_id='ccc'), MockTask(_id='ddd')] expected.map(tasks[0], tasks[1]) expected.map(tasks[2], tasks[3]) filename = NamedTemporaryFile(suffix='.tm') expected.persist(filename.name) actual = TaskMap(filename.name) assert actual.get_dst_id(tasks[0].id) == tasks[1].id assert actual.get_dst_id(tasks[2].id) == tasks[3].id assert actual.get_src_id(tasks[1].id) == tasks[0].id assert actual.get_src_id(tasks[3].id) == tasks[2].id def test_duplicate_src(self): s = MockTask('1') d = MockTask('a') dd = MockTask('aa') self.tm.map(s, d) with pytest.raises(KeyDuplicationError): self.tm.map(s, dd) def test_duplicate_dst(self): src2 = MockTask(_id='9') self.tm.map(self.src, self.dst) with pytest.raises(ValueDuplicationError): self.tm.map(src2, self.dst) def test_duplicate_src_dst(self): tasks = [MockTask(_id=i) for i in range(4)] self.tm.map(tasks[0], tasks[1]) self.tm.map(tasks[2], tasks[3]) with pytest.raises(KeyAndValueDuplicationError): # both src and dst are already mapped to something self.tm.map(tasks[0], tasks[3]) def test_valid_forward(self): self.tm.map(self.src, self.dst) assert self.tm.get_dst_id(self.src.id) == self.dst.id def test_invalid_forward(self): with pytest.raises(KeyError): self.tm.get_dst_id(self.missing) def test_valid_reverse(self): self.tm.map(self.src, self.dst) assert self.tm.get_src_id(self.dst.id) == self.src.id def test_invalid_reverse(self): with pytest.raises(KeyError): self.tm.get_src_id(self.missing.id) def test_valid_forward_try_get(self): self.tm.map(self.src, self.dst) assert self.tm.try_get_dst_id(self.src.id) == self.dst.id def test_invalid_forward_try_get(self): assert not self.tm.try_get_dst_id(self.missing.id) def test_valid_reverse_try_get(self): self.tm.map(self.src, self.dst) assert self.tm.try_get_src_id(self.dst.id) == self.src.id def test_invalid_reverse_try_get(self): assert not self.tm.try_get_src_id(self.missing.id) def test_get_all_src_keys(self): src_keys = (1,2,3,4) src_tasks = [MockTask(_id=i) for i in src_keys] dst_tasks = [MockTask(_id=i+20) for i in range(4)] map = TaskMap() for s,d in zip(src_tasks, dst_tasks): map.map(s,d) for actual in map.get_all_src_keys(): assert actual in src_keys def test_get_all_dst_keys(self): src_tasks = [MockTask(_id=i+20) for i in range(4)] dst_keys = (1,2,3,4) dst_tasks = [MockTask(_id=i) for i in dst_keys] map = TaskMap() for s,d in zip(src_tasks, dst_tasks): map.map(s,d) for actual in map.get_all_dst_keys(): assert actual in dst_keys def test_delete_mapping(self): self.tm.map(self.src, self.dst) self.tm.unmap(self.src.id) assert not self.tm.try_get_dst_id(self.src.id) assert not self.tm.try_get_src_id(self.dst.id)
def update(self): """ This update method will be called once on every update cycle, with the frequency determined by the value returned from `update_interval_minutes()`. If a plugin implements a single-shot function, then update should return `False`. Returns: bool: True if further updates are required; False if the plugin is finished and the application should shut down. """ # retrieve the boards to sync boards = self.__tc.list_boards(board_filter='open') sync_boards = [ b for b in boards if b.name in self.__boards] self.__ensure_labels_exist(sync_boards) # Build a list of sync lists by matching the sync # list names in each board sync_lists = [] done_lists = [] for b in sync_boards: for l in b.open_lists(): if l.name in self._config.trello_lists: sync_lists.append(l) elif l.name in self._config.trello_done_lists: done_lists.append(l) # some additional information on the source boards and lists message = 'Syncing the following lists' for l in sync_lists: message += '\n {0}.{1}'.format(l.board.name, l.name) message += '\nTreating cards in the following lists as completed' for l in done_lists: message += '\n {0}.{1}'.format(l.board.name, l.name) logging.getLogger(__name__).debug(message) # Load the task map from disk task_map = TaskMap(self.__task_map_file) # Create the services source_service = TrelloTaskService( self.__tc, sync_lists, done_lists, self.__boards) # synchronise sync = TaskSync( source_service, self.__habitica_task_service, task_map, last_sync=self.__data.last_sync, sync_description=self._config.trello_sync_description) stats = sync.synchronise(clean_orphans=False) self.__notify(stats) # Checkpoint the sync data self.__data.last_sync = sync.last_sync if not self.dry_run: task_map.persist(self.__task_map_file) self.__save_persistent_data() # return False if finished, and True to be updated again. return True