Merge lp:~stolowski/unity-scope-click/departments-db into lp:unity-scope-click/devel
- departments-db
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Alejandro J. Cura |
Approved revision: | 316 |
Merged at revision: | 328 |
Proposed branch: | lp:~stolowski/unity-scope-click/departments-db |
Merge into: | lp:unity-scope-click/devel |
Diff against target: |
745 lines (+651/-1) 7 files modified
debian/changelog (+6/-0) debian/control (+1/-0) libclickscope/click/CMakeLists.txt (+4/-0) libclickscope/click/departments-db.cpp (+311/-0) libclickscope/click/departments-db.h (+99/-0) libclickscope/tests/CMakeLists.txt (+4/-1) libclickscope/tests/test_departments-db.cpp (+226/-0) |
To merge this branch: | bzr merge lp:~stolowski/unity-scope-click/departments-db |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alejandro J. Cura (community) | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Review via email: mp+225322@code.launchpad.net |
Commit message
Provide DepartmentsDb class used for storing and retrieving package - department mapping.
Description of the change
Provide DepartmentsDb class used for storing and retrieving package - department mapping, as well as department hierarchy for installed apps. There will be separate MPs that make use of it.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 314. By Paweł Stołowski
-
Updated changelog.
- 315. By Paweł Stołowski
-
Merged devel
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:315
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 316. By Paweł Stołowski
-
Merged devel
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:316
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alejandro J. Cura (alecu) wrote : | # |
Small comment inline
Paweł Stołowski (stolowski) wrote : | # |
Replied inline.
Alejandro J. Cura (alecu) wrote : | # |
btw, I like the testing done in this branch, +2!
Preview Diff
1 | === modified file 'debian/changelog' |
2 | --- debian/changelog 2014-07-07 15:15:08 +0000 |
3 | +++ debian/changelog 2014-07-10 07:20:52 +0000 |
4 | @@ -28,6 +28,12 @@ |
5 | |
6 | -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 25 Jun 2014 13:25:34 +0000 |
7 | |
8 | +unity-scope-click (0.1+14.10.20140618.1-0ubuntu2) UNRELEASED; urgency=medium |
9 | + |
10 | + * Introduced explicit sqlite3 >= 3.8.5 runtime dependency to ensure support for recursive SQL. |
11 | + |
12 | + -- Pawel Stolowski <pawel.stolowski@ubuntu.com> Wed, 02 Jul 2014 15:20:16 +0200 |
13 | + |
14 | unity-scope-click (0.1+14.10.20140618.1-0ubuntu1) utopic; urgency=low |
15 | |
16 | [ Pawel Stolowski ] |
17 | |
18 | === modified file 'debian/control' |
19 | --- debian/control 2014-07-08 20:32:51 +0000 |
20 | +++ debian/control 2014-07-10 07:20:52 +0000 |
21 | @@ -30,6 +30,7 @@ |
22 | ubuntu-app-launch-tools, |
23 | ubuntu-download-manager, |
24 | ubuntu-sdk-libs, |
25 | + sqlite3 (>= 3.8.5), |
26 | ${misc:Depends}, |
27 | ${shlibs:Depends}, |
28 | Breaks: unity (<< 7.0), |
29 | |
30 | === modified file 'libclickscope/click/CMakeLists.txt' |
31 | --- libclickscope/click/CMakeLists.txt 2014-07-03 13:12:08 +0000 |
32 | +++ libclickscope/click/CMakeLists.txt 2014-07-10 07:20:52 +0000 |
33 | @@ -1,6 +1,7 @@ |
34 | SET (CMAKE_INCLUDE_CURRENT_DIR ON) |
35 | SET (CMAKE_AUTOMOC ON) |
36 | find_package (Qt5Core REQUIRED) |
37 | +find_package (Qt5Sql REQUIRED) |
38 | pkg_check_modules(JSON_CPP REQUIRED jsoncpp) |
39 | pkg_check_modules(GSETTINGS_QT REQUIRED gsettings-qt) |
40 | |
41 | @@ -16,6 +17,7 @@ |
42 | download-manager.cpp |
43 | department-lookup.cpp |
44 | departments.cpp |
45 | + departments-db.cpp |
46 | highlights.cpp |
47 | index.cpp |
48 | interface.cpp |
49 | @@ -32,6 +34,8 @@ |
50 | webclient.cpp |
51 | ) |
52 | |
53 | +qt5_use_modules(${SCOPE_LIB_NAME} Sql) |
54 | + |
55 | include_directories( |
56 | ${JSON_CPP_INCLUDE_DIRS} |
57 | ${GSETTINGS_QT_INCLUDE_DIRS} |
58 | |
59 | === added file 'libclickscope/click/departments-db.cpp' |
60 | --- libclickscope/click/departments-db.cpp 1970-01-01 00:00:00 +0000 |
61 | +++ libclickscope/click/departments-db.cpp 2014-07-10 07:20:52 +0000 |
62 | @@ -0,0 +1,311 @@ |
63 | +/* |
64 | + * Copyright (C) 2014 Canonical Ltd. |
65 | + * |
66 | + * This program is free software: you can redistribute it and/or modify it |
67 | + * under the terms of the GNU General Public License version 3, as published |
68 | + * by the Free Software Foundation. |
69 | + * |
70 | + * This program is distributed in the hope that it will be useful, but |
71 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
72 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
73 | + * PURPOSE. See the GNU General Public License for more details. |
74 | + * |
75 | + * You should have received a copy of the GNU General Public License along |
76 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
77 | + * |
78 | + * In addition, as a special exception, the copyright holders give |
79 | + * permission to link the code of portions of this program with the |
80 | + * OpenSSL library under certain conditions as described in each |
81 | + * individual source file, and distribute linked combinations |
82 | + * including the two. |
83 | + * You must obey the GNU General Public License in all respects |
84 | + * for all of the code used other than OpenSSL. If you modify |
85 | + * file(s) with this exception, you may extend this exception to your |
86 | + * version of the file(s), but you are not obligated to do so. If you |
87 | + * do not wish to do so, delete this exception statement from your |
88 | + * version. If you delete this exception statement from all source |
89 | + * files in the program, then also delete it here. |
90 | + */ |
91 | + |
92 | +#include "departments-db.h" |
93 | +#include <stdexcept> |
94 | +#include <iostream> |
95 | +#include <QSqlError> |
96 | +#include <QSqlRecord> |
97 | +#include <QVariant> |
98 | +#include <QStandardPaths> |
99 | +#include <QDir> |
100 | + |
101 | +namespace click |
102 | +{ |
103 | + |
104 | +std::unique_ptr<click::DepartmentsDb> DepartmentsDb::create_db() |
105 | +{ |
106 | + auto const path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); |
107 | + if (!path.isEmpty()) |
108 | + { |
109 | + QDir("/").mkpath(path); |
110 | + const std::string dbpath = path.toStdString() + "/clickscope/click-departments.db"; |
111 | + return std::unique_ptr<DepartmentsDb>(new DepartmentsDb(dbpath)); |
112 | + } |
113 | + throw std::runtime_error("Cannot determine cache directory"); |
114 | +} |
115 | + |
116 | +DepartmentsDb::DepartmentsDb(const std::string& name) |
117 | +{ |
118 | + init_db(name); |
119 | + |
120 | + delete_pkgmap_query_.reset(new QSqlQuery(db_)); |
121 | + insert_pkgmap_query_.reset(new QSqlQuery(db_)); |
122 | + insert_dept_id_query_.reset(new QSqlQuery(db_)); |
123 | + insert_dept_name_query_.reset(new QSqlQuery(db_)); |
124 | + select_pkgs_by_dept_.reset(new QSqlQuery(db_)); |
125 | + select_pkgs_by_dept_recursive_.reset(new QSqlQuery(db_)); |
126 | + select_parent_dept_.reset(new QSqlQuery(db_)); |
127 | + select_children_depts_.reset(new QSqlQuery(db_)); |
128 | + select_dept_name_.reset(new QSqlQuery(db_)); |
129 | + |
130 | + delete_pkgmap_query_->prepare("DELETE FROM pkgmap WHERE pkgid=:pkgid"); |
131 | + insert_pkgmap_query_->prepare("INSERT OR REPLACE INTO pkgmap (pkgid, deptid) VALUES (:pkgid, :deptid)"); |
132 | + insert_dept_id_query_->prepare("INSERT OR REPLACE INTO depts (deptid, parentid) VALUES (:deptid, :parentid)"); |
133 | + insert_dept_name_query_->prepare("INSERT OR REPLACE INTO deptnames (deptid, locale, name) VALUES (:deptid, :locale, :name)"); |
134 | + select_pkgs_by_dept_->prepare("SELECT pkgid FROM pkgmap WHERE deptid=:deptid"); |
135 | + select_pkgs_by_dept_recursive_->prepare("WITH RECURSIVE recdepts(deptid) AS (SELECT deptid FROM depts_v WHERE deptid=:deptid UNION SELECT depts_v.deptid FROM recdepts,depts_v WHERE recdepts.deptid=depts_v.parentid) SELECT pkgid FROM pkgmap NATURAL JOIN recdepts"); |
136 | + select_children_depts_->prepare("SELECT deptid,(SELECT COUNT(1) from DEPTS_V AS inner WHERE inner.parentid=outer.deptid) FROM DEPTS_V AS outer WHERE parentid=:parentid"); |
137 | + select_parent_dept_->prepare("SELECT parentid FROM depts_v WHERE deptid=:deptid"); |
138 | + select_dept_name_->prepare("SELECT name FROM deptnames WHERE deptid=:deptid AND locale=:locale"); |
139 | +} |
140 | + |
141 | +void DepartmentsDb::init_db(const std::string& name) |
142 | +{ |
143 | + db_ = QSqlDatabase::addDatabase("QSQLITE"); |
144 | + db_.setDatabaseName(QString::fromStdString(name)); |
145 | + if (!db_.open()) |
146 | + { |
147 | + throw std::runtime_error("Cannot open departments database"); |
148 | + } |
149 | + |
150 | + QSqlQuery query; |
151 | + |
152 | + // FIXME: for some reason enabling foreign keys gives errors about number of arguments of prepared queries when doing query.exec(); do not enable |
153 | + // them for now. |
154 | + // query.exec("PRAGMA foreign_keys = ON"); |
155 | + |
156 | + // package id -> department id mapping table |
157 | + if (!query.exec("CREATE TABLE IF NOT EXISTS pkgmap (pkgid TEXT, deptid TEXT, CONSTRAINT pkey PRIMARY KEY (pkgid, deptid))")) |
158 | + { |
159 | + report_db_error(query.lastError(), "Failed to create pkgmap table"); |
160 | + } |
161 | + |
162 | + // department id -> parent department id mapping table |
163 | + if (!query.exec("CREATE TABLE IF NOT EXISTS depts (deptid TEXT, parentid TEXT, CONSTRAINT pkey PRIMARY KEY (deptid, parentid), CONSTRAINT fkey FOREIGN KEY (deptid) REFERENCES deptnames(deptid))")) |
164 | + { |
165 | + report_db_error(query.lastError(), "Failed to create depts table"); |
166 | + } |
167 | + |
168 | + // department id, locale -> deparment name mapping table |
169 | + if (!query.exec("CREATE TABLE IF NOT EXISTS deptnames (deptid TEXT, locale TEXT, name TEXT, CONSTRAINT deptuniq PRIMARY KEY (deptid, locale))")) |
170 | + { |
171 | + report_db_error(query.lastError(), "Failed to create depts table"); |
172 | + } |
173 | + |
174 | + // name -> value table for storing arbitrary values such as schema version |
175 | + if (!query.exec("CREATE TABLE IF NOT EXISTS meta (name TEXT PRIMARY KEY, value TEXT)")) |
176 | + { |
177 | + report_db_error(query.lastError(), "Failed to create meta table"); |
178 | + } |
179 | + query.exec("INSERT INTO meta (name, value) VALUES ('version', 1)"); |
180 | + |
181 | + // view of the depts table that automatically adds fake "" department for root departments |
182 | + if (!query.exec("CREATE VIEW IF NOT EXISTS depts_v AS SELECT deptid, parentid FROM depts UNION SELECT deptid,'' AS parentid FROM deptnames WHERE NOT EXISTS " |
183 | + "(SELECT * FROM depts WHERE depts.deptid=deptnames.deptid)")) |
184 | + { |
185 | + report_db_error(query.lastError(), "Failed to create depts_v view"); |
186 | + } |
187 | +} |
188 | + |
189 | +void DepartmentsDb::report_db_error(const QSqlError& error, const std::string& message) |
190 | +{ |
191 | + throw std::runtime_error(message + ": " + error.text().toStdString()); |
192 | +} |
193 | + |
194 | +std::string DepartmentsDb::get_department_name(const std::string& department_id, const std::list<std::string>& locales) |
195 | +{ |
196 | + for (auto const& locale: locales) |
197 | + { |
198 | + select_dept_name_->bindValue(":deptid", QVariant(QString::fromStdString(department_id))); |
199 | + select_dept_name_->bindValue(":locale", QVariant(QString::fromStdString(locale))); |
200 | + |
201 | + if (!select_dept_name_->exec()) |
202 | + { |
203 | + report_db_error(select_dept_name_->lastError(), "Failed to query for department name of " + department_id + ", locale " + locale); |
204 | + } |
205 | + |
206 | + if (select_dept_name_->next()) |
207 | + { |
208 | + return select_dept_name_->value(0).toString().toStdString(); |
209 | + } |
210 | + } |
211 | + throw std::logic_error("No name for department " + department_id); |
212 | +} |
213 | + |
214 | +std::string DepartmentsDb::get_parent_department_id(const std::string& department_id) |
215 | +{ |
216 | + select_parent_dept_->bindValue(":deptid", QVariant(QString::fromStdString(department_id))); |
217 | + if (!select_parent_dept_->exec()) |
218 | + { |
219 | + report_db_error(select_parent_dept_->lastError(), "Failed to query for parent department " + department_id); |
220 | + } |
221 | + if (!select_parent_dept_->next()) |
222 | + { |
223 | + throw std::logic_error("Unknown department '" + department_id + "'"); |
224 | + } |
225 | + return select_parent_dept_->value(0).toString().toStdString(); |
226 | +} |
227 | + |
228 | +std::list<DepartmentsDb::DepartmentInfo> DepartmentsDb::get_children_departments(const std::string& department_id) |
229 | +{ |
230 | + // FIXME: this should only return departments that have results, and set 'has_children' flag on the same basis. |
231 | + select_children_depts_->bindValue(":parentid", QVariant(QString::fromStdString(department_id))); |
232 | + if (!select_children_depts_->exec()) |
233 | + { |
234 | + report_db_error(select_children_depts_->lastError(), "Failed to query for children departments of " + department_id); |
235 | + } |
236 | + |
237 | + std::list<DepartmentInfo> depts; |
238 | + while (select_children_depts_->next()) |
239 | + { |
240 | + const DepartmentInfo inf(select_children_depts_->value(0).toString().toStdString(), select_children_depts_->value(1).toBool()); |
241 | + depts.push_back(inf); |
242 | + } |
243 | + |
244 | + return depts; |
245 | +} |
246 | + |
247 | +std::unordered_set<std::string> DepartmentsDb::get_packages_for_department(const std::string& department_id, bool recursive) |
248 | +{ |
249 | + std::unordered_set<std::string> pkgs; |
250 | + QSqlQuery *query = recursive ? select_pkgs_by_dept_recursive_.get() : select_pkgs_by_dept_.get(); |
251 | + query->bindValue(":deptid", QVariant(QString::fromStdString(department_id))); |
252 | + if (!query->exec()) |
253 | + { |
254 | + report_db_error(query->lastError(), "Failed to query for packages of department " + department_id); |
255 | + } |
256 | + while (query->next()) |
257 | + { |
258 | + pkgs.insert(query->value(0).toString().toStdString()); |
259 | + } |
260 | + return pkgs; |
261 | +} |
262 | + |
263 | +void DepartmentsDb::store_package_mapping(const std::string& package_id, const std::string& department_id) |
264 | +{ |
265 | + if (package_id.empty()) |
266 | + { |
267 | + throw std::logic_error("Invalid empty package_id"); |
268 | + } |
269 | + |
270 | + if (department_id.empty()) |
271 | + { |
272 | + throw std::logic_error("Invalid empty department id"); |
273 | + } |
274 | + |
275 | + if (!db_.transaction()) |
276 | + { |
277 | + std::cerr << "Failed to start transaction" << std::endl; |
278 | + } |
279 | + |
280 | + // delete package mapping first from any departments |
281 | + delete_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id))); |
282 | + delete_pkgmap_query_->exec(); |
283 | + |
284 | + insert_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id))); |
285 | + insert_pkgmap_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id))); |
286 | + if (!insert_pkgmap_query_->exec()) |
287 | + { |
288 | + if (!db_.rollback()) |
289 | + { |
290 | + std::cerr << "Failed to rollback transaction" << std::endl; |
291 | + } |
292 | + report_db_error(insert_pkgmap_query_->lastError(), "Failed to insert into pkgmap"); |
293 | + } |
294 | + |
295 | + if (!db_.commit()) |
296 | + { |
297 | + std::cerr << "Failed to commit transaction" << std::endl; |
298 | + } |
299 | +} |
300 | + |
301 | +void DepartmentsDb::store_department_mapping(const std::string& department_id, const std::string& parent_department_id) |
302 | +{ |
303 | + if (department_id.empty()) |
304 | + { |
305 | + throw std::logic_error("Invalid empty department id"); |
306 | + } |
307 | + |
308 | + if (parent_department_id.empty()) |
309 | + { |
310 | + throw std::logic_error("Invalid empty parent department id"); |
311 | + } |
312 | + |
313 | + insert_dept_id_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id))); |
314 | + insert_dept_id_query_->bindValue(":parentid", QVariant(QString::fromStdString(parent_department_id))); |
315 | + if (!insert_dept_id_query_->exec()) |
316 | + { |
317 | + report_db_error(insert_dept_id_query_->lastError(), "Failed to insert into depts"); |
318 | + } |
319 | +} |
320 | + |
321 | +void DepartmentsDb::store_department_name(const std::string& department_id, const std::string& locale, const std::string& name) |
322 | +{ |
323 | + if (department_id.empty()) |
324 | + { |
325 | + throw std::logic_error("Invalid empty department id"); |
326 | + } |
327 | + |
328 | + if (name.empty()) |
329 | + { |
330 | + throw std::logic_error("Invalid empty department name"); |
331 | + } |
332 | + |
333 | + insert_dept_name_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id))); |
334 | + insert_dept_name_query_->bindValue(":locale", QVariant(QString::fromStdString(locale))); |
335 | + insert_dept_name_query_->bindValue(":name", QVariant(QString::fromStdString(name))); |
336 | + |
337 | + if (!insert_dept_name_query_->exec()) |
338 | + { |
339 | + report_db_error(insert_dept_name_query_->lastError(), "Failed to insert into deptnames"); |
340 | + } |
341 | +} |
342 | + |
343 | +int DepartmentsDb::department_mapping_count() const |
344 | +{ |
345 | + QSqlQuery q(db_); |
346 | + if (!q.exec("SELECT COUNT(*) FROM depts") || !q.next()) |
347 | + { |
348 | + report_db_error(q.lastError(), "Failed to query depts table"); |
349 | + } |
350 | + return q.value(0).toInt(); |
351 | +} |
352 | + |
353 | +int DepartmentsDb::package_count() const |
354 | +{ |
355 | + QSqlQuery q(db_); |
356 | + if (!q.exec("SELECT COUNT(*) FROM pkgmap") || !q.next()) |
357 | + { |
358 | + report_db_error(q.lastError(), "Failed to query pkgmap table"); |
359 | + } |
360 | + return q.value(0).toInt(); |
361 | +} |
362 | + |
363 | +int DepartmentsDb::department_name_count() const |
364 | +{ |
365 | + QSqlQuery q(db_); |
366 | + if (!q.exec("SELECT COUNT(*) FROM deptnames") || !q.next()) |
367 | + { |
368 | + report_db_error(q.lastError(), "Failed to query deptnames table"); |
369 | + } |
370 | + return q.value(0).toInt(); |
371 | +} |
372 | + |
373 | +} |
374 | |
375 | === added file 'libclickscope/click/departments-db.h' |
376 | --- libclickscope/click/departments-db.h 1970-01-01 00:00:00 +0000 |
377 | +++ libclickscope/click/departments-db.h 2014-07-10 07:20:52 +0000 |
378 | @@ -0,0 +1,99 @@ |
379 | +/* |
380 | + * Copyright (C) 2014 Canonical Ltd. |
381 | + * |
382 | + * This program is free software: you can redistribute it and/or modify it |
383 | + * under the terms of the GNU General Public License version 3, as published |
384 | + * by the Free Software Foundation. |
385 | + * |
386 | + * This program is distributed in the hope that it will be useful, but |
387 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
388 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
389 | + * PURPOSE. See the GNU General Public License for more details. |
390 | + * |
391 | + * You should have received a copy of the GNU General Public License along |
392 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
393 | + * |
394 | + * In addition, as a special exception, the copyright holders give |
395 | + * permission to link the code of portions of this program with the |
396 | + * OpenSSL library under certain conditions as described in each |
397 | + * individual source file, and distribute linked combinations |
398 | + * including the two. |
399 | + * You must obey the GNU General Public License in all respects |
400 | + * for all of the code used other than OpenSSL. If you modify |
401 | + * file(s) with this exception, you may extend this exception to your |
402 | + * version of the file(s), but you are not obligated to do so. If you |
403 | + * do not wish to do so, delete this exception statement from your |
404 | + * version. If you delete this exception statement from all source |
405 | + * files in the program, then also delete it here. |
406 | + */ |
407 | + |
408 | +#ifndef CLICK_DEPARTMENTS_DB_H |
409 | +#define CLICK_DEPARTMENTS_DB_H |
410 | + |
411 | +#include <string> |
412 | +#include <set> |
413 | +#include <unordered_set> |
414 | +#include <list> |
415 | +#include <QSqlDatabase> |
416 | +#include <QSqlQuery> |
417 | +#include <memory> |
418 | + |
419 | +class QSqlError; |
420 | + |
421 | +namespace click |
422 | +{ |
423 | + |
424 | +class DepartmentsDb final |
425 | +{ |
426 | +public: |
427 | + struct DepartmentInfo |
428 | + { |
429 | + DepartmentInfo(const std::string &id, bool children): id(id), has_children(children) {} |
430 | + std::string id; |
431 | + bool has_children; |
432 | + |
433 | + bool operator==(const DepartmentInfo& other) const |
434 | + { |
435 | + return id == other.id && has_children == other.has_children; |
436 | + } |
437 | + }; |
438 | + |
439 | + DepartmentsDb(const std::string& name); |
440 | + DepartmentsDb(const DepartmentsDb& other) = delete; |
441 | + DepartmentsDb& operator=(const DepartmentsDb&) = delete; |
442 | + |
443 | + std::string get_department_name(const std::string& department_id, const std::list<std::string>& locales); |
444 | + std::unordered_set<std::string> get_packages_for_department(const std::string& department_id, bool recursive = true); |
445 | + std::string get_parent_department_id(const std::string& department_id); |
446 | + std::list<DepartmentInfo> get_children_departments(const std::string& department_id); |
447 | + |
448 | + void store_package_mapping(const std::string& package_id, const std::string& department_id); |
449 | + void store_department_mapping(const std::string& department_id, const std::string& parent_department_id); |
450 | + void store_department_name(const std::string& department_id, const std::string& locale, const std::string& name); |
451 | + |
452 | + // these methods are mostly for tests |
453 | + int department_mapping_count() const; |
454 | + int package_count() const; |
455 | + int department_name_count() const; |
456 | + |
457 | + static std::unique_ptr<DepartmentsDb> create_db(); |
458 | + |
459 | +private: |
460 | + void init_db(const std::string& name); |
461 | + static void report_db_error(const QSqlError& error, const std::string& message); |
462 | + |
463 | + QSqlDatabase db_; |
464 | + std::unique_ptr<QSqlQuery> delete_pkgmap_query_; |
465 | + std::unique_ptr<QSqlQuery> insert_pkgmap_query_; |
466 | + std::unique_ptr<QSqlQuery> insert_dept_id_query_; |
467 | + std::unique_ptr<QSqlQuery> insert_dept_name_query_; |
468 | + std::unique_ptr<QSqlQuery> select_pkgs_by_dept_; |
469 | + std::unique_ptr<QSqlQuery> select_pkgs_by_dept_recursive_; |
470 | + std::unique_ptr<QSqlQuery> select_parent_dept_; |
471 | + std::unique_ptr<QSqlQuery> select_children_depts_; |
472 | + std::unique_ptr<QSqlQuery> select_dept_name_; |
473 | +}; |
474 | + |
475 | +} |
476 | + |
477 | +#endif |
478 | |
479 | === modified file 'libclickscope/tests/CMakeLists.txt' |
480 | --- libclickscope/tests/CMakeLists.txt 2014-06-26 17:52:31 +0000 |
481 | +++ libclickscope/tests/CMakeLists.txt 2014-07-10 07:20:52 +0000 |
482 | @@ -6,6 +6,7 @@ |
483 | SET (CMAKE_AUTOMOC ON) |
484 | |
485 | find_package(Qt5Core REQUIRED) |
486 | +find_package(Qt5Sql REQUIRED) |
487 | |
488 | include_directories ( |
489 | ${CMAKE_SOURCE_DIR}/libclickscope |
490 | @@ -15,6 +16,7 @@ |
491 | ) |
492 | |
493 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test_data.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/test_data.cpp) |
494 | +add_definitions(-DTEST_DIR="${CMAKE_CURRENT_BINARY_DIR}") |
495 | |
496 | add_executable (${LIBCLICKSCOPE_TESTS_TARGET} |
497 | mock_network_access_manager.h |
498 | @@ -24,6 +26,7 @@ |
499 | test_bootstrap.cpp |
500 | test_configuration.cpp |
501 | test_departments.cpp |
502 | + test_departments-db.cpp |
503 | test_download_manager.cpp |
504 | test_index.cpp |
505 | test_interface.cpp |
506 | @@ -35,7 +38,7 @@ |
507 | ${CMAKE_CURRENT_BINARY_DIR}/test_data.cpp |
508 | ) |
509 | |
510 | -qt5_use_modules(${LIBCLICKSCOPE_TESTS_TARGET} Core) |
511 | +qt5_use_modules(${LIBCLICKSCOPE_TESTS_TARGET} Core Sql) |
512 | |
513 | target_link_libraries(${LIBCLICKSCOPE_TESTS_TARGET} |
514 | ${SCOPE_LIB_NAME} |
515 | |
516 | === added file 'libclickscope/tests/test_departments-db.cpp' |
517 | --- libclickscope/tests/test_departments-db.cpp 1970-01-01 00:00:00 +0000 |
518 | +++ libclickscope/tests/test_departments-db.cpp 2014-07-10 07:20:52 +0000 |
519 | @@ -0,0 +1,226 @@ |
520 | +/* |
521 | + * Copyright (C) 2014 Canonical Ltd. |
522 | + * |
523 | + * This program is free software: you can redistribute it and/or modify it |
524 | + * under the terms of the GNU General Public License version 3, as published |
525 | + * by the Free Software Foundation. |
526 | + * |
527 | + * This program is distributed in the hope that it will be useful, but |
528 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
529 | + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
530 | + * PURPOSE. See the GNU General Public License for more details. |
531 | + * |
532 | + * You should have received a copy of the GNU General Public License along |
533 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
534 | + * |
535 | + * In addition, as a special exception, the copyright holders give |
536 | + * permission to link the code of portions of this program with the |
537 | + * OpenSSL library under certain conditions as described in each |
538 | + * individual source file, and distribute linked combinations |
539 | + * including the two. |
540 | + * You must obey the GNU General Public License in all respects |
541 | + * for all of the code used other than OpenSSL. If you modify |
542 | + * file(s) with this exception, you may extend this exception to your |
543 | + * version of the file(s), but you are not obligated to do so. If you |
544 | + * do not wish to do so, delete this exception statement from your |
545 | + * version. If you delete this exception statement from all source |
546 | + * files in the program, then also delete it here. |
547 | + */ |
548 | + |
549 | +#include <gtest/gtest.h> |
550 | +#include "fake_json.h" |
551 | +#include <click/departments-db.h> |
552 | +#include <memory> |
553 | +#include <algorithm> |
554 | +#include <unistd.h> |
555 | + |
556 | +using namespace click; |
557 | + |
558 | +class DepartmentsDbTest: public ::testing::Test |
559 | +{ |
560 | +public: |
561 | + const std::string db_path = TEST_DIR "/departments-db-test.sqlite"; |
562 | + |
563 | + void SetUp() override |
564 | + { |
565 | + db.reset(new DepartmentsDb(db_path)); |
566 | + db->store_department_name("tools", "", "Tools"); |
567 | + db->store_department_name("office", "", "Office"); |
568 | + |
569 | + db->store_department_mapping("office", "tools"); |
570 | + |
571 | + db->store_package_mapping("app1", "tools"); |
572 | + db->store_package_mapping("app2", "office"); |
573 | + |
574 | + db->store_department_name("games", "", "Games"); |
575 | + db->store_department_name("games", "pl_PL", "Gry"); |
576 | + db->store_department_name("rpg", "", "RPG"); |
577 | + db->store_department_name("card", "", "Card"); |
578 | + db->store_department_name("fps", "", "First Person Shooter"); |
579 | + |
580 | + db->store_department_mapping("rpg", "games"); |
581 | + db->store_department_mapping("card", "games"); |
582 | + db->store_department_mapping("fps", "games"); |
583 | + |
584 | + db->store_package_mapping("game1", "rpg"); |
585 | + db->store_package_mapping("game2", "fps"); |
586 | + } |
587 | + |
588 | + void TearDown() override |
589 | + { |
590 | + unlink(db_path.c_str()); |
591 | + } |
592 | + |
593 | +protected: |
594 | + std::unique_ptr<DepartmentsDb> db; |
595 | +}; |
596 | + |
597 | +TEST_F(DepartmentsDbTest, testDepartmentNameLookup) |
598 | +{ |
599 | + { |
600 | + EXPECT_EQ("Games", db->get_department_name("games", {"en_EN", ""})); |
601 | + EXPECT_EQ("Gry", db->get_department_name("games", {"pl_PL", ""})); |
602 | + EXPECT_EQ("First Person Shooter", db->get_department_name("fps", {"en_EN", ""})); |
603 | + EXPECT_EQ("Office", db->get_department_name("office", {"en_EN", ""})); |
604 | + EXPECT_EQ("Tools", db->get_department_name("tools", {"en_EN", ""})); |
605 | + |
606 | + EXPECT_THROW(db->get_department_name("xyz", {"en_EN", ""}), std::logic_error); |
607 | + } |
608 | +} |
609 | + |
610 | +TEST_F(DepartmentsDbTest, testDepartmentNameUpdates) |
611 | +{ |
612 | + { |
613 | + EXPECT_EQ(7u, db->department_name_count()); |
614 | + db->store_department_name("tools", "", "Tools!"); |
615 | + EXPECT_EQ("Tools!", db->get_department_name("tools", {"en_EN", ""})); |
616 | + db->store_department_name("games", "pl_PL", "Gry!!!"); |
617 | + EXPECT_EQ("Gry!!!", db->get_department_name("games", {"pl_PL"})); |
618 | + EXPECT_EQ(7u, db->department_name_count()); |
619 | + } |
620 | +} |
621 | + |
622 | +TEST_F(DepartmentsDbTest, testDepartmentParentLookup) |
623 | +{ |
624 | + { |
625 | + EXPECT_EQ("games", db->get_parent_department_id("rpg")); |
626 | + EXPECT_EQ("games", db->get_parent_department_id("card")); |
627 | + EXPECT_EQ("", db->get_parent_department_id("games")); |
628 | + |
629 | + EXPECT_EQ("tools", db->get_parent_department_id("office")); |
630 | + EXPECT_EQ("", db->get_parent_department_id("tools")); |
631 | + |
632 | + EXPECT_THROW(db->get_parent_department_id("xyz"), std::logic_error); |
633 | + } |
634 | +} |
635 | + |
636 | +TEST_F(DepartmentsDbTest, testDepartmentChildrenLookup) |
637 | +{ |
638 | + { |
639 | + EXPECT_EQ(0, db->get_children_departments("xyz").size()); |
640 | + } |
641 | + { |
642 | + auto depts = db->get_children_departments(""); |
643 | + EXPECT_EQ(2u, depts.size()); |
644 | + EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("tools", true)) != depts.end()); |
645 | + EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("games", true)) != depts.end()); |
646 | + } |
647 | + { |
648 | + auto depts = db->get_children_departments("tools"); |
649 | + EXPECT_EQ(1u, depts.size()); |
650 | + EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("office", false)) != depts.end()); |
651 | + } |
652 | + { |
653 | + auto depts = db->get_children_departments("games"); |
654 | + EXPECT_EQ(3u, depts.size()); |
655 | + EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("rpg", false)) != depts.end()); |
656 | + EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("fps", false)) != depts.end()); |
657 | + EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("card", false)) != depts.end()); |
658 | + } |
659 | +} |
660 | + |
661 | +TEST_F(DepartmentsDbTest, testPackageLookup) |
662 | +{ |
663 | + { |
664 | + auto pkgs = db->get_packages_for_department("rpg", false); |
665 | + EXPECT_EQ(1u, pkgs.size()); |
666 | + EXPECT_TRUE(pkgs.find("game1") != pkgs.end()); |
667 | + } |
668 | + { |
669 | + auto pkgs = db->get_packages_for_department("fps", false); |
670 | + EXPECT_EQ(1u, pkgs.size()); |
671 | + EXPECT_TRUE(pkgs.find("game2") != pkgs.end()); |
672 | + } |
673 | + { |
674 | + auto pkgs = db->get_packages_for_department("card", false); |
675 | + EXPECT_EQ(0, pkgs.size()); |
676 | + } |
677 | +} |
678 | + |
679 | +TEST_F(DepartmentsDbTest, testRecursivePackageLookup) |
680 | +{ |
681 | + { |
682 | + // get packages from subdepartments of games department |
683 | + auto pkgs = db->get_packages_for_department("games", true); |
684 | + EXPECT_EQ(2u, pkgs.size()); |
685 | + EXPECT_TRUE(pkgs.find("game1") != pkgs.end()); |
686 | + EXPECT_TRUE(pkgs.find("game2") != pkgs.end()); |
687 | + } |
688 | + { |
689 | + // rpg has no subdepartments, so we just get the packages of rpg department |
690 | + auto pkgs = db->get_packages_for_department("rpg", true); |
691 | + EXPECT_EQ(1u, pkgs.size()); |
692 | + EXPECT_TRUE(pkgs.find("game1") != pkgs.end()); |
693 | + } |
694 | + { |
695 | + auto pkgs = db->get_packages_for_department("card", true); |
696 | + EXPECT_EQ(0, pkgs.size()); |
697 | + } |
698 | +} |
699 | + |
700 | +TEST_F(DepartmentsDbTest, testPackageUpdates) |
701 | +{ |
702 | + auto pkgs = db->get_packages_for_department("fps"); |
703 | + EXPECT_EQ(1, pkgs.size()); |
704 | + EXPECT_TRUE(pkgs.find("game2") != pkgs.end()); |
705 | + |
706 | + // game2 gets moved to card and removed from fps |
707 | + db->store_package_mapping("game2", "card"); |
708 | + |
709 | + pkgs = db->get_packages_for_department("fps", false); |
710 | + EXPECT_EQ(0, pkgs.size()); |
711 | + EXPECT_TRUE(pkgs.find("game2") == pkgs.end()); |
712 | + pkgs = db->get_packages_for_department("card", false); |
713 | + EXPECT_EQ(1, pkgs.size()); |
714 | + EXPECT_TRUE(pkgs.find("game2") != pkgs.end()); |
715 | +} |
716 | + |
717 | +TEST_F(DepartmentsDbTest, testInvalidDepartments) |
718 | +{ |
719 | + EXPECT_THROW(db->get_parent_department_id(""), std::logic_error); |
720 | + EXPECT_THROW(db->get_parent_department_id("foooo"), std::logic_error); |
721 | +} |
722 | + |
723 | +TEST_F(DepartmentsDbTest, testEmptyArguments) |
724 | +{ |
725 | + EXPECT_THROW(db->store_department_name("", "", "Foo"), std::logic_error); |
726 | + EXPECT_THROW(db->store_department_name("foo", "", ""), std::logic_error); |
727 | + EXPECT_THROW(db->store_department_mapping("", "foo"), std::logic_error); |
728 | + EXPECT_THROW(db->store_department_mapping("foo", ""), std::logic_error); |
729 | + EXPECT_THROW(db->store_package_mapping("", "foo"), std::logic_error); |
730 | + EXPECT_THROW(db->store_package_mapping("foo", ""), std::logic_error); |
731 | +} |
732 | + |
733 | +TEST_F(DepartmentsDbTest, testNoDuplicates) |
734 | +{ |
735 | + db->store_package_mapping("game2", "fps"); |
736 | + db->store_package_mapping("game2", "fps"); |
737 | + db->store_department_name("games", "pl_PL", "Gry"); |
738 | + db->store_department_name("games", "pl_PL", "Gry"); |
739 | + db->store_department_mapping("office", "tools"); |
740 | + db->store_department_mapping("office", "tools"); |
741 | + |
742 | + EXPECT_EQ(7u, db->department_name_count()); |
743 | + EXPECT_EQ(4u, db->package_count()); |
744 | +} |
745 | + |
PASSED: Continuous integration, rev:313 jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- ci/160/ jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- amd64-ci/ 135 jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- armhf-ci/ 134 jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- armhf-ci/ 134/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ unity-team- unity-scope- click-devel- utopic- i386-ci/ 134
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- team-unity- scope-click- devel-ci/ 160/rebuild
http://