Merge lp:~michael.nelson/software-center/833982-purchased-app-not-available into lp:software-center
- 833982-purchased-app-not-available
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 2670 |
Proposed branch: | lp:~michael.nelson/software-center/833982-purchased-app-not-available |
Merge into: | lp:software-center |
Diff against target: |
588 lines (+354/-63) 4 files modified
.bzrignore (+5/-0) README (+22/-5) softwarecenter/db/update.py (+84/-32) test/test_reinstall_purchased.py (+243/-26) |
To merge this branch: | bzr merge lp:~michael.nelson/software-center/833982-purchased-app-not-available |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Lasker (community) | Approve | ||
Review via email: mp+88827@code.launchpad.net |
Commit message
Description of the change
This branch is part 1 of a fix for bug 917137 - Previous purchases empty in precise.
The cause of the bug seemed to be that the SoftwareCenterA
The 2.0 api does not do this, intentionally nesting the application attributes.
After chatting with mvo, we decided 2 parsers should be included - one for parsing applications (from 2.0 available apps call), the other for parsing subscriptions ("Purchased applications", from 2.0 subscriptions_
Also done:
* Removed the 'icon' attribute from the mapping, as we no-longer support it via the API (check with mvo).
* Fixed get_desktop_
Testing
=======
cd test;make
Note: I get two failures (test_reviews, test_database) as well as python-coverage crashing while running the tests: http://
Anyway, still to do in a follow-up branch coming shortly:
* Check if we can update PURCHASED_
* remove the need for the SCASubscription
* Filter results of the my_subscriptions call (only keep Complete ones), as currently it looks like this: http://
* Rename s/SoftwareCente
* Replace the MockAvailableFo
- 2676. By Michael Nelson
-
REFACTOR: replaced help/* in bzr ignore, updated requirement in README.
Gary Lasker (gary-lasker) wrote : | # |
- 2677. By Michael Nelson
-
RED: applications and purchased applications should have the correct magic channel.
- 2678. By Michael Nelson
-
GREEN: applications and purchased applications should have the correct magic channel.
Michael Nelson (michael.nelson) wrote : | # |
> Hi Michael! This is really nice work and the additional changes that you are
> working on sound just right.
>
> As we discussed in IRC earlier, there's one problem that I found in the
> current branch, and that is that with this change we getting an empty list for
> For Purchase apps. THis appears to be an issue with setting the "magic
> channel" to PURCHASED_
> SoftwareCenterA
Right - I'd not realised it was being set on the entry before instantiating the parser - thanks for the pointer.
>
> I made a quick attempt to fix this in the following branch:
>
> lp:~gary-lasker/software-center/noodles-833982-purchased-app-not-available
>
> You can see that I separated the two "magic channel" settings into the
> corresponding parser classes. This indeed restores the apps for purchase , but
> now I'm getting an empty list for the previous purchases, so it's not correct
> yet. Unfortunately, I had to leave for the day and and could not go further,
> but hopefully it's something simple and it won't be too hard to finish up
> tomorrow. In any case, I hope this branch is some help. I'll check in with you
> when I get back online in the morning.
Thanks - that helped. I've pushed some failing tests with r2677 then fixed the issue with r2678. Now I see both for purchase apps and my previously installed.
Gary Lasker (gary-lasker) wrote : | # |
So, it's indeed fixed (nice, clean fix!) and already merged so I'll set this a approved. Thanks again for this and also thanks for the README dev setup instructions!
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2011-05-27 08:25:15 +0000 |
3 | +++ .bzrignore 2012-01-18 09:00:32 +0000 |
4 | @@ -17,3 +17,8 @@ |
5 | data/xapian/spelling.baseA |
6 | data/xapian/spelling.baseB |
7 | build/ |
8 | +test/.coverage |
9 | +test/coverage_html |
10 | +test/coverage_summary |
11 | +test/output |
12 | +help/* |
13 | |
14 | === modified file 'README' |
15 | --- README 2011-07-27 16:43:26 +0000 |
16 | +++ README 2012-01-18 09:00:32 +0000 |
17 | @@ -7,6 +7,23 @@ |
18 | |
19 | All non UI code must come with tests in the test/ subdirectoy. |
20 | |
21 | +To setup your development environment, you'll need to ensure the following |
22 | +extra packages are installed: |
23 | + |
24 | +sudo apt-get install xvfb python-coverage python-mock python-aptdaemon.test \ |
25 | + python-qt4 python-unittest2 |
26 | +sudo apt-get build-dep software-center |
27 | + |
28 | +You can then run tests with: |
29 | + |
30 | +cd test;make |
31 | + |
32 | +You can run a developer instance with: |
33 | + |
34 | +python setup.py build |
35 | +./software-center |
36 | + |
37 | + |
38 | == query parser == |
39 | |
40 | The query parser understands : |
41 | @@ -15,7 +32,7 @@ |
42 | |
43 | == aptdaemon == |
44 | * the dbus limits for the system bus are rather low, this means that |
45 | - adding <limit name="max_match_rules_per_connection">512</limit> |
46 | + adding <limit name="max_match_rules_per_connection">512</limit> |
47 | and using something bigger than 512 is a good idea |
48 | |
49 | == environment == |
50 | @@ -54,7 +71,7 @@ |
51 | SCPkgname - e.g. "gimp" |
52 | |
53 | Additional .menu files can be added in: |
54 | -/usr/share/app-install/menu.d |
55 | +/usr/share/app-install/menu.d |
56 | that software-center will read and parse. |
57 | |
58 | == XAPIAN == |
59 | @@ -66,9 +83,9 @@ |
60 | AS - archive pocket (main) |
61 | AE - archive section (mail, base, ...) |
62 | AC - category (AudioVideo) |
63 | -AM - MimeType (application/x-ogg) |
64 | +AM - MimeType (application/x-ogg) |
65 | AT - type (Application) |
66 | -AH - channel |
67 | +AH - channel |
68 | |
69 | |
70 | The following values are used: |
71 | @@ -89,5 +106,5 @@ |
72 | XAPIAN_VALUE_PURCHASED_DATE - the data a for-pay app was purchased (only available after the software-center-agent server was queried) |
73 | XAPIAN_VALUE_SCREENSHOT_URL - a (optional) screenshot url that overrides the default |
74 | XAPIAN_VALUE_ICON_NEEDS_DOWNLOAD - icon needs to be fetched |
75 | -XAPIAN_VALUE_THUMBNAIL_URL - thumbnail url |
76 | +XAPIAN_VALUE_THUMBNAIL_URL - thumbnail url |
77 | |
78 | |
79 | === modified file 'softwarecenter/db/update.py' |
80 | --- softwarecenter/db/update.py 2012-01-05 08:52:42 +0000 |
81 | +++ softwarecenter/db/update.py 2012-01-18 09:00:32 +0000 |
82 | @@ -26,6 +26,7 @@ |
83 | import time |
84 | |
85 | from gi.repository import GObject |
86 | +from piston_mini_client import PistonResponseObject |
87 | |
88 | from softwarecenter.utils import utf8 |
89 | |
90 | @@ -131,6 +132,7 @@ |
91 | def desktopf(self): |
92 | """ return the file that the AppInfo comes from """ |
93 | |
94 | + |
95 | class SoftwareCenterAgentParser(AppInfoParserBase): |
96 | """ map the data we get from the software-center-agent """ |
97 | |
98 | @@ -140,20 +142,17 @@ |
99 | 'Package' : 'package_name', |
100 | 'Categories' : 'categories', |
101 | 'Channel' : 'channel', |
102 | - 'Deb-Line' : 'deb_line', |
103 | 'Signing-Key-Id' : 'signing_key_id', |
104 | 'License' : 'license', |
105 | 'Date-Published' : 'date_published', |
106 | - 'Purchased-Date' : 'purchase_date', |
107 | - 'License-Key' : 'license_key', |
108 | - 'License-Key-Path' : 'license_key_path', |
109 | 'PPA' : 'archive_id', |
110 | - 'Icon' : 'icon', |
111 | 'Screenshot-Url' : 'screenshot_url', |
112 | 'Thumbnail-Url' : 'thumbnail_url', |
113 | 'Video-Url' : 'video_url', |
114 | 'Icon-Url' : 'icon_url', |
115 | 'Support-Url' : 'support_url', |
116 | + 'Description' : 'Description', |
117 | + 'Comment' : 'Comment', |
118 | } |
119 | |
120 | # map from requested key to a static data element |
121 | @@ -164,6 +163,7 @@ |
122 | self.sca_entry = sca_entry |
123 | self.origin = "software-center-agent" |
124 | self._apply_exceptions() |
125 | + |
126 | def _apply_exceptions(self): |
127 | # for items from the agent, we use the full-size screenshot for |
128 | # the thumbnail and scale it for display, this is done because |
129 | @@ -172,25 +172,86 @@ |
130 | not hasattr(self.sca_entry, "thumbnail_url")): |
131 | self.sca_entry.thumbnail_url = self.sca_entry.screenshot_url |
132 | if hasattr(self.sca_entry, "description"): |
133 | - self.sca_entry.Comment = self.sca_entry.description.split("\n")[0] |
134 | - self.sca_entry.Description = "\n".join(self.sca_entry.description.split("\n")[1:]) |
135 | + self.sca_entry.Comment = self.sca_entry.description.split("\n")[0].strip() |
136 | + self.sca_entry.Description = "\n".join( |
137 | + self.sca_entry.description.split("\n")[1:]).strip() |
138 | + # WARNING: item.name needs to be different than |
139 | + # the item.name in the DB otherwise the DB |
140 | + # gets confused about (appname, pkgname) duplication |
141 | + self.sca_entry.name = utf8(_("%s (already purchased)")) % utf8( |
142 | + self.sca_entry.name) |
143 | + |
144 | + # XXX 2012-01-16 bug=917109 |
145 | + # We can remove these work-arounds once the above bug is fixed on |
146 | + # the server. Until then, we fake a channel here and empty category |
147 | + # to make the parser happy. Note: available_apps api call includes |
148 | + # these already, it's just the apps with subscriptions_for_me which |
149 | + # don't currently. |
150 | + self.sca_entry.channel = AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME |
151 | + if not hasattr(self.sca_entry, 'categories'): |
152 | + self.sca_entry.categories = "" |
153 | + |
154 | def get_desktop(self, key, translated=True): |
155 | if key in self.STATIC_DATA: |
156 | return self.STATIC_DATA[key] |
157 | return getattr(self.sca_entry, self._apply_mapping(key)) |
158 | + |
159 | def get_desktop_categories(self): |
160 | try: |
161 | return ['DEPARTMENT:' + self.sca_entry.department[-1]] + self._get_desktop_list("Categories") |
162 | except: |
163 | return self._get_desktop_list("Categories") |
164 | + |
165 | def has_option_desktop(self, key): |
166 | return (key in self.STATIC_DATA or |
167 | hasattr(self.sca_entry, self._apply_mapping(key))) |
168 | + |
169 | @property |
170 | def desktopf(self): |
171 | return self.origin |
172 | |
173 | |
174 | +class SCAPurchasedApplicationParser(SoftwareCenterAgentParser): |
175 | + """A subscription has its own attrs with a subset of the app attributes. |
176 | + |
177 | + We inherit from SoftwareCenterAgentParser so that we get other methods for |
178 | + free, and we compose a SoftwareCenterAgentParser because we need the |
179 | + get_desktop method with the correct MAPPING. TODO: There must be a nicer |
180 | + way to organise this so that we don't need both inheritance and composition |
181 | + for a DRY implementation. |
182 | + """ |
183 | + |
184 | + def __init__(self, sca_subscription): |
185 | + # The sca_subscription is a PistonResponseObject, whereas any child |
186 | + # objects are normal Python dicts. |
187 | + self.sca_subscription = sca_subscription |
188 | + self.application_parser = SoftwareCenterAgentParser( |
189 | + PistonResponseObject.from_dict(sca_subscription.application)) |
190 | + super(SCAPurchasedApplicationParser, self).__init__( |
191 | + PistonResponseObject.from_dict(sca_subscription.application)) |
192 | + self.application_parser.sca_entry.channel = ( |
193 | + PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME) |
194 | + |
195 | + MAPPING = { 'Deb-Line' : 'deb_line', |
196 | + 'Purchased-Date' : 'purchase_date', |
197 | + 'License-Key' : 'license_key', |
198 | + 'License-Key-Path' : 'license_key_path', |
199 | + } |
200 | + |
201 | + def get_desktop(self, key, translated=True): |
202 | + if self.application_parser.has_option_desktop(key): |
203 | + return self.application_parser.get_desktop(key, translated) |
204 | + |
205 | + return getattr(self.sca_subscription, self._apply_mapping(key)) |
206 | + |
207 | + def has_option_desktop(self, key): |
208 | + subscription_has_option = hasattr( |
209 | + self.sca_subscription, self._apply_mapping(key)) |
210 | + application_has_option = ( |
211 | + self.application_parser.has_option_desktop(key)) |
212 | + return subscription_has_option or application_has_option |
213 | + |
214 | + |
215 | class JsonTagSectionParser(AppInfoParserBase): |
216 | |
217 | MAPPING = { 'Name' : 'application_name', |
218 | @@ -505,15 +566,7 @@ |
219 | # continue |
220 | # index the item |
221 | try: |
222 | - # we fake a channel here |
223 | - item.channel = PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME |
224 | - # and empty category to make the parser happy |
225 | - item.categories = "" |
226 | - # WARNING: item.name needs to be different than |
227 | - # the item.name in the DB otherwise the DB |
228 | - # gets confused about (appname, pkgname) duplication |
229 | - item.name = utf8(_("%s (already purchased)")) % utf8(item.name) |
230 | - parser = SoftwareCenterAgentParser(item) |
231 | + parser = SCAPurchasedApplicationParser(item) |
232 | index_app_info_from_parser(parser, db_purchased, cache) |
233 | except Exception as e: |
234 | LOG.exception("error processing: %s " % e) |
235 | @@ -560,8 +613,6 @@ |
236 | while context.pending(): |
237 | context.iteration() |
238 | try: |
239 | - # magic channel |
240 | - entry.channel = AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME |
241 | # now the normal parser |
242 | parser = SoftwareCenterAgentParser(entry) |
243 | index_app_info_from_parser(parser, db, cache) |
244 | @@ -663,20 +714,21 @@ |
245 | # date published |
246 | if parser.has_option_desktop("X-AppInstall-Date-Published"): |
247 | date_published = parser.get_desktop("X-AppInstall-Date-Published") |
248 | - # strip the subseconds from the end of the published date string |
249 | - date_published = str(date_published).split(".")[0] |
250 | - doc.add_value(XapianValues.DATE_PUBLISHED, |
251 | - date_published) |
252 | - # we use the date published value for the cataloged time as well |
253 | - if "catalogedtime" in axi_values: |
254 | - LOG.debug( |
255 | - ("pkgname: %s, date_published cataloged time is: %s" % |
256 | - (pkgname, parser.get_desktop("date_published")))) |
257 | - date_published_sec = time.mktime( |
258 | - time.strptime(date_published, |
259 | - "%Y-%m-%d %H:%M:%S")) |
260 | - doc.add_value(axi_values["catalogedtime"], |
261 | - xapian.sortable_serialise(date_published_sec)) |
262 | + if date_published: |
263 | + # strip the subseconds from the end of the published date string |
264 | + date_published = str(date_published).split(".")[0] |
265 | + doc.add_value(XapianValues.DATE_PUBLISHED, |
266 | + date_published) |
267 | + # we use the date published value for the cataloged time as well |
268 | + if "catalogedtime" in axi_values: |
269 | + LOG.debug( |
270 | + ("pkgname: %s, date_published cataloged time is: %s" % |
271 | + (pkgname, parser.get_desktop("date_published")))) |
272 | + date_published_sec = time.mktime( |
273 | + time.strptime(date_published, |
274 | + "%Y-%m-%d %H:%M:%S")) |
275 | + doc.add_value(axi_values["catalogedtime"], |
276 | + xapian.sortable_serialise(date_published_sec)) |
277 | # purchased date |
278 | if parser.has_option_desktop("X-AppInstall-Purchased-Date"): |
279 | date = parser.get_desktop("X-AppInstall-Purchased-Date") |
280 | |
281 | === modified file 'test/test_reinstall_purchased.py' |
282 | --- test/test_reinstall_purchased.py 2012-01-16 14:42:49 +0000 |
283 | +++ test/test_reinstall_purchased.py 2012-01-18 09:00:32 +0000 |
284 | @@ -7,28 +7,89 @@ |
285 | import unittest |
286 | import xapian |
287 | |
288 | +from piston_mini_client import PistonResponseObject |
289 | from testutils import setup_test_env |
290 | setup_test_env() |
291 | -from softwarecenter.enums import XapianValues |
292 | + |
293 | +from softwarecenter.enums import (XapianValues, |
294 | + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, |
295 | + PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME, |
296 | + ) |
297 | from softwarecenter.db.database import StoreDatabase |
298 | -from softwarecenter.db.update import add_from_purchased_but_needs_reinstall_data |
299 | +from softwarecenter.db.update import ( |
300 | + add_from_purchased_but_needs_reinstall_data, |
301 | + SCAPurchasedApplicationParser, |
302 | + SoftwareCenterAgentParser, |
303 | + ) |
304 | |
305 | -# from |
306 | -# https://wiki.canonical.com/Ubuntu/SoftwareCenter/10.10/Roadmap/SoftwareCenterAgent |
307 | -AVAILABLE_FOR_ME_JSON = """ |
308 | -[ |
309 | - { |
310 | - "archive_id": "mvo/private-test", |
311 | - "deb_line": "deb https://username:randomp3atoken@private-ppa.launchpad.net/mvo/private-test/ubuntu maverick main #Personal access of username to private-test", |
312 | - "purchase_price": "19.95", |
313 | - "purchase_date": "2010-06-24 20:08:23", |
314 | - "name": "Ubiteme", |
315 | - "description": "One of the best strategy games you\'ll ever play!", |
316 | - "package_name": "hellox", |
317 | - "signing_key_id": "1024R/0EB12F05", |
318 | - "series": {"natty": ["i386", "amd64"], |
319 | - "maverick": ["i386", "amd64"], |
320 | - "lucid": ["i386", "amd64"]} |
321 | +# Example taken from running: |
322 | +# PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py --output=pickle \ |
323 | +# --debug --needs-auth SoftwareCenterAgentAPI subscriptions_for_me |
324 | +# then: |
325 | +# f = open('my_subscriptions.pickle') |
326 | +# subscriptions = pickle.load(f) |
327 | +# completed_subs = [subs for subs in subscriptions if subs.state=='Complete'] |
328 | +# completed_subs[0].__dict__ |
329 | +SUBSCRIPTIONS_FOR_ME_JSON = """ |
330 | +[ |
331 | + { |
332 | + "deb_line": "deb https://username:random3atoken@private-ppa.launchpad.net/commercial-ppa-uploaders/photobomb/ubuntu natty main", |
333 | + "purchase_price": "2.99", |
334 | + "purchase_date": "2011-09-16 06:37:52", |
335 | + "state": "Complete", |
336 | + "failures": [], |
337 | + "open_id": "https://login.ubuntu.com/+id/ABCDEF", |
338 | + "application": { |
339 | + "archive_id": "commercial-ppa-uploaders/photobomb", |
340 | + "signing_key_id": "1024R/75254D99", |
341 | + "name": "Photobomb", |
342 | + "package_name": "photobomb", |
343 | + "description": "Easy and Social Image Editor\\nPhotobomb give you easy access to images in your social networking feeds, pictures on your computer and peripherals, and pictures on the web, and let\'s you draw, write, crop, combine, and generally have a blast mashing \'em all up. Then you can save off your photobomb, or tweet your creation right back to your social network." |
344 | + }, |
345 | + "distro_series": {"code_name": "natty", "version": "11.04"} |
346 | + } |
347 | +] |
348 | +""" |
349 | +# Taken directly from: |
350 | +# https://software-center.ubuntu.com/api/2.0/applications/en/ubuntu/oneiric/i386/ |
351 | +AVAILABLE_APPS_JSON = """ |
352 | +[ |
353 | + { |
354 | + "archive_id": "commercial-ppa-uploaders/fluendo-dvd", |
355 | + "signing_key_id": "1024R/75254D99", |
356 | + "license": "Proprietary", |
357 | + "name": "Fluendo DVD Player", |
358 | + "package_name": "fluendo-dvd", |
359 | + "support_url": "", |
360 | + "series": { |
361 | + "maverick": [ |
362 | + "i386", |
363 | + "amd64" |
364 | + ], |
365 | + "natty": [ |
366 | + "i386", |
367 | + "amd64" |
368 | + ], |
369 | + "oneiric": [ |
370 | + "i386", |
371 | + "amd64" |
372 | + ] |
373 | + }, |
374 | + "price": "24.95", |
375 | + "demo": null, |
376 | + "date_published": "2011-12-05 18:43:21.653868", |
377 | + "status": "Published", |
378 | + "channel": "For Purchase", |
379 | + "icon_data": "...", |
380 | + "department": [ |
381 | + "Sound & Video" |
382 | + ], |
383 | + "archive_root": "https://private-ppa.launchpad.net/", |
384 | + "screenshot_url": "http://software-center.ubuntu.com/site_media/screenshots/2011/05/fluendo-dvd-maverick_.png", |
385 | + "tos_url": "https://software-center.ubuntu.com/licenses/3/", |
386 | + "icon_url": "http://software-center.ubuntu.com/site_media/icons/2011/05/fluendo-dvd.png", |
387 | + "categories": "AudioVideo", |
388 | + "description": "Play DVD-Videos\\r\\n\\r\\nFluendo DVD Player is a software application specially designed to\\r\\nreproduce DVD on Linux/Unix platforms, which provides end users with\\r\\nhigh quality standards.\\r\\n\\r\\nThe following features are provided:\\r\\n* Full DVD Playback\\r\\n* DVD Menu support\\r\\n* Fullscreen support\\r\\n* Dolby Digital pass-through\\r\\n* Dolby Digital 5.1 output and stereo downmixing support\\r\\n* Resume from last position support\\r\\n* Subtitle support\\r\\n* Audio selection support\\r\\n* Multiple Angles support\\r\\n* Support for encrypted discs\\r\\n* Multiregion, works in all regions\\r\\n* Multiple video deinterlacing algorithms" |
389 | } |
390 | ] |
391 | """ |
392 | @@ -39,11 +100,11 @@ |
393 | setattr(self, key, value) |
394 | self.MimeType = "" |
395 | self.department = [] |
396 | - |
397 | + |
398 | class MockAvailableForMeList(list): |
399 | |
400 | def __init__(self): |
401 | - alist = json.loads(AVAILABLE_FOR_ME_JSON) |
402 | + alist = json.loads(SUBSCRIPTIONS_FOR_ME_JSON) |
403 | for entry_dict in alist: |
404 | self.append(MockAvailableForMeItem(entry_dict)) |
405 | |
406 | @@ -62,7 +123,8 @@ |
407 | def test_reinstall_purchased_mock(self): |
408 | # test if the mocks are ok |
409 | self.assertEqual(len(self.available_to_me), 1) |
410 | - self.assertEqual(self.available_to_me[0].package_name, "hellox") |
411 | + self.assertEqual( |
412 | + self.available_to_me[0].application['package_name'], "photobomb") |
413 | |
414 | def test_reinstall_purchased_xapian(self): |
415 | db = StoreDatabase("/var/cache/software-center/xapian", self.cache) |
416 | @@ -81,12 +143,167 @@ |
417 | self.assertEqual(len(matches), 1) |
418 | for m in matches: |
419 | doc = db.xapiandb.get_document(m.docid) |
420 | - self.assertEqual(doc.get_value(XapianValues.PKGNAME), "hellox") |
421 | - self.assertEqual(doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID), "1024R/0EB12F05") |
422 | + self.assertEqual(doc.get_value(XapianValues.PKGNAME), "photobomb") |
423 | + self.assertEqual( |
424 | + doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID), |
425 | + "1024R/75254D99") |
426 | self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE), |
427 | - "deb https://username:randomp3atoken@private-ppa.launchpad.net/mvo/private-test/ubuntu maverick main #Personal access of username to private-test") |
428 | - break # only one match |
429 | - |
430 | + "deb https://username:random3atoken@" |
431 | + "private-ppa.launchpad.net/commercial-ppa-uploaders" |
432 | + "/photobomb/ubuntu natty main") |
433 | + |
434 | + |
435 | +class SoftwareCenterAgentParserTestCase(unittest.TestCase): |
436 | + |
437 | + def _make_application_parser(self, piston_application=None): |
438 | + if piston_application is None: |
439 | + piston_application = PistonResponseObject.from_dict( |
440 | + json.loads(AVAILABLE_APPS_JSON)[0]) |
441 | + return SoftwareCenterAgentParser(piston_application) |
442 | + |
443 | + def test_parses_application_from_available_apps(self): |
444 | + parser = self._make_application_parser() |
445 | + inverse_map = dict( |
446 | + (val, key) for key, val in SoftwareCenterAgentParser.MAPPING.items()) |
447 | + |
448 | + # Delete the keys which are not yet provided via the API: |
449 | + del(inverse_map['video_url']) |
450 | + |
451 | + for key in inverse_map: |
452 | + self.assertTrue(parser.has_option_desktop(inverse_map[key])) |
453 | + self.assertEqual( |
454 | + getattr(parser.sca_entry, key), |
455 | + parser.get_desktop(inverse_map[key])) |
456 | + |
457 | + def test_keys_not_provided_by_api(self): |
458 | + parser = self._make_application_parser() |
459 | + |
460 | + self.assertFalse(parser.has_option_desktop('Video-Url')) |
461 | + self.assertTrue(parser.has_option_desktop('Type')) |
462 | + self.assertEqual('Application', parser.get_desktop('Type')) |
463 | + |
464 | + def test_thumbnail_is_screenshot(self): |
465 | + parser = self._make_application_parser() |
466 | + |
467 | + self.assertEqual( |
468 | + "http://software-center.ubuntu.com/site_media/screenshots/" |
469 | + "2011/05/fluendo-dvd-maverick_.png", |
470 | + parser.get_desktop('Thumbnail-Url')) |
471 | + |
472 | + def test_extracts_description(self): |
473 | + parser = self._make_application_parser() |
474 | + |
475 | + self.assertEqual("Play DVD-Videos", parser.get_desktop('Comment')) |
476 | + self.assertEqual( |
477 | + "Fluendo DVD Player is a software application specially designed " |
478 | + "to\r\nreproduce DVD on Linux/Unix platforms, which provides end " |
479 | + "users with\r\nhigh quality standards.\r\n\r\nThe following " |
480 | + "features are provided:\r\n* Full DVD Playback\r\n* DVD Menu " |
481 | + "support\r\n* Fullscreen support\r\n* Dolby Digital pass-through" |
482 | + "\r\n* Dolby Digital 5.1 output and stereo downmixing support\r\n" |
483 | + "* Resume from last position support\r\n* Subtitle support\r\n" |
484 | + "* Audio selection support\r\n* Multiple Angles support\r\n" |
485 | + "* Support for encrypted discs\r\n" |
486 | + "* Multiregion, works in all regions\r\n" |
487 | + "* Multiple video deinterlacing algorithms", |
488 | + parser.get_desktop('Description')) |
489 | + |
490 | + def test_desktop_categories_uses_department(self): |
491 | + parser = self._make_application_parser() |
492 | + |
493 | + self.assertEqual([u'DEPARTMENT:Sound & Video', "AudioVideo"], |
494 | + parser.get_desktop_categories()) |
495 | + |
496 | + def test_desktop_categories_no_department(self): |
497 | + piston_app = PistonResponseObject.from_dict( |
498 | + json.loads(AVAILABLE_APPS_JSON)[0]) |
499 | + del(piston_app.department) |
500 | + parser = self._make_application_parser(piston_app) |
501 | + |
502 | + self.assertEqual(["AudioVideo"], parser.get_desktop_categories()) |
503 | + |
504 | + def test_magic_channel(self): |
505 | + parser = self._make_application_parser() |
506 | + |
507 | + self.assertEqual( |
508 | + AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME, |
509 | + parser.get_desktop('Channel')) |
510 | + |
511 | + |
512 | +class SCAPurchasedApplicationParserTestCase(unittest.TestCase): |
513 | + |
514 | + def _make_application_parser(self, piston_subscription=None): |
515 | + if piston_subscription is None: |
516 | + piston_subscription = PistonResponseObject.from_dict( |
517 | + json.loads(SUBSCRIPTIONS_FOR_ME_JSON)[0]) |
518 | + |
519 | + return SCAPurchasedApplicationParser(piston_subscription) |
520 | + |
521 | + def test_get_desktop_subscription(self): |
522 | + parser = self._make_application_parser() |
523 | + |
524 | + expected_results = { |
525 | + "Deb-Line": "deb https://username:random3atoken@" |
526 | + "private-ppa.launchpad.net/commercial-ppa-uploaders" |
527 | + "/photobomb/ubuntu natty main", |
528 | + "Purchased-Date": "2011-09-16 06:37:52", |
529 | + } |
530 | + for key in expected_results: |
531 | + result = parser.get_desktop(key) |
532 | + self.assertEqual(expected_results[key], result) |
533 | + |
534 | + def test_get_desktop_application(self): |
535 | + # The parser passes application attributes through to |
536 | + # an application parser for handling. |
537 | + parser = self._make_application_parser() |
538 | + |
539 | + expected_results = { |
540 | + "Name": "Photobomb (already purchased)", |
541 | + "Package": "photobomb", |
542 | + "Signing-Key-Id": "1024R/75254D99", |
543 | + "PPA": "commercial-ppa-uploaders/photobomb", |
544 | + } |
545 | + for key in expected_results.keys(): |
546 | + result = parser.get_desktop(key) |
547 | + self.assertEqual(expected_results[key], result) |
548 | + |
549 | + def test_has_option_desktop_includes_app_keys(self): |
550 | + # The SCAPurchasedApplicationParser handles application keys also |
551 | + # (passing them through to the composited application parser). |
552 | + parser = self._make_application_parser() |
553 | + |
554 | + for key in ('Name', 'Package', 'Signing-Key-Id', 'PPA'): |
555 | + self.assertTrue(parser.has_option_desktop(key)) |
556 | + for key in ('Deb-Line', 'Purchased-Date'): |
557 | + self.assertTrue(parser.has_option_desktop(key), |
558 | + 'Key: {0} was not an option.'.format(key)) |
559 | + |
560 | + def test_license_key_present(self): |
561 | + piston_subscription = PistonResponseObject.from_dict( |
562 | + json.loads(SUBSCRIPTIONS_FOR_ME_JSON)[0]) |
563 | + piston_subscription.license_key = 'abcd' |
564 | + piston_subscription.license_key_path = '/foo' |
565 | + parser = self._make_application_parser(piston_subscription) |
566 | + |
567 | + self.assertTrue(parser.has_option_desktop('License-Key')) |
568 | + self.assertTrue(parser.has_option_desktop('License-Key-Path')) |
569 | + self.assertEqual('abcd', parser.get_desktop('License-Key')) |
570 | + self.assertEqual('/foo', parser.get_desktop('License-Key-Path')) |
571 | + |
572 | + def test_license_key_not_present(self): |
573 | + parser = self._make_application_parser() |
574 | + |
575 | + self.assertFalse(parser.has_option_desktop('License-Key')) |
576 | + self.assertFalse(parser.has_option_desktop('License-Key-Path')) |
577 | + |
578 | + def test_magic_channel(self): |
579 | + parser = self._make_application_parser() |
580 | + |
581 | + self.assertEqual( |
582 | + PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME, |
583 | + parser.get_desktop('Channel')) |
584 | + |
585 | + |
586 | if __name__ == "__main__": |
587 | logging.basicConfig(level=logging.DEBUG) |
588 | unittest.main() |
Hi Michael! This is really nice work and the additional changes that you are working on sound just right.
As we discussed in IRC earlier, there's one problem that I found in the current branch, and that is that with this change we getting an empty list for For Purchase apps. THis appears to be an issue with setting the "magic channel" to PURCHASED_ NEEDS_REINSTALL _MAGIC_ CHANNEL_ NAME in the SoftwareCenterA gentParser class.
I made a quick attempt to fix this in the following branch:
lp:~gary-lasker/software-center/noodles-833982-purchased-app-not-available
You can see that I separated the two "magic channel" settings into the corresponding parser classes. This indeed restores the apps for purchase , but now I'm getting an empty list for the previous purchases, so it's not correct yet. Unfortunately, I had to leave for the day and and could not go further, but hopefully it's something simple and it won't be too hard to finish up tomorrow. In any case, I hope this branch is some help. I'll check in with you when I get back online in the morning.
Btw, I'm testing using the Oneiric apps for purchase by using:
SOFTWARE_ CENTER_ DISTRO_ CODENAME= "oneiric" PYTHONPATH=. python ./software-center
Thanks!