Merge ~woutervb/snapstore-client:move_to_pytest into snapstore-client:master

Proposed by Wouter van Bommel
Status: Merged
Approved by: Wouter van Bommel
Approved revision: 36af623c06a1bf7199697f412f5ce55450dfcdd7
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~woutervb/snapstore-client:move_to_pytest
Merge into: snapstore-client:master
Diff against target: 1820 lines (+534/-698)
10 files modified
Makefile (+4/-3)
conftest.py (+70/-0)
dev/null (+0/-67)
requirements-dev.txt (+2/-3)
snapstore_client/logic/tests/test_login.py (+95/-126)
snapstore_client/logic/tests/test_overrides.py (+132/-156)
snapstore_client/logic/tests/test_push.py (+32/-54)
snapstore_client/tests/test_config.py (+52/-76)
snapstore_client/tests/test_presentation_helpers.py (+71/-108)
snapstore_client/tests/test_webservices.py (+76/-105)
Reviewer Review Type Date Requested Status
Maximiliano Bertacchini Approve
Jonathan Hartley (community) Approve
Review via email: mp+414940@code.launchpad.net

This proposal supersedes a proposal from 2022-01-28.

Commit message

Convert the testing to pytest

This will simplify changes in the future and resulted in a big cleanup.
The test_cli.py has been removed, as the cli will be replaced by click and has to be done completely.

Description of the change

Convert the testing to pytest

This will simplify changes in the future and resulted in a big cleanup.
The test_cli.py has been removed, as the cli will be replaced by click and has to be done completely.

To post a comment you must log in.
Revision history for this message
Maximiliano Bertacchini (maxiberta) wrote : Posted in a previous version of this proposal

Looks great to me, thanks. But, I think it'd be wise to announce this migration publicly to the team, given the relative backlash in December's monthly meeting. That said, I'm +1! But please first check comments below.

Revision history for this message
Wouter van Bommel (woutervb) wrote : Posted in a previous version of this proposal

Thanks for the feedback, will work on the comments.
Won't hide it for broader public, but it at least made me realize that we need some kind of styleguide on how / where to define fixtures

Revision history for this message
Jonathan Hartley (tartley) : Posted in a previous version of this proposal
review: Approve
Revision history for this message
Jonathan Hartley (tartley) wrote :

Bravely fought, tackling this churn!

I have one minor superficial FYI in the diffs, and one here. Ignore them if they are not today's problem:

Just FYI, when I run these test on a focal lxc, I get deprecation warnings from the use of 'responses' in test_login.py. I don't think this is due to your edits, but thought I'd mention it just in case you weren't aware and wanted to know:

