Merge lp:~michael.nelson/software-center/833982-purchased-app-not-available into lp:software-center

Proposed by Michael Nelson
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
Reviewer Review Type Date Requested Status
Gary Lasker (community) Approve
Review via email: mp+88827@code.launchpad.net

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 SoftwareCenterAgentParser was expecting a json object from the 1.0 'my subscriptions' api - where the attributes for the application related to each subscription were flattened into the one dict.

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_for_me call).

Also done:
 * Removed the 'icon' attribute from the mapping, as we no-longer support it via the API (check with mvo).
 * Fixed get_desktop_categories

Testing
=======
cd test;make
Note: I get two failures (test_reviews, test_database) as well as python-coverage crashing while running the tests: http://paste.ubuntu.com/807433/

Anyway, still to do in a follow-up branch coming shortly:
 * Check if we can update PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME = "For Purchase" (value returned via API). How will this affect existing clients, if at all?
 * remove the need for the SCASubscriptionParser to both inherit from and encapsulate the SoftwareCenterAgentParser,
 * Filter results of the my_subscriptions call (only keep Complete ones), as currently it looks like this: http://people.canonical.com/~michaeln/tmp/833982-previous-purchases-working-but-dupes.png
 * Rename s/SoftwareCenterAgentParser/SCAApplicationParser for consistency
 * Replace the MockAvailableForMeItem/List with real PistonResponseObjects

To post a comment you must log in.
2676. By Michael Nelson

REFACTOR: replaced help/* in bzr ignore, updated requirement in README.

Revision history for this message
Gary Lasker (gary-lasker) 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_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME in the SoftwareCenterAgentParser 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!

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.

Revision history for this message
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_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME in the
> SoftwareCenterAgentParser class.

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.

Revision history for this message
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!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2011-05-27 08:25:15 +0000
+++ .bzrignore 2012-01-18 09:00:32 +0000
@@ -17,3 +17,8 @@
17data/xapian/spelling.baseA17data/xapian/spelling.baseA
18data/xapian/spelling.baseB18data/xapian/spelling.baseB
19build/19build/
20test/.coverage
21test/coverage_html
22test/coverage_summary
23test/output
24help/*
2025
=== modified file 'README'
--- README 2011-07-27 16:43:26 +0000
+++ README 2012-01-18 09:00:32 +0000
@@ -7,6 +7,23 @@
77
8All non UI code must come with tests in the test/ subdirectoy.8All non UI code must come with tests in the test/ subdirectoy.
99
10To setup your development environment, you'll need to ensure the following
11extra packages are installed:
12
13sudo apt-get install xvfb python-coverage python-mock python-aptdaemon.test \
14 python-qt4 python-unittest2
15sudo apt-get build-dep software-center
16
17You can then run tests with:
18
19cd test;make
20
21You can run a developer instance with:
22
23python setup.py build
24./software-center
25
26
10== query parser ==27== query parser ==
1128
12The query parser understands :29The query parser understands :
@@ -15,7 +32,7 @@
1532
16== aptdaemon ==33== aptdaemon ==
17 * the dbus limits for the system bus are rather low, this means that34 * the dbus limits for the system bus are rather low, this means that
18 adding <limit name="max_match_rules_per_connection">512</limit> 35 adding <limit name="max_match_rules_per_connection">512</limit>
19 and using something bigger than 512 is a good idea36 and using something bigger than 512 is a good idea
2037
21== environment ==38== environment ==
@@ -54,7 +71,7 @@
54SCPkgname - e.g. "gimp"71SCPkgname - e.g. "gimp"
5572
56Additional .menu files can be added in:73Additional .menu files can be added in:
57/usr/share/app-install/menu.d 74/usr/share/app-install/menu.d
58that software-center will read and parse.75that software-center will read and parse.
5976
60== XAPIAN ==77== XAPIAN ==
@@ -66,9 +83,9 @@
66AS - archive pocket (main)83AS - archive pocket (main)
67AE - archive section (mail, base, ...)84AE - archive section (mail, base, ...)
68AC - category (AudioVideo)85AC - category (AudioVideo)
69AM - MimeType (application/x-ogg) 86AM - MimeType (application/x-ogg)
70AT - type (Application)87AT - type (Application)
71AH - channel 88AH - channel
7289
7390
74The following values are used:91The following values are used:
@@ -89,5 +106,5 @@
89XAPIAN_VALUE_PURCHASED_DATE - the data a for-pay app was purchased (only available after the software-center-agent server was queried)106XAPIAN_VALUE_PURCHASED_DATE - the data a for-pay app was purchased (only available after the software-center-agent server was queried)
90XAPIAN_VALUE_SCREENSHOT_URL - a (optional) screenshot url that overrides the default107XAPIAN_VALUE_SCREENSHOT_URL - a (optional) screenshot url that overrides the default
91XAPIAN_VALUE_ICON_NEEDS_DOWNLOAD - icon needs to be fetched108XAPIAN_VALUE_ICON_NEEDS_DOWNLOAD - icon needs to be fetched
92XAPIAN_VALUE_THUMBNAIL_URL - thumbnail url 109XAPIAN_VALUE_THUMBNAIL_URL - thumbnail url
93110
94111
=== modified file 'softwarecenter/db/update.py'
--- softwarecenter/db/update.py 2012-01-05 08:52:42 +0000
+++ softwarecenter/db/update.py 2012-01-18 09:00:32 +0000
@@ -26,6 +26,7 @@
26import time26import time
2727
28from gi.repository import GObject28from gi.repository import GObject
29from piston_mini_client import PistonResponseObject
2930
30from softwarecenter.utils import utf831from softwarecenter.utils import utf8
3132
@@ -131,6 +132,7 @@
131 def desktopf(self):132 def desktopf(self):
132 """ return the file that the AppInfo comes from """133 """ return the file that the AppInfo comes from """
133134
135
134class SoftwareCenterAgentParser(AppInfoParserBase):136class SoftwareCenterAgentParser(AppInfoParserBase):
135 """ map the data we get from the software-center-agent """137 """ map the data we get from the software-center-agent """
136138
@@ -140,20 +142,17 @@
140 'Package' : 'package_name',142 'Package' : 'package_name',
141 'Categories' : 'categories',143 'Categories' : 'categories',
142 'Channel' : 'channel',144 'Channel' : 'channel',
143 'Deb-Line' : 'deb_line',
144 'Signing-Key-Id' : 'signing_key_id',145 'Signing-Key-Id' : 'signing_key_id',
145 'License' : 'license',146 'License' : 'license',
146 'Date-Published' : 'date_published',147 'Date-Published' : 'date_published',
147 'Purchased-Date' : 'purchase_date',
148 'License-Key' : 'license_key',
149 'License-Key-Path' : 'license_key_path',
150 'PPA' : 'archive_id',148 'PPA' : 'archive_id',
151 'Icon' : 'icon',
152 'Screenshot-Url' : 'screenshot_url',149 'Screenshot-Url' : 'screenshot_url',
153 'Thumbnail-Url' : 'thumbnail_url',150 'Thumbnail-Url' : 'thumbnail_url',
154 'Video-Url' : 'video_url',151 'Video-Url' : 'video_url',
155 'Icon-Url' : 'icon_url',152 'Icon-Url' : 'icon_url',
156 'Support-Url' : 'support_url',153 'Support-Url' : 'support_url',
154 'Description' : 'Description',
155 'Comment' : 'Comment',
157 }156 }
158157
159 # map from requested key to a static data element158 # map from requested key to a static data element
@@ -164,6 +163,7 @@
164 self.sca_entry = sca_entry163 self.sca_entry = sca_entry
165 self.origin = "software-center-agent"164 self.origin = "software-center-agent"
166 self._apply_exceptions()165 self._apply_exceptions()
166
167 def _apply_exceptions(self):167 def _apply_exceptions(self):
168 # for items from the agent, we use the full-size screenshot for168 # for items from the agent, we use the full-size screenshot for
169 # the thumbnail and scale it for display, this is done because169 # the thumbnail and scale it for display, this is done because
@@ -172,25 +172,86 @@
172 not hasattr(self.sca_entry, "thumbnail_url")):172 not hasattr(self.sca_entry, "thumbnail_url")):
173 self.sca_entry.thumbnail_url = self.sca_entry.screenshot_url173 self.sca_entry.thumbnail_url = self.sca_entry.screenshot_url
174 if hasattr(self.sca_entry, "description"):174 if hasattr(self.sca_entry, "description"):
175 self.sca_entry.Comment = self.sca_entry.description.split("\n")[0]175 self.sca_entry.Comment = self.sca_entry.description.split("\n")[0].strip()
176 self.sca_entry.Description = "\n".join(self.sca_entry.description.split("\n")[1:])176 self.sca_entry.Description = "\n".join(
177 self.sca_entry.description.split("\n")[1:]).strip()
178 # WARNING: item.name needs to be different than
179 # the item.name in the DB otherwise the DB
180 # gets confused about (appname, pkgname) duplication
181 self.sca_entry.name = utf8(_("%s (already purchased)")) % utf8(
182 self.sca_entry.name)
183
184 # XXX 2012-01-16 bug=917109
185 # We can remove these work-arounds once the above bug is fixed on
186 # the server. Until then, we fake a channel here and empty category
187 # to make the parser happy. Note: available_apps api call includes
188 # these already, it's just the apps with subscriptions_for_me which
189 # don't currently.
190 self.sca_entry.channel = AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME
191 if not hasattr(self.sca_entry, 'categories'):
192 self.sca_entry.categories = ""
193
177 def get_desktop(self, key, translated=True):194 def get_desktop(self, key, translated=True):
178 if key in self.STATIC_DATA:195 if key in self.STATIC_DATA:
179 return self.STATIC_DATA[key]196 return self.STATIC_DATA[key]
180 return getattr(self.sca_entry, self._apply_mapping(key))197 return getattr(self.sca_entry, self._apply_mapping(key))
198
181 def get_desktop_categories(self):199 def get_desktop_categories(self):
182 try:200 try:
183 return ['DEPARTMENT:' + self.sca_entry.department[-1]] + self._get_desktop_list("Categories")201 return ['DEPARTMENT:' + self.sca_entry.department[-1]] + self._get_desktop_list("Categories")
184 except:202 except:
185 return self._get_desktop_list("Categories")203 return self._get_desktop_list("Categories")
204
186 def has_option_desktop(self, key):205 def has_option_desktop(self, key):
187 return (key in self.STATIC_DATA or206 return (key in self.STATIC_DATA or
188 hasattr(self.sca_entry, self._apply_mapping(key)))207 hasattr(self.sca_entry, self._apply_mapping(key)))
208
189 @property209 @property
190 def desktopf(self):210 def desktopf(self):
191 return self.origin211 return self.origin
192212
193213
214class SCAPurchasedApplicationParser(SoftwareCenterAgentParser):
215 """A subscription has its own attrs with a subset of the app attributes.
216
217 We inherit from SoftwareCenterAgentParser so that we get other methods for
218 free, and we compose a SoftwareCenterAgentParser because we need the
219 get_desktop method with the correct MAPPING. TODO: There must be a nicer
220 way to organise this so that we don't need both inheritance and composition
221 for a DRY implementation.
222 """
223
224 def __init__(self, sca_subscription):
225 # The sca_subscription is a PistonResponseObject, whereas any child
226 # objects are normal Python dicts.
227 self.sca_subscription = sca_subscription
228 self.application_parser = SoftwareCenterAgentParser(
229 PistonResponseObject.from_dict(sca_subscription.application))
230 super(SCAPurchasedApplicationParser, self).__init__(
231 PistonResponseObject.from_dict(sca_subscription.application))
232 self.application_parser.sca_entry.channel = (
233 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME)
234
235 MAPPING = { 'Deb-Line' : 'deb_line',
236 'Purchased-Date' : 'purchase_date',
237 'License-Key' : 'license_key',
238 'License-Key-Path' : 'license_key_path',
239 }
240
241 def get_desktop(self, key, translated=True):
242 if self.application_parser.has_option_desktop(key):
243 return self.application_parser.get_desktop(key, translated)
244
245 return getattr(self.sca_subscription, self._apply_mapping(key))
246
247 def has_option_desktop(self, key):
248 subscription_has_option = hasattr(
249 self.sca_subscription, self._apply_mapping(key))
250 application_has_option = (
251 self.application_parser.has_option_desktop(key))
252 return subscription_has_option or application_has_option
253
254
194class JsonTagSectionParser(AppInfoParserBase):255class JsonTagSectionParser(AppInfoParserBase):
195256
196 MAPPING = { 'Name' : 'application_name',257 MAPPING = { 'Name' : 'application_name',
@@ -505,15 +566,7 @@
505 # continue566 # continue
506 # index the item567 # index the item
507 try:568 try:
508 # we fake a channel here569 parser = SCAPurchasedApplicationParser(item)
509 item.channel = PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME
510 # and empty category to make the parser happy
511 item.categories = ""
512 # WARNING: item.name needs to be different than
513 # the item.name in the DB otherwise the DB
514 # gets confused about (appname, pkgname) duplication
515 item.name = utf8(_("%s (already purchased)")) % utf8(item.name)
516 parser = SoftwareCenterAgentParser(item)
517 index_app_info_from_parser(parser, db_purchased, cache)570 index_app_info_from_parser(parser, db_purchased, cache)
518 except Exception as e:571 except Exception as e:
519 LOG.exception("error processing: %s " % e)572 LOG.exception("error processing: %s " % e)
@@ -560,8 +613,6 @@
560 while context.pending():613 while context.pending():
561 context.iteration()614 context.iteration()
562 try:615 try:
563 # magic channel
564 entry.channel = AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME
565 # now the normal parser616 # now the normal parser
566 parser = SoftwareCenterAgentParser(entry)617 parser = SoftwareCenterAgentParser(entry)
567 index_app_info_from_parser(parser, db, cache)618 index_app_info_from_parser(parser, db, cache)
@@ -663,20 +714,21 @@
663 # date published714 # date published
664 if parser.has_option_desktop("X-AppInstall-Date-Published"):715 if parser.has_option_desktop("X-AppInstall-Date-Published"):
665 date_published = parser.get_desktop("X-AppInstall-Date-Published")716 date_published = parser.get_desktop("X-AppInstall-Date-Published")
666 # strip the subseconds from the end of the published date string717 if date_published:
667 date_published = str(date_published).split(".")[0]718 # strip the subseconds from the end of the published date string
668 doc.add_value(XapianValues.DATE_PUBLISHED,719 date_published = str(date_published).split(".")[0]
669 date_published)720 doc.add_value(XapianValues.DATE_PUBLISHED,
670 # we use the date published value for the cataloged time as well721 date_published)
671 if "catalogedtime" in axi_values:722 # we use the date published value for the cataloged time as well
672 LOG.debug(723 if "catalogedtime" in axi_values:
673 ("pkgname: %s, date_published cataloged time is: %s" %724 LOG.debug(
674 (pkgname, parser.get_desktop("date_published"))))725 ("pkgname: %s, date_published cataloged time is: %s" %
675 date_published_sec = time.mktime(726 (pkgname, parser.get_desktop("date_published"))))
676 time.strptime(date_published,727 date_published_sec = time.mktime(
677 "%Y-%m-%d %H:%M:%S"))728 time.strptime(date_published,
678 doc.add_value(axi_values["catalogedtime"], 729 "%Y-%m-%d %H:%M:%S"))
679 xapian.sortable_serialise(date_published_sec))730 doc.add_value(axi_values["catalogedtime"],
731 xapian.sortable_serialise(date_published_sec))
680 # purchased date732 # purchased date
681 if parser.has_option_desktop("X-AppInstall-Purchased-Date"):733 if parser.has_option_desktop("X-AppInstall-Purchased-Date"):
682 date = parser.get_desktop("X-AppInstall-Purchased-Date")734 date = parser.get_desktop("X-AppInstall-Purchased-Date")
683735
=== modified file 'test/test_reinstall_purchased.py'
--- test/test_reinstall_purchased.py 2012-01-16 14:42:49 +0000
+++ test/test_reinstall_purchased.py 2012-01-18 09:00:32 +0000
@@ -7,28 +7,89 @@
7import unittest7import unittest
8import xapian8import xapian
99
10from piston_mini_client import PistonResponseObject
10from testutils import setup_test_env11from testutils import setup_test_env
11setup_test_env()12setup_test_env()
12from softwarecenter.enums import XapianValues13
14from softwarecenter.enums import (XapianValues,
15 AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,
16 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME,
17 )
13from softwarecenter.db.database import StoreDatabase18from softwarecenter.db.database import StoreDatabase
14from softwarecenter.db.update import add_from_purchased_but_needs_reinstall_data19from softwarecenter.db.update import (
20 add_from_purchased_but_needs_reinstall_data,
21 SCAPurchasedApplicationParser,
22 SoftwareCenterAgentParser,
23 )
1524
16# from25# Example taken from running:
17# https://wiki.canonical.com/Ubuntu/SoftwareCenter/10.10/Roadmap/SoftwareCenterAgent26# PYTHONPATH=. utils/piston-helpers/piston_generic_helper.py --output=pickle \
18AVAILABLE_FOR_ME_JSON = """27# --debug --needs-auth SoftwareCenterAgentAPI subscriptions_for_me
19[28# then:
20 {29# f = open('my_subscriptions.pickle')
21 "archive_id": "mvo/private-test",30# subscriptions = pickle.load(f)
22 "deb_line": "deb https://username:randomp3atoken@private-ppa.launchpad.net/mvo/private-test/ubuntu maverick main #Personal access of username to private-test",31# completed_subs = [subs for subs in subscriptions if subs.state=='Complete']
23 "purchase_price": "19.95",32# completed_subs[0].__dict__
24 "purchase_date": "2010-06-24 20:08:23",33SUBSCRIPTIONS_FOR_ME_JSON = """
25 "name": "Ubiteme",34[
26 "description": "One of the best strategy games you\'ll ever play!",35 {
27 "package_name": "hellox",36 "deb_line": "deb https://username:random3atoken@private-ppa.launchpad.net/commercial-ppa-uploaders/photobomb/ubuntu natty main",
28 "signing_key_id": "1024R/0EB12F05",37 "purchase_price": "2.99",
29 "series": {"natty": ["i386", "amd64"], 38 "purchase_date": "2011-09-16 06:37:52",
30 "maverick": ["i386", "amd64"], 39 "state": "Complete",
31 "lucid": ["i386", "amd64"]}40 "failures": [],
41 "open_id": "https://login.ubuntu.com/+id/ABCDEF",
42 "application": {
43 "archive_id": "commercial-ppa-uploaders/photobomb",
44 "signing_key_id": "1024R/75254D99",
45 "name": "Photobomb",
46 "package_name": "photobomb",
47 "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."
48 },
49 "distro_series": {"code_name": "natty", "version": "11.04"}
50 }
51]
52"""
53# Taken directly from:
54# https://software-center.ubuntu.com/api/2.0/applications/en/ubuntu/oneiric/i386/
55AVAILABLE_APPS_JSON = """
56[
57 {
58 "archive_id": "commercial-ppa-uploaders/fluendo-dvd",
59 "signing_key_id": "1024R/75254D99",
60 "license": "Proprietary",
61 "name": "Fluendo DVD Player",
62 "package_name": "fluendo-dvd",
63 "support_url": "",
64 "series": {
65 "maverick": [
66 "i386",
67 "amd64"
68 ],
69 "natty": [
70 "i386",
71 "amd64"
72 ],
73 "oneiric": [
74 "i386",
75 "amd64"
76 ]
77 },
78 "price": "24.95",
79 "demo": null,
80 "date_published": "2011-12-05 18:43:21.653868",
81 "status": "Published",
82 "channel": "For Purchase",
83 "icon_data": "...",
84 "department": [
85 "Sound & Video"
86 ],
87 "archive_root": "https://private-ppa.launchpad.net/",
88 "screenshot_url": "http://software-center.ubuntu.com/site_media/screenshots/2011/05/fluendo-dvd-maverick_.png",
89 "tos_url": "https://software-center.ubuntu.com/licenses/3/",
90 "icon_url": "http://software-center.ubuntu.com/site_media/icons/2011/05/fluendo-dvd.png",
91 "categories": "AudioVideo",
92 "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"
32 }93 }
33]94]
34"""95"""
@@ -39,11 +100,11 @@
39 setattr(self, key, value)100 setattr(self, key, value)
40 self.MimeType = ""101 self.MimeType = ""
41 self.department = []102 self.department = []
42 103
43class MockAvailableForMeList(list):104class MockAvailableForMeList(list):
44105
45 def __init__(self):106 def __init__(self):
46 alist = json.loads(AVAILABLE_FOR_ME_JSON)107 alist = json.loads(SUBSCRIPTIONS_FOR_ME_JSON)
47 for entry_dict in alist:108 for entry_dict in alist:
48 self.append(MockAvailableForMeItem(entry_dict))109 self.append(MockAvailableForMeItem(entry_dict))
49110
@@ -62,7 +123,8 @@
62 def test_reinstall_purchased_mock(self):123 def test_reinstall_purchased_mock(self):
63 # test if the mocks are ok124 # test if the mocks are ok
64 self.assertEqual(len(self.available_to_me), 1)125 self.assertEqual(len(self.available_to_me), 1)
65 self.assertEqual(self.available_to_me[0].package_name, "hellox")126 self.assertEqual(
127 self.available_to_me[0].application['package_name'], "photobomb")
66128
67 def test_reinstall_purchased_xapian(self):129 def test_reinstall_purchased_xapian(self):
68 db = StoreDatabase("/var/cache/software-center/xapian", self.cache)130 db = StoreDatabase("/var/cache/software-center/xapian", self.cache)
@@ -81,12 +143,167 @@
81 self.assertEqual(len(matches), 1)143 self.assertEqual(len(matches), 1)
82 for m in matches:144 for m in matches:
83 doc = db.xapiandb.get_document(m.docid)145 doc = db.xapiandb.get_document(m.docid)
84 self.assertEqual(doc.get_value(XapianValues.PKGNAME), "hellox")146 self.assertEqual(doc.get_value(XapianValues.PKGNAME), "photobomb")
85 self.assertEqual(doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID), "1024R/0EB12F05")147 self.assertEqual(
148 doc.get_value(XapianValues.ARCHIVE_SIGNING_KEY_ID),
149 "1024R/75254D99")
86 self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE),150 self.assertEqual(doc.get_value(XapianValues.ARCHIVE_DEB_LINE),
87 "deb https://username:randomp3atoken@private-ppa.launchpad.net/mvo/private-test/ubuntu maverick main #Personal access of username to private-test")151 "deb https://username:random3atoken@"
88 break # only one match152 "private-ppa.launchpad.net/commercial-ppa-uploaders"
89 153 "/photobomb/ubuntu natty main")
154
155
156class SoftwareCenterAgentParserTestCase(unittest.TestCase):
157
158 def _make_application_parser(self, piston_application=None):
159 if piston_application is None:
160 piston_application = PistonResponseObject.from_dict(
161 json.loads(AVAILABLE_APPS_JSON)[0])
162 return SoftwareCenterAgentParser(piston_application)
163
164 def test_parses_application_from_available_apps(self):
165 parser = self._make_application_parser()
166 inverse_map = dict(
167 (val, key) for key, val in SoftwareCenterAgentParser.MAPPING.items())
168
169 # Delete the keys which are not yet provided via the API:
170 del(inverse_map['video_url'])
171
172 for key in inverse_map:
173 self.assertTrue(parser.has_option_desktop(inverse_map[key]))
174 self.assertEqual(
175 getattr(parser.sca_entry, key),
176 parser.get_desktop(inverse_map[key]))
177
178 def test_keys_not_provided_by_api(self):
179 parser = self._make_application_parser()
180
181 self.assertFalse(parser.has_option_desktop('Video-Url'))
182 self.assertTrue(parser.has_option_desktop('Type'))
183 self.assertEqual('Application', parser.get_desktop('Type'))
184
185 def test_thumbnail_is_screenshot(self):
186 parser = self._make_application_parser()
187
188 self.assertEqual(
189 "http://software-center.ubuntu.com/site_media/screenshots/"
190 "2011/05/fluendo-dvd-maverick_.png",
191 parser.get_desktop('Thumbnail-Url'))
192
193 def test_extracts_description(self):
194 parser = self._make_application_parser()
195
196 self.assertEqual("Play DVD-Videos", parser.get_desktop('Comment'))
197 self.assertEqual(
198 "Fluendo DVD Player is a software application specially designed "
199 "to\r\nreproduce DVD on Linux/Unix platforms, which provides end "
200 "users with\r\nhigh quality standards.\r\n\r\nThe following "
201 "features are provided:\r\n* Full DVD Playback\r\n* DVD Menu "
202 "support\r\n* Fullscreen support\r\n* Dolby Digital pass-through"
203 "\r\n* Dolby Digital 5.1 output and stereo downmixing support\r\n"
204 "* Resume from last position support\r\n* Subtitle support\r\n"
205 "* Audio selection support\r\n* Multiple Angles support\r\n"
206 "* Support for encrypted discs\r\n"
207 "* Multiregion, works in all regions\r\n"
208 "* Multiple video deinterlacing algorithms",
209 parser.get_desktop('Description'))
210
211 def test_desktop_categories_uses_department(self):
212 parser = self._make_application_parser()
213
214 self.assertEqual([u'DEPARTMENT:Sound & Video', "AudioVideo"],
215 parser.get_desktop_categories())
216
217 def test_desktop_categories_no_department(self):
218 piston_app = PistonResponseObject.from_dict(
219 json.loads(AVAILABLE_APPS_JSON)[0])
220 del(piston_app.department)
221 parser = self._make_application_parser(piston_app)
222
223 self.assertEqual(["AudioVideo"], parser.get_desktop_categories())
224
225 def test_magic_channel(self):
226 parser = self._make_application_parser()
227
228 self.assertEqual(
229 AVAILABLE_FOR_PURCHASE_MAGIC_CHANNEL_NAME,
230 parser.get_desktop('Channel'))
231
232
233class SCAPurchasedApplicationParserTestCase(unittest.TestCase):
234
235 def _make_application_parser(self, piston_subscription=None):
236 if piston_subscription is None:
237 piston_subscription = PistonResponseObject.from_dict(
238 json.loads(SUBSCRIPTIONS_FOR_ME_JSON)[0])
239
240 return SCAPurchasedApplicationParser(piston_subscription)
241
242 def test_get_desktop_subscription(self):
243 parser = self._make_application_parser()
244
245 expected_results = {
246 "Deb-Line": "deb https://username:random3atoken@"
247 "private-ppa.launchpad.net/commercial-ppa-uploaders"
248 "/photobomb/ubuntu natty main",
249 "Purchased-Date": "2011-09-16 06:37:52",
250 }
251 for key in expected_results:
252 result = parser.get_desktop(key)
253 self.assertEqual(expected_results[key], result)
254
255 def test_get_desktop_application(self):
256 # The parser passes application attributes through to
257 # an application parser for handling.
258 parser = self._make_application_parser()
259
260 expected_results = {
261 "Name": "Photobomb (already purchased)",
262 "Package": "photobomb",
263 "Signing-Key-Id": "1024R/75254D99",
264 "PPA": "commercial-ppa-uploaders/photobomb",
265 }
266 for key in expected_results.keys():
267 result = parser.get_desktop(key)
268 self.assertEqual(expected_results[key], result)
269
270 def test_has_option_desktop_includes_app_keys(self):
271 # The SCAPurchasedApplicationParser handles application keys also
272 # (passing them through to the composited application parser).
273 parser = self._make_application_parser()
274
275 for key in ('Name', 'Package', 'Signing-Key-Id', 'PPA'):
276 self.assertTrue(parser.has_option_desktop(key))
277 for key in ('Deb-Line', 'Purchased-Date'):
278 self.assertTrue(parser.has_option_desktop(key),
279 'Key: {0} was not an option.'.format(key))
280
281 def test_license_key_present(self):
282 piston_subscription = PistonResponseObject.from_dict(
283 json.loads(SUBSCRIPTIONS_FOR_ME_JSON)[0])
284 piston_subscription.license_key = 'abcd'
285 piston_subscription.license_key_path = '/foo'
286 parser = self._make_application_parser(piston_subscription)
287
288 self.assertTrue(parser.has_option_desktop('License-Key'))
289 self.assertTrue(parser.has_option_desktop('License-Key-Path'))
290 self.assertEqual('abcd', parser.get_desktop('License-Key'))
291 self.assertEqual('/foo', parser.get_desktop('License-Key-Path'))
292
293 def test_license_key_not_present(self):
294 parser = self._make_application_parser()
295
296 self.assertFalse(parser.has_option_desktop('License-Key'))
297 self.assertFalse(parser.has_option_desktop('License-Key-Path'))
298
299 def test_magic_channel(self):
300 parser = self._make_application_parser()
301
302 self.assertEqual(
303 PURCHASED_NEEDS_REINSTALL_MAGIC_CHANNEL_NAME,
304 parser.get_desktop('Channel'))
305
306
90if __name__ == "__main__":307if __name__ == "__main__":
91 logging.basicConfig(level=logging.DEBUG)308 logging.basicConfig(level=logging.DEBUG)
92 unittest.main()309 unittest.main()