Merge lp:~tealeg/landscape-client/warn-on-unicode into lp:~landscape/landscape-client/trunk

Proposed by Geoff Teale
Status: Merged
Approved by: Alberto Donato
Approved revision: 555
Merged at revision: 547
Proposed branch: lp:~tealeg/landscape-client/warn-on-unicode
Merge into: lp:~landscape/landscape-client/trunk
Prerequisite: lp:~tealeg/landscape-client/sanitise-hostname
Diff against target: 713 lines (+426/-76)
7 files modified
landscape/ui/view/configuration.py (+97/-26)
landscape/ui/view/tests/test_configuration.py (+302/-31)
man/landscape-client.1 (+1/-1)
man/landscape-config.1 (+1/-1)
man/landscape-message.1 (+1/-1)
po/fr.po (+12/-8)
po/landscape-client.pot (+12/-8)
To merge this branch: bzr merge lp:~tealeg/landscape-client/warn-on-unicode
Reviewer Review Type Date Requested Status
Alberto Donato (community) Approve
Mike Milner (community) Approve
Review via email: mp+99339@code.launchpad.net

Description of the change

Fixes bug #956612.

 - Extends the InfoBar implementation from the sanitise-hostname branch to allow for more than one error to be displayed in parallel.
 - Adds a check for non-ASCII input in Gtk.Entry fields.

To post a comment you must log in.
Revision history for this message
Mike Milner (milner) wrote :

Looks good, but there are two aspects that should be looked at:

[1]
35 - def is_valid_host_name(self, host_name):
36 - return HOSTNAME_REGEXP.match(host_name) is not None
37 + def is_valid_host_name(self, entry):
38 + host_name = self.sanitise_host_name(entry.get_text())

You're mixing concerns here. The is_valid_host_name (and is_ascii) functions now have side effects on the dialog. It would be better to keep the is_valid_host_name separate from the dialog-specific code. This makes the functions easy to test without worrying about the dialog side effects.

[2]
165 + def test_input_is_ascii(self):
166 + """
167 + Test that we can correctly identify non-ASCII input in a L{Gtk.Entry}.
168 + """
169 + dialog = ClientSettingsDialog(self.controller)
170 + self.run_gtk_eventloop()
171 + dialog.use_type_combobox.set_active(1)
172 + self.run_gtk_eventloop()
173 + dialog.hosted_account_name_entry.set_text("Toodleoo")
174 + self.assertTrue(dialog.is_ascii(dialog.hosted_account_name_entry))
175 + self.assertEqual(0, len(dialog._errored_entries))
176 + dialog.hosted_account_name_entry.set_text(u"T\xc3dle\xc4")
177 + self.assertFalse(dialog.is_ascii(dialog.hosted_account_name_entry))
178 + self.assertEqual(1, len(dialog._errored_entries))

This test should be broken into two. Each test should have the minimum amount of code to verify one expected behaviour. Generally, tests should have the following structure: SETUP, RUN-CODE, ASSERT. If there are two groups of asserts there should probably be two tests. Same goes for test_dismiss_validation_errors.

review: Needs Fixing
552. By Geoff Teale

Restructure to avoid side effects and structure tests better (from milners review)

553. By Geoff Teale

merged forwards from trunk.

554. By Geoff Teale

lint

Revision history for this message
Geoff Teale (tealeg) wrote :

> Looks good, but there are two aspects that should be looked at:
>
> [1]
> 35 - def is_valid_host_name(self, host_name):
> 36 - return HOSTNAME_REGEXP.match(host_name) is not None
> 37 + def is_valid_host_name(self, entry):
> 38 + host_name = self.sanitise_host_name(entry.get_text())
>
> You're mixing concerns here. The is_valid_host_name (and is_ascii) functions
> now have side effects on the dialog. It would be better to keep the
> is_valid_host_name separate from the dialog-specific code. This makes the
> functions easy to test without worrying about the dialog side effects.

Very good point indeed - for some reason I couldn't see a sensible way around this the other day, but today it seems obvious. Anyhow, done.

>
>
> [2]
> 165 + def test_input_is_ascii(self):
> 166 + """
> 167 + Test that we can correctly identify non-ASCII input in a
> L{Gtk.Entry}.
> 168 + """
> 169 + dialog = ClientSettingsDialog(self.controller)
> 170 + self.run_gtk_eventloop()
> 171 + dialog.use_type_combobox.set_active(1)
> 172 + self.run_gtk_eventloop()
> 173 + dialog.hosted_account_name_entry.set_text("Toodleoo")
> 174 +
> self.assertTrue(dialog.is_ascii(dialog.hosted_account_name_entry))
> 175 + self.assertEqual(0, len(dialog._errored_entries))
> 176 + dialog.hosted_account_name_entry.set_text(u"T\xc3dle\xc4")
> 177 +
> self.assertFalse(dialog.is_ascii(dialog.hosted_account_name_entry))
> 178 + self.assertEqual(1, len(dialog._errored_entries))
>
> This test should be broken into two. Each test should have the minimum amount
> of code to verify one expected behaviour. Generally, tests should have the
> following structure: SETUP, RUN-CODE, ASSERT. If there are two groups of
> asserts there should probably be two tests. Same goes for
> test_dismiss_validation_errors.

I've done quite an extensive clean up here now. With specific regard to test_dismiss_validation_errors - I have split it into local and hosted tests, but I retain assertions halfway through those tests - that stems from my personal preference to assert that a condition exists in my test if I am testing a later action that changes that state. I can see that some people might think that pollutes the meaning of the test, but I find it ensures the validity of the result. Please advise if you find this distasteful.

555. By Geoff Teale

Split test as per milner's review request.

Revision history for this message
Mike Milner (milner) wrote :

> I've done quite an extensive clean up here now. With specific regard to
> test_dismiss_validation_errors - I have split it into local and hosted tests,
> but I retain assertions halfway through those tests - that stems from my
> personal preference to assert that a condition exists in my test if I am
> testing a later action that changes that state. I can see that some people
> might think that pollutes the meaning of the test, but I find it ensures the
> validity of the result. Please advise if you find this distasteful.

Yeah checking preconditions makes sense, it was mainly about keeping the tests focused. If you're ever bored check out the S3 tests in boto for a great example of a monolithic super mega test and you'll see the bad extreme of this :)

Looks great! I especially like the Bob the Builder references ;) +1

review: Approve
Revision history for this message
Geoff Teale (tealeg) wrote :

>
> Yeah checking preconditions makes sense, it was mainly about keeping the tests
> focused. If you're ever bored check out the S3 tests in boto for a great
> example of a monolithic super mega test and you'll see the bad extreme of this
> :)

I'll save that for a slow day ;-)

> Looks great! I especially like the Bob the Builder references ;) +1

Parenthood is pervasive.

Cheers for the review!

Revision history for this message
Alberto Donato (ack) wrote :

Great! +1

#1:
+ else:
+ self.indicate_error_on_entry(self.local_landscape_host_entry)
+ if not host_name_ok:
+ self._validation_errors.add(self.INVALID_HOST_NAME)
+ if not ascii_ok:
+ self._validation_errors.add(self.UNICODE_IN_ENTRY)
+ return False

return False should be up one level of indentation (under the 'else' case)

review: Approve
556. By Geoff Teale

Correctly indent return. From ack's review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/ui/view/configuration.py'
2--- landscape/ui/view/configuration.py 2012-03-28 08:32:37 +0000
3+++ landscape/ui/view/configuration.py 2012-03-28 14:18:17 +0000
4@@ -9,8 +9,34 @@
5 CANONICAL_MANAGED, LOCAL_MANAGED, NOT_MANAGED)
6
7 # Note, I think this may not be fully compliant with the changes in RFC 1123
8-HOSTNAME_REGEXP = re.compile("^(([a-zA-Z][a-zA-Z0-9\-]*)?[a-zA-Z0-9][\.]?)*"
9- "(([A-Za-z][A-Za-z0-9\-]*)?[A-Za-z0-9])$")
10+HOST_NAME_REGEXP = re.compile("^(([a-zA-Z][a-zA-Z0-9\-]*)?[a-zA-Z0-9][\.]?)*"
11+ "(([A-Za-z][A-Za-z0-9\-]*)?[A-Za-z0-9])$")
12+
13+
14+def sanitise_host_name(host_name):
15+ """
16+ Do some minimal host name sanitation.
17+ """
18+ return host_name.strip()
19+
20+
21+def is_valid_host_name(host_name):
22+ """
23+ Check that the provided host name complies with L{HOST_NAME_REGEXP} and is
24+ therefor valid.
25+ """
26+ return HOST_NAME_REGEXP.match(host_name) is not None
27+
28+
29+def is_ascii(text):
30+ """
31+ Test that the provided string contains only characters from the ASCII set.
32+ """
33+ try:
34+ text.decode("ascii")
35+ return True
36+ except UnicodeDecodeError:
37+ return False
38
39
40 class ClientSettingsDialog(Gtk.Dialog):
41@@ -21,6 +47,8 @@
42 """
43
44 GLADE_FILE = "landscape-client-settings.glade"
45+ INVALID_HOST_NAME = 0
46+ UNICODE_IN_ENTRY = 1
47
48 def __init__(self, controller):
49 super(ClientSettingsDialog, self).__init__(
50@@ -29,37 +57,67 @@
51 self.set_default_icon_name("preferences-management-service")
52 self.set_resizable(False)
53 self._initialised = False
54+ self._validation_errors = set()
55+ self._errored_entries = []
56 self.controller = controller
57 self.setup_ui()
58 self.load_data()
59 # One extra revert to reset after loading data
60 self.controller.revert()
61
62- def sanitise_host_name(self, host_name):
63- """
64- Do some minimal input sanitation.
65- """
66- return host_name.strip()
67-
68- def is_valid_host_name(self, host_name):
69- return HOSTNAME_REGEXP.match(host_name) is not None
70+ def indicate_error_on_entry(self, entry):
71+ """
72+ Show a warning icon on a L{Gtk.Entry} to indicate some associated
73+ error.
74+ """
75+ entry.set_icon_from_stock(
76+ Gtk.EntryIconPosition.PRIMARY, Gtk.STOCK_DIALOG_WARNING)
77+ self._errored_entries.append(entry)
78+
79+ def check_local_landscape_host_name_entry(self):
80+ host_name = sanitise_host_name(
81+ self.local_landscape_host_entry.get_text())
82+ ascii_ok = is_ascii(host_name)
83+ host_name_ok = is_valid_host_name(host_name)
84+ if ascii_ok and host_name_ok:
85+ self.local_landscape_host_entry.set_text(host_name)
86+ return True
87+ else:
88+ self.indicate_error_on_entry(self.local_landscape_host_entry)
89+ if not host_name_ok:
90+ self._validation_errors.add(self.INVALID_HOST_NAME)
91+ if not ascii_ok:
92+ self._validation_errors.add(self.UNICODE_IN_ENTRY)
93+ return False
94+
95+ def check_entry(self, entry):
96+ """
97+ Check that the text content of a L{Gtk.Entry} is acceptable.
98+ """
99+ if is_ascii(entry.get_text()):
100+ return True
101+ else:
102+ self.indicate_error_on_entry(entry)
103+ self._validation_errors.add(self.UNICODE_IN_ENTRY)
104+ return False
105
106 def validity_check(self):
107- if self.use_type_combobox.get_active() < 2:
108+ self._validation_errors = set()
109+ if self._info_bar_container.get_visible():
110+ self.dismiss_infobar(None)
111+ active_iter = self.liststore.get_iter(
112+ self.use_type_combobox.get_active())
113+ [management_type] = self.liststore.get(active_iter, 0)
114+ if management_type == NOT_MANAGED:
115 return True
116+ elif management_type == CANONICAL_MANAGED:
117+ account_name_ok = self.check_entry(self.hosted_account_name_entry)
118+ password_ok = self.check_entry(self.hosted_password_entry)
119+ return account_name_ok and password_ok
120 else:
121- host_name = self.sanitise_host_name(
122- self.local_landscape_host_entry.get_text())
123- if self.is_valid_host_name(host_name):
124- self.local_landscape_host_entry.set_text(host_name)
125- return True
126- else:
127- self.info_message.set_text(self.INVALID_HOSTNAME_MESSAGE)
128- self.local_landscape_host_entry.set_icon_from_stock(
129- Gtk.EntryIconPosition.PRIMARY,
130- Gtk.STOCK_DIALOG_WARNING)
131- self._info_bar_container.show()
132- return False
133+ host_name_ok = self.check_local_landscape_host_name_entry()
134+ password_ok = self.check_entry(self.local_password_entry)
135+ return host_name_ok and password_ok
136
137 @property
138 def NO_SERVICE_TEXT(self):
139@@ -82,9 +140,13 @@
140 return _("Disable")
141
142 @property
143- def INVALID_HOSTNAME_MESSAGE(self):
144+ def INVALID_HOST_NAME_MESSAGE(self):
145 return _("Invalid host name.")
146
147+ @property
148+ def UNICODE_IN_ENTRY_MESSAGE(self):
149+ return _("Only ASCII characters are allowed.")
150+
151 def _set_use_type_combobox_from_controller(self):
152 """
153 Load the persisted L{management_type} from the controller and set the
154@@ -177,6 +239,14 @@
155 def register_response(self, widget):
156 if self.validity_check():
157 self.response(Gtk.ResponseType.OK)
158+ else:
159+ error_text = []
160+ if self.UNICODE_IN_ENTRY in self._validation_errors:
161+ error_text.append(self.UNICODE_IN_ENTRY_MESSAGE)
162+ if self.INVALID_HOST_NAME in self._validation_errors:
163+ error_text.append(self.INVALID_HOST_NAME_MESSAGE)
164+ self.info_message.set_text("\n".join(error_text))
165+ self._info_bar_container.show()
166
167 def set_button_text(self, management_type):
168 [alignment] = self.register_button.get_children()
169@@ -203,8 +273,9 @@
170
171 def dismiss_infobar(self, widget):
172 self._info_bar_container.hide()
173- self.local_landscape_host_entry.set_icon_from_stock(
174- Gtk.EntryIconPosition.PRIMARY, None)
175+ for entry in self._errored_entries:
176+ entry.set_icon_from_stock(Gtk.EntryIconPosition.PRIMARY, None)
177+ self._errored_entries = []
178
179 def setup_info_bar(self):
180 labels_size_group = self._builder.get_object("labels-sizegroup")
181
182=== modified file 'landscape/ui/view/tests/test_configuration.py'
183--- landscape/ui/view/tests/test_configuration.py 2012-03-28 08:18:47 +0000
184+++ landscape/ui/view/tests/test_configuration.py 2012-03-28 14:18:17 +0000
185@@ -6,7 +6,8 @@
186
187 if got_gobject_introspection:
188 from gi.repository import Gtk
189- from landscape.ui.view.configuration import ClientSettingsDialog
190+ from landscape.ui.view.configuration import (
191+ ClientSettingsDialog, sanitise_host_name, is_valid_host_name)
192 from landscape.ui.controller.configuration import ConfigController
193 import landscape.ui.model.configuration.state
194 from landscape.ui.model.configuration.state import (
195@@ -16,6 +17,47 @@
196 from landscape.tests.helpers import LandscapeTest
197
198
199+class ViewFunctionsTest(LandscapeTest):
200+
201+ def test_sanitise_host_name(self):
202+ """
203+ Test UI level host_name sanitation.
204+ """
205+ self.assertEqual("foo.bar", sanitise_host_name(" foo.bar"))
206+ self.assertEqual("foo.bar", sanitise_host_name("foo.bar "))
207+ self.assertEqual("foo.bar", sanitise_host_name(" foo.bar "))
208+
209+ def test_is_valid_host_name_ok(self):
210+ """
211+ Test that valid host names cause L{is_valid_host_name} to return
212+ L{True}.
213+ """
214+ self.assertTrue(is_valid_host_name("a"))
215+ self.assertTrue(is_valid_host_name("a.b"))
216+ self.assertTrue(is_valid_host_name("a.b.c"))
217+ self.assertTrue(is_valid_host_name("stop-squark"))
218+ self.assertTrue(is_valid_host_name("stop-squark.teale.DE"))
219+ self.assertTrue(is_valid_host_name("a2.b3.c4"))
220+
221+ def test_is_valid_host_name_bad(self):
222+ """
223+ Test that invalid host names cause L{is_valid_host_name} to return
224+ L{False}.
225+ """
226+ self.assertFalse(is_valid_host_name(".a"))
227+ self.assertFalse(is_valid_host_name("a."))
228+ self.assertFalse(is_valid_host_name("a b"))
229+ self.assertFalse(is_valid_host_name("a .b"))
230+ self.assertFalse(is_valid_host_name("a. b"))
231+
232+ def test_is_valid_host_name_unicode(self):
233+ """
234+ Test that host names containing Unicode cause L{is_valid_host_name} to
235+ return L{False}.
236+ """
237+ self.assertFalse(is_valid_host_name(u"\xc3a"))
238+
239+
240 class ConfigurationViewTest(LandscapeTest):
241
242 helpers = [ConfigurationProxyHelper]
243@@ -210,58 +252,287 @@
244 self.assertEqual("foo", dialog.hosted_account_name_entry.get_text())
245 self.assertEqual("bar", dialog.hosted_password_entry.get_text())
246
247- def test_sanitise_host_name(self):
248- """
249- Test that L{ClientSettingsDialog.sanitise_host_name} removes both
250- leading and trailing white space from a host name.
251- """
252- dialog = ClientSettingsDialog(self.controller)
253- self.assertEquals("foo.bar", dialog.sanitise_host_name(" foo.bar"))
254- self.assertEquals("foo.bar", dialog.sanitise_host_name("foo.bar "))
255- self.assertEquals("foo.bar", dialog.sanitise_host_name(" foo.bar "))
256-
257- def test_is_valid_host_name(self):
258- """
259- Test that L{is_valid_host_name} checks host names correctly.
260- """
261- dialog = ClientSettingsDialog(self.controller)
262- self.assertTrue(dialog.is_valid_host_name("foo.bar"))
263- self.assertFalse(dialog.is_valid_host_name("foo bar"))
264- self.assertFalse(dialog.is_valid_host_name("f\xc3.bar"))
265-
266- def test_validity_check(self):
267- """
268- Test that the L{validity_check} returns True for valid input and False
269- for invalid input.
270- """
271- dialog = ClientSettingsDialog(self.controller)
272- self.run_gtk_eventloop()
273-
274- # Checking disable should always return True
275+ def test_check_local_landscape_host_name_entry_ok(self):
276+ """
277+ Test that L{check_local_landscape_host_name_entry} returns L{True} when
278+ the input is a valid host name.
279+ """
280+ dialog = ClientSettingsDialog(self.controller)
281+ dialog.use_type_combobox.set_active(2)
282+ dialog.local_landscape_host_entry.set_text("foo.bar")
283+ self.assertTrue(dialog.check_local_landscape_host_name_entry())
284+
285+ def test_check_local_landscape_host_name_entry_ok_not_recorded(self):
286+ """
287+ Test that L{check_local_landscape_host_name_entry} does not add the
288+ entry to L{ClientSettingsDialog._errored_entries} when the input is a
289+ valid host name.
290+ """
291+ dialog = ClientSettingsDialog(self.controller)
292+ dialog.use_type_combobox.set_active(2)
293+ dialog.local_landscape_host_entry.set_text("foo.bar")
294+ dialog.check_local_landscape_host_name_entry()
295+ self.assertEqual(0, len(dialog._errored_entries))
296+
297+ def test_check_local_landscape_host_name_entry_bad_host_name(self):
298+ """
299+ Test that L{check_local_landscape_host_name_entry} returns L{False}
300+ when the input is not a valid host name.
301+ """
302+ dialog = ClientSettingsDialog(self.controller)
303+ dialog.use_type_combobox.set_active(2)
304+ dialog.local_landscape_host_entry.set_text("foo bar")
305+ self.assertFalse(dialog.check_local_landscape_host_name_entry())
306+
307+ def test_check_local_landscape_host_name_entry_bad_recorded(self):
308+ """
309+ Test that L{check_local_landscape_host_name_entry} does add the
310+ entry to L{ClientSettingsDialog._errored_entries} when the input is not
311+ a valid host name.
312+ """
313+ dialog = ClientSettingsDialog(self.controller)
314+ dialog.use_type_combobox.set_active(2)
315+ dialog.local_landscape_host_entry.set_text("foo bar")
316+ dialog.check_local_landscape_host_name_entry()
317+ self.assertEqual(1, len(dialog._errored_entries))
318+
319+ def test_check_local_landscape_host_name_entry_bad_error_type(self):
320+ """
321+ Test that L{check_local_landscape_host_name_entry} adds the correct
322+ error type to L{ClientSettingsDialog._validation_errors} when the input
323+ is not a valid host name.
324+ """
325+ dialog = ClientSettingsDialog(self.controller)
326+ dialog.use_type_combobox.set_active(2)
327+ dialog.local_landscape_host_entry.set_text("foo bar")
328+ dialog.check_local_landscape_host_name_entry()
329+ self.assertEqual(set([dialog.INVALID_HOST_NAME]),
330+ dialog._validation_errors)
331+
332+ def test_check_local_landscape_host_name_entry_unicode_in_host_name(self):
333+ """
334+ Test that L{check_local_landscape_host_name_entry} returns L{False}
335+ when the input contains Unicode.
336+ """
337+ dialog = ClientSettingsDialog(self.controller)
338+ dialog.use_type_combobox.set_active(2)
339+ dialog.local_landscape_host_entry.set_text(u"f\xc3.bar")
340+ self.assertFalse(dialog.check_local_landscape_host_name_entry())
341+
342+ def test_check_local_landscape_host_name_entry_unicode_recorded(self):
343+ """
344+ Test that L{check_local_landscape_host_name_entry} does add the
345+ entry to L{ClientSettingsDialog._errored_entries} when the input
346+ contains Unicode.
347+ """
348+ dialog = ClientSettingsDialog(self.controller)
349+ dialog.use_type_combobox.set_active(2)
350+ dialog.local_landscape_host_entry.set_text(u"f\xc3.bar")
351+ dialog.check_local_landscape_host_name_entry()
352+ self.assertEqual(1, len(dialog._errored_entries))
353+
354+ def test_check_local_landscape_host_name_entry_unicode_error_type(self):
355+ """
356+ Test that L{check_local_landscape_host_name_entry} adds the correct
357+ error type to L{ClientSettingsDialog._validation_errors} when the input
358+ contains Unicode.
359+ """
360+ dialog = ClientSettingsDialog(self.controller)
361+ dialog.use_type_combobox.set_active(2)
362+ dialog.local_landscape_host_entry.set_text(u"f\xc3.bar")
363+ dialog.check_local_landscape_host_name_entry()
364+ self.assertEqual(
365+ set([dialog.INVALID_HOST_NAME, dialog.UNICODE_IN_ENTRY]),
366+ dialog._validation_errors)
367+
368+ def test_check_entry_ok(self):
369+ """
370+ Test that we return L{True} when the text of a L{Gtk.Entry} is valid
371+ input.
372+ """
373+ dialog = ClientSettingsDialog(self.controller)
374+ self.run_gtk_eventloop()
375+ dialog.use_type_combobox.set_active(1)
376+ self.run_gtk_eventloop()
377+ dialog.hosted_account_name_entry.set_text("Toodleoo")
378+ self.assertTrue(dialog.check_entry(dialog.hosted_account_name_entry))
379+
380+ def test_check_entry_doesnt_record_entry_when_ok(self):
381+ """
382+ Test that, when the text of a L{Gtk.Entry} is valid nothing is added to
383+ L{ClientSettingsDialog._errored_entries}.
384+ """
385+ dialog = ClientSettingsDialog(self.controller)
386+ self.run_gtk_eventloop()
387+ dialog.use_type_combobox.set_active(1)
388+ self.run_gtk_eventloop()
389+ dialog.hosted_account_name_entry.set_text("Toodleoo")
390+ dialog.check_entry(dialog.hosted_account_name_entry)
391+ self.assertEqual(0, len(dialog._errored_entries))
392+
393+ def test_check_entry_non_ascii(self):
394+ """
395+ Test that we return L{False} when the text of a L{Gtk.Entry} contains
396+ Unicode input.
397+ """
398+ dialog = ClientSettingsDialog(self.controller)
399+ self.run_gtk_eventloop()
400+ dialog.use_type_combobox.set_active(1)
401+ self.run_gtk_eventloop()
402+ dialog.hosted_account_name_entry.set_text(u"T\xc3dle\xc4")
403+ self.assertFalse(dialog.check_entry(dialog.hosted_account_name_entry))
404+
405+ def test_check_entry_records_entry_when_non_ascii(self):
406+ """
407+ Test that, when the text of a L{Gtk.Entry} contains Unicode it is
408+ added to L{ClientSettingsDialog._errored_entries}.
409+ """
410+ dialog = ClientSettingsDialog(self.controller)
411+ self.run_gtk_eventloop()
412+ dialog.use_type_combobox.set_active(1)
413+ self.run_gtk_eventloop()
414+ dialog.hosted_account_name_entry.set_text(u"T\xc3dle\xc4")
415+ dialog.check_entry(dialog.hosted_account_name_entry)
416+ self.assertEqual(1, len(dialog._errored_entries))
417+
418+ def test_dismiss_validation_errors_local(self):
419+ """
420+ Test that dismissing the validation errors tidies up indicators that
421+ have been set against local settings.
422+ """
423+ dialog = ClientSettingsDialog(self.controller)
424+ self.run_gtk_eventloop()
425+ dialog.use_type_combobox.set_active(1)
426+ self.run_gtk_eventloop()
427+ dialog.hosted_account_name_entry.set_text(u"T\xc3dle\xc4")
428+ dialog.hosted_password_entry.set_text(u"T\xc3dle\xc4")
429+ self.run_gtk_eventloop()
430+ dialog.validity_check()
431+ self.run_gtk_eventloop()
432+ self.assertEqual(2, len(dialog._errored_entries))
433+ [entry1, entry2] = dialog._errored_entries
434+ self.assertEqual(Gtk.STOCK_DIALOG_WARNING,
435+ entry1.get_icon_stock(Gtk.EntryIconPosition.PRIMARY))
436+ self.assertEqual(Gtk.STOCK_DIALOG_WARNING,
437+ entry2.get_icon_stock(Gtk.EntryIconPosition.PRIMARY))
438+ dialog.dismiss_infobar(None)
439+ self.run_gtk_eventloop()
440+ self.assertEqual(0, len(dialog._errored_entries))
441+ self.assertNotEqual(
442+ Gtk.STOCK_DIALOG_WARNING,
443+ entry1.get_icon_stock(Gtk.EntryIconPosition.PRIMARY))
444+ self.assertNotEqual(
445+ Gtk.STOCK_DIALOG_WARNING,
446+ entry2.get_icon_stock(Gtk.EntryIconPosition.PRIMARY))
447+
448+ def test_dismiss_validation_errors_hosted(self):
449+ """
450+ Test that dismissing the validation errors tidies up indicators that
451+ have been set against hosted fields.
452+ """
453+ dialog = ClientSettingsDialog(self.controller)
454+ self.run_gtk_eventloop()
455+ dialog.use_type_combobox.set_active(2)
456+ self.run_gtk_eventloop()
457+ dialog.local_landscape_host_entry.set_text("dodgy as hell")
458+ self.run_gtk_eventloop()
459+ dialog.validity_check()
460+ self.run_gtk_eventloop()
461+ self.assertEqual(1, len(dialog._errored_entries))
462+ [entry1] = dialog._errored_entries
463+ self.assertEqual(Gtk.STOCK_DIALOG_WARNING,
464+ entry1.get_icon_stock(Gtk.EntryIconPosition.PRIMARY))
465+ dialog.dismiss_infobar(None)
466+ self.run_gtk_eventloop()
467+ self.assertEqual(0, len(dialog._errored_entries))
468+ self.assertNotEqual(
469+ Gtk.STOCK_DIALOG_WARNING,
470+ entry1.get_icon_stock(Gtk.EntryIconPosition.PRIMARY))
471+
472+ def test_validity_check_disabled(self):
473+ """
474+ Test that the L{validity_check} returns True when we disable landscape
475+ client.
476+ """
477+ dialog = ClientSettingsDialog(self.controller)
478+ self.run_gtk_eventloop()
479 dialog.use_type_combobox.set_active(0)
480 self.run_gtk_eventloop()
481 self.assertTrue(dialog.validity_check())
482
483- # Check for hosted - currently returns True always
484+ def test_validity_check_hosted(self):
485+ """
486+ Test that the L{validity_check} returns True when the hosted fields are
487+ valid.
488+ """
489+ dialog = ClientSettingsDialog(self.controller)
490+ self.run_gtk_eventloop()
491 dialog.use_type_combobox.set_active(1)
492+ dialog.hosted_account_name_entry.set_text("Bob")
493+ dialog.hosted_password_entry.set_text("the builder")
494 self.run_gtk_eventloop()
495 self.assertTrue(dialog.validity_check())
496
497- # Checks for local
498+ def test_validity_check_hosted_unicode(self):
499+ """
500+ Test that the L{validity_check} returns False when the hosted fields
501+ contain Unicode.
502+ """
503+ dialog = ClientSettingsDialog(self.controller)
504+ self.run_gtk_eventloop()
505+ dialog.use_type_combobox.set_active(1)
506+ dialog.hosted_account_name_entry.set_text(u"B\xc3b")
507+ self.run_gtk_eventloop()
508+ self.assertFalse(dialog.validity_check())
509+
510+ def test_validity_check_local_ok(self):
511+ """
512+ Test that the L{validity_check} returns True when the local fields
513+ are valid.
514+ """
515+ dialog = ClientSettingsDialog(self.controller)
516+ self.run_gtk_eventloop()
517 dialog.use_type_combobox.set_active(2)
518 self.run_gtk_eventloop()
519 dialog.local_landscape_host_entry.set_text("foo.bar")
520 self.run_gtk_eventloop()
521 self.assertTrue(dialog.validity_check())
522+
523+ def test_validity_check_local_sanitisable(self):
524+ """
525+ Test that the L{validity_check} returns True when the local fields
526+ are valid after sanitation.
527+ """
528+ dialog = ClientSettingsDialog(self.controller)
529+ self.run_gtk_eventloop()
530+ dialog.use_type_combobox.set_active(2)
531 dialog.local_landscape_host_entry.set_text(" foo.bar")
532 self.run_gtk_eventloop()
533 self.assertTrue(dialog.validity_check())
534 dialog.local_landscape_host_entry.set_text("foo.bar ")
535 self.run_gtk_eventloop()
536 self.assertTrue(dialog.validity_check())
537+
538+ def test_validity_check_local_invalid_host_name(self):
539+ """
540+ Test that the L{validity_check} returns False when the host name is
541+ invalid.
542+ """
543+ dialog = ClientSettingsDialog(self.controller)
544+ self.run_gtk_eventloop()
545+ dialog.use_type_combobox.set_active(2)
546 dialog.local_landscape_host_entry.set_text("foo bar")
547 self.run_gtk_eventloop()
548 self.assertFalse(dialog.validity_check())
549+
550+ def test_validity_check_local_unicode(self):
551+ """
552+ Test that the L{validity_check} returns False when the host name
553+ contains Unicode.
554+ """
555+ dialog = ClientSettingsDialog(self.controller)
556+ self.run_gtk_eventloop()
557+ dialog.use_type_combobox.set_active(2)
558 dialog.local_landscape_host_entry.set_text(u"f\xc3.bar")
559 self.run_gtk_eventloop()
560 self.assertFalse(dialog.validity_check())
561
562=== modified file 'man/landscape-client.1'
563--- man/landscape-client.1 2012-03-28 08:38:34 +0000
564+++ man/landscape-client.1 2012-03-28 14:18:17 +0000
565@@ -1,5 +1,5 @@
566 .\"Text automatically generated by txt2man
567-.TH landscape-client "18 January 2010" "" ""
568+.TH landscape-client 1 "27 March 2012" "" ""
569 .SH NAME
570 \fBlandscape-client \fP- Landscape system client
571 \fB
572
573=== modified file 'man/landscape-config.1'
574--- man/landscape-config.1 2012-03-28 08:38:34 +0000
575+++ man/landscape-config.1 2012-03-28 14:18:17 +0000
576@@ -1,5 +1,5 @@
577 .\"Text automatically generated by txt2man
578-.TH landscape-config "18 January 2010" "" ""
579+.TH landscape-config 1 "27 March 2012" "" ""
580 .SH NAME
581 \fBlandscape-config \fP- configure the Landscape management client
582 \fB
583
584=== modified file 'man/landscape-message.1'
585--- man/landscape-message.1 2012-03-28 08:38:34 +0000
586+++ man/landscape-message.1 2012-03-28 14:18:17 +0000
587@@ -1,5 +1,5 @@
588 .\"Text automatically generated by txt2man
589-.TH landscape-message "18 January 2010" "" ""
590+.TH landscape-message 1 "27 March 2012" "" ""
591 .SH NAME
592 \fBlandscape-message \fP- Send a message to the landscape web interface
593 \fB
594
595=== modified file 'po/fr.po'
596--- po/fr.po 2012-03-28 08:38:34 +0000
597+++ po/fr.po 2012-03-28 14:18:17 +0000
598@@ -8,7 +8,7 @@
599 msgstr ""
600 "Project-Id-Version: PACKAGE VERSION\n"
601 "Report-Msgid-Bugs-To: \n"
602-"POT-Creation-Date: 2012-03-28 10:34+0200\n"
603+"POT-Creation-Date: 2012-03-28 11:07+0200\n"
604 "PO-Revision-Date: 2012-03-23 11:25+0100\n"
605 "Last-Translator: Thomas Hervé <thomas.herve@canonical.com>\n"
606 "Language-Team: français <>\n"
607@@ -56,35 +56,39 @@
608 msgid "Attempting to disable landscape client."
609 msgstr "Tentative de désactivation du client Landscape."
610
611-#: ../landscape/ui/view/configuration.py:27
612+#: ../landscape/ui/view/configuration.py:55
613 #: ../applications/landscape-client-settings.desktop.in.h:1
614 msgid "Management Service"
615 msgstr "Service de gestion"
616
617-#: ../landscape/ui/view/configuration.py:66
618+#: ../landscape/ui/view/configuration.py:124
619 msgid "None"
620 msgstr "Aucun"
621
622-#: ../landscape/ui/view/configuration.py:70
623+#: ../landscape/ui/view/configuration.py:128
624 msgid "Landscape - hosted by Canonical"
625 msgstr "Landscape - hébergé par Canonical"
626
627-#: ../landscape/ui/view/configuration.py:74
628+#: ../landscape/ui/view/configuration.py:132
629 msgid "Landscape - dedicated server"
630 msgstr "Landscape - serveur dédié"
631
632-#: ../landscape/ui/view/configuration.py:78
633+#: ../landscape/ui/view/configuration.py:136
634 msgid "Register"
635 msgstr "S'enregistrer"
636
637-#: ../landscape/ui/view/configuration.py:82
638+#: ../landscape/ui/view/configuration.py:140
639 msgid "Disable"
640 msgstr "Désactiver"
641
642-#: ../landscape/ui/view/configuration.py:86
643+#: ../landscape/ui/view/configuration.py:144
644 msgid "Invalid host name."
645 msgstr "Nom d'hôte invalide."
646
647+#: ../landscape/ui/view/configuration.py:148
648+msgid "Only ASCII characters are allowed."
649+msgstr "Seuls les caractères ASCII sont autritorisés."
650+
651 #: ../landscape/ui/view/ui/landscape-client-settings.glade.h:1
652 msgid ""
653 "Landscape is a remote administration service from Canonical. If you allow "
654
655=== modified file 'po/landscape-client.pot'
656--- po/landscape-client.pot 2012-03-28 08:38:34 +0000
657+++ po/landscape-client.pot 2012-03-28 14:18:17 +0000
658@@ -8,7 +8,7 @@
659 msgstr ""
660 "Project-Id-Version: PACKAGE VERSION\n"
661 "Report-Msgid-Bugs-To: \n"
662-"POT-Creation-Date: 2012-03-28 10:34+0200\n"
663+"POT-Creation-Date: 2012-03-28 11:07+0200\n"
664 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
665 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
666 "Language-Team: LANGUAGE <LL@li.org>\n"
667@@ -55,35 +55,39 @@
668 msgid "Attempting to disable landscape client."
669 msgstr ""
670
671-#: ../landscape/ui/view/configuration.py:27
672+#: ../landscape/ui/view/configuration.py:55
673 #: ../applications/landscape-client-settings.desktop.in.h:1
674 msgid "Management Service"
675 msgstr ""
676
677-#: ../landscape/ui/view/configuration.py:66
678+#: ../landscape/ui/view/configuration.py:124
679 msgid "None"
680 msgstr ""
681
682-#: ../landscape/ui/view/configuration.py:70
683+#: ../landscape/ui/view/configuration.py:128
684 msgid "Landscape - hosted by Canonical"
685 msgstr ""
686
687-#: ../landscape/ui/view/configuration.py:74
688+#: ../landscape/ui/view/configuration.py:132
689 msgid "Landscape - dedicated server"
690 msgstr ""
691
692-#: ../landscape/ui/view/configuration.py:78
693+#: ../landscape/ui/view/configuration.py:136
694 msgid "Register"
695 msgstr ""
696
697-#: ../landscape/ui/view/configuration.py:82
698+#: ../landscape/ui/view/configuration.py:140
699 msgid "Disable"
700 msgstr ""
701
702-#: ../landscape/ui/view/configuration.py:86
703+#: ../landscape/ui/view/configuration.py:144
704 msgid "Invalid host name."
705 msgstr ""
706
707+#: ../landscape/ui/view/configuration.py:148
708+msgid "Only ASCII characters are allowed."
709+msgstr ""
710+
711 #: ../landscape/ui/view/ui/landscape-client-settings.glade.h:1
712 msgid ""
713 "Landscape is a remote administration service from Canonical. If you allow "

Subscribers

People subscribed via source and target branches

to all changes: