TestDatabase.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * Copyright (C) 2017 Vladimir Svyatski <v.unreal@gmail.com>
  3. * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 2 or (at your option)
  8. * version 3 of the License.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "TestDatabase.h"
  19. #include <QRegularExpression>
  20. #include <QSignalSpy>
  21. #include <QTest>
  22. #include "config-keepassx-tests.h"
  23. #include "core/Group.h"
  24. #include "core/Metadata.h"
  25. #include "core/Tools.h"
  26. #include "crypto/Crypto.h"
  27. #include "format/KeePass2Writer.h"
  28. #include "util/TemporaryFile.h"
  29. #ifdef Q_OS_WIN
  30. #include <QFileInfo>
  31. #include <Windows.h>
  32. #endif
  33. QTEST_GUILESS_MAIN(TestDatabase)
  34. static QString dbFileName = QStringLiteral(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.kdbx");
  35. void TestDatabase::initTestCase()
  36. {
  37. QVERIFY(Crypto::init());
  38. }
  39. void TestDatabase::testOpen()
  40. {
  41. auto db = QSharedPointer<Database>::create();
  42. QVERIFY(!db->isInitialized());
  43. QVERIFY(!db->isModified());
  44. auto key = QSharedPointer<CompositeKey>::create();
  45. key->addKey(QSharedPointer<PasswordKey>::create("a"));
  46. bool ok = db->open(dbFileName, key);
  47. QVERIFY(ok);
  48. QVERIFY(db->isInitialized());
  49. QVERIFY(!db->isModified());
  50. db->metadata()->setName("test");
  51. QVERIFY(db->isModified());
  52. }
  53. void TestDatabase::testSave()
  54. {
  55. TemporaryFile tempFile;
  56. QVERIFY(tempFile.copyFromFile(dbFileName));
  57. auto db = QSharedPointer<Database>::create();
  58. auto key = QSharedPointer<CompositeKey>::create();
  59. key->addKey(QSharedPointer<PasswordKey>::create("a"));
  60. QString error;
  61. bool ok = db->open(tempFile.fileName(), key, &error);
  62. QVERIFY(ok);
  63. // Test safe saves
  64. db->metadata()->setName("test");
  65. QVERIFY(db->isModified());
  66. QVERIFY2(db->save(Database::Atomic, {}, &error), error.toLatin1());
  67. QVERIFY(!db->isModified());
  68. // Test temp-file saves
  69. db->metadata()->setName("test2");
  70. QVERIFY2(db->save(Database::TempFile, QString(), &error), error.toLatin1());
  71. QVERIFY(!db->isModified());
  72. // Test direct-write saves
  73. db->metadata()->setName("test3");
  74. QVERIFY2(db->save(Database::DirectWrite, QString(), &error), error.toLatin1());
  75. QVERIFY(!db->isModified());
  76. // Test save backups
  77. TemporaryFile backupFile;
  78. auto backupFilePath = backupFile.fileName();
  79. db->metadata()->setName("test4");
  80. QVERIFY2(db->save(Database::Atomic, backupFilePath, &error), error.toLatin1());
  81. QVERIFY(!db->isModified());
  82. QVERIFY(QFile::exists(backupFilePath));
  83. QFile::remove(backupFilePath);
  84. QVERIFY(!QFile::exists(backupFilePath));
  85. }
  86. void TestDatabase::testSaveAs()
  87. {
  88. TemporaryFile tempFile;
  89. QVERIFY(tempFile.copyFromFile(dbFileName));
  90. auto db = QSharedPointer<Database>::create();
  91. auto key = QSharedPointer<CompositeKey>::create();
  92. key->addKey(QSharedPointer<PasswordKey>::create("a"));
  93. QString error;
  94. QVERIFY(db->open(tempFile.fileName(), key, &error));
  95. // Happy path case when try to save as new DB.
  96. QSignalSpy spyFilePathChanged(db.data(), SIGNAL(filePathChanged(const QString&, const QString&)));
  97. QString newDbFileName = QStringLiteral(KEEPASSX_TEST_DATA_DIR).append("/SaveAsNewDatabase.kdbx");
  98. QVERIFY2(db->saveAs(newDbFileName, Database::Atomic, QString(), &error), error.toLatin1());
  99. QVERIFY(!db->isModified());
  100. QCOMPARE(spyFilePathChanged.count(), 1);
  101. QVERIFY(QFile::exists(newDbFileName));
  102. #ifdef Q_OS_WIN
  103. QVERIFY(!QFileInfo(newDbFileName).isHidden());
  104. SetFileAttributes(newDbFileName.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN);
  105. QVERIFY2(db->saveAs(newDbFileName, Database::Atomic, QString(), &error), error.toLatin1());
  106. QVERIFY(QFileInfo(newDbFileName).isHidden());
  107. #endif
  108. QFile::remove(newDbFileName);
  109. QVERIFY(!QFile::exists(newDbFileName));
  110. // Negative case when try to save not initialized DB.
  111. db->releaseData();
  112. QVERIFY2(!db->saveAs(newDbFileName, Database::Atomic, QString(), &error), error.toLatin1());
  113. QCOMPARE(error, QString("Could not save, database has not been initialized!"));
  114. }
  115. void TestDatabase::testSignals()
  116. {
  117. TemporaryFile tempFile;
  118. QVERIFY(tempFile.copyFromFile(dbFileName));
  119. auto db = QSharedPointer<Database>::create();
  120. auto key = QSharedPointer<CompositeKey>::create();
  121. key->addKey(QSharedPointer<PasswordKey>::create("a"));
  122. QSignalSpy spyFilePathChanged(db.data(), SIGNAL(filePathChanged(const QString&, const QString&)));
  123. QString error;
  124. bool ok = db->open(tempFile.fileName(), key, &error);
  125. QVERIFY(ok);
  126. QCOMPARE(spyFilePathChanged.count(), 1);
  127. QSignalSpy spyModified(db.data(), SIGNAL(modified()));
  128. db->metadata()->setName("test1");
  129. QTRY_COMPARE(spyModified.count(), 1);
  130. QSignalSpy spySaved(db.data(), SIGNAL(databaseSaved()));
  131. QVERIFY(db->save(Database::Atomic, {}, &error));
  132. QCOMPARE(spySaved.count(), 1);
  133. // Short delay to allow file system settling to reduce test failures
  134. Tools::wait(100);
  135. QSignalSpy spyFileChanged(db.data(), SIGNAL(databaseFileChanged()));
  136. QVERIFY(tempFile.copyFromFile(dbFileName));
  137. QTRY_COMPARE(spyFileChanged.count(), 1);
  138. QTRY_VERIFY(!db->isModified());
  139. db->metadata()->setName("test2");
  140. QTRY_VERIFY(db->isModified());
  141. QSignalSpy spyDiscarded(db.data(), SIGNAL(databaseDiscarded()));
  142. QVERIFY(db->open(tempFile.fileName(), key, &error));
  143. QCOMPARE(spyDiscarded.count(), 1);
  144. }
  145. void TestDatabase::testEmptyRecycleBinOnDisabled()
  146. {
  147. QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinDisabled.kdbx");
  148. auto key = QSharedPointer<CompositeKey>::create();
  149. key->addKey(QSharedPointer<PasswordKey>::create("123"));
  150. auto db = QSharedPointer<Database>::create();
  151. QVERIFY(db->open(filename, key, nullptr));
  152. QSignalSpy spyModified(db.data(), SIGNAL(modified()));
  153. db->emptyRecycleBin();
  154. // The database must be unmodified in this test after emptying the recycle bin.
  155. QTRY_COMPARE(spyModified.count(), 0);
  156. }
  157. void TestDatabase::testEmptyRecycleBinOnNotCreated()
  158. {
  159. QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinNotYetCreated.kdbx");
  160. auto key = QSharedPointer<CompositeKey>::create();
  161. key->addKey(QSharedPointer<PasswordKey>::create("123"));
  162. auto db = QSharedPointer<Database>::create();
  163. QVERIFY(db->open(filename, key, nullptr));
  164. QSignalSpy spyModified(db.data(), SIGNAL(modified()));
  165. db->emptyRecycleBin();
  166. // The database must be unmodified in this test after emptying the recycle bin.
  167. QTRY_COMPARE(spyModified.count(), 0);
  168. }
  169. void TestDatabase::testEmptyRecycleBinOnEmpty()
  170. {
  171. QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinEmpty.kdbx");
  172. auto key = QSharedPointer<CompositeKey>::create();
  173. key->addKey(QSharedPointer<PasswordKey>::create("123"));
  174. auto db = QSharedPointer<Database>::create();
  175. QVERIFY(db->open(filename, key, nullptr));
  176. QSignalSpy spyModified(db.data(), SIGNAL(modified()));
  177. db->emptyRecycleBin();
  178. // The database must be unmodified in this test after emptying the recycle bin.
  179. QTRY_COMPARE(spyModified.count(), 0);
  180. }
  181. void TestDatabase::testEmptyRecycleBinWithHierarchicalData()
  182. {
  183. QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/RecycleBinWithData.kdbx");
  184. auto key = QSharedPointer<CompositeKey>::create();
  185. key->addKey(QSharedPointer<PasswordKey>::create("123"));
  186. auto db = QSharedPointer<Database>::create();
  187. QVERIFY(db->open(filename, key, nullptr));
  188. QFile originalFile(filename);
  189. qint64 initialSize = originalFile.size();
  190. db->emptyRecycleBin();
  191. QVERIFY(db->metadata()->recycleBin());
  192. QVERIFY(db->metadata()->recycleBin()->entries().empty());
  193. QVERIFY(db->metadata()->recycleBin()->children().empty());
  194. QTemporaryFile afterCleanup;
  195. afterCleanup.open();
  196. KeePass2Writer writer;
  197. writer.writeDatabase(&afterCleanup, db.data());
  198. QVERIFY(afterCleanup.size() < initialSize);
  199. }
  200. void TestDatabase::testCustomIcons()
  201. {
  202. Database db;
  203. QUuid uuid1 = QUuid::createUuid();
  204. QByteArray icon1("icon 1");
  205. Q_ASSERT(!icon1.isNull());
  206. db.metadata()->addCustomIcon(uuid1, icon1);
  207. Metadata::CustomIconData iconData = db.metadata()->customIcon(uuid1);
  208. QCOMPARE(iconData.data, icon1);
  209. QVERIFY(iconData.name.isNull());
  210. QVERIFY(iconData.lastModified.isNull());
  211. QUuid uuid2 = QUuid::createUuid();
  212. QByteArray icon2("icon 2");
  213. QDateTime date = QDateTime::currentDateTimeUtc();
  214. db.metadata()->addCustomIcon(uuid2, icon2, "Test", date);
  215. iconData = db.metadata()->customIcon(uuid2);
  216. QCOMPARE(iconData.data, icon2);
  217. QCOMPARE(iconData.name, QString("Test"));
  218. QCOMPARE(iconData.lastModified, date);
  219. }