TestEntrySearcher.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. /*
  2. * Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 2 or (at your option)
  7. * version 3 of the License.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include "TestEntrySearcher.h"
  18. #include "core/Group.h"
  19. #include "core/Tools.h"
  20. #include <QTest>
  21. QTEST_GUILESS_MAIN(TestEntrySearcher)
  22. void TestEntrySearcher::init()
  23. {
  24. m_rootGroup = new Group();
  25. m_entrySearcher = EntrySearcher();
  26. }
  27. void TestEntrySearcher::cleanup()
  28. {
  29. delete m_rootGroup;
  30. }
  31. void TestEntrySearcher::testSearch()
  32. {
  33. /**
  34. * Root
  35. * - group1 (search disabled)
  36. * - group11
  37. * - group2
  38. * - group21
  39. * - group211
  40. * - group2111
  41. */
  42. auto group1 = new Group();
  43. auto group2 = new Group();
  44. auto group3 = new Group();
  45. group1->setParent(m_rootGroup);
  46. group2->setParent(m_rootGroup);
  47. group3->setParent(m_rootGroup);
  48. auto group11 = new Group();
  49. group11->setParent(group1);
  50. auto group21 = new Group();
  51. auto group211 = new Group();
  52. auto group2111 = new Group();
  53. group21->setParent(group2);
  54. group211->setParent(group21);
  55. group2111->setParent(group211);
  56. group1->setSearchingEnabled(Group::Disable);
  57. auto eRoot = new Entry();
  58. eRoot->setTitle("test search term test");
  59. eRoot->setGroup(m_rootGroup);
  60. auto eRoot2 = new Entry();
  61. eRoot2->setNotes("test term test");
  62. eRoot2->setGroup(m_rootGroup);
  63. // Searching is disabled for these
  64. auto e1 = new Entry();
  65. e1->setUsername("test search term test");
  66. e1->setGroup(group1);
  67. auto e11 = new Entry();
  68. e11->setNotes("test search term test");
  69. e11->setGroup(group11);
  70. // End searching disabled
  71. auto e2111 = new Entry();
  72. e2111->setTitle("test search term test");
  73. e2111->setGroup(group2111);
  74. auto e2111b = new Entry();
  75. e2111b->setNotes("test search test");
  76. e2111b->setUsername("user123");
  77. e2111b->setPassword("testpass");
  78. e2111b->setGroup(group2111);
  79. auto e3 = new Entry();
  80. e3->setUrl("test search term test");
  81. e3->setGroup(group3);
  82. auto e3b = new Entry();
  83. e3b->setTitle("test search test 123");
  84. e3b->setUsername("test@email.com");
  85. e3b->setPassword("realpass");
  86. e3b->setGroup(group3);
  87. // Simple search term testing
  88. m_searchResult = m_entrySearcher.search("search", m_rootGroup);
  89. QCOMPARE(m_searchResult.count(), 5);
  90. m_searchResult = m_entrySearcher.search("search term", m_rootGroup);
  91. QCOMPARE(m_searchResult.count(), 3);
  92. m_searchResult = m_entrySearcher.search("123", m_rootGroup);
  93. QCOMPARE(m_searchResult.count(), 2);
  94. m_searchResult = m_entrySearcher.search("search term", group211);
  95. QCOMPARE(m_searchResult.count(), 1);
  96. // Test advanced search terms
  97. m_searchResult = m_entrySearcher.search("title:123", m_rootGroup);
  98. QCOMPARE(m_searchResult.count(), 1);
  99. m_searchResult = m_entrySearcher.search("t:123", m_rootGroup);
  100. QCOMPARE(m_searchResult.count(), 1);
  101. m_searchResult = m_entrySearcher.search("password:testpass", m_rootGroup);
  102. QCOMPARE(m_searchResult.count(), 1);
  103. m_searchResult = m_entrySearcher.search("pw:testpass", m_rootGroup);
  104. QCOMPARE(m_searchResult.count(), 1);
  105. m_searchResult = m_entrySearcher.search("!user:email.com", m_rootGroup);
  106. QCOMPARE(m_searchResult.count(), 5);
  107. m_searchResult = m_entrySearcher.search("!u:email.com", m_rootGroup);
  108. QCOMPARE(m_searchResult.count(), 5);
  109. m_searchResult = m_entrySearcher.search("*user:\".*@.*\\.com\"", m_rootGroup);
  110. QCOMPARE(m_searchResult.count(), 1);
  111. m_searchResult = m_entrySearcher.search("+user:email", m_rootGroup);
  112. QCOMPARE(m_searchResult.count(), 0);
  113. // Terms are logical AND together
  114. m_searchResult = m_entrySearcher.search("password:pass user:user", m_rootGroup);
  115. QCOMPARE(m_searchResult.count(), 1);
  116. // Parent group has search disabled
  117. m_searchResult = m_entrySearcher.search("search term", group11);
  118. QCOMPARE(m_searchResult.count(), 0);
  119. }
  120. void TestEntrySearcher::testAndConcatenationInSearch()
  121. {
  122. auto entry = new Entry();
  123. entry->setNotes("abc def ghi");
  124. entry->setTitle("jkl");
  125. entry->setGroup(m_rootGroup);
  126. m_searchResult = m_entrySearcher.search("", m_rootGroup);
  127. QCOMPARE(m_searchResult.count(), 1);
  128. m_searchResult = m_entrySearcher.search("def", m_rootGroup);
  129. QCOMPARE(m_searchResult.count(), 1);
  130. m_searchResult = m_entrySearcher.search(" abc ghi ", m_rootGroup);
  131. QCOMPARE(m_searchResult.count(), 1);
  132. m_searchResult = m_entrySearcher.search("ghi ef", m_rootGroup);
  133. QCOMPARE(m_searchResult.count(), 1);
  134. m_searchResult = m_entrySearcher.search("abc ef xyz", m_rootGroup);
  135. QCOMPARE(m_searchResult.count(), 0);
  136. m_searchResult = m_entrySearcher.search("abc kl", m_rootGroup);
  137. QCOMPARE(m_searchResult.count(), 1);
  138. }
  139. void TestEntrySearcher::testAllAttributesAreSearched()
  140. {
  141. auto entry = new Entry();
  142. entry->setGroup(m_rootGroup);
  143. entry->setTitle("testTitle");
  144. entry->setUsername("testUsername");
  145. entry->setUrl("testUrl");
  146. entry->setNotes("testNote");
  147. // Default is to AND all terms together
  148. m_searchResult = m_entrySearcher.search("testTitle testUsername testUrl testNote", m_rootGroup);
  149. QCOMPARE(m_searchResult.count(), 1);
  150. }
  151. void TestEntrySearcher::testSearchTermParser()
  152. {
  153. // Test standard search terms
  154. m_entrySearcher.parseSearchTerms("-test \"quoted \\\"string\\\"\" user:user pass:\"test me\" noquote ");
  155. auto terms = m_entrySearcher.m_searchTerms;
  156. QCOMPARE(terms.length(), 5);
  157. QCOMPARE(terms[0].field, EntrySearcher::Field::Undefined);
  158. QCOMPARE(terms[0].word, QString("test"));
  159. QCOMPARE(terms[0].exclude, true);
  160. QCOMPARE(terms[1].field, EntrySearcher::Field::Undefined);
  161. QCOMPARE(terms[1].word, QString("quoted \"string\""));
  162. QCOMPARE(terms[1].exclude, false);
  163. QCOMPARE(terms[2].field, EntrySearcher::Field::Username);
  164. QCOMPARE(terms[2].word, QString("user"));
  165. QCOMPARE(terms[3].field, EntrySearcher::Field::Password);
  166. QCOMPARE(terms[3].word, QString("test me"));
  167. QCOMPARE(terms[4].field, EntrySearcher::Field::Undefined);
  168. QCOMPARE(terms[4].word, QString("noquote"));
  169. // Test wildcard and regex search terms
  170. m_entrySearcher.parseSearchTerms("+url:*.google.com *user:\\d+\\w{2}");
  171. terms = m_entrySearcher.m_searchTerms;
  172. QCOMPARE(terms.length(), 2);
  173. QCOMPARE(terms[0].field, EntrySearcher::Field::Url);
  174. QCOMPARE(terms[0].regex.pattern(), QString("^(?:.*\\.google\\.com)$"));
  175. QCOMPARE(terms[1].field, EntrySearcher::Field::Username);
  176. QCOMPARE(terms[1].regex.pattern(), QString("\\d+\\w{2}"));
  177. // Test custom attribute search terms
  178. m_entrySearcher.parseSearchTerms("+_abc:efg _def:\"ddd\"");
  179. terms = m_entrySearcher.m_searchTerms;
  180. QCOMPARE(terms.length(), 2);
  181. QCOMPARE(terms[0].field, EntrySearcher::Field::AttributeValue);
  182. QCOMPARE(terms[0].word, QString("abc"));
  183. QCOMPARE(terms[0].regex.pattern(), QString("^(?:efg)$"));
  184. QCOMPARE(terms[1].field, EntrySearcher::Field::AttributeValue);
  185. QCOMPARE(terms[1].word, QString("def"));
  186. QCOMPARE(terms[1].regex.pattern(), QString("ddd"));
  187. }
  188. void TestEntrySearcher::testCustomAttributesAreSearched()
  189. {
  190. QScopedPointer<Entry> e1(new Entry());
  191. e1->setGroup(m_rootGroup);
  192. e1->attributes()->set("testAttribute", "testE1");
  193. e1->attributes()->set("testProtected", "testP", true);
  194. QScopedPointer<Entry> e2(new Entry());
  195. e2->setGroup(m_rootGroup);
  196. e2->attributes()->set("testAttribute", "testE2");
  197. e2->attributes()->set("testProtected", "testP2", true);
  198. // search for custom entries
  199. m_searchResult = m_entrySearcher.search("_testAttribute:test", m_rootGroup);
  200. QCOMPARE(m_searchResult.count(), 2);
  201. // protected attributes are ignored
  202. m_entrySearcher = EntrySearcher(false, true);
  203. m_searchResult = m_entrySearcher.search("_testAttribute:test _testProtected:testP2", m_rootGroup);
  204. QCOMPARE(m_searchResult.count(), 2);
  205. }
  206. void TestEntrySearcher::testGroup()
  207. {
  208. /**
  209. * Root
  210. * - group1 (1 entry)
  211. * - subgroup1 (2 entries)
  212. * - group2
  213. * - subgroup2 (1 entry)
  214. */
  215. auto group1 = new Group();
  216. auto group2 = new Group();
  217. group1->setParent(m_rootGroup);
  218. group1->setName("group1");
  219. group2->setParent(m_rootGroup);
  220. group2->setName("group2");
  221. auto subgroup1 = new Group();
  222. subgroup1->setName("subgroup1");
  223. subgroup1->setParent(group1);
  224. auto subgroup2 = new Group();
  225. subgroup2->setName("subgroup2");
  226. subgroup2->setParent(group2);
  227. auto eGroup1 = new Entry();
  228. eGroup1->setTitle("Entry Group 1");
  229. eGroup1->setGroup(group1);
  230. auto eSub1 = new Entry();
  231. eSub1->setTitle("test search term test");
  232. eSub1->setGroup(subgroup1);
  233. auto eSub2 = new Entry();
  234. eSub2->setNotes("test test");
  235. eSub2->setGroup(subgroup1);
  236. auto eSub3 = new Entry();
  237. eSub3->setNotes("test term test");
  238. eSub3->setGroup(subgroup2);
  239. m_searchResult = m_entrySearcher.search("group:subgroup", m_rootGroup);
  240. QCOMPARE(m_searchResult.count(), 3);
  241. m_searchResult = m_entrySearcher.search("g:subgroup1", m_rootGroup);
  242. QCOMPARE(m_searchResult.count(), 2);
  243. m_searchResult = m_entrySearcher.search("g:subgroup1 search", m_rootGroup);
  244. QCOMPARE(m_searchResult.count(), 1);
  245. m_searchResult = m_entrySearcher.search("g:*1/sub*1", m_rootGroup);
  246. QCOMPARE(m_searchResult.count(), 2);
  247. m_searchResult = m_entrySearcher.search("g:/group1 search", m_rootGroup);
  248. QCOMPARE(m_searchResult.count(), 1);
  249. }
  250. void TestEntrySearcher::testSkipProtected()
  251. {
  252. QScopedPointer<Entry> e1(new Entry());
  253. e1->setGroup(m_rootGroup);
  254. e1->attributes()->set("testAttribute", "testE1");
  255. e1->attributes()->set("testProtected", "apple", true);
  256. QScopedPointer<Entry> e2(new Entry());
  257. e2->setGroup(m_rootGroup);
  258. e2->attributes()->set("testAttribute", "testE2");
  259. e2->attributes()->set("testProtected", "banana", true);
  260. const QList<Entry*> expectE1{e1.data()};
  261. const QList<Entry*> expectE2{e2.data()};
  262. const QList<Entry*> expectBoth{e1.data(), e2.data()};
  263. // when not skipping protected, empty term matches everything
  264. m_searchResult = m_entrySearcher.search("", m_rootGroup);
  265. QCOMPARE(m_searchResult, expectBoth);
  266. // now test the searcher with skipProtected = true
  267. m_entrySearcher = EntrySearcher(false, true);
  268. // when skipping protected, empty term matches nothing
  269. m_searchResult = m_entrySearcher.search("", m_rootGroup);
  270. QCOMPARE(m_searchResult, {});
  271. // having a protected entry in terms should not affect the results in anyways
  272. m_searchResult = m_entrySearcher.search("_testProtected:apple", m_rootGroup);
  273. QCOMPARE(m_searchResult, {});
  274. m_searchResult = m_entrySearcher.search("_testProtected:apple _testAttribute:testE2", m_rootGroup);
  275. QCOMPARE(m_searchResult, expectE2);
  276. m_searchResult = m_entrySearcher.search("_testProtected:apple _testAttribute:testE1", m_rootGroup);
  277. QCOMPARE(m_searchResult, expectE1);
  278. m_searchResult =
  279. m_entrySearcher.search("_testProtected:apple _testAttribute:testE1 _testAttribute:testE2", m_rootGroup);
  280. QCOMPARE(m_searchResult, {});
  281. // also move the protected term around to exercise the short-circuit logic
  282. m_searchResult = m_entrySearcher.search("_testAttribute:testE2 _testProtected:apple", m_rootGroup);
  283. QCOMPARE(m_searchResult, expectE2);
  284. m_searchResult = m_entrySearcher.search("_testAttribute:testE1 _testProtected:apple", m_rootGroup);
  285. QCOMPARE(m_searchResult, expectE1);
  286. m_searchResult =
  287. m_entrySearcher.search("_testAttribute:testE1 _testProtected:apple _testAttribute:testE2", m_rootGroup);
  288. QCOMPARE(m_searchResult, {});
  289. }
  290. void TestEntrySearcher::testUUIDSearch()
  291. {
  292. auto entry1 = new Entry();
  293. entry1->setGroup(m_rootGroup);
  294. entry1->setTitle("testTitle");
  295. auto uuid1 = QUuid::createUuid();
  296. entry1->setUuid(uuid1);
  297. auto entry2 = new Entry();
  298. entry2->setGroup(m_rootGroup);
  299. entry2->setTitle("testTitle2");
  300. auto uuid2 = QUuid::createUuid();
  301. entry2->setUuid(uuid2);
  302. m_searchResult = m_entrySearcher.search("uuid:", m_rootGroup);
  303. QCOMPARE(m_searchResult.count(), 2);
  304. m_searchResult = m_entrySearcher.search("uuid:" + Tools::uuidToHex(uuid1), m_rootGroup);
  305. QCOMPARE(m_searchResult.count(), 1);
  306. }