=============================== warnings summary ===============================
snapstore_client/logic/tests/test_login.py: 16 warnings
  /home/jhartley/src/snapstore-client/env/lib/python3.8/site-packages/responses/__init__.py:334: DeprecationWarning: Argument 'match_querystring' is deprecated. Use 'responses.matchers.query_param_matcher' or 'responses.matchers.query_string_matcher'
    warn(

-- Docs: https://docs.pytest.org/en/stable/warnings.html

review: Approve
Revision history for this message
Jonathan Hartley (tartley) wrote :

Another thought...

Revision history for this message
Maximiliano Bertacchini (maxiberta) wrote :

+1 awesome. Check a couple of non-blocking comments below. Thanks!

review: Approve
Revision history for this message
Wouter van Bommel (woutervb) wrote :

Thanks for the responses, will update (most) things you both found.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/Makefile b/Makefile
2index 76ddd0a..be1be80 100644
3--- a/Makefile
4+++ b/Makefile
5@@ -28,7 +28,7 @@ snap:
6 snapcraft
7
8 test: $(ENV)/dev
9- $(PYTHON3) -m unittest $(TESTS) 2>&1
10+ $(PYTHON3) -m pytest
11 $(MAKE) --silent lint
12
13 /snap/bin/documentation-builder:
14@@ -39,7 +39,7 @@ docs: /snap/bin/documentation-builder
15
16
17 lint: $(ENV)/dev
18- $(FLAKE8) $(SERVICE_PACKAGE)
19+ $(FLAKE8) $(SERVICE_PACKAGE) conftest.py
20 $(BLACK) --check .
21
22 black: $(ENV)/dev
23@@ -47,8 +47,9 @@ black: $(ENV)/dev
24
25 coverage: $(ENV)/dev
26 $(PYTHON3) -m coverage erase
27- $(PYTHON3) -m coverage run --include "$(SERVICE_PACKAGE)*" -m unittest $(TESTS)
28+ $(PYTHON3) -m coverage run --include "$(SERVICE_PACKAGE)*" -m pytest
29 $(PYTHON3) -m coverage html
30+ $(PYTHON3) -m coverage report
31
32 clean:
33 rm -rf $(TMPDIR)
34diff --git a/conftest.py b/conftest.py
35new file mode 100644
36index 0000000..06c7ca0
37--- /dev/null
38+++ b/conftest.py
39@@ -0,0 +1,70 @@
40+import os.path
41+import tempfile
42+from unittest import mock
43+from unittest.mock import patch
44+
45+from pymacaroons import Macaroon
46+import pytest
47+
48+from snapstore_client.config import Config
49+
50+
51+@pytest.fixture(scope="function")
52+def ftmpdir(tmpdir):
53+ """Takes the tmpdir, but add a random path per function invocation"""
54+ with tempfile.TemporaryDirectory(dir=tmpdir) as new_tmpdir:
55+ yield new_tmpdir
56+
57+
58+@pytest.fixture
59+def mocked_xdgconfig(ftmpdir):
60+ with patch("xdg.BaseDirectory.xdg_config_home", ftmpdir), patch(
61+ "xdg.BaseDirectory.xdg_config_dirs", [ftmpdir]
62+ ):
63+ yield ftmpdir
64+
65+
66+@pytest.fixture
67+def mocked_config(mocked_xdgconfig):
68+ app_config_path = os.path.join(mocked_xdgconfig, "snap-store-proxy-client")
69+ os.makedirs(app_config_path)
70+ root = Macaroon(key="random-key")
71+ root.add_third_party_caveat("login.example.com", "sso-key", "payload")
72+ unbound_discharge = Macaroon(
73+ location="login.example.com", identifier="payload", key="sso-key"
74+ )
75+ with open(os.path.join(app_config_path, "config.ini"), "w") as f:
76+ f.write(
77+ (
78+ "[store:default]\n"
79+ "gw_url = {gw_url}\n"
80+ "sso_url = {sso_url}\n"
81+ "root = {root}\n"
82+ "unbound_discharge = {unbound_discharge}\n"
83+ ).format(
84+ gw_url="http://store.local/",
85+ sso_url="http://sso.local/",
86+ root=root.serialize(),
87+ unbound_discharge=unbound_discharge.serialize(),
88+ )
89+ )
90+
91+ yield Config()
92+
93+
94+@pytest.fixture
95+def mocked_empty_config(mocked_xdgconfig):
96+ app_config_path = os.path.join(mocked_xdgconfig, "snap-store-proxy-client")
97+ os.makedirs(app_config_path)
98+
99+
100+@pytest.fixture
101+def mock_input():
102+ with mock.patch("builtins.input") as input:
103+ yield input
104+
105+
106+@pytest.fixture
107+def mock_getpass():
108+ with mock.patch("getpass.getpass") as getpass:
109+ yield getpass
110diff --git a/requirements-dev.txt b/requirements-dev.txt
111index 72daeb2..d9285a2 100644
112--- a/requirements-dev.txt
113+++ b/requirements-dev.txt
114@@ -1,8 +1,7 @@
115 acceptable>=0.9
116 coverage
117-fixtures
118 flake8
119 responses
120-testscenarios
121-testtools
122 black
123+pytest
124+testtools
125\ No newline at end of file
126diff --git a/snapstore_client/logic/tests/test_login.py b/snapstore_client/logic/tests/test_login.py
127index d61ac1b..7dbfea1 100644
128--- a/snapstore_client/logic/tests/test_login.py
129+++ b/snapstore_client/logic/tests/test_login.py
130@@ -1,13 +1,13 @@
131 # Copyright 2017 Canonical Ltd.
132
133+import pytest
134 import json
135 from unittest import mock
136 from urllib.parse import urljoin, urlparse
137
138-import fixtures
139+
140 from pymacaroons import Macaroon
141 import responses
142-from testtools import TestCase
143 from testtools.matchers import (
144 ContainsDict,
145 Equals,
146@@ -22,23 +22,11 @@ from snapstore_client.logic.login import login
147 from snapstore_client.tests import factory
148
149
150-class LoginTests(TestCase):
151- def setUp(self):
152- super().setUp()
153- self.default_gw_url = "http://store.local/"
154- self.default_sso_url = "https://login.staging.ubuntu.com/"
155- self.logger = self.useFixture(fixtures.FakeLogger())
156- self.config_path = self.useFixture(fixtures.TempDir()).path
157- self.useFixture(
158- fixtures.MonkeyPatch("xdg.BaseDirectory.xdg_config_home", self.config_path)
159- )
160- self.useFixture(
161- fixtures.MonkeyPatch(
162- "xdg.BaseDirectory.xdg_config_dirs", [self.config_path]
163- )
164- )
165- self.mock_input = self.useFixture(fixtures.MockPatch("builtins.input")).mock
166- self.mock_getpass = self.useFixture(fixtures.MockPatch("getpass.getpass")).mock
167+@pytest.mark.usefixtures("mocked_config")
168+class TestLogin:
169+
170+ default_gw_url = "http://store.local/"
171+ default_sso_url = "https://login.staging.ubuntu.com/"
172
173 def make_responses_callback(self, response_templates):
174 full_responses = []
175@@ -86,21 +74,21 @@ class LoginTests(TestCase):
176 return macaroon.serialize()
177
178 @responses.activate
179- def test_login_sso_mismatch(self):
180- self.mock_input.return_value = "user@example.org"
181- self.mock_getpass.return_value = "secret"
182+ def test_login_sso_mismatch(self, mock_input, mock_getpass):
183+ mock_input.return_value = "user@example.org"
184+ mock_getpass.returnvalue = "secret"
185 macaroon = Macaroon()
186 macaroon.add_third_party_caveat("another.example.com", "", "")
187 self.add_issue_store_admin_response(
188 {"status": 200, "json": {"macaroon": macaroon.serialize()}}
189 )
190 self.add_get_sso_discharge_response({"status": 401})
191- self.assertRaises(exceptions.StoreMacaroonSSOMismatch, login, self.make_args())
192+ pytest.raises(exceptions.StoreMacaroonSSOMismatch, login, self.make_args())
193
194 @responses.activate
195- def test_login_sso_bad_email(self):
196- self.mock_input.return_value = ""
197- self.mock_getpass.return_value = ""
198+ def test_login_sso_bad_email(self, mock_input, mock_getpass, caplog):
199+ mock_input.return_value = ""
200+ mock_getpass.return_value = ""
201 self.add_issue_store_admin_response(
202 {"status": 200, "json": {"macaroon": self.make_root_macaroon()}}
203 )
204@@ -111,19 +99,17 @@ class LoginTests(TestCase):
205 self.add_get_sso_discharge_response(
206 {"status": 401, "json": {"error_list": [auth_error]}}
207 )
208- self.assertEqual(1, login(self.make_args()))
209- self.assertEqual(
210- "Enter your Ubuntu One SSO credentials.\n"
211- "Login failed.\n"
212- "Authentication error: Invalid request data\n"
213- "email: Enter a valid email address.\n",
214- self.logger.output,
215- )
216+ assert 1 == login(self.make_args())
217+ assert [
218+ "Login failed.",
219+ "Authentication error: Invalid request data",
220+ "email: Enter a valid email address.",
221+ ] == caplog.messages
222
223 @responses.activate
224- def test_login_sso_unauthorized(self):
225- self.mock_input.return_value = "user@example.org"
226- self.mock_getpass.return_value = "secret"
227+ def test_login_sso_unauthorized(self, mock_input, mock_getpass, caplog):
228+ mock_input.return_value = "user@example.org"
229+ mock_getpass.return_value = "secret"
230 self.add_issue_store_admin_response(
231 {"status": 200, "json": {"macaroon": self.make_root_macaroon()}}
232 )
233@@ -131,51 +117,41 @@ class LoginTests(TestCase):
234 self.add_get_sso_discharge_response(
235 {"status": 401, "json": {"error_list": [auth_error]}}
236 )
237- self.assertEqual(1, login(self.make_args()))
238- self.assertEqual(
239- "Enter your Ubuntu One SSO credentials.\n"
240- "Login failed.\n"
241- "Authentication error: Provided email/password is not correct.\n",
242- self.logger.output,
243- )
244+ assert 1 == login(self.make_args())
245+ assert [
246+ "Login failed.",
247+ "Authentication error: Provided email/password is not correct.",
248+ ] == caplog.messages
249
250 @responses.activate
251- def test_login_twofactor_required(self):
252- self.mock_input.side_effect = ("user@example.org", "123456")
253- self.mock_getpass.return_value = "secret"
254+ def test_login_twofactor_required(self, mock_input, mock_getpass):
255+ mock_input.side_effect = ("user@example.org", "123456")
256+ mock_getpass.return_value = "secret"
257 root = self.make_root_macaroon()
258 self.add_issue_store_admin_response({"status": 200, "json": {"macaroon": root}})
259 self.add_get_sso_discharge_response(
260 {"status": 401, "json": {"error_list": [{"code": "twofactor-required"}]}},
261 {"status": 200, "json": {"discharge_macaroon": "dummy"}},
262 )
263- self.assertEqual(0, login(self.make_args()))
264+ assert 0 == login(self.make_args())
265
266- self.assertIn("Enter your Ubuntu One SSO credentials.", self.logger.output)
267- self.mock_input.assert_has_calls(
268+ mock_input.assert_has_calls(
269 [mock.call("Email: "), mock.call("Second-factor auth: ")]
270 )
271- self.mock_getpass.assert_called_once_with("Password: ")
272- self.assertEqual(3, len(responses.calls))
273- self.assertEqual(
274- {
275- "email": "user@example.org",
276- "password": "secret",
277- "caveat_id": "payload",
278- },
279- json.loads(responses.calls[1].request.body.decode()),
280- )
281- self.assertEqual(
282- {
283- "email": "user@example.org",
284- "password": "secret",
285- "caveat_id": "payload",
286- "otp": "123456",
287- },
288- json.loads(responses.calls[2].request.body.decode()),
289- )
290- self.assertThat(
291- config.Config().parser,
292+ mock_getpass.assert_called_once_with("Password: ")
293+ assert 3 == len(responses.calls)
294+ assert {
295+ "email": "user@example.org",
296+ "password": "secret",
297+ "caveat_id": "payload",
298+ } == json.loads(responses.calls[1].request.body.decode())
299+ assert {
300+ "email": "user@example.org",
301+ "password": "secret",
302+ "caveat_id": "payload",
303+ "otp": "123456",
304+ } == json.loads(responses.calls[2].request.body.decode())
305+ assert (
306 ContainsDict(
307 {
308 "store:default": MatchesDict(
309@@ -188,13 +164,14 @@ class LoginTests(TestCase):
310 }
311 ),
312 }
313- ),
314+ ).match(config.Config().parser)
315+ is None
316 )
317
318 @responses.activate
319- def test_login_twofactor_not_required(self):
320- self.mock_input.return_value = "user@example.org"
321- self.mock_getpass.return_value = "secret"
322+ def test_login_twofactor_not_required(self, mock_input, mock_getpass):
323+ mock_input.return_value = "user@example.org"
324+ mock_getpass.return_value = "secret"
325 root = self.make_root_macaroon()
326 self.add_issue_store_admin_response({"status": 200, "json": {"macaroon": root}})
327 self.add_get_sso_discharge_response(
328@@ -202,20 +179,15 @@ class LoginTests(TestCase):
329 )
330 login(self.make_args())
331
332- self.assertIn("Enter your Ubuntu One SSO credentials.", self.logger.output)
333- self.mock_input.assert_called_once_with("Email: ")
334- self.mock_getpass.assert_called_once_with("Password: ")
335- self.assertEqual(2, len(responses.calls))
336- self.assertEqual(
337- {
338- "email": "user@example.org",
339- "password": "secret",
340- "caveat_id": "payload",
341- },
342- json.loads(responses.calls[1].request.body.decode()),
343- )
344- self.assertThat(
345- config.Config().parser,
346+ mock_input.assert_called_once_with("Email: ")
347+ mock_getpass.assert_called_once_with("Password: ")
348+ assert 2 == len(responses.calls)
349+ assert {
350+ "email": "user@example.org",
351+ "password": "secret",
352+ "caveat_id": "payload",
353+ } == json.loads(responses.calls[1].request.body.decode())
354+ assert (
355 ContainsDict(
356 {
357 "store:default": MatchesDict(
358@@ -228,13 +200,14 @@ class LoginTests(TestCase):
359 }
360 ),
361 }
362- ),
363+ ).match(config.Config().parser)
364+ is None
365 )
366
367 @responses.activate
368- def test_login_with_email(self):
369- self.mock_input.side_effect = Exception("shouldn't be called")
370- self.mock_getpass.return_value = "secret"
371+ def test_login_with_email(self, mock_input, mock_getpass):
372+ mock_input.side_effect = Exception("shouldn't be called")
373+ mock_getpass.return_value = "secret"
374 root = self.make_root_macaroon()
375 self.add_issue_store_admin_response({"status": 200, "json": {"macaroon": root}})
376 self.add_get_sso_discharge_response(
377@@ -242,20 +215,15 @@ class LoginTests(TestCase):
378 )
379 login(self.make_args(email="user@example.org"))
380
381- self.assertIn("Enter your Ubuntu One SSO credentials.", self.logger.output)
382- self.mock_input.assert_not_called()
383- self.mock_getpass.assert_called_once_with("Password: ")
384- self.assertEqual(2, len(responses.calls))
385- self.assertEqual(
386- {
387- "email": "user@example.org",
388- "password": "secret",
389- "caveat_id": "payload",
390- },
391- json.loads(responses.calls[1].request.body.decode()),
392- )
393- self.assertThat(
394- config.Config().parser,
395+ mock_input.assert_not_called()
396+ mock_getpass.assert_called_once_with("Password: ")
397+ assert 2 == len(responses.calls)
398+ assert {
399+ "email": "user@example.org",
400+ "password": "secret",
401+ "caveat_id": "payload",
402+ } == json.loads(responses.calls[1].request.body.decode())
403+ assert (
404 ContainsDict(
405 {
406 "store:default": MatchesDict(
407@@ -268,15 +236,16 @@ class LoginTests(TestCase):
408 }
409 ),
410 }
411- ),
412+ ).match(config.Config().parser)
413+ is None
414 )
415
416 @responses.activate
417- def test_store_url(self):
418+ def test_store_url(self, mock_input, mock_getpass):
419 gw_url = "http://otherstore.local:1234/"
420
421- self.mock_input.return_value = "user@example.org"
422- self.mock_getpass.return_value = "secret"
423+ mock_input.return_value = "user@example.org"
424+ mock_getpass.return_value = "secret"
425 root = self.make_root_macaroon()
426 self.add_issue_store_admin_response(
427 {"status": 200, "json": {"macaroon": root}}, gw_url=gw_url
428@@ -286,11 +255,10 @@ class LoginTests(TestCase):
429 )
430 login(self.make_args(store_url=gw_url))
431
432- self.assertEqual(2, len(responses.calls))
433- self.assertEqual(responses.calls[0].request.url[: len(gw_url)], gw_url)
434- self.assertTrue(responses.calls[1].request.url.startswith(self.default_sso_url))
435- self.assertThat(
436- config.Config().parser,
437+ assert 2 == len(responses.calls)
438+ assert responses.calls[0].request.url[: len(gw_url)] == gw_url
439+ assert responses.calls[1].request.url.startswith(self.default_sso_url)
440+ assert (
441 ContainsDict(
442 {
443 "store:default": ContainsDict(
444@@ -300,15 +268,16 @@ class LoginTests(TestCase):
445 }
446 ),
447 }
448- ),
449+ ).match(config.Config().parser)
450+ is None
451 )
452
453 @responses.activate
454- def test_sso_url(self):
455+ def test_sso_url(self, mock_input, mock_getpass):
456 sso_url = "https://othersso.local:1234/"
457
458- self.mock_input.return_value = "user@example.org"
459- self.mock_getpass.return_value = "secret"
460+ mock_input.return_value = "user@example.org"
461+ mock_getpass.return_value = "secret"
462 root = self.make_root_macaroon(sso_url=sso_url)
463 self.add_issue_store_admin_response({"status": 200, "json": {"macaroon": root}})
464 self.add_get_sso_discharge_response(
465@@ -316,11 +285,10 @@ class LoginTests(TestCase):
466 )
467 login(self.make_args(sso_url=sso_url))
468
469- self.assertEqual(2, len(responses.calls))
470- self.assertTrue(responses.calls[0].request.url.startswith(self.default_gw_url))
471- self.assertEqual(responses.calls[1].request.url[: len(sso_url)], sso_url)
472- self.assertThat(
473- config.Config().parser,
474+ assert 2 == len(responses.calls)
475+ assert responses.calls[0].request.url.startswith(self.default_gw_url)
476+ assert responses.calls[1].request.url[: len(sso_url)] == sso_url
477+ assert (
478 ContainsDict(
479 {
480 "store:default": ContainsDict(
481@@ -330,5 +298,6 @@ class LoginTests(TestCase):
482 }
483 ),
484 }
485- ),
486+ ).match(config.Config().parser)
487+ is None
488 )
489diff --git a/snapstore_client/logic/tests/test_overrides.py b/snapstore_client/logic/tests/test_overrides.py
490index 591b55d..c628810 100644
491--- a/snapstore_client/logic/tests/test_overrides.py
492+++ b/snapstore_client/logic/tests/test_overrides.py
493@@ -1,11 +1,11 @@
494 # Copyright 2017 Canonical Ltd.
495
496 import json
497+import logging
498 from urllib.parse import urljoin
499
500-import fixtures
501 import responses
502-from testtools import TestCase
503+import pytest
504
505 from snapstore_client import config
506 from snapstore_client.logic.overrides import (
507@@ -15,26 +15,23 @@ from snapstore_client.logic.overrides import (
508 )
509 from snapstore_client.tests import (
510 factory,
511- testfixtures,
512 )
513
514
515-class OverridesTests(TestCase):
516- def test_list_overrides_no_store_config(self):
517- self.useFixture(testfixtures.ConfigFixture(empty=True))
518- logger = self.useFixture(fixtures.FakeLogger())
519+class TestOverrides:
520+ @pytest.mark.usefixtures("mocked_empty_config")
521+ def test_list_overrides_no_store_config(self, caplog):
522 rc = list_overrides(factory.Args(snap_name="some-snap", series="16"))
523- self.assertEqual(rc, 1)
524- self.assertEqual(
525- logger.output,
526+ assert rc == 1
527+ assert [
528 "No store configuration found. "
529- 'Have you run "snap-store-proxy-client login"?\n',
530- )
531+ 'Have you run "snap-store-proxy-client login"?'
532+ ] == caplog.messages
533
534+ @pytest.mark.usefixtures("mocked_config")
535 @responses.activate
536- def test_list_overrides_online(self):
537- self.useFixture(testfixtures.ConfigFixture())
538- logger = self.useFixture(fixtures.FakeLogger())
539+ def test_list_overrides_online(self, caplog):
540+ caplog.set_level(logging.DEBUG)
541 snap_id = factory.generate_snap_id()
542 overrides = [
543 factory.SnapDeviceGateway.Override(snap_id=snap_id, snap_name="mysnap"),
544@@ -56,18 +53,17 @@ class OverridesTests(TestCase):
545 responses.add("GET", overrides_url, status=200, json={"overrides": overrides})
546
547 list_overrides(factory.Args(snap_name="mysnap", series="16", password=False))
548- self.assertEqual(
549- "mysnap stable amd64 1 (upstream 2)\n"
550- "mysnap foo/stable i386 3 (upstream 4)\n",
551- logger.output,
552- )
553+ assert [
554+ "mysnap stable amd64 1 (upstream 2)",
555+ "mysnap foo/stable i386 3 (upstream 4)",
556+ ] == caplog.messages
557 # We shouldn't have Basic Authorization headers, but Macaroon
558- self.assertNotIn("Basic", responses.calls[0].request.headers["Authorization"])
559+ assert "Basic" not in responses.calls[0].request.headers["Authorization"]
560
561+ @pytest.mark.usefixtures("mocked_config")
562 @responses.activate
563- def test_list_overrides_offline(self):
564- self.useFixture(testfixtures.ConfigFixture())
565- logger = self.useFixture(fixtures.FakeLogger())
566+ def test_list_overrides_offline(self, caplog):
567+ caplog.set_level(logging.DEBUG)
568 snap_id = factory.generate_snap_id()
569 overrides = [
570 factory.SnapDeviceGateway.Override(snap_id=snap_id, snap_name="mysnap"),
571@@ -89,19 +85,17 @@ class OverridesTests(TestCase):
572 responses.add("GET", overrides_url, status=200, json={"overrides": overrides})
573
574 list_overrides(factory.Args(snap_name="mysnap", series="16", password="test"))
575- self.assertEqual(
576- "mysnap stable amd64 1 (upstream 2)\n"
577- "mysnap foo/stable i386 3 (upstream 4)\n",
578- logger.output,
579- )
580- self.assertEqual(
581- "Basic YWRtaW46dGVzdA==",
582- responses.calls[0].request.headers["Authorization"],
583+ assert [
584+ "mysnap stable amd64 1 (upstream 2)",
585+ "mysnap foo/stable i386 3 (upstream 4)",
586+ ] == caplog.messages
587+ assert (
588+ "Basic YWRtaW46dGVzdA=="
589+ == responses.calls[0].request.headers["Authorization"]
590 )
591
592- def test_override_no_store_config(self):
593- self.useFixture(testfixtures.ConfigFixture(empty=True))
594- logger = self.useFixture(fixtures.FakeLogger())
595+ @pytest.mark.usefixtures("mocked_empty_config")
596+ def test_override_no_store_config(self, caplog):
597 rc = override(
598 factory.Args(
599 snap_name="some-snap",
600@@ -110,17 +104,16 @@ class OverridesTests(TestCase):
601 password=False,
602 )
603 )
604- self.assertEqual(rc, 1)
605- self.assertEqual(
606- logger.output,
607+ assert rc == 1
608+ assert [
609 "No store configuration found. "
610- 'Have you run "snap-store-proxy-client login"?\n',
611- )
612+ 'Have you run "snap-store-proxy-client login"?'
613+ ] == caplog.messages
614
615+ @pytest.mark.usefixtures("mocked_config")
616 @responses.activate
617- def test_override_online(self):
618- self.useFixture(testfixtures.ConfigFixture())
619- logger = self.useFixture(fixtures.FakeLogger())
620+ def test_override_online(self, caplog):
621+ caplog.set_level(logging.DEBUG)
622 snap_id = factory.generate_snap_id()
623 overrides = [
624 factory.SnapDeviceGateway.Override(snap_id=snap_id, snap_name="mysnap"),
625@@ -149,35 +142,32 @@ class OverridesTests(TestCase):
626 password=False,
627 )
628 )
629- self.assertEqual(
630- [
631- {
632- "snap_name": "mysnap",
633- "revision": 1,
634- "channel": "stable",
635- "series": "16",
636- },
637- {
638- "snap_name": "mysnap",
639- "revision": 3,
640- "channel": "foo/stable",
641- "series": "16",
642- },
643- ],
644- json.loads(responses.calls[0].request.body.decode()),
645- )
646- self.assertEqual(
647- "mysnap stable amd64 1 (upstream 2)\n"
648- "mysnap foo/stable i386 3 (upstream 4)\n",
649- logger.output,
650- )
651+ assert [
652+ {
653+ "snap_name": "mysnap",
654+ "revision": 1,
655+ "channel": "stable",
656+ "series": "16",
657+ },
658+ {
659+ "snap_name": "mysnap",
660+ "revision": 3,
661+ "channel": "foo/stable",
662+ "series": "16",
663+ },
664+ ] == json.loads(responses.calls[0].request.body.decode())
665+ assert [
666+ "mysnap stable amd64 1 (upstream 2)",
667+ "mysnap foo/stable i386 3 (upstream 4)",
668+ ] == caplog.messages
669+
670 # We shouldn't have Basic Authorization headers, but Macaroon
671- self.assertNotIn("Basic", responses.calls[0].request.headers["Authorization"])
672+ assert "Basic" not in responses.calls[0].request.headers["Authorization"]
673
674+ @pytest.mark.usefixtures("mocked_config")
675 @responses.activate
676- def test_override_offline(self):
677- self.useFixture(testfixtures.ConfigFixture())
678- logger = self.useFixture(fixtures.FakeLogger())
679+ def test_override_offline(self, caplog):
680+ caplog.set_level(logging.DEBUG)
681 snap_id = factory.generate_snap_id()
682 overrides = [
683 factory.SnapDeviceGateway.Override(snap_id=snap_id, snap_name="mysnap"),
684@@ -206,52 +196,46 @@ class OverridesTests(TestCase):
685 password="test",
686 )
687 )
688- self.assertEqual(
689- [
690- {
691- "snap_name": "mysnap",
692- "revision": 1,
693- "channel": "stable",
694- "series": "16",
695- },
696- {
697- "snap_name": "mysnap",
698- "revision": 3,
699- "channel": "foo/stable",
700- "series": "16",
701- },
702- ],
703- json.loads(responses.calls[0].request.body.decode()),
704- )
705- self.assertEqual(
706- "mysnap stable amd64 1 (upstream 2)\n"
707- "mysnap foo/stable i386 3 (upstream 4)\n",
708- logger.output,
709- )
710- self.assertEqual(
711- "Basic YWRtaW46dGVzdA==",
712- responses.calls[0].request.headers["Authorization"],
713+ assert [
714+ {
715+ "snap_name": "mysnap",
716+ "revision": 1,
717+ "channel": "stable",
718+ "series": "16",
719+ },
720+ {
721+ "snap_name": "mysnap",
722+ "revision": 3,
723+ "channel": "foo/stable",
724+ "series": "16",
725+ },
726+ ] == json.loads(responses.calls[0].request.body.decode())
727+ assert [
728+ "mysnap stable amd64 1 (upstream 2)",
729+ "mysnap foo/stable i386 3 (upstream 4)",
730+ ] == caplog.messages
731+ assert (
732+ "Basic YWRtaW46dGVzdA=="
733+ == responses.calls[0].request.headers["Authorization"]
734 )
735
736- def test_delete_override_no_store_config(self):
737- self.useFixture(testfixtures.ConfigFixture(empty=True))
738- logger = self.useFixture(fixtures.FakeLogger())
739+ @pytest.mark.usefixtures("mocked_empty_config")
740+ def test_delete_override_no_store_config(self, caplog):
741 rc = delete_override(
742 factory.Args(
743 snap_name="some-snap", channels=["stable"], series="16", password=False
744 )
745 )
746- self.assertEqual(rc, 1)
747- self.assertEqual(
748- logger.output,
749+ assert rc == 1
750+ assert [
751 "No store configuration found. "
752- 'Have you run "snap-store-proxy-client login"?\n',
753- )
754+ 'Have you run "snap-store-proxy-client login"?'
755+ ] == caplog.messages
756
757+ @pytest.mark.usefixtures("mocked_config")
758 @responses.activate
759- def test_delete_override_online(self):
760- self.useFixture(testfixtures.ConfigFixture())
761- logger = self.useFixture(fixtures.FakeLogger())
762+ def test_delete_override_online(self, caplog):
763+ caplog.set_level(logging.DEBUG)
764 snap_id = factory.generate_snap_id()
765 overrides = [
766 factory.SnapDeviceGateway.Override(
767@@ -282,35 +266,31 @@ class OverridesTests(TestCase):
768 password=False,
769 )
770 )
771- self.assertEqual(
772- [
773- {
774- "snap_name": "mysnap",
775- "revision": None,
776- "channel": "stable",
777- "series": "16",
778- },
779- {
780- "snap_name": "mysnap",
781- "revision": None,
782- "channel": "foo/stable",
783- "series": "16",
784- },
785- ],
786- json.loads(responses.calls[0].request.body.decode()),
787- )
788- self.assertEqual(
789- "mysnap stable amd64 is tracking upstream (revision 2)\n"
790- "mysnap foo/stable i386 is tracking upstream (revision 4)\n",
791- logger.output,
792- )
793+ assert [
794+ {
795+ "snap_name": "mysnap",
796+ "revision": None,
797+ "channel": "stable",
798+ "series": "16",
799+ },
800+ {
801+ "snap_name": "mysnap",
802+ "revision": None,
803+ "channel": "foo/stable",
804+ "series": "16",
805+ },
806+ ] == json.loads(responses.calls[0].request.body.decode())
807+ assert [
808+ "mysnap stable amd64 is tracking upstream (revision 2)",
809+ "mysnap foo/stable i386 is tracking upstream (revision 4)",
810+ ] == caplog.messages
811 # We shouldn't have Basic Authorization headers, but Macaroon
812- self.assertNotIn("Basic", responses.calls[0].request.headers["Authorization"])
813+ assert "Basic" not in responses.calls[0].request.headers["Authorization"]
814
815+ @pytest.mark.usefixtures("mocked_config")
816 @responses.activate
817- def test_delete_override_offline(self):
818- self.useFixture(testfixtures.ConfigFixture())
819- logger = self.useFixture(fixtures.FakeLogger())
820+ def test_delete_override_offline(self, caplog):
821+ caplog.set_level(logging.DEBUG)
822 snap_id = factory.generate_snap_id()
823 overrides = [
824 factory.SnapDeviceGateway.Override(
825@@ -341,29 +321,25 @@ class OverridesTests(TestCase):
826 password="test",
827 )
828 )
829- self.assertEqual(
830- [
831- {
832- "snap_name": "mysnap",
833- "revision": None,
834- "channel": "stable",
835- "series": "16",
836- },
837- {
838- "snap_name": "mysnap",
839- "revision": None,
840- "channel": "foo/stable",
841- "series": "16",
842- },
843- ],
844- json.loads(responses.calls[0].request.body.decode()),
845- )
846- self.assertEqual(
847- "mysnap stable amd64 is tracking upstream (revision 2)\n"
848- "mysnap foo/stable i386 is tracking upstream (revision 4)\n",
849- logger.output,
850- )
851- self.assertEqual(
852- "Basic YWRtaW46dGVzdA==",
853- responses.calls[0].request.headers["Authorization"],
854+ assert [
855+ {
856+ "snap_name": "mysnap",
857+ "revision": None,
858+ "channel": "stable",
859+ "series": "16",
860+ },
861+ {
862+ "snap_name": "mysnap",
863+ "revision": None,
864+ "channel": "foo/stable",
865+ "series": "16",
866+ },
867+ ] == json.loads(responses.calls[0].request.body.decode())
868+ assert [
869+ "mysnap stable amd64 is tracking upstream (revision 2)",
870+ "mysnap foo/stable i386 is tracking upstream (revision 4)",
871+ ] == caplog.messages
872+ assert (
873+ "Basic YWRtaW46dGVzdA=="
874+ == responses.calls[0].request.headers["Authorization"]
875 )
876diff --git a/snapstore_client/logic/tests/test_push.py b/snapstore_client/logic/tests/test_push.py
877index e96088f..05b4cee 100644
878--- a/snapstore_client/logic/tests/test_push.py
879+++ b/snapstore_client/logic/tests/test_push.py
880@@ -1,10 +1,9 @@
881+import pytest
882 import json
883 from pathlib import Path
884 from urllib.parse import urljoin
885
886-import fixtures
887 import responses
888-from testtools import TestCase
889
890 from snapstore_client.logic.push import (
891 ChannelMapExistsException,
892@@ -17,11 +16,9 @@ from snapstore_client.logic.push import (
893 )
894
895
896-class pushTests(TestCase):
897- def setUp(self):
898- super().setUp()
899+class Testpush:
900+ def setup_method(self):
901 self.default_gw_url = "http://store.local"
902- self.logger = self.useFixture(fixtures.FakeLogger())
903 current_path = Path(__file__).resolve().parent
904 with (current_path / "test-snap-map.json").open() as fh:
905 self.snap_map = json.load(fh)
906@@ -39,21 +36,15 @@ class pushTests(TestCase):
907 request = responses.calls[0].request
908 payload = json.loads(request.body.decode("utf-8"))
909
910- self.assertEqual(
911- payload["snaps"][0]["package_type"],
912- "snap",
913- )
914- self.assertEqual(
915- "Basic YWRtaW46dGVzdA==",
916- request.headers["Authorization"],
917- )
918+ assert payload["snaps"][0]["package_type"] == "snap"
919+ assert "Basic YWRtaW46dGVzdA==" == request.headers["Authorization"]
920
921 @responses.activate
922 def test_push_ident_failed(self):
923 ident_url = urljoin(self.default_gw_url, "/snaps/update")
924 responses.add("POST", ident_url, status=500)
925
926- self.assertRaises(pushException, _push_ident, self.store, "test", self.snap_map)
927+ pytest.raises(pushException, _push_ident, self.store, "test", self.snap_map)
928
929 @responses.activate
930 def test_push_revs(self):
931@@ -65,25 +56,22 @@ class pushTests(TestCase):
932 request = responses.calls[0].request
933 payload = json.loads(request.body.decode("utf-8"))
934
935- self.assertEqual(payload[0]["package_type"], "snap")
936- self.assertEqual(
937- "Basic YWRtaW46dGVzdA==",
938- request.headers["Authorization"],
939- )
940+ assert payload[0]["package_type"] == "snap"
941+ assert "Basic YWRtaW46dGVzdA==" == request.headers["Authorization"]
942
943 @responses.activate
944 def test_push_revs_unexpected_status_code(self):
945 revs_url = urljoin(self.default_gw_url, "/revisions/create")
946 responses.add("POST", revs_url, status=302)
947
948- self.assertRaises(pushException, _push_revs, self.store, "test", self.snap_map)
949+ pytest.raises(pushException, _push_revs, self.store, "test", self.snap_map)
950
951 @responses.activate
952 def test_push_revs_failed(self):
953 revs_url = urljoin(self.default_gw_url, "/revisions/create")
954 responses.add("POST", revs_url, status=500)
955
956- self.assertRaises(pushException, _push_revs, self.store, "test", self.snap_map)
957+ pytest.raises(pushException, _push_revs, self.store, "test", self.snap_map)
958
959 @responses.activate
960 def test_push_map(self):
961@@ -96,29 +84,20 @@ class pushTests(TestCase):
962
963 filter_request = responses.calls[0].request
964 filter_payload = json.loads(filter_request.body.decode("utf-8"))
965- self.assertEqual(
966- filter_payload["filters"],
967- [
968- {
969- "series": "16",
970- "package_type": "snap",
971- "snap_id": self.snap_map["snap-id"],
972- },
973- ],
974- )
975- self.assertEqual(
976- "Basic YWRtaW46dGVzdA==",
977- filter_request.headers["Authorization"],
978- )
979+ assert filter_payload["filters"] == [
980+ {
981+ "series": "16",
982+ "package_type": "snap",
983+ "snap_id": self.snap_map["snap-id"],
984+ },
985+ ]
986+ assert "Basic YWRtaW46dGVzdA==" == filter_request.headers["Authorization"]
987
988 chanmap_update_request = responses.calls[1].request
989 chanmap_update_payload = json.loads(
990 chanmap_update_request.body.decode("utf-8"),
991 )
992- self.assertEqual(
993- chanmap_update_payload["release_requests"][0]["package_type"],
994- "snap",
995- )
996+ assert chanmap_update_payload["release_requests"][0]["package_type"] == "snap"
997
998 @responses.activate
999 def test_push_map_failed(self):
1000@@ -127,7 +106,7 @@ class pushTests(TestCase):
1001 responses.add("POST", filter_url, status=200, json={})
1002 responses.add("POST", update_url, status=500)
1003
1004- self.assertRaises(
1005+ pytest.raises(
1006 pushException, _push_channelmap, self.store, "test", self.snap_map
1007 )
1008
1009@@ -139,7 +118,7 @@ class pushTests(TestCase):
1010 responses.add("POST", filter_url, status=200, json=exists)
1011 responses.add("POST", update_url, status=200)
1012
1013- self.assertRaises(
1014+ pytest.raises(
1015 ChannelMapExistsException,
1016 _push_channelmap,
1017 self.store,
1018@@ -157,9 +136,9 @@ class pushTests(TestCase):
1019
1020 _push_channelmap(self.store, "test", self.snap_map, True)
1021
1022- self.assertEqual(
1023- "Basic YWRtaW46dGVzdA==",
1024- responses.calls[0].request.headers["Authorization"],
1025+ assert (
1026+ "Basic YWRtaW46dGVzdA=="
1027+ == responses.calls[0].request.headers["Authorization"]
1028 )
1029
1030 def test_split_assertions(self):
1031@@ -180,13 +159,12 @@ class pushTests(TestCase):
1032 split_assertions = _split_assertions(self.snap_assert)
1033 _add_assertion_to_service(self.store, "test", split_assertions)
1034
1035- self.assertIn(
1036- "display-name: Tom Wardill (Ω)",
1037- responses.calls[0].request.body.decode("utf-8"),
1038- )
1039- self.assertEqual(
1040- "Basic YWRtaW46dGVzdA==",
1041- responses.calls[0].request.headers["Authorization"],
1042+ assert "display-name: Tom Wardill (Ω)" in responses.calls[
1043+ 0
1044+ ].request.body.decode("utf-8")
1045+ assert (
1046+ "Basic YWRtaW46dGVzdA=="
1047+ == responses.calls[0].request.headers["Authorization"]
1048 )
1049
1050 @responses.activate
1051@@ -195,7 +173,7 @@ class pushTests(TestCase):
1052 responses.add("POST", assert_url, status=302)
1053
1054 split_assertions = _split_assertions(self.snap_assert)
1055- self.assertRaises(
1056+ pytest.raises(
1057 pushException,
1058 _add_assertion_to_service,
1059 self.store,
1060@@ -209,7 +187,7 @@ class pushTests(TestCase):
1061 responses.add("POST", assert_url, status=500)
1062
1063 split_assertions = _split_assertions(self.snap_assert)
1064- self.assertRaises(
1065+ pytest.raises(
1066 pushException,
1067 _add_assertion_to_service,
1068 self.store,
1069diff --git a/snapstore_client/tests/test_cli.py b/snapstore_client/tests/test_cli.py
1070deleted file mode 100644
1071index 3ba9dc9..0000000
1072--- a/snapstore_client/tests/test_cli.py
1073+++ /dev/null
1074@@ -1,67 +0,0 @@
1075-# Copyright 2017 Canonical Ltd. This software is licensed under the
1076-# GNU General Public License version 3 (see the file LICENSE).
1077-
1078-import logging
1079-
1080-import fixtures
1081-from testtools import TestCase
1082-
1083-from snapstore_client import cli
1084-
1085-
1086-class ConfigureLoggingTests(TestCase):
1087- def setUp(self):
1088- super().setUp()
1089- self.logger = logging.getLogger(__name__)
1090- self.addCleanup(
1091- self._restoreLogger,
1092- self.logger,
1093- self.logger.level,
1094- list(self.logger.handlers),
1095- )
1096- self.stdout = self.useFixture(fixtures.StringStream("stdout")).stream
1097- self.stdout.fileno = lambda: 1
1098- self.useFixture(fixtures.MonkeyPatch("sys.stdout", self.stdout))
1099- self.stderr = self.useFixture(fixtures.StringStream("stderr")).stream
1100- self.useFixture(fixtures.MonkeyPatch("sys.stderr", self.stderr))
1101-
1102- @staticmethod
1103- def _restoreLogger(logger, level, handlers):
1104- logger.setLevel(logger.level)
1105- for handler in list(logger.handlers):
1106- logger.removeHandler(handler)
1107- for handler in handlers:
1108- logger.addHandler(handler)
1109-
1110- def test_log_levels(self):
1111- self.useFixture(fixtures.MonkeyPatch("os.isatty", lambda fd: True))
1112- cli.configure_logging(__name__)
1113- self.assertEqual(logging.INFO, self.logger.level)
1114- self.logger.debug("Debug")
1115- self.logger.info("Info")
1116- self.logger.warning("Warning: %s", "smoke")
1117- self.logger.error("Error: %s", "fire")
1118- self.stdout.seek(0)
1119- self.assertEqual("Info\nWarning: smoke\n", self.stdout.read())
1120- self.stderr.seek(0)
1121- self.assertEqual("\033[0;31mError: fire\033[0m\n", self.stderr.read())
1122-
1123- def test_requests_log_level_default(self):
1124- cli.configure_logging(__name__)
1125- self.assertEqual(logging.WARNING, logging.getLogger("requests").level)
1126-
1127- def test_requests_log_level_debug(self):
1128- cli.configure_logging(__name__, logging.DEBUG)
1129- self.assertEqual(logging.DEBUG, logging.getLogger("requests").level)
1130-
1131- def test_requests_log_level_error(self):
1132- cli.configure_logging(__name__, logging.ERROR)
1133- self.assertEqual(logging.ERROR, logging.getLogger("requests").level)
1134-
1135- def test_no_tty(self):
1136- self.useFixture(fixtures.MonkeyPatch("os.isatty", lambda fd: False))
1137- self.useFixture(fixtures.EnvironmentVariable("TERM", "xterm"))
1138- cli.configure_logging(__name__)
1139- self.logger.error("Error: %s", "fire")
1140- self.stderr.seek(0)
1141- self.assertEqual("Error: fire\n", self.stderr.read())
1142diff --git a/snapstore_client/tests/test_config.py b/snapstore_client/tests/test_config.py
1143index 7243554..c2cca69 100644
1144--- a/snapstore_client/tests/test_config.py
1145+++ b/snapstore_client/tests/test_config.py
1146@@ -1,81 +1,57 @@
1147 # Copyright 2017 Canonical Ltd. This software is licensed under the
1148 # GNU General Public License version 3 (see the file LICENSE).
1149+import os
1150
1151-import os.path
1152-from textwrap import dedent
1153+from snapstore_client.config import Config
1154
1155-from testtools import TestCase
1156
1157-from snapstore_client.config import Config
1158-from snapstore_client.tests.testfixtures import (
1159- ConfigFixture,
1160- XDGConfigDirFixture,
1161-)
1162-
1163-
1164-class ConfigTests(TestCase):
1165- def test_no_config(self):
1166- xdg_path = self.useFixture(XDGConfigDirFixture()).path
1167- config_ini = os.path.join(xdg_path, Config.xdg_name, "config.ini")
1168- self.assertFalse(os.path.exists(config_ini))
1169- Config()
1170-
1171- def test_init_loads(self):
1172- self.useFixture(ConfigFixture())
1173- cfg = Config()
1174- self.assertIsNotNone(cfg.get("store:default", "gw_url"))
1175-
1176- def test_save(self):
1177- xdg_path = self.useFixture(XDGConfigDirFixture()).path
1178- config_ini = os.path.join(xdg_path, "snap-store-proxy-client", "config.ini")
1179- self.assertFalse(os.path.exists(config_ini))
1180-
1181- cfg = Config()
1182- cfg.set("s", "k", "v")
1183- cfg.save()
1184-
1185- self.assertTrue(os.path.exists(config_ini))
1186- with open(config_ini) as f:
1187- content = f.read()
1188- self.assertEqual(
1189- content,
1190- dedent(
1191- """\
1192- [s]
1193- k = v
1194-
1195- """
1196- ),
1197- )
1198-
1199- def test_get_missing(self):
1200- self.useFixture(XDGConfigDirFixture())
1201- cfg = Config()
1202- self.assertIsNone(cfg.get("s", "k"))
1203-
1204- def test_get(self):
1205- self.useFixture(XDGConfigDirFixture())
1206- cfg = Config()
1207- cfg.set("s", "k", "v")
1208- self.assertEqual(cfg.get("s", "k"), "v")
1209-
1210- def test_section(self):
1211- self.useFixture(XDGConfigDirFixture())
1212- cfg = Config()
1213- s = cfg.section("s")
1214- s.set("k", "v")
1215- self.assertEqual(s.get("k"), "v")
1216- self.assertEqual(cfg.get("s", "k"), "v")
1217-
1218- def test_store_section(self):
1219- self.useFixture(XDGConfigDirFixture())
1220- cfg = Config()
1221- store = cfg.store_section("foo")
1222- store.set("k", "v")
1223- self.assertEqual(cfg.get("store:foo", "k"), "v")
1224-
1225-
1226-def test_suite():
1227- from unittest import TestLoader
1228-
1229- return TestLoader().loadTestsFromName(__name__)
1230+def test_no_config(mocked_xdgconfig):
1231+ config_ini = os.path.join(mocked_xdgconfig, Config.xdg_name, "config.ini")
1232+ assert not os.path.exists(config_ini)
1233+ Config()
1234+
1235+
1236+def test_init_loads(mocked_config):
1237+ cfg = Config()
1238+ assert cfg.get("store:default", "gw_url")
1239+
1240+
1241+def test_save(mocked_xdgconfig):
1242+ config_ini = os.path.join(mocked_xdgconfig, "snap-store-proxy-client", "config.ini")
1243+ assert not os.path.exists(config_ini)
1244+
1245+ cfg = Config()
1246+ cfg.set("s", "k", "v")
1247+ cfg.save()
1248+
1249+ assert os.path.exists(config_ini)
1250+
1251+ with open(config_ini) as f:
1252+ content = f.read()
1253+ assert content == ("[s]\n" "k = v\n" "\n")
1254+
1255+
1256+def test_get_missing(mocked_xdgconfig):
1257+ cfg = Config()
1258+ assert not cfg.get("s", "k")
1259+
1260+
1261+def test_get(mocked_xdgconfig):
1262+ cfg = Config()
1263+ cfg.set("s", "k", "v")
1264+ assert cfg.get("s", "k") == "v"
1265+
1266+
1267+def test_section(mocked_xdgconfig):
1268+ cfg = Config()
1269+ s = cfg.section("s")
1270+ s.set("k", "v")
1271+ assert s.get("k") == "v"
1272+ assert cfg.get("s", "k") == "v"
1273+
1274+
1275+def test_store_section(mocked_xdgconfig):
1276+ cfg = Config()
1277+ store = cfg.store_section("foo")
1278+ store.set("k", "v")
1279+ assert cfg.get("store:foo", "k") == "v"
1280diff --git a/snapstore_client/tests/test_presentation_helpers.py b/snapstore_client/tests/test_presentation_helpers.py
1281index 7399f91..e58e9e9 100644
1282--- a/snapstore_client/tests/test_presentation_helpers.py
1283+++ b/snapstore_client/tests/test_presentation_helpers.py
1284@@ -1,7 +1,6 @@
1285 # Copyright 2017 Canonical Ltd.
1286
1287-from testtools import TestCase
1288-from testscenarios import WithScenarios
1289+import pytest
1290
1291 from snapstore_client.presentation_helpers import (
1292 channel_map_string_to_tuple,
1293@@ -9,129 +8,93 @@ from snapstore_client.presentation_helpers import (
1294 )
1295
1296
1297-class ChannelMapStringToTupleScenarioTests(WithScenarios, TestCase):
1298-
1299- scenarios = [
1300- (
1301- "with risk",
1302- {
1303- "channel_map": "stable=1",
1304- "terms": ("stable", 1),
1305- },
1306- ),
1307- (
1308- "with track and risk",
1309- {
1310- "channel_map": "2.1/stable=2",
1311- "terms": ("2.1/stable", 2),
1312- },
1313- ),
1314- (
1315- "with risk and branch",
1316- {
1317- "channel_map": "stable/hot-fix=42",
1318- "terms": ("stable/hot-fix", 42),
1319- },
1320- ),
1321- (
1322- "with track, risk and branch",
1323- {
1324- "channel_map": "2.1/stable/hot-fix=123",
1325- "terms": ("2.1/stable/hot-fix", 123),
1326- },
1327- ),
1328- ]
1329-
1330- def test_run_scenario(self):
1331- self.assertEqual(self.terms, channel_map_string_to_tuple(self.channel_map))
1332-
1333-
1334-class ChannelMapStringToTupleErrorTests(WithScenarios, TestCase):
1335-
1336- scenarios = [
1337- (
1338- "missing revision",
1339- {
1340- "channel_map": "stable",
1341- "error_message": "Invalid channel map string: 'stable'",
1342- },
1343- ),
1344- (
1345- "non-integer revision",
1346- {
1347- "channel_map": "stable=nonsense",
1348- "error_message": "Invalid revision string: 'nonsense'",
1349- },
1350- ),
1351- ]
1352+@pytest.mark.parametrize(
1353+ "channel_map,terms",
1354+ [
1355+ ("stable=1", ("stable", 1)),
1356+ ("2.1/stable=2", ("2.1/stable", 2)),
1357+ ("stable/hot-fix=42", ("stable/hot-fix", 42)),
1358+ ("2.1/stable/hot-fix=123", ("2.1/stable/hot-fix", 123)),
1359+ ],
1360+ ids=[
1361+ "with risk",
1362+ "with track and risk",
1363+ "with risk and branch",
1364+ "with track, risk and branch",
1365+ ],
1366+)
1367+def test_channel_map_string_to_tuple(channel_map, terms):
1368+ assert terms == channel_map_string_to_tuple(channel_map)
1369
1370- def test_run_scenario(self):
1371- error = self.assertRaises(
1372- ValueError, channel_map_string_to_tuple, self.channel_map
1373- )
1374- self.assertEqual(self.error_message, str(error))
1375
1376+@pytest.mark.parametrize(
1377+ "channel_map,error_message",
1378+ [
1379+ ("stable", "Invalid channel map string: 'stable'"),
1380+ ("stable=nonsense", "Invalid revision string: 'nonsense'"),
1381+ ],
1382+ ids=["missing revision", "non-integer revision"],
1383+)
1384+def test_channel_map_string_to_tuple_exceptions(channel_map, error_message):
1385+ with pytest.raises(ValueError) as error:
1386+ channel_map_string_to_tuple(channel_map)
1387+ assert error_message == str(error.value)
1388
1389-class OverrideToStringTests(WithScenarios, TestCase):
1390
1391- scenarios = [
1392+@pytest.mark.parametrize(
1393+ "override,string",
1394+ [
1395 (
1396- "without revision or upstream revision",
1397 {
1398- "override": {
1399- "snap_id": "dummy",
1400- "snap_name": "mysnap",
1401- "revision": None,
1402- "upstream_revision": None,
1403- "channel": "stable",
1404- "architecture": "amd64",
1405- },
1406- "string": "mysnap stable amd64 is tracking upstream",
1407+ "snap_id": "dummy",
1408+ "snap_name": "mysnap",
1409+ "revision": None,
1410+ "upstream_revision": None,
1411+ "channel": "stable",
1412+ "architecture": "amd64",
1413 },
1414+ "mysnap stable amd64 is tracking upstream",
1415 ),
1416 (
1417- "without revision but with upstream revision",
1418 {
1419- "override": {
1420- "snap_id": "dummy",
1421- "snap_name": "mysnap",
1422- "revision": None,
1423- "upstream_revision": 2,
1424- "channel": "stable",
1425- "architecture": "amd64",
1426- },
1427- "string": "mysnap stable amd64 is tracking upstream (revision 2)",
1428+ "snap_id": "dummy",
1429+ "snap_name": "mysnap",
1430+ "revision": None,
1431+ "upstream_revision": 2,
1432+ "channel": "stable",
1433+ "architecture": "amd64",
1434 },
1435+ "mysnap stable amd64 is tracking upstream (revision 2)",
1436 ),
1437 (
1438- "with revision but without upstream revision",
1439 {
1440- "override": {
1441- "snap_id": "dummy",
1442- "snap_name": "mysnap",
1443- "revision": 1,
1444- "upstream_revision": None,
1445- "channel": "stable",
1446- "architecture": "amd64",
1447- },
1448- "string": "mysnap stable amd64 1",
1449+ "snap_id": "dummy",
1450+ "snap_name": "mysnap",
1451+ "revision": 1,
1452+ "upstream_revision": None,
1453+ "channel": "stable",
1454+ "architecture": "amd64",
1455 },
1456+ "mysnap stable amd64 1",
1457 ),
1458 (
1459- "with revision and upstream revision",
1460 {
1461- "override": {
1462- "snap_id": "dummy",
1463- "snap_name": "mysnap",
1464- "revision": 1,
1465- "upstream_revision": 2,
1466- "channel": "stable",
1467- "architecture": "amd64",
1468- },
1469- "string": "mysnap stable amd64 1 (upstream 2)",
1470+ "snap_id": "dummy",
1471+ "snap_name": "mysnap",
1472+ "revision": 1,
1473+ "upstream_revision": 2,
1474+ "channel": "stable",
1475+ "architecture": "amd64",
1476 },
1477+ "mysnap stable amd64 1 (upstream 2)",
1478 ),
1479- ]
1480-
1481- def test_run_scenario(self):
1482- self.assertEqual(self.string, override_to_string(self.override))
1483+ ],
1484+ ids=[
1485+ "without revision or upstream revision",
1486+ "without revision but with upstream revision",
1487+ "with revision but without upstream revision",
1488+ "with revision and upstream revision",
1489+ ],
1490+)
1491+def test_override_to_string(override, string):
1492+ assert string == override_to_string(override)
1493diff --git a/snapstore_client/tests/test_webservices.py b/snapstore_client/tests/test_webservices.py
1494index ec2019d..3c667bb 100644
1495--- a/snapstore_client/tests/test_webservices.py
1496+++ b/snapstore_client/tests/test_webservices.py
1497@@ -1,13 +1,12 @@
1498 # Copyright 2017 Canonical Ltd.
1499
1500+import pytest
1501 import json
1502 import sys
1503 from urllib.parse import urljoin
1504
1505-import fixtures
1506 from requests.exceptions import HTTPError
1507 import responses
1508-from testtools import TestCase
1509
1510 from snapstore_client import (
1511 config,
1512@@ -17,18 +16,14 @@ from snapstore_client import (
1513 from snapstore_client.tests import (
1514 factory,
1515 matchers,
1516- testfixtures,
1517 )
1518
1519 if sys.version < "3.6":
1520 import sha3 # noqa
1521
1522
1523-class WebservicesTests(TestCase):
1524- def setUp(self):
1525- super().setUp()
1526- self.config = self.useFixture(testfixtures.ConfigFixture())
1527-
1528+@pytest.mark.usefixtures("mocked_config")
1529+class TestWebservices:
1530 @responses.activate
1531 def test_issue_store_admin_success(self):
1532 gw_url = "http://store.local/"
1533@@ -37,11 +32,10 @@ class WebservicesTests(TestCase):
1534 "POST", issue_store_admin_url, status=200, json={"macaroon": "dummy"}
1535 )
1536
1537- self.assertEqual("dummy", webservices.issue_store_admin(gw_url))
1538+ assert "dummy" == webservices.issue_store_admin(gw_url)
1539
1540 @responses.activate
1541- def test_issue_store_admin_error(self):
1542- logger = self.useFixture(fixtures.FakeLogger())
1543+ def test_issue_store_admin_error(self, caplog):
1544 gw_url = "http://store.local/"
1545 issue_store_admin_url = urljoin(gw_url, "/v2/auth/issue-store-admin")
1546 responses.add(
1547@@ -51,11 +45,11 @@ class WebservicesTests(TestCase):
1548 json=factory.APIError.single("Something went wrong").to_dict(),
1549 )
1550
1551- self.assertRaises(HTTPError, webservices.issue_store_admin, gw_url)
1552- self.assertEqual(
1553- "Failed to issue store_admin macaroon:\nSomething went wrong\n",
1554- logger.output,
1555- )
1556+ pytest.raises(HTTPError, webservices.issue_store_admin, gw_url)
1557+ assert [
1558+ "Failed to issue store_admin macaroon:",
1559+ "Something went wrong",
1560+ ] == caplog.messages
1561
1562 @responses.activate
1563 def test_get_sso_discharge_success(self):
1564@@ -65,22 +59,16 @@ class WebservicesTests(TestCase):
1565 "POST", discharge_url, status=200, json={"discharge_macaroon": "dummy"}
1566 )
1567
1568- self.assertEqual(
1569- "dummy",
1570- webservices.get_sso_discharge(
1571- sso_url, "user@example.org", "secret", "caveat"
1572- ),
1573+ assert "dummy" == webservices.get_sso_discharge(
1574+ sso_url, "user@example.org", "secret", "caveat"
1575 )
1576 request = responses.calls[0].request
1577- self.assertEqual("application/json", request.headers["Content-Type"])
1578- self.assertEqual(
1579- {
1580- "email": "user@example.org",
1581- "password": "secret",
1582- "caveat_id": "caveat",
1583- },
1584- json.loads(request.body.decode()),
1585- )
1586+ assert "application/json" == request.headers["Content-Type"]
1587+ assert {
1588+ "email": "user@example.org",
1589+ "password": "secret",
1590+ "caveat_id": "caveat",
1591+ } == json.loads(request.body.decode())
1592
1593 @responses.activate
1594 def test_get_sso_discharge_success_with_otp(self):
1595@@ -90,27 +78,21 @@ class WebservicesTests(TestCase):
1596 "POST", discharge_url, status=200, json={"discharge_macaroon": "dummy"}
1597 )
1598
1599- self.assertEqual(
1600- "dummy",
1601- webservices.get_sso_discharge(
1602- sso_url,
1603- "user@example.org",
1604- "secret",
1605- "caveat",
1606- one_time_password="123456",
1607- ),
1608+ assert "dummy" == webservices.get_sso_discharge(
1609+ sso_url,
1610+ "user@example.org",
1611+ "secret",
1612+ "caveat",
1613+ one_time_password="123456",
1614 )
1615 request = responses.calls[0].request
1616- self.assertEqual("application/json", request.headers["Content-Type"])
1617- self.assertEqual(
1618- {
1619- "email": "user@example.org",
1620- "password": "secret",
1621- "caveat_id": "caveat",
1622- "otp": "123456",
1623- },
1624- json.loads(request.body.decode()),
1625- )
1626+ assert "application/json" == request.headers["Content-Type"]
1627+ assert {
1628+ "email": "user@example.org",
1629+ "password": "secret",
1630+ "caveat_id": "caveat",
1631+ "otp": "123456",
1632+ } == json.loads(request.body.decode())
1633
1634 @responses.activate
1635 def test_get_sso_discharge_twofactor_required(self):
1636@@ -123,7 +105,7 @@ class WebservicesTests(TestCase):
1637 json={"error_list": [{"code": "twofactor-required"}]},
1638 )
1639
1640- self.assertRaises(
1641+ pytest.raises(
1642 exceptions.StoreTwoFactorAuthenticationRequired,
1643 webservices.get_sso_discharge,
1644 sso_url,
1645@@ -147,7 +129,7 @@ class WebservicesTests(TestCase):
1646 },
1647 )
1648
1649- e = self.assertRaises(
1650+ e = pytest.raises(
1651 exceptions.StoreAuthenticationError,
1652 webservices.get_sso_discharge,
1653 sso_url,
1654@@ -155,16 +137,15 @@ class WebservicesTests(TestCase):
1655 "secret",
1656 "caveat",
1657 )
1658- self.assertEqual("Something went wrong", e.message)
1659+ assert "Authentication error: Something went wrong" == str(e.value)
1660
1661 @responses.activate
1662- def test_get_sso_discharge_unstructured_error(self):
1663- logger = self.useFixture(fixtures.FakeLogger())
1664+ def test_get_sso_discharge_unstructured_error(self, caplog):
1665 sso_url = "http://sso.local/"
1666 discharge_url = urljoin(sso_url, "/api/v2/tokens/discharge")
1667 responses.add("POST", discharge_url, status=503, body="Try again later.")
1668
1669- self.assertRaises(
1670+ pytest.raises(
1671 HTTPError,
1672 webservices.get_sso_discharge,
1673 sso_url,
1674@@ -172,17 +153,15 @@ class WebservicesTests(TestCase):
1675 "secret",
1676 "caveat",
1677 )
1678- self.assertEqual(
1679- "Failed to get SSO discharge:\n"
1680- "====================\n"
1681- "Try again later.\n"
1682- "====================\n",
1683- logger.output,
1684- )
1685+ assert [
1686+ "Failed to get SSO discharge:",
1687+ "====================",
1688+ "Try again later.",
1689+ "====================",
1690+ ] == caplog.messages
1691
1692 @responses.activate
1693- def test_get_overrides_success(self):
1694- logger = self.useFixture(fixtures.FakeLogger())
1695+ def test_get_overrides_success(self, caplog):
1696 overrides = [factory.SnapDeviceGateway.Override()]
1697 # XXX cjwatson 2017-06-26: Use acceptable-generated double once it
1698 # exists.
1699@@ -190,17 +169,18 @@ class WebservicesTests(TestCase):
1700 overrides_url = urljoin(store.get("gw_url"), "/v2/metadata/overrides/mysnap")
1701 responses.add("GET", overrides_url, status=200, json=overrides)
1702
1703- self.assertEqual(overrides, webservices.get_overrides(store, "mysnap"))
1704+ assert overrides == webservices.get_overrides(store, "mysnap")
1705 request = responses.calls[0].request
1706- self.assertThat(
1707- request.headers["Authorization"],
1708- matchers.MacaroonHeaderVerifies(self.config.key),
1709+ assert (
1710+ matchers.MacaroonHeaderVerifies("random-key").match(
1711+ request.headers["Authorization"]
1712+ )
1713+ is None
1714 )
1715- self.assertNotIn("Failed to get overrides:", logger.output)
1716+ assert "Failed to get overrides:" not in caplog.messages
1717
1718 @responses.activate
1719- def test_get_overrides_error(self):
1720- logger = self.useFixture(fixtures.FakeLogger())
1721+ def test_get_overrides_error(self, caplog):
1722 store = config.Config().store_section("default")
1723 overrides_url = urljoin(store.get("gw_url"), "/v2/metadata/overrides/mysnap")
1724 responses.add(
1725@@ -210,14 +190,11 @@ class WebservicesTests(TestCase):
1726 json=factory.APIError.single("Something went wrong").to_dict(),
1727 )
1728
1729- self.assertRaises(HTTPError, webservices.get_overrides, store, "mysnap")
1730- self.assertEqual(
1731- "Failed to get overrides:\nSomething went wrong\n", logger.output
1732- )
1733+ pytest.raises(HTTPError, webservices.get_overrides, store, "mysnap")
1734+ assert ["Failed to get overrides:", "Something went wrong"] == caplog.messages
1735
1736 @responses.activate
1737- def test_set_overrides_success(self):
1738- logger = self.useFixture(fixtures.FakeLogger())
1739+ def test_set_overrides_success(self, caplog):
1740 override = factory.SnapDeviceGateway.Override()
1741 # XXX cjwatson 2017-06-26: Use acceptable-generated double once it
1742 # exists.
1743@@ -225,26 +202,8 @@ class WebservicesTests(TestCase):
1744 overrides_url = urljoin(store.get("gw_url"), "/v2/metadata/overrides")
1745 responses.add("POST", overrides_url, status=200, json=[override])
1746
1747- self.assertEqual(
1748- [override],
1749- webservices.set_overrides(
1750- store,
1751- [
1752- {
1753- "snap_name": override["snap_name"],
1754- "revision": override["revision"],
1755- "channel": override["channel"],
1756- "series": override["series"],
1757- }
1758- ],
1759- ),
1760- )
1761- request = responses.calls[0].request
1762- self.assertThat(
1763- request.headers["Authorization"],
1764- matchers.MacaroonHeaderVerifies(self.config.key),
1765- )
1766- self.assertEqual(
1767+ assert [override] == webservices.set_overrides(
1768+ store,
1769 [
1770 {
1771 "snap_name": override["snap_name"],
1772@@ -253,13 +212,27 @@ class WebservicesTests(TestCase):
1773 "series": override["series"],
1774 }
1775 ],
1776- json.loads(request.body.decode()),
1777 )
1778- self.assertNotIn("Failed to set override:", logger.output)
1779+ request = responses.calls[0].request
1780+ assert (
1781+ matchers.MacaroonHeaderVerifies("random-key").match(
1782+ request.headers["Authorization"]
1783+ )
1784+ is None
1785+ )
1786+
1787+ assert [
1788+ {
1789+ "snap_name": override["snap_name"],
1790+ "revision": override["revision"],
1791+ "channel": override["channel"],
1792+ "series": override["series"],
1793+ }
1794+ ] == json.loads(request.body.decode())
1795+ assert "Failed to set override:" not in caplog.messages
1796
1797 @responses.activate
1798- def test_set_overrides_error(self):
1799- logger = self.useFixture(fixtures.FakeLogger())
1800+ def test_set_overrides_error(self, caplog):
1801 override = factory.SnapDeviceGateway.Override()
1802 # XXX cjwatson 2017-06-26: Use acceptable-generated double once it
1803 # exists.
1804@@ -272,7 +245,7 @@ class WebservicesTests(TestCase):
1805 json=factory.APIError.single("Something went wrong").to_dict(),
1806 )
1807
1808- self.assertRaises(
1809+ pytest.raises(
1810 HTTPError,
1811 lambda: webservices.set_overrides(
1812 store,
1813@@ -284,6 +257,4 @@ class WebservicesTests(TestCase):
1814 },
1815 ),
1816 )
1817- self.assertEqual(
1818- "Failed to set override:\nSomething went wrong\n", logger.output
1819- )
1820+ assert ["Failed to set override:", "Something went wrong"] == caplog.messages

Subscribers

People subscribed via source and target branches

to all changes: