Merge lp:~matiasb/click-toolbelt/get-channel-details into lp:click-toolbelt

Proposed by Matias Bordese
Status: Merged
Approved by: Matias Bordese
Approved revision: 41
Merged at revision: 40
Proposed branch: lp:~matiasb/click-toolbelt/get-channel-details
Merge into: lp:click-toolbelt
Diff against target: 224 lines (+193/-1)
4 files modified
click_toolbelt/channels.py (+64/-0)
click_toolbelt/tests/test_channels.py (+128/-0)
click_toolbelt/tests/test_upload.py (+0/-1)
setup.py (+1/-0)
To merge this branch: bzr merge lp:~matiasb/click-toolbelt/get-channel-details
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+279514@code.launchpad.net

Commit message

Added initial command for channels handling.

To post a comment you must log in.
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'click_toolbelt/channels.py'
--- click_toolbelt/channels.py 1970-01-01 00:00:00 +0000
+++ click_toolbelt/channels.py 2015-12-04 18:34:17 +0000
@@ -0,0 +1,64 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU General Public License version 3 (see the file LICENSE).
3from __future__ import absolute_import, unicode_literals
4import logging
5import os
6
7from click_toolbelt.common import (
8 AuthenticatedCommand,
9 CommandError,
10)
11from click_toolbelt.compat import urljoin
12from click_toolbelt.constants import MYAPPS_API_ROOT_URL
13
14
15class Channels(AuthenticatedCommand):
16 """Get/Update channels configuration for a package."""
17
18 log = logging.getLogger(__name__)
19
20 def get_parser(self, prog_name):
21 parser = super(Channels, self).get_parser(prog_name)
22 parser.add_argument('package_name')
23 return parser
24
25 def get_channels(self, session, package_name):
26 """Get current channels config for package through API."""
27 result = {'success': False, 'errors': [], 'data': None}
28 myapps_url = os.environ.get('MYAPPS_API_ROOT_URL', MYAPPS_API_ROOT_URL)
29 channels_endpoint = urljoin(
30 myapps_url, 'package-channels/%s/' % package_name)
31
32 response = session.get(channels_endpoint)
33 if response.ok:
34 result['success'] = True
35 result['data'] = response.json()
36 else:
37 result['errors'] = [response.text]
38 return result
39
40 def take_action(self, parsed_args):
41 package_name = parsed_args.package_name
42
43 session = self.get_oauth_session()
44 if session is None:
45 self.log.info('No valid credentials found.')
46 # raise an exception to exit with proper code
47 raise CommandError()
48
49 result = self.get_channels(session, package_name)
50
51 if not result.get('success', False):
52 self.log.info(
53 'Could not get information. An error ocurred:\n\n%s\n\n',
54 '\n'.join(result['errors']))
55 # raise an exception to exit with proper code
56 raise CommandError()
57
58 data = result['data']
59 for config in data:
60 value = config.get('current')
61 if value is not None:
62 value = 'Sequence %d (version %s)' % (
63 value.get('sequence'), value.get('version'))
64 self.log.info('%s: %s', config.get('channel'), value)
065
=== added file 'click_toolbelt/tests/test_channels.py'
--- click_toolbelt/tests/test_channels.py 1970-01-01 00:00:00 +0000
+++ click_toolbelt/tests/test_channels.py 2015-12-04 18:34:17 +0000
@@ -0,0 +1,128 @@
1# -*- coding: utf-8 -*-
2# Copyright 2015 Canonical Ltd. This software is licensed under the
3# GNU General Public License version 3 (see the file LICENSE).
4from __future__ import absolute_import, unicode_literals
5from collections import namedtuple
6from unittest import TestCase
7
8from mock import call, patch
9
10from click_toolbelt.common import CommandError
11from click_toolbelt.channels import (
12 Channels,
13)
14
15
16class ChannelsCommandTestCase(TestCase):
17
18 def setUp(self):
19 super(ChannelsCommandTestCase, self).setUp()
20 app = None
21 args = None
22 self.command = Channels(app, args)
23
24 patcher = patch('click_toolbelt.channels.Channels.log')
25 self.mock_log = patcher.start()
26 self.addCleanup(patcher.stop)
27
28 oauth_session = 'click_toolbelt.channels.Channels.get_oauth_session'
29 patcher = patch(oauth_session)
30 self.mock_get_oauth_session = patcher.start()
31 self.mock_session = self.mock_get_oauth_session.return_value
32 self.mock_get = self.mock_session.get
33 self.mock_response = self.mock_get.return_value
34 self.addCleanup(patcher.stop)
35
36 self.parsed_args = namedtuple('parsed_args', 'package_name')
37 self.args = self.parsed_args('package.name')
38
39 self.channels_data = [
40 {'channel': 'stable', 'current': {'sequence': 2, 'version': '1'}},
41 {'channel': 'beta', 'current': {'sequence': 4, 'version': '1.5'}},
42 {'channel': 'edge', 'current': None},
43 ]
44
45 def set_channels_success_response(self):
46 self.mock_response.ok = True
47 self.mock_response.json.return_value = self.channels_data
48
49 def set_channels_error_response(self, error_msg):
50 self.mock_response.ok = False
51 self.mock_response.text = error_msg
52
53 def test_parser(self):
54 parser = self.command.get_parser('prog_name')
55 # only one argument -- the first item is the default help option
56 self.assertEqual(len(parser._actions), 2)
57 self.assertEqual(parser._actions[0].dest, 'help')
58 # package_name is optional
59 self.assertEqual(parser._actions[1].dest, 'package_name')
60 self.assertTrue(parser._actions[1].required)
61
62 def test_get_channels(self):
63 self.set_channels_success_response()
64
65 data = self.command.get_channels(self.mock_session, 'package.name')
66
67 expected = {
68 'success': True,
69 'errors': [],
70 'data': self.channels_data,
71 }
72 self.assertEqual(data, expected)
73
74 def test_get_channels_with_error_response(self):
75 error_msg = 'some error'
76 self.set_channels_error_response(error_msg)
77
78 data = self.command.get_channels(self.mock_session, 'package.name')
79
80 expected = {
81 'success': False,
82 'errors': [error_msg],
83 'data': None,
84 }
85 self.assertEqual(data, expected)
86
87 def test_get_channels_uses_environment_variables(self):
88 with patch('click_toolbelt.channels.os.environ',
89 {'MYAPPS_API_ROOT_URL': 'http://example.com'}):
90 self.command.get_channels(self.mock_session, 'package.name')
91 self.mock_get.assert_called_once_with(
92 'http://example.com/package-channels/package.name/')
93
94 def test_take_action_invalid_credentials(self):
95 self.mock_get_oauth_session.return_value = None
96
97 with self.assertRaises(CommandError):
98 self.command.take_action(self.args)
99
100 self.mock_log.info.assert_called_once_with(
101 'No valid credentials found.')
102
103 def test_take_action(self):
104 self.set_channels_success_response()
105
106 self.command.take_action(self.args)
107
108 expected_calls = []
109 for config in self.channels_data:
110 expected = None
111 upload = config['current']
112 if upload is not None:
113 expected = 'Sequence %d (version %s)' % (
114 upload['sequence'], upload['version'])
115 channel_call = call('%s: %s', config['channel'], expected)
116 expected_calls.append(channel_call)
117 self.assertEqual(self.mock_log.info.call_args_list, expected_calls)
118
119 def test_take_action_with_error(self):
120 error_msg = 'some error'
121 self.set_channels_error_response(error_msg)
122
123 with self.assertRaises(CommandError):
124 self.command.take_action(self.args)
125
126 self.mock_log.info.assert_called_once_with(
127 'Could not get information. An error ocurred:\n\n%s\n\n',
128 'some error')
0129
=== modified file 'click_toolbelt/tests/test_upload.py'
--- click_toolbelt/tests/test_upload.py 2015-12-03 14:45:57 +0000
+++ click_toolbelt/tests/test_upload.py 2015-12-04 18:34:17 +0000
@@ -5,7 +5,6 @@
5import os5import os
6import tempfile6import tempfile
7from collections import namedtuple7from collections import namedtuple
8from unittest import TestCase, skip
98
10from mock import ANY, DEFAULT, patch9from mock import ANY, DEFAULT, patch
11from requests import Response10from requests import Response
1211
=== modified file 'setup.py'
--- setup.py 2015-09-23 12:40:58 +0000
+++ setup.py 2015-12-04 18:34:17 +0000
@@ -61,6 +61,7 @@
61 'login = click_toolbelt.login:Login',61 'login = click_toolbelt.login:Login',
62 'upload = click_toolbelt.upload:Upload',62 'upload = click_toolbelt.upload:Upload',
63 'info = click_toolbelt.info:Info',63 'info = click_toolbelt.info:Info',
64 'channels = click_toolbelt.channels:Channels',
64 ],65 ],
65 },66 },
6667

Subscribers

People subscribed via source and target branches