Merge lp:~frankban/juju-quickstart/interactive-jenvs into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 110
Proposed branch: lp:~frankban/juju-quickstart/interactive-jenvs
Merge into: lp:juju-quickstart
Diff against target: 1162 lines (+528/-104)
8 files modified
quickstart/cli/ui.py (+2/-0)
quickstart/cli/views.py (+115/-19)
quickstart/manage.py (+3/-3)
quickstart/models/jenv.py (+61/-9)
quickstart/tests/cli/test_views.py (+217/-40)
quickstart/tests/helpers.py (+15/-3)
quickstart/tests/models/test_jenv.py (+86/-16)
quickstart/tests/test_manage.py (+29/-14)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/interactive-jenvs
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+244976@code.launchpad.net

Description of the change

Display the jenv environments in interactive mode.

The jenv environments are now displayed in the
interactive session, and it's possible to use them.

Also implemented a simple jenv detail view in uwrid.

Quickstart now also supports highlighting active
environments, i.e. those that are supposed to be
running because they have a corresponding jenv file
in the juju gome.

Tests: `make check`.

QA:
user the new juju, bootstrap one or more environments
with quickstart (use .venv/bin/python juju-quickstart --upload-tools
if you are using a non-local provider).
Start the interactive session, and check those environments are
properly marked as acrive.
Run quickstart again to check it is idempotent.
Create new users and copy the resulting jenv file to ~/.juju/environments/.
Run quickstart interactive session again, and check the
new environments are reported, and it is possible to use them.

https://codereview.appspot.com/188330043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+244976_code.launchpad.net,

Message:
Please take a look.

Description:
Display the jenv environments in interactive mode.

The jenv environments are now displayed in the
interactive session, and it's possible to use them.

Also implemented a simple jenv detail view in uwrid.

Quickstart now also supports highlighting active
environments, i.e. those that are supposed to be
running because they have a corresponding jenv file
in the juju gome.

Tests: `make check`.

QA:
user the new juju, bootstrap one or more environments
with quickstart (use .venv/bin/python juju-quickstart --upload-tools
if you are using a non-local provider).
Start the interactive session, and check those environments are
properly marked as acrive.
Run quickstart again to check it is idempotent.
Create new users and copy the resulting jenv file to
~/.juju/environments/.
Run quickstart interactive session again, and check the
new environments are reported, and it is possible to use them.

https://code.launchpad.net/~frankban/juju-quickstart/interactive-jenvs/+merge/244976

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/188330043/

Affected files (+530, -104 lines):
   A [revision details]
   M quickstart/cli/ui.py
   M quickstart/cli/views.py
   M quickstart/manage.py
   M quickstart/models/jenv.py
   M quickstart/tests/cli/test_views.py
   M quickstart/tests/helpers.py
   M quickstart/tests/models/test_jenv.py
   M quickstart/tests/test_manage.py

Revision history for this message
Richard Harding (rharding) wrote :
Revision history for this message
j.c.sackett (jcsackett) wrote :

LGTM, Franceso. Just one question, and it's not directly related to your
branch.

https://codereview.appspot.com/188330043/diff/1/quickstart/tests/helpers.py
File quickstart/tests/helpers.py (right):

https://codereview.appspot.com/188330043/diff/1/quickstart/tests/helpers.py#newcode266
quickstart/tests/helpers.py:266: 'type': '__unknown__',
This isn't from your branch, but why are we listing ec2 as an "unknown"?

https://codereview.appspot.com/188330043/

Revision history for this message
Francesco Banconi (frankban) wrote :

Thanks for the reviews!

https://codereview.appspot.com/188330043/diff/1/quickstart/tests/helpers.py
File quickstart/tests/helpers.py (right):

https://codereview.appspot.com/188330043/diff/1/quickstart/tests/helpers.py#newcode266
quickstart/tests/helpers.py:266: 'type': '__unknown__',
On 2014/12/17 16:13:07, j.c.sackett wrote:
> This isn't from your branch, but why are we listing ec2 as an
"unknown"?

All the jenv files created by "juju user add" don't include the provider
type. So the type can be unknown even if the name suggests a specific
provider.

https://codereview.appspot.com/188330043/

Revision history for this message
j.c.sackett (jcsackett) wrote :

QA notes:

I ran juju-quickstart -i locally.

I saw my ec2 setup with a check, as it is default.
My two local envs were listed, with the active one showing a green
bullet.
My manual provider env was listed with nothing special, as expected.

I simlinked the juju ci jenv into my environments; it was listed as an
imported environment, and I could see its details once selected.

QA OK.

https://codereview.appspot.com/188330043/

Revision history for this message
Francesco Banconi (frankban) wrote :

*** Submitted:

Display the jenv environments in interactive mode.

The jenv environments are now displayed in the
interactive session, and it's possible to use them.

Also implemented a simple jenv detail view in uwrid.

Quickstart now also supports highlighting active
environments, i.e. those that are supposed to be
running because they have a corresponding jenv file
in the juju gome.

Tests: `make check`.

QA:
user the new juju, bootstrap one or more environments
with quickstart (use .venv/bin/python juju-quickstart --upload-tools
if you are using a non-local provider).
Start the interactive session, and check those environments are
properly marked as acrive.
Run quickstart again to check it is idempotent.
Create new users and copy the resulting jenv file to
~/.juju/environments/.
Run quickstart interactive session again, and check the
new environments are reported, and it is possible to use them.

R=rharding, j.c.sackett
CC=
https://codereview.appspot.com/188330043

https://codereview.appspot.com/188330043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'quickstart/cli/ui.py'
2--- quickstart/cli/ui.py 2014-01-07 15:41:55 +0000
3+++ quickstart/cli/ui.py 2014-12-17 12:34:18 +0000
4@@ -31,6 +31,8 @@
5 # See <http://excess.org/urwid/docs/reference/constants.html
6 # foreground-and-background-colors>.
7 (None, 'light gray', 'black'),
8+ ('active', 'light green', 'black'),
9+ ('active status', 'dark green', 'light gray'),
10 ('dialog', 'dark gray', 'light gray'),
11 ('dialog header', 'light gray,bold', 'dark blue'),
12 ('control alert', 'light red', 'light gray'),
13
14=== modified file 'quickstart/cli/views.py'
15--- quickstart/cli/views.py 2014-11-10 14:11:11 +0000
16+++ quickstart/cli/views.py 2014-12-17 12:34:18 +0000
17@@ -115,7 +115,10 @@
18 forms,
19 ui,
20 )
21-from quickstart.models import envs
22+from quickstart.models import (
23+ envs,
24+ jenv,
25+)
26
27
28 def show(view, *args):
29@@ -152,7 +155,7 @@
30 raise ui.AppExit((env_db, env_data))
31
32
33-def env_index(app, env_type_db, env_db, save_callable):
34+def env_index(app, env_type_db, env_db, jenv_db, save_callable):
35 """Show the Juju environments list.
36
37 The env_detail view is displayed when the user clicks on an environment.
38@@ -162,17 +165,22 @@
39 Receives:
40 - env_type_db: the environments meta information;
41 - env_db: the environments database;
42+ - jenv_db: the jenv files database;
43 - save_callable: a function called to save a new environment database.
44 """
45+ # XXX frankban 16/12/2014: this function is too long, subdivide it.
46 env_db = copy.deepcopy(env_db)
47+ jenv_db = copy.deepcopy(jenv_db)
48 # All the environment views return a tuple (new_env_db, env_data).
49 # Set the env_data to None in the case the user quits the application
50 # without selecting an environment to use.
51 app.set_return_value_on_exit((env_db, None))
52 detail_view = functools.partial(
53- env_detail, app, env_type_db, env_db, save_callable)
54+ env_detail, app, env_type_db, env_db, jenv_db, save_callable)
55+ jenv_view = functools.partial(
56+ jenv_detail, app, env_type_db, env_db, jenv_db, save_callable)
57 edit_view = functools.partial(
58- env_edit, app, env_type_db, env_db, save_callable)
59+ env_edit, app, env_type_db, env_db, jenv_db, save_callable)
60 # Alphabetically sort the existing environments.
61 environments = sorted([
62 envs.get_env_data(env_db, env_name)
63@@ -269,8 +277,9 @@
64 # "juju status" for each environment in the list, which is expensive and
65 # time consuming.
66 focus_position = None
67- errors_found = default_found = False
68+ active_found = default_found = errors_found = False
69 existing_widgets_num = len(widgets)
70+ remaining_jenv_db = copy.deepcopy(jenv_db)
71 for position, env_data in enumerate(environments):
72 bullet = '\N{BULLET}'
73 # Is this environment the default one?
74@@ -279,22 +288,48 @@
75 # The first two positions are the section header and the divider.
76 focus_position = position + existing_widgets_num
77 bullet = '\N{CHECK MARK}'
78- # Is this environment valid?
79- env_metadata = envs.get_env_metadata(env_type_db, env_data)
80- errors = envs.validate(env_metadata, env_data)
81- if errors:
82- errors_found = True
83- bullet = ('error', bullet)
84+ if remaining_jenv_db['environments'].pop(env_data['name'], None):
85+ # This is an active environment. Is it running? Who knows...
86+ active_found = True
87+ bullet = ('active', bullet)
88+ else:
89+ # Check if this environment is valid.
90+ env_metadata = envs.get_env_metadata(env_type_db, env_data)
91+ errors = envs.validate(env_metadata, env_data)
92+ if errors:
93+ errors_found = True
94+ bullet = ('error', bullet)
95 # Create a label for the environment.
96 env_short_description = envs.get_env_short_description(env_data)
97 text = [bullet, ' {}'.format(env_short_description)]
98 widgets.append(ui.MenuButton(text, ui.thunk(detail_view, env_data)))
99
100+ # Alphabetically sort the remaining environments not included in the
101+ # environments.yaml file.
102+ environments = sorted([
103+ envs.get_env_data(remaining_jenv_db, env_name)
104+ for env_name in remaining_jenv_db['environments']
105+ ], key=operator.itemgetter('name'))
106+
107+ # List the remaining active environments. Those environments are not
108+ # included in the environments.yaml file: they are probably imported and
109+ # supposed to be working/active. The user has the ability to select them.
110+ widgets.extend([
111+ urwid.Divider(),
112+ urwid.Text(('highlight', 'Other active environments')),
113+ urwid.Text('(imported/not included in your environments.yaml file):'),
114+ urwid.Divider(),
115+ ])
116+ bullet = ('active', '\N{BULLET}')
117+ for env_data in environments:
118+ env_short_description = jenv.get_env_short_description(env_data)
119+ text = [bullet, ' {}'.format(env_short_description)]
120+ widgets.append(ui.MenuButton(text, ui.thunk(jenv_view, env_data)))
121+
122 # Set up the "create a new environment" section.
123 widgets.extend([
124 urwid.Divider(),
125- urwid.Text((
126- 'highlight', 'Create a new environment:')),
127+ urwid.Text(('highlight', 'Create a new environment:')),
128 urwid.Divider(),
129 ])
130 # The Juju GUI can be safely installed in the bootstrap node only if its
131@@ -322,6 +357,8 @@
132 status = [' \N{UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW} navigate ']
133 if default_found:
134 status.append(' \N{CHECK MARK} default ')
135+ if active_found:
136+ status.extend([('active status', ' \N{BULLET}'), ' active '])
137 if errors_found:
138 status.extend([('error status', ' \N{BULLET}'), ' has errors '])
139 app.set_status(status)
140@@ -332,7 +369,7 @@
141 app.set_contents(contents)
142
143
144-def env_detail(app, env_type_db, env_db, save_callable, env_data):
145+def env_detail(app, env_type_db, env_db, jenv_db, save_callable, env_data):
146 """Show details on a Juju environment.
147
148 From this view it is possible to start the environment, set it as default,
149@@ -341,18 +378,20 @@
150 Receives:
151 - env_type_db: the environments meta information;
152 - env_db: the environments database;
153+ - jenv_db: the jenv files database;
154 - save_callable: a function called to save a new environment database;
155 - env_data: the environment data.
156 """
157 env_db = copy.deepcopy(env_db)
158+ jenv_db = copy.deepcopy(jenv_db)
159 # All the environment views return a tuple (new_env_db, env_data).
160 # Set the env_data to None in the case the user quits the application
161 # without selecting an environment to use.
162 app.set_return_value_on_exit((env_db, None))
163 index_view = functools.partial(
164- env_index, app, env_type_db, env_db, save_callable)
165+ env_index, app, env_type_db, env_db, jenv_db, save_callable)
166 edit_view = functools.partial(
167- env_edit, app, env_type_db, env_db, save_callable, env_data)
168+ env_edit, app, env_type_db, env_db, jenv_db, save_callable, env_data)
169
170 def use(env_data):
171 # Quit the interactive session returning the (possibly modified)
172@@ -425,7 +464,62 @@
173 app.set_contents(listbox)
174
175
176-def env_edit(app, env_type_db, env_db, save_callable, env_data):
177+def jenv_detail(app, env_type_db, env_db, jenv_db, save_callable, env_data):
178+ """Show details on a Juju imported environment.
179+
180+ The environment is not included in the environments.yaml file, but just
181+ found in the jenv database.
182+ From this view it is possible to start the environment.
183+
184+ Receives:
185+ - env_type_db: the environments meta information;
186+ - env_db: the environments database;
187+ - jenv_db: the jenv files database;
188+ - save_callable: a function called to save a new environment database;
189+ - env_data: the environment data.
190+ """
191+ env_db = copy.deepcopy(env_db)
192+ jenv_db = copy.deepcopy(jenv_db)
193+ # All the environment views return a tuple (new_env_db, env_data).
194+ # Set the env_data to None in the case the user quits the application
195+ # without selecting an environment to use.
196+ app.set_return_value_on_exit((env_db, None))
197+ index_view = functools.partial(
198+ env_index, app, env_type_db, env_db, jenv_db, save_callable)
199+
200+ def use(env_data):
201+ # Quit the interactive session returning the (possibly modified)
202+ # environment database and the environment data corresponding to the
203+ # selected environment.
204+ raise ui.AppExit((env_db, env_data))
205+
206+ app.set_title(jenv.get_env_short_description(env_data))
207+ widgets = []
208+ for key, value in jenv.get_env_details(env_data):
209+ widgets.append(urwid.Text(['{}: '.format(key), ('highlight', value)]))
210+ widgets.extend([
211+ urwid.Divider(),
212+ urwid.Text([
213+ ('highlight', 'Imported active environment.\n'),
214+ 'This environment is not included in your environments.yaml file.'
215+ '\nFor this reason, it is not possible to edit or remove it.\n'
216+ 'However, you can use the link below to ',
217+ ('highlight', 'use Juju Quickstart'),
218+ ' with this environment.',
219+ ]),
220+ ])
221+
222+ controls = [
223+ ui.MenuButton('back', ui.thunk(index_view)),
224+ ui.MenuButton('use', ui.thunk(use, env_data)),
225+ ]
226+ widgets.append(ui.create_controls(*controls))
227+ listbox = urwid.ListBox(urwid.SimpleFocusListWalker(widgets))
228+ app.set_contents(listbox)
229+ app.set_status([' \N{RIGHTWARDS ARROW OVER LEFTWARDS ARROW} navigate '])
230+
231+
232+def env_edit(app, env_type_db, env_db, jenv_db, save_callable, env_data):
233 """Create or modify a Juju environment.
234
235 This view displays an edit form allowing for environment
236@@ -435,6 +529,7 @@
237 Receives:
238 - env_type_db: the environments meta information;
239 - env_db: the environments database;
240+ - jenv_db: the jenv files database;
241 - save_callable: a function called to save a new environment database;
242 - env_data: the environment data.
243
244@@ -444,15 +539,16 @@
245 env_data includes the "name" key and all the other environment info.
246 """
247 env_db = copy.deepcopy(env_db)
248+ jenv_db = copy.deepcopy(jenv_db)
249 # All the environment views return a tuple (new_env_db, env_data).
250 # Set the env_data to None in the case the user quits the application
251 # without selecting an environment to use.
252 app.set_return_value_on_exit((env_db, None))
253 env_metadata = envs.get_env_metadata(env_type_db, env_data)
254 index_view = functools.partial(
255- env_index, app, env_type_db, env_db, save_callable)
256+ env_index, app, env_type_db, env_db, jenv_db, save_callable)
257 detail_view = functools.partial(
258- env_detail, app, env_type_db, env_db, save_callable)
259+ env_detail, app, env_type_db, env_db, jenv_db, save_callable)
260 if 'name' in env_data:
261 exists = True
262 title = 'Edit the {} environment'
263
264=== modified file 'quickstart/manage.py'
265--- quickstart/manage.py 2014-12-16 14:12:44 +0000
266+++ quickstart/manage.py 2014-12-17 12:34:18 +0000
267@@ -203,7 +203,7 @@
268 return save_callable
269
270
271-def _start_interactive_session(parser, env_type_db, env_db, env_file):
272+def _start_interactive_session(parser, env_type_db, env_db, jenv_db, env_file):
273 """Start the Urwid interactive session.
274
275 Return the env_data corresponding to the user selected environment.
276@@ -212,7 +212,7 @@
277 """
278 save_callable = _create_save_callable(parser, env_file)
279 new_env_db, env_data = views.show(
280- views.env_index, env_type_db, env_db, save_callable)
281+ views.env_index, env_type_db, env_db, jenv_db, save_callable)
282 if new_env_db != env_db:
283 print('changes to the environments file have been saved')
284 if env_data is None:
285@@ -290,7 +290,7 @@
286 if interactive:
287 # Start the interactive session.
288 env_data = _start_interactive_session(
289- parser, env_type_db, env_db, env_file)
290+ parser, env_type_db, env_db, jenv_db, env_file)
291 else:
292 # This is a non-interactive session and we need to validate the
293 # selected environment before proceeding.
294
295=== modified file 'quickstart/models/jenv.py'
296--- quickstart/models/jenv.py 2014-12-16 15:53:25 +0000
297+++ quickstart/models/jenv.py 2014-12-17 12:34:18 +0000
298@@ -130,9 +130,11 @@
299 When a jenv file is created using "juju user add", the resulting YAML data
300 is very concise, not even including the environment type.
301 For this reason, the environment database returned by this function does
302- not contain the usual fields used as bootstrap options, but just the
303- environment name and the type. If the environment type is not included in
304- the jenv, UNKNOWN_ENV_TYPE is used.
305+ not contain the usual fields used as bootstrap options.
306+ The included fields are: "name", "type", "user" and "state-servers".
307+
308+ If the environment type is not included in the jenv, UNKNOWN_ENV_TYPE is
309+ used.
310 """
311 db = {'environments': {}}
312 path = os.path.expanduser(os.path.join(settings.JUJU_HOME, 'environments'))
313@@ -153,7 +155,7 @@
314 # Validate the jenv contents.
315 try:
316 data = serializers.yaml_load_from_path(fullpath)
317- validate(data)
318+ credentials, servers = validate(data)
319 except ValueError as err:
320 logging.warn('ignoring invalid jenv file {}: {}'. format(
321 filename, bytes(err).decode('utf-8')))
322@@ -164,7 +166,11 @@
323 except ValueError:
324 # This is expected when a jenv is generated with "juju user add".
325 env_type = UNKNOWN_ENV_TYPE
326- environments[name] = {'type': env_type}
327+ environments[name] = {
328+ 'type': env_type,
329+ 'user': credentials[0],
330+ 'state-servers': servers,
331+ }
332 return db
333
334
335@@ -178,13 +184,59 @@
336 - the environment file is not found;
337 - the environment file contents are not parsable by YAML;
338 - the environment data is not valid.
339- """
340- _get_credentials(data)
341+
342+ Return the environment credentials and state servers.
343+ """
344+ credentials = _get_credentials(data)
345+ servers = _get_state_servers(data)
346+ if not len(servers):
347+ raise ValueError(b'no state-servers found')
348+ return credentials, servers
349+
350+
351+def get_env_short_description(env_data):
352+ """Return a short description of the given env_data.
353+
354+ The given env_data must include at least the "name" and "type" keys.
355+ """
356+ parts = [env_data['name']]
357+ env_type = env_data['type']
358+ if env_type != UNKNOWN_ENV_TYPE:
359+ parts.append('(type: {})'.format(env_type))
360+ return ' '.join(parts)
361+
362+
363+def get_env_details(env_data):
364+ """Return the environment details as a sequence of tuples (label, value).
365+
366+ In each tuple, the label is the field name, and the value is the
367+ corresponding value in the given env_data.
368+ """
369+ details = []
370+ # Add the environment type.
371+ env_type = env_data['type']
372+ if env_type != UNKNOWN_ENV_TYPE:
373+ details.append(('type', env_type))
374+ # Add the environment name and user.
375+ details.extend([
376+ ('name', env_data['name']),
377+ ('user', env_data['user']),
378+ ])
379+ # Add the state servers.
380+ servers = ', '.join(env_data['state-servers'])
381+ details.append(('state servers', servers))
382+ return details
383+
384+
385+def _get_state_servers(data):
386+ """Return a tuple of Juju state servers for the given jenv data.
387+
388+ Raise a ValueError if the state servers cannot be retrieved.
389+ """
390 servers = data.get('state-servers')
391 if not isinstance(servers, (list, tuple)):
392 raise ValueError(b'invalid state-servers field')
393- if not len(servers):
394- raise ValueError(b'no state-servers found')
395+ return tuple(servers)
396
397
398 def _get_value_from_yaml(data, *args):
399
400=== modified file 'quickstart/tests/cli/test_views.py'
401--- quickstart/tests/cli/test_views.py 2014-11-12 15:41:40 +0000
402+++ quickstart/tests/cli/test_views.py 2014-12-17 12:34:18 +0000
403@@ -34,7 +34,10 @@
404 ui,
405 views,
406 )
407-from quickstart.models import envs
408+from quickstart.models import (
409+ envs,
410+ jenv,
411+)
412 from quickstart.tests import helpers
413 from quickstart.tests.cli import helpers as cli_helpers
414
415@@ -178,7 +181,9 @@
416 # a tuple including a copy of the given env_db and None, the latter
417 # meaning no environment has been selected.
418 env_db = helpers.make_env_db()
419- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
420+ jenv_db = helpers.make_jenv_db()
421+ views.env_index(
422+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
423 new_env_db, env_data = self.get_on_exit_return_value(self.loop)
424 self.assertEqual(env_db, new_env_db)
425 self.assertIsNot(env_db, new_env_db)
426@@ -187,7 +192,9 @@
427 def test_view_title(self):
428 # The application title is correctly set up.
429 env_db = helpers.make_env_db()
430- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
431+ jenv_db = helpers.make_jenv_db()
432+ views.env_index(
433+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
434 self.assertEqual(
435 'Select an existing Juju environment or create a new one',
436 self.app.get_title())
437@@ -195,7 +202,9 @@
438 def test_view_title_no_environments(self):
439 # The application title changes if the env_db has no environments.
440 env_db = {'environments': {}}
441- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
442+ jenv_db = helpers.make_jenv_db()
443+ views.env_index(
444+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
445 self.assertEqual(
446 'No Juju environments already set up: please create one',
447 self.app.get_title())
448@@ -204,9 +213,11 @@
449 # The view displays a list of the environments in env_db, and buttons
450 # to create new environments.
451 env_db = helpers.make_env_db()
452+ jenv_db = {'environments': {}}
453 with local_envs_supported(True):
454 views.env_index(
455- self.app, self.env_type_db, env_db, self.save_callable)
456+ self.app, self.env_type_db, env_db, jenv_db,
457+ self.save_callable)
458 buttons = self.get_widgets_in_contents(
459 filter_function=self.is_a(ui.MenuButton))
460 # A button is created for each existing environment (see details) and
461@@ -215,13 +226,39 @@
462 expected_buttons_number = len(env_db['environments']) + len(env_types)
463 self.assertEqual(expected_buttons_number, len(buttons))
464
465+ def test_view_contents_with_imported_envs(self):
466+ # The view displays a list of active imported environments, and buttons
467+ # to create new environments.
468+ env_db = {'environments': {}}
469+ jenv_db = helpers.make_jenv_db()
470+ with local_envs_supported(True):
471+ views.env_index(
472+ self.app, self.env_type_db, env_db, jenv_db,
473+ self.save_callable)
474+ buttons = self.get_widgets_in_contents(
475+ filter_function=self.is_a(ui.MenuButton))
476+ # A button is created for each existing environment (see details) and
477+ # for each environment type supported by quickstart (create).
478+ env_types = envs.get_supported_env_types(self.env_type_db)
479+ expected_buttons_number = (
480+ # The number of active environments.
481+ len(jenv_db['environments']) +
482+ # The buttons to create new environments.
483+ len(env_types) +
484+ # The button to automatically create a new local environment.
485+ 1
486+ )
487+ self.assertEqual(expected_buttons_number, len(buttons))
488+
489 def test_new_local_environment_disabled(self):
490 # The option to create a new local environment is not present if they
491 # are not supported in the current platform.
492 env_db = helpers.make_env_db()
493+ jenv_db = helpers.make_jenv_db()
494 with local_envs_supported(False):
495 views.env_index(
496- self.app, self.env_type_db, env_db, self.save_callable)
497+ self.app, self.env_type_db, env_db, jenv_db,
498+ self.save_callable)
499 buttons = self.get_widgets_in_contents(
500 filter_function=self.is_a(ui.MenuButton))
501 captions = map(cli_helpers.get_button_caption, buttons)
502@@ -235,7 +272,9 @@
503 def test_environment_clicked(self, mock_env_detail):
504 # The environment detail view is called when clicking an environment.
505 env_db = helpers.make_env_db()
506- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
507+ jenv_db = {'environments': {}}
508+ views.env_index(
509+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
510 buttons = self.get_widgets_in_contents(
511 filter_function=self.is_a(ui.MenuButton))
512 # The environments are listed in alphabetical order.
513@@ -250,20 +289,51 @@
514 # corresponding environment data.
515 cli_helpers.emit(button)
516 mock_env_detail.assert_called_once_with(
517- self.app, self.env_type_db, env_db, self.save_callable,
518- env_data)
519+ self.app, self.env_type_db, env_db, jenv_db,
520+ self.save_callable, env_data)
521 # Reset the mock so that it does not include any calls on the next
522 # loop cycle.
523 mock_env_detail.reset_mock()
524
525+ @mock.patch('quickstart.cli.views.jenv_detail')
526+ def test_imported_environment_clicked(self, mock_jenv_detail):
527+ # The jenv detail view is called when clicking an imported environment.
528+ env_db = {'environments': {}}
529+ jenv_db = helpers.make_jenv_db()
530+ with local_envs_supported(False):
531+ views.env_index(
532+ self.app, self.env_type_db, env_db, jenv_db,
533+ self.save_callable)
534+ buttons = self.get_widgets_in_contents(
535+ filter_function=self.is_a(ui.MenuButton))
536+ # The environments are listed in alphabetical order.
537+ environments = sorted(jenv_db['environments'])
538+ for env_name, button in zip(environments, buttons):
539+ env_data = envs.get_env_data(jenv_db, env_name)
540+ # The caption includes the environment description.
541+ env_description = jenv.get_env_short_description(env_data)
542+ self.assertIn(
543+ env_description, cli_helpers.get_button_caption(button))
544+ # When the button is clicked, the jenv detail view is called
545+ # passing the corresponding environment data.
546+ cli_helpers.emit(button)
547+ mock_jenv_detail.assert_called_once_with(
548+ self.app, self.env_type_db, env_db, jenv_db,
549+ self.save_callable, env_data)
550+ # Reset the mock so that it does not include any calls on the next
551+ # loop cycle.
552+ mock_jenv_detail.reset_mock()
553+
554 @mock.patch('quickstart.cli.views.env_edit')
555 def test_create_new_environment_clicked(self, mock_env_edit):
556 # The environment edit view is called when clicking to create a new
557 # environment.
558 env_db = helpers.make_env_db()
559+ jenv_db = {'environments': {}}
560 with local_envs_supported(True):
561 views.env_index(
562- self.app, self.env_type_db, env_db, self.save_callable)
563+ self.app, self.env_type_db, env_db, jenv_db,
564+ self.save_callable)
565 buttons = self.get_widgets_in_contents(
566 filter_function=self.is_a(ui.MenuButton))
567 env_types = envs.get_supported_env_types(self.env_type_db)
568@@ -277,9 +347,11 @@
569 # corresponding environment data.
570 cli_helpers.emit(button)
571 mock_env_edit.assert_called_once_with(
572- self.app, self.env_type_db, env_db, self.save_callable,
573- {'type': env_type,
574- 'default-series': settings.JUJU_GUI_SUPPORTED_SERIES[-1]})
575+ self.app, self.env_type_db, env_db, jenv_db,
576+ self.save_callable, {
577+ 'type': env_type,
578+ 'default-series': settings.JUJU_GUI_SUPPORTED_SERIES[-1],
579+ })
580 # Reset the mock so that it does not include any calls on the next
581 # loop cycle.
582 mock_env_edit.reset_mock()
583@@ -290,10 +362,12 @@
584 # If that option is clicked, the view quits the application returning
585 # the newly created env_data.
586 env_db = envs.create_empty_env_db()
587+ jenv_db = helpers.make_jenv_db()
588 with maas_env_detected(False):
589 with local_envs_supported(True):
590 views.env_index(
591- self.app, self.env_type_db, env_db, self.save_callable)
592+ self.app, self.env_type_db, env_db, jenv_db,
593+ self.save_callable)
594 buttons = self.get_widgets_in_contents(
595 filter_function=self.is_a(ui.MenuButton))
596 # The "create and bootstrap" button is the first one in the contents.
597@@ -314,9 +388,11 @@
598 # environment is not displayed if the current platform does not support
599 # local environments.
600 env_db = envs.create_empty_env_db()
601+ jenv_db = helpers.make_jenv_db()
602 with local_envs_supported(False):
603 views.env_index(
604- self.app, self.env_type_db, env_db, self.save_callable)
605+ self.app, self.env_type_db, env_db, jenv_db,
606+ self.save_callable)
607 buttons = self.get_widgets_in_contents(
608 filter_function=self.is_a(ui.MenuButton))
609 # No "create and bootstrap local" buttons are present.
610@@ -330,9 +406,11 @@
611 # If that option is clicked, the view quits the application returning
612 # the newly created env_data.
613 env_db = envs.create_empty_env_db()
614+ jenv_db = helpers.make_jenv_db()
615 with maas_env_detected(True):
616 views.env_index(
617- self.app, self.env_type_db, env_db, self.save_callable)
618+ self.app, self.env_type_db, env_db, jenv_db,
619+ self.save_callable)
620 buttons = self.get_widgets_in_contents(
621 filter_function=self.is_a(ui.MenuButton))
622 # The "create and bootstrap" button is the first one in the contents.
623@@ -353,9 +431,11 @@
624 # environment is not displayed if no MAAS API endpoints are
625 # available on the system
626 env_db = envs.create_empty_env_db()
627+ jenv_db = helpers.make_jenv_db()
628 with maas_env_detected(False):
629 views.env_index(
630- self.app, self.env_type_db, env_db, self.save_callable)
631+ self.app, self.env_type_db, env_db, jenv_db,
632+ self.save_callable)
633 buttons = self.get_widgets_in_contents(
634 filter_function=self.is_a(ui.MenuButton))
635 # No "create and bootstrap MAAS" buttons are present.
636@@ -365,7 +445,9 @@
637 def test_selected_environment(self):
638 # The default environment is already selected in the list.
639 env_db = helpers.make_env_db(default='lxc')
640- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
641+ jenv_db = helpers.make_jenv_db()
642+ views.env_index(
643+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
644 env_data = envs.get_env_data(env_db, 'lxc')
645 env_description = envs.get_env_short_description(env_data)
646 contents = self.app.get_contents()
647@@ -377,31 +459,50 @@
648 def test_status_with_errors(self):
649 # The status message explains how errors are displayed.
650 env_db = helpers.make_env_db()
651- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
652+ jenv_db = {'environments': {}}
653+ views.env_index(
654+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
655 status = self.app.get_status()
656 self.assertEqual(self.base_status + ' \N{BULLET} has errors ', status)
657
658 def test_status_with_default(self):
659 # The status message explains how default environment is represented.
660 env_db = helpers.make_env_db(default='lxc', exclude_invalid=True)
661- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
662+ jenv_db = {'environments': {}}
663+ views.env_index(
664+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
665 status = self.app.get_status()
666 self.assertEqual(self.base_status + ' \N{CHECK MARK} default ', status)
667
668- def test_status_with_default_and_errors(self):
669- # The status message includes both default and errors explanations.
670+ def test_status_with_active(self):
671+ # The status message explains how active environments are displayed.
672+ env_db = helpers.make_env_db(exclude_invalid=True)
673+ jenv_db = helpers.make_jenv_db()
674+ views.env_index(
675+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
676+ status = self.app.get_status()
677+ self.assertEqual(self.base_status + ' \N{BULLET} active ', status)
678+
679+ def test_complete_status(self):
680+ # The status message includes default, active and errors explanations.
681 env_db = helpers.make_env_db(default='lxc')
682- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
683+ jenv_db = helpers.make_jenv_db()
684+ views.env_index(
685+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
686 status = self.app.get_status()
687 self.assertEqual(
688 self.base_status +
689- ' \N{CHECK MARK} default \N{BULLET} has errors ',
690+ ' \N{CHECK MARK} default ' +
691+ ' \N{BULLET} active ' +
692+ ' \N{BULLET} has errors ',
693 status)
694
695- def test_status(self):
696+ def test_base_status(self):
697 # The status only includes navigation info if there are no errors.
698 env_db = helpers.make_env_db(exclude_invalid=True)
699- views.env_index(self.app, self.env_type_db, env_db, self.save_callable)
700+ jenv_db = {'environments': {}}
701+ views.env_index(
702+ self.app, self.env_type_db, env_db, jenv_db, self.save_callable)
703 status = self.app.get_status()
704 self.assertEqual(self.base_status, status)
705
706@@ -410,13 +511,14 @@
707
708 base_status = ' \N{RIGHTWARDS ARROW OVER LEFTWARDS ARROW} navigate '
709 env_db = helpers.make_env_db(default='lxc')
710+ jenv_db = helpers.make_jenv_db()
711
712 def call_view(self, env_name='lxc'):
713 """Call the view passing the env_data corresponding to env_name."""
714 self.env_data = envs.get_env_data(self.env_db, env_name)
715 return views.env_detail(
716- self.app, self.env_type_db, self.env_db, self.save_callable,
717- self.env_data)
718+ self.app, self.env_type_db, self.env_db, self.jenv_db,
719+ self.save_callable, self.env_data)
720
721 def test_view_default_return_value_on_exit(self):
722 # The view configures the app so that the return value on user exit is
723@@ -483,7 +585,8 @@
724 back_button = self.get_control_buttons()[0]
725 cli_helpers.emit(back_button)
726 mock_env_index.assert_called_once_with(
727- self.app, self.env_type_db, self.env_db, self.save_callable)
728+ self.app, self.env_type_db, self.env_db, self.jenv_db,
729+ self.save_callable)
730
731 def test_use_button(self):
732 # The application exits if the "use" button is clicked.
733@@ -522,8 +625,8 @@
734 edit_button = self.get_control_buttons()[3]
735 cli_helpers.emit(edit_button)
736 mock_env_edit.assert_called_once_with(
737- self.app, self.env_type_db, self.env_db, self.save_callable,
738- self.env_data)
739+ self.app, self.env_type_db, self.env_db, self.jenv_db,
740+ self.save_callable, self.env_data)
741
742 def test_remove_button(self):
743 # A confirmation dialog is displayed if the "remove" button is clicked.
744@@ -598,9 +701,82 @@
745 self.assertEqual(self.base_status, status)
746
747
748+class TestJenvDetail(EnvViewTestsMixin, unittest.TestCase):
749+
750+ env_db = helpers.make_env_db(default='lxc')
751+ jenv_db = helpers.make_jenv_db()
752+
753+ def call_view(self, env_name='lxc'):
754+ """Call the view passing the env_data corresponding to env_name."""
755+ self.env_data = envs.get_env_data(self.jenv_db, env_name)
756+ return views.jenv_detail(
757+ self.app, self.env_type_db, self.env_db, self.jenv_db,
758+ self.save_callable, self.env_data)
759+
760+ def test_view_default_return_value_on_exit(self):
761+ # The view configures the app so that the return value on user exit is
762+ # a tuple including a copy of the given env_db and None, the latter
763+ # meaning no environment has been selected (for now).
764+ self.call_view()
765+ new_env_db, env_data = self.get_on_exit_return_value(self.loop)
766+ self.assertEqual(self.env_db, new_env_db)
767+ self.assertIsNot(self.env_db, new_env_db)
768+ self.assertIsNone(env_data)
769+
770+ def test_view_title(self):
771+ # The application title is correctly set up: it shows the description
772+ # of the current jenv environment.
773+ self.call_view()
774+ env_description = jenv.get_env_short_description(self.env_data)
775+ self.assertEqual(env_description, self.app.get_title())
776+
777+ def test_view_contents(self):
778+ # The view displays the jenv details.
779+ self.call_view()
780+ widgets = self.get_widgets_in_contents(
781+ filter_function=self.is_a(urwid.Text))
782+ expected_texts = [
783+ '{}: {}'.format(label, value) for label, value
784+ in jenv.get_env_details(self.env_data)
785+ ]
786+ for expected_text, widget in zip(expected_texts, widgets):
787+ self.assertEqual(expected_text, widget.text)
788+
789+ def test_view_buttons(self):
790+ # The "back" and "use" buttons are displayed.
791+ self.call_view(env_name='ec2-west')
792+ buttons = self.get_control_buttons()
793+ captions = map(cli_helpers.get_button_caption, buttons)
794+ self.assertEqual(['back', 'use'], captions)
795+
796+ @mock.patch('quickstart.cli.views.env_index')
797+ def test_back_button(self, mock_env_index):
798+ # The index view is called if the "back" button is clicked.
799+ self.call_view(env_name='ec2-west')
800+ # The "back" button is the first one.
801+ back_button = self.get_control_buttons()[0]
802+ cli_helpers.emit(back_button)
803+ mock_env_index.assert_called_once_with(
804+ self.app, self.env_type_db, self.env_db, self.jenv_db,
805+ self.save_callable)
806+
807+ def test_use_button(self):
808+ # The application exits if the "use" button is clicked.
809+ # The env_db and the current environment data are returned.
810+ self.call_view(env_name='ec2-west')
811+ # The "use" button is the second one.
812+ use_button = self.get_control_buttons()[1]
813+ with self.assertRaises(ui.AppExit) as context_manager:
814+ cli_helpers.emit(use_button)
815+ expected_return_value = (self.env_db, self.env_data)
816+ self.assertEqual(
817+ expected_return_value, context_manager.exception.return_value)
818+
819+
820 class TestEnvEdit(EnvViewTestsMixin, unittest.TestCase):
821
822 env_db = helpers.make_env_db(default='lxc')
823+ jenv_db = helpers.make_jenv_db()
824
825 def call_view(self, env_name='lxc', env_type=None):
826 """Call the view passing the env_data corresponding to env_name.
827@@ -613,8 +789,8 @@
828 else:
829 self.env_data = {'type': env_type}
830 return views.env_edit(
831- self.app, self.env_type_db, self.env_db, self.save_callable,
832- self.env_data)
833+ self.app, self.env_type_db, self.env_db, self.jenv_db,
834+ self.save_callable, self.env_data)
835
836 def get_form_contents(self):
837 """Return the form contents included in the app page.
838@@ -749,8 +925,8 @@
839 'ec2-west successfully modified', self.app.get_message())
840 # The application displays the environment detail view.
841 mock_env_detail.assert_called_once_with(
842- self.app, self.env_type_db, self.env_db, self.save_callable,
843- new_env_data)
844+ self.app, self.env_type_db, self.env_db, self.jenv_db,
845+ self.save_callable, new_env_data)
846
847 @mock.patch('quickstart.cli.views.env_detail')
848 def test_save_empty_db(self, mock_env_detail):
849@@ -769,8 +945,8 @@
850 expected_new_env_data.update({'type': 'local', 'is-default': True})
851 envs.set_env_data(self.env_db, None, expected_new_env_data)
852 mock_env_detail.assert_called_once_with(
853- self.app, self.env_type_db, self.env_db, self.save_callable,
854- expected_new_env_data)
855+ self.app, self.env_type_db, self.env_db, self.jenv_db,
856+ self.save_callable, expected_new_env_data)
857
858 def test_save_invalid_form_data(self):
859 # Errors are displayed if the user tries to save invalid data.
860@@ -815,7 +991,8 @@
861 cancel_button = self.get_control_buttons()[1]
862 cli_helpers.emit(cancel_button)
863 mock_env_index.assert_called_once_with(
864- self.app, self.env_type_db, self.env_db, self.save_callable)
865+ self.app, self.env_type_db, self.env_db, self.jenv_db,
866+ self.save_callable)
867
868 @mock.patch('quickstart.cli.views.env_detail')
869 def test_modification_view_cancel_button(self, mock_env_detail):
870@@ -826,5 +1003,5 @@
871 cancel_button = self.get_control_buttons()[1]
872 cli_helpers.emit(cancel_button)
873 mock_env_detail.assert_called_once_with(
874- self.app, self.env_type_db, self.env_db, self.save_callable,
875- self.env_data)
876+ self.app, self.env_type_db, self.env_db, self.jenv_db,
877+ self.save_callable, self.env_data)
878
879=== modified file 'quickstart/tests/helpers.py'
880--- quickstart/tests/helpers.py 2014-12-16 16:16:04 +0000
881+++ quickstart/tests/helpers.py 2014-12-17 12:34:18 +0000
882@@ -262,9 +262,21 @@
883 def make_jenv_db():
884 """Create and return a jenv files database."""
885 environments = {
886- 'ec2-west': {'type': '__unknown__'},
887- 'lxc': {'type': 'local'},
888- 'test-jenv': {'type': '__unknown__'},
889+ 'ec2-west': {
890+ 'type': '__unknown__',
891+ 'user': 'who',
892+ 'state-servers': ('1.2.3.4:42', '1.2.3.4:47'),
893+ },
894+ 'lxc': {
895+ 'type': 'local',
896+ 'user': 'dalek',
897+ 'state-servers': ('localhost:17070', '10.0.3.1:17070'),
898+ },
899+ 'test-jenv': {
900+ 'type': '__unknown__',
901+ 'user': 'my-user',
902+ 'state-servers': ('10.0.3.1:17070',),
903+ },
904 }
905 return {'environments': environments}
906
907
908=== modified file 'quickstart/tests/models/test_jenv.py'
909--- quickstart/tests/models/test_jenv.py 2014-12-16 16:04:16 +0000
910+++ quickstart/tests/models/test_jenv.py 2014-12-17 12:34:18 +0000
911@@ -200,9 +200,14 @@
912 'ec2': yaml.safe_dump(self.jenv_data),
913 }):
914 jenv_db = jenv.get_env_db()
915- self.assertEqual({
916- 'environments': {'ec2': {'type': 'ec2'}},
917- }, jenv_db)
918+ expected_environments = {
919+ 'ec2': {
920+ 'type': 'ec2',
921+ 'user': 'admin',
922+ 'state-servers': ('localhost:17070', '10.0.3.1:17070'),
923+ },
924+ }
925+ self.assertEqual({'environments': expected_environments}, jenv_db)
926
927 def test_multiple_jenv_files(self):
928 # Multiple environments are correctly returned.
929@@ -213,22 +218,29 @@
930 'bootstrap-config': {'type': 'hp'},
931 }
932 jenv_data2 = {
933- 'user': 'admin',
934+ 'user': 'my-user',
935 'password': 'Secret!',
936- 'state-servers': ['localhost:17070'],
937+ 'state-servers': ['1.2.3.4:5', '1.2.3.4:42'],
938 'bootstrap-config': {'type': 'maas'},
939 }
940 with self.make_multiple_jenvs({
941- 'hp': yaml.safe_dump(jenv_data1),
942+ 'hp-cloud': yaml.safe_dump(jenv_data1),
943 'maas': yaml.safe_dump(jenv_data2),
944 }):
945 jenv_db = jenv.get_env_db()
946- self.assertEqual({
947- 'environments': {
948- 'hp': {'type': 'hp'},
949- 'maas': {'type': 'maas'},
950- },
951- }, jenv_db)
952+ expected_environments = {
953+ 'hp-cloud': {
954+ 'type': 'hp',
955+ 'user': 'admin',
956+ 'state-servers': ('localhost:17070',),
957+ },
958+ 'maas': {
959+ 'type': 'maas',
960+ 'user': 'my-user',
961+ 'state-servers': ('1.2.3.4:5', '1.2.3.4:42'),
962+ },
963+ }
964+ self.assertEqual({'environments': expected_environments}, jenv_db)
965
966 def test_unknown_env_type(self):
967 # If the jenv file does not include the env type, jenv.UNKNOWN_ENV_TYPE
968@@ -240,9 +252,14 @@
969 }
970 with self.make_jenv('local', yaml.safe_dump(jenv_data)):
971 jenv_db = jenv.get_env_db()
972- self.assertEqual({
973- 'environments': {'local': {'type': jenv.UNKNOWN_ENV_TYPE}},
974- }, jenv_db)
975+ expected_environments = {
976+ 'local': {
977+ 'type': jenv.UNKNOWN_ENV_TYPE,
978+ 'user': 'admin',
979+ 'state-servers': ('localhost:17070',),
980+ },
981+ }
982+ self.assertEqual({'environments': expected_environments}, jenv_db)
983
984 def test_extraneous_files(self):
985 # Extraneous files are ignored.
986@@ -261,7 +278,9 @@
987
988 def test_validation_success(self):
989 # A valid jenv file is successfully validated.
990- jenv.validate(self.jenv_data)
991+ credentials, servers = jenv.validate(self.jenv_data)
992+ self.assertEqual(('admin', 'Secret!'), credentials)
993+ self.assertEqual(('localhost:17070', '10.0.3.1:17070'), servers)
994
995 def test_invalid_credentials(self):
996 # A ValueError is raised if the credentials cannot be retrieved.
997@@ -296,3 +315,54 @@
998 'password': 'Secret!',
999 'state-servers': [],
1000 })
1001+
1002+
1003+class TestGetEnvShortDescription(unittest.TestCase):
1004+
1005+ def test_env(self):
1006+ # The env description includes the environment name and type.
1007+ env_data = {'name': 'lxc', 'type': 'local'}
1008+ description = jenv.get_env_short_description(env_data)
1009+ self.assertEqual('lxc (type: local)', description)
1010+
1011+ def test_env_without_type(self):
1012+ # Without the type we can only show the environment name.
1013+ env_data = {'name': 'ec2', 'type': jenv.UNKNOWN_ENV_TYPE}
1014+ description = jenv.get_env_short_description(env_data)
1015+ self.assertEqual('ec2', description)
1016+
1017+
1018+class TestGetEnvDetails(unittest.TestCase):
1019+
1020+ def test_env(self):
1021+ # The environment details are properly returned.
1022+ env_data = {
1023+ 'name': 'lxc',
1024+ 'type': 'local',
1025+ 'user': 'who',
1026+ 'state-servers': ('1.2.3.4:17060', 'localhost:17070'),
1027+ }
1028+ expected_details = [
1029+ ('type', 'local'),
1030+ ('name', 'lxc'),
1031+ ('user', 'who'),
1032+ ('state servers', '1.2.3.4:17060, localhost:17070'),
1033+ ]
1034+ details = jenv.get_env_details(env_data)
1035+ self.assertEqual(expected_details, details)
1036+
1037+ def test_env_without_type(self):
1038+ # The environment type is not included if unknown.
1039+ env_data = {
1040+ 'name': 'aws',
1041+ 'type': jenv.UNKNOWN_ENV_TYPE,
1042+ 'user': 'the-doctor',
1043+ 'state-servers': ('1.2.3.4:17060',),
1044+ }
1045+ expected_details = [
1046+ ('name', 'aws'),
1047+ ('user', 'the-doctor'),
1048+ ('state servers', '1.2.3.4:17060'),
1049+ ]
1050+ details = jenv.get_env_details(env_data)
1051+ self.assertEqual(expected_details, details)
1052
1053=== modified file 'quickstart/tests/test_manage.py'
1054--- quickstart/tests/test_manage.py 2014-12-16 16:04:16 +0000
1055+++ quickstart/tests/test_manage.py 2014-12-17 12:34:18 +0000
1056@@ -36,7 +36,10 @@
1057 settings,
1058 )
1059 from quickstart.cli import views
1060-from quickstart.models import envs
1061+from quickstart.models import (
1062+ envs,
1063+ jenv,
1064+)
1065 from quickstart.tests import helpers
1066
1067
1068@@ -372,13 +375,15 @@
1069 self.env_type_db = envs.get_env_type_db()
1070 self.env_file = self.make_env_file()
1071 self.env_db = envs.load(self.env_file)
1072+ self.jenv_db = helpers.make_jenv_db()
1073
1074 @contextmanager
1075- def patch_interactive_mode(self, env_db, return_value):
1076+ def patch_interactive_mode(self, env_db, jenv_db, return_value):
1077 """Patch the quickstart.cli.views.show function.
1078
1079 Ensure the interactive mode is started by the code in the context block
1080- passing the given env_db. Make the view return the given return_value.
1081+ passing the given env_db and jenv_db.
1082+ Make the view return the given return_value.
1083 """
1084 create_save_callable_path = 'quickstart.manage._create_save_callable'
1085 mock_show = mock.Mock(return_value=return_value)
1086@@ -387,16 +392,18 @@
1087 yield
1088 mock_save_callable.assert_called_once_with(self.parser, self.env_file)
1089 mock_show.assert_called_once_with(
1090- views.env_index, self.env_type_db, env_db,
1091+ views.env_index, self.env_type_db, env_db, jenv_db,
1092 mock_save_callable())
1093
1094 def test_resulting_env_data(self):
1095 # The interactive session can be used to select an environment, in
1096 # which case the function returns the corresponding env_data.
1097 env_data = envs.get_env_data(self.env_db, 'aws')
1098- with self.patch_interactive_mode(self.env_db, [self.env_db, env_data]):
1099+ with self.patch_interactive_mode(
1100+ self.env_db, self.jenv_db, [self.env_db, env_data]):
1101 obtained_env_data = manage._start_interactive_session(
1102- self.parser, self.env_type_db, self.env_db, self.env_file)
1103+ self.parser, self.env_type_db, self.env_db, self.jenv_db,
1104+ self.env_file)
1105 self.assertEqual(env_data, obtained_env_data)
1106
1107 @helpers.mock_print
1108@@ -405,9 +412,11 @@
1109 # during the interactive session.
1110 env_data = envs.get_env_data(self.env_db, 'aws')
1111 new_env_db = helpers.make_env_db()
1112- with self.patch_interactive_mode(self.env_db, [new_env_db, env_data]):
1113+ with self.patch_interactive_mode(
1114+ self.env_db, self.jenv_db, [new_env_db, env_data]):
1115 manage._start_interactive_session(
1116- self.parser, self.env_type_db, self.env_db, self.env_file)
1117+ self.parser, self.env_type_db, self.env_db, self.jenv_db,
1118+ self.env_file)
1119 mock_print.assert_called_once_with(
1120 'changes to the environments file have been saved')
1121
1122@@ -415,9 +424,11 @@
1123 def test_interactive_mode_quit(self, mock_exit):
1124 # If the user explicitly quits the interactive mode, the program exits
1125 # without proceeding with the environment bootstrapping.
1126- with self.patch_interactive_mode(self.env_db, [self.env_db, None]):
1127+ with self.patch_interactive_mode(
1128+ self.env_db, self.jenv_db, [self.env_db, None]):
1129 manage._start_interactive_session(
1130- self.parser, self.env_type_db, self.env_db, self.env_file)
1131+ self.parser, self.env_type_db, self.env_db, self.jenv_db,
1132+ self.env_file)
1133 mock_exit.assert_called_once_with('quitting')
1134
1135
1136@@ -488,7 +499,9 @@
1137 self.assertIsNone(result)
1138
1139
1140-class TestSetupEnv(helpers.EnvFileTestsMixin, unittest.TestCase):
1141+class TestSetupEnv(
1142+ helpers.EnvFileTestsMixin, helpers.JenvFileTestsMixin,
1143+ unittest.TestCase):
1144
1145 def setUp(self):
1146 self.parser = mock.Mock()
1147@@ -596,10 +609,12 @@
1148 env_data = envs.get_env_data(env_db, 'aws')
1149 get_env_type_db_path = 'quickstart.models.envs.get_env_type_db'
1150 with mock.patch(get_env_type_db_path) as mock_get_env_type_db:
1151- with self.patch_interactive_mode(env_data) as mock_interactive:
1152- manage._setup_env(options, self.parser)
1153+ with self.make_jenv('ec2', yaml.safe_dump(self.jenv_data)):
1154+ jenv_db = jenv.get_env_db()
1155+ with self.patch_interactive_mode(env_data) as mock_interactive:
1156+ manage._setup_env(options, self.parser)
1157 mock_interactive.assert_called_once_with(
1158- self.parser, mock_get_env_type_db(), env_db, env_file)
1159+ self.parser, mock_get_env_type_db(), env_db, jenv_db, env_file)
1160 # The options is updated with data from the selected environment.
1161 self.assertEqual(env_file, options.env_file)
1162 self.assertEqual('aws', options.env_name)

Subscribers

People subscribed via source and target branches