Remove superfluous manipulation of import path.
[dput.git] / test / test_crypto.py
blob4f25301bc4c8826c4304a2a28507ef3ddced089a
1 # -*- coding: utf-8; -*-
3 # test/test_crypto.py
4 # Part of ‘dput’, a Debian package upload toolkit.
6 # This is free software, and you are welcome to redistribute it under
7 # certain conditions; see the end of this file for copyright
8 # information, grant of license, and disclaimer of warranty.
10 """ Unit tests for ‘crypto’ module. """
12 from __future__ import (absolute_import, unicode_literals)
14 import doctest
15 import functools
16 import operator
17 import sys
18 import textwrap
20 import gpgme
21 import testscenarios
22 import testtools
24 import dput.crypto
26 from .helper import (
27 mock,
28 patch_system_interfaces,
29 set_fake_file_scenario,
30 setup_fake_file_fixtures,
34 def make_gpgme_signature_scenarios():
35 """ Make a collection of scenarios for `gpgme.Signature` instances. """
37 scenarios = [
38 ('signature-good validity-unknown', {
39 'signature': mock.MagicMock(
40 gpgme.Signature,
41 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
42 status=gpgme.ERR_NO_ERROR,
43 summary=functools.reduce(
44 operator.ior, [gpgme.SIGSUM_GREEN]),
45 validity=gpgme.VALIDITY_UNKNOWN),
46 'expected_character': "good",
47 'expected_description': (
48 "Good signature from F00DBEEFDECAFBAD"),
49 }),
50 ('signature-good validity-never', {
51 'signature': mock.MagicMock(
52 gpgme.Signature,
53 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
54 status=gpgme.ERR_NO_ERROR,
55 summary=functools.reduce(
56 operator.ior, [gpgme.SIGSUM_GREEN]),
57 validity=gpgme.VALIDITY_NEVER),
58 'expected_character': "good",
59 'expected_description': (
60 "Good signature from F00DBEEFDECAFBAD"),
61 }),
62 ('signature-good validity-full key-expired', {
63 'signature': mock.MagicMock(
64 gpgme.Signature,
65 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
66 status=gpgme.ERR_NO_ERROR,
67 summary=functools.reduce(operator.ior, [
68 gpgme.SIGSUM_GREEN, gpgme.SIGSUM_KEY_EXPIRED]),
69 validity=gpgme.VALIDITY_FULL),
70 'expected_character': "good",
71 'expected_description': (
72 "Good signature from F00DBEEFDECAFBAD"),
73 }),
74 ('signature-good validity-full', {
75 'signature': mock.MagicMock(
76 gpgme.Signature,
77 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
78 status=gpgme.ERR_NO_ERROR,
79 summary=functools.reduce(operator.ior, [
80 gpgme.SIGSUM_VALID, gpgme.SIGSUM_GREEN]),
81 validity=gpgme.VALIDITY_FULL),
82 'expected_character': "valid",
83 'expected_description': (
84 "Valid signature from F00DBEEFDECAFBAD"),
85 }),
86 ('signature-bad', {
87 'signature': mock.MagicMock(
88 gpgme.Signature,
89 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
90 status=gpgme.ERR_BAD_SIGNATURE,
91 summary=functools.reduce(
92 operator.ior, [gpgme.SIGSUM_RED]),
93 validity=gpgme.VALIDITY_FULL),
94 'expected_character': "bad",
95 'expected_description': (
96 "Bad signature from F00DBEEFDECAFBAD"),
97 }),
100 return scenarios
103 class characterise_signature_TestCase(
104 testscenarios.WithScenarios,
105 testtools.TestCase):
106 """ Test cases for function `characterise_signature`. """
108 scenarios = make_gpgme_signature_scenarios()
110 def test_returns_expected_character(self):
111 """ Should return expected character for signature. """
112 result = dput.crypto.characterise_signature(self.signature)
113 self.assertEqual(result, self.expected_character)
116 class describe_signature_TestCase(
117 testscenarios.WithScenarios,
118 testtools.TestCase):
119 """ Test cases for function `describe_signature`. """
121 scenarios = make_gpgme_signature_scenarios()
123 def test_returns_expected_character(self):
124 """ Should return expected character for signature. """
125 result = dput.crypto.describe_signature(self.signature)
126 self.assertEqual(result, self.expected_description)
129 def make_gpgme_verify_scenarios():
130 """ Make a collection of scenarios for ‘Context.verify’ method.
132 :return: A collection of scenarios for tests.
134 The collection is a mapping from scenario name to a dictionary of
135 scenario attributes.
139 signatures_by_name = {
140 name: scenario['signature']
141 for (name, scenario) in make_gpgme_signature_scenarios()}
143 scenarios_by_name = {
144 'goodsig': {
145 'result': [
146 signatures_by_name['signature-good validity-unknown'],
149 'validsig': {
150 'result': [
151 signatures_by_name['signature-good validity-full'],
154 'badsig': {
155 'exception': gpgme.GpgmeError(
156 gpgme.ERR_SOURCE_GPGME, gpgme.ERR_BAD_SIGNATURE,
157 "Bad signature"),
159 'errsig': {
160 'exception': gpgme.GpgmeError(
161 gpgme.ERR_SOURCE_GPGME, gpgme.ERR_SIG_EXPIRED,
162 "Signature expired"),
164 'nodata': {
165 'exception': gpgme.GpgmeError(
166 gpgme.ERR_SOURCE_GPGME, gpgme.ERR_NO_DATA,
167 "No data"),
169 'bogus': {
170 'exception': ValueError,
174 scenarios = {
175 'default': scenarios_by_name['goodsig'],
177 scenarios.update(
178 (name, scenario)
179 for (name, scenario) in scenarios_by_name.items())
181 return scenarios
184 def setup_gpgme_verify_fixtures(testcase):
185 """ Set up fixtures for GPGME interaction behaviour. """
186 scenarios = make_gpgme_verify_scenarios()
187 testcase.gpgme_verify_scenarios = scenarios
190 class check_file_signature_TestCase(testtools.TestCase):
191 """ Test cases for `check_file_signature` function. """
193 def setUp(self):
194 """ Set up test fixtures. """
195 super(check_file_signature_TestCase, self).setUp()
196 patch_system_interfaces(self)
198 setup_fake_file_fixtures(self)
199 set_fake_file_scenario(self, 'exist-minimal')
201 self.set_test_args()
203 self.patch_gpgme_context()
205 setup_gpgme_verify_fixtures(self)
206 self.set_gpgme_verify_scenario('default')
208 def set_test_args(self):
209 """ Set the arguments for the test call to the function. """
210 self.test_args = dict(
211 infile=self.file_double.fake_file,
214 def patch_gpgme_context(self):
215 """ Patch the ‘gpgme.Context’ class for this test case. """
216 class_patcher = mock.patch.object(gpgme, 'Context')
217 class_patcher.start()
218 self.addCleanup(class_patcher.stop)
220 def set_gpgme_verify_scenario(self, name):
221 """ Set the status scenario for the ‘Context.verify’ call. """
222 scenario = self.gpgme_verify_scenarios[name]
223 mock_class = gpgme.Context
224 self.mock_gpgme_context = mock_class.return_value
225 mock_func = self.mock_gpgme_context.verify
226 if 'exception' in scenario:
227 mock_func.side_effect = scenario['exception']
228 else:
229 mock_func.return_value = scenario['result']
231 def assert_stderr_contains_gpgme_error(self, code):
232 """ Assert the `stderr` content contains the GPGME message. """
233 expected_output = textwrap.dedent("""\
234 gpgme: {path}: error {code}: ...
235 """).format(
236 path=self.file_double.path, code=code)
237 self.assertThat(
238 sys.stderr.getvalue(),
239 testtools.matchers.DocTestMatches(
240 expected_output, doctest.ELLIPSIS))
242 def test_calls_gpgme_verify_with_expected_args(self):
243 """ Should call `gpgme.Context.verify` with expected args. """
244 dput.crypto.check_file_signature(**self.test_args)
245 gpgme.Context.return_value.verify.assert_called_with(
246 self.file_double.fake_file, None, None)
248 def test_calls_sys_exit_if_gnupg_reports_bad_signature(self):
249 """ Should call `sys.exit` if GnuPG reports bad signature. """
250 self.set_gpgme_verify_scenario('badsig')
251 with testtools.ExpectedException(gpgme.GpgmeError):
252 dput.crypto.check_file_signature(**self.test_args)
253 self.assert_stderr_contains_gpgme_error(gpgme.ERR_BAD_SIGNATURE)
255 def test_calls_sys_exit_if_gnupg_reports_sig_expired(self):
256 """ Should call `sys.exit` if GnuPG reports signature expired. """
257 self.set_gpgme_verify_scenario('errsig')
258 with testtools.ExpectedException(gpgme.GpgmeError):
259 dput.crypto.check_file_signature(**self.test_args)
260 self.assert_stderr_contains_gpgme_error(gpgme.ERR_SIG_EXPIRED)
262 def test_calls_sys_exit_if_gnupg_reports_nodata(self):
263 """ Should call `sys.exit` if GnuPG reports no data. """
264 self.set_gpgme_verify_scenario('nodata')
265 with testtools.ExpectedException(gpgme.GpgmeError):
266 dput.crypto.check_file_signature(**self.test_args)
267 self.assert_stderr_contains_gpgme_error(gpgme.ERR_NO_DATA)
269 def test_outputs_message_if_gnupg_reports_goodsig(self):
270 """ Should output a message if GnuPG reports a good signature. """
271 self.set_gpgme_verify_scenario('goodsig')
272 dput.crypto.check_file_signature(**self.test_args)
273 expected_output = textwrap.dedent("""\
274 gpgme: {path}: Good signature from ...
275 """).format(path=self.file_double.path)
276 self.assertThat(
277 sys.stderr.getvalue(),
278 testtools.matchers.DocTestMatches(
279 expected_output, doctest.ELLIPSIS))
281 def test_outputs_message_if_gnupg_reports_validsig(self):
282 """ Should output a message if GnuPG reports a valid signature. """
283 self.set_gpgme_verify_scenario('validsig')
284 dput.crypto.check_file_signature(**self.test_args)
285 expected_output = textwrap.dedent("""\
286 gpgme: {path}: Valid signature from ...
287 """).format(path=self.file_double.path)
288 self.assertThat(
289 sys.stderr.getvalue(),
290 testtools.matchers.DocTestMatches(
291 expected_output, doctest.ELLIPSIS))
294 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
296 # This is free software: you may copy, modify, and/or distribute this work
297 # under the terms of the GNU General Public License as published by the
298 # Free Software Foundation; version 3 of that license or any later version.
299 # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.
302 # Local variables:
303 # coding: utf-8
304 # mode: python
305 # End:
306 # vim: fileencoding=utf-8 filetype=python :