import 'package:clock/clock.dart'; import 'package:kdbx/kdbx.dart'; import 'package:kdbx/src/kdbx_xml.dart'; import 'package:kdbx/src/utils/print_utils.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; import 'package:xml/xml.dart'; import '../internal/test_utils.dart'; import '../kdbx_test.dart'; final _logger = Logger('kdbx_merge_test'); void main() { final testUtil = TestUtil(); var now = DateTime.fromMillisecondsSinceEpoch(0); final fakeClock = Clock(() => now); void proceedSeconds(int seconds) { now = now.add(Duration(seconds: seconds)); } setUp(() { now = DateTime.fromMillisecondsSinceEpoch(0); }); group('Simple merges', () { Future createSimpleFile() async { final file = testUtil.createEmptyFile(); _createEntry(file, file.body.rootGroup, 'test1', 'test1'); final subGroup = file.createGroup(parent: file.body.rootGroup, name: 'Sub Group'); _createEntry(file, subGroup, 'test2', 'test2'); proceedSeconds(10); return await testUtil.saveAndRead(file); } test('Noop merge', () async { final file = await createSimpleFile(); final file2 = await testUtil.saveAndRead(file); final merge = file.merge(file2); final set = Set.from(merge.merged.keys); expect(set, hasLength(4)); expect(merge.changes, isEmpty); }); test('Username change', () async { await withClock(fakeClock, () async { final file = await createSimpleFile(); final fileMod = await testUtil.saveAndRead(file); fileMod.body.rootGroup.entries.first .setString(KdbxKeyCommon.USER_NAME, PlainValue('changed.')); _logger.info('mod date: ' + fileMod.body.rootGroup.entries.first.times.lastModificationTime .get() .toString()); final file2 = await testUtil.saveAndRead(fileMod); _logger.info('\n\n\nstarting merge.\n'); final merge = file.merge(file2); expect(file.body.rootGroup.entries.first.history, hasLength(1)); final set = Set.from(merge.merged.keys); expect(set, hasLength(4)); expect(merge.changes, hasLength(1)); }); }); test( 'Change Group Name', () async => await withClock(fakeClock, () async { final file = await createSimpleFile(); final fileMod = await testUtil.saveAndRead(file); fileMod.body.rootGroup.groups.first.name.set('Sub Group New Name.'); final file2 = await testUtil.saveAndRead(fileMod); final merge = file.merge(file2); final set = Set.from(merge.merged.keys); expect(set, hasLength(4)); expect(merge.changes, hasLength(1)); }), ); test( 'Move Entry to recycle bin', () async => await withClock(fakeClock, () async { final file = await createSimpleFile(); final fileMod = await testUtil.saveAndRead(file); expect(fileMod.recycleBin, isNull); fileMod.deleteEntry(fileMod.body.rootGroup.entries.first); expect(fileMod.recycleBin, isNotNull); final file2 = await testUtil.saveAndRead(fileMod); final merge = file.merge(file2); _logger.info('Merged file:\n' '${KdbxPrintUtils().catGroupToString(file.body.rootGroup)}'); final set = Set.from(merge.merged.keys); expect(set, hasLength(5)); expect( Set.from(merge.changes.map((e) => e.object)), hasLength(2)); }), ); test( 'permanently delete an entry', () async => await withClock(fakeClock, () async { final file = await createSimpleFile(); final objCount = file.body.rootGroup.getAllGroupsAndEntries().length; final fileMod = await testUtil.saveAndRead(file); final entryDelete = fileMod.body.rootGroup.entries.first; fileMod.deletePermanently(entryDelete); expect(fileMod.body.rootGroup.getAllGroupsAndEntries(), hasLength(objCount - 1)); final file2 = await testUtil.saveAndRead(fileMod); final merge = file.merge(file2); _logger.info('Merged file:\n' '${KdbxPrintUtils().catGroupToString(file.body.rootGroup)}'); expect(merge.deletedObjects, hasLength(1)); expect( file.body.rootGroup.getAllGroupsAndEntries().length, objCount - 1); final xml = file.body.generateXml(FakeProtectedSaltGenerator()); final deleted = xml.findAllElements(KdbxXml.NODE_DELETED_OBJECT); expect(deleted, hasLength(1)); expect( deleted.first.findAllElements(KdbxXml.NODE_UUID).map((e) => e.text), [entryDelete.uuid.uuid]); }), ); }); } KdbxEntry _createEntry( KdbxFile file, KdbxGroup group, String username, String password) { final entry = KdbxEntry.create(file, group); group.addEntry(entry); entry.setString(KdbxKeyCommon.USER_NAME, PlainValue(username)); entry.setString(KdbxKeyCommon.PASSWORD, ProtectedValue.fromString(password)); return entry; }