Correct copyright information for Debian packaging files.
[dput.git] / test / test_crypto.py
blobf9dc5b5169a262c6f39e6ed1d086060169385748
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 os
18 import os.path
19 import sys
20 import textwrap
22 import gpgme
23 import testscenarios
24 import testtools
26 __package__ = str("test")
27 __import__(__package__)
28 sys.path.insert(1, os.path.dirname(os.path.dirname(__file__)))
29 import dput.crypto
31 from .helper import (
32 mock,
33 patch_system_interfaces,
34 set_fake_file_scenario,
35 setup_fake_file_fixtures,
39 def make_gpgme_signature_scenarios():
40 """ Make a collection of scenarios for `gpgme.Signature` instances. """
42 scenarios = [
43 ('signature-good validity-unknown', {
44 'signature': mock.MagicMock(
45 gpgme.Signature,
46 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
47 status=gpgme.ERR_NO_ERROR,
48 summary=functools.reduce(
49 operator.ior, [gpgme.SIGSUM_GREEN]),
50 validity=gpgme.VALIDITY_UNKNOWN),
51 'expected_character': "good",
52 'expected_description': (
53 "Good signature from F00DBEEFDECAFBAD"),
54 }),
55 ('signature-good validity-never', {
56 'signature': mock.MagicMock(
57 gpgme.Signature,
58 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
59 status=gpgme.ERR_NO_ERROR,
60 summary=functools.reduce(
61 operator.ior, [gpgme.SIGSUM_GREEN]),
62 validity=gpgme.VALIDITY_NEVER),
63 'expected_character': "good",
64 'expected_description': (
65 "Good signature from F00DBEEFDECAFBAD"),
66 }),
67 ('signature-good validity-full key-expired', {
68 'signature': mock.MagicMock(
69 gpgme.Signature,
70 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
71 status=gpgme.ERR_NO_ERROR,
72 summary=functools.reduce(operator.ior, [
73 gpgme.SIGSUM_GREEN, gpgme.SIGSUM_KEY_EXPIRED]),
74 validity=gpgme.VALIDITY_FULL),
75 'expected_character': "good",
76 'expected_description': (
77 "Good signature from F00DBEEFDECAFBAD"),
78 }),
79 ('signature-good validity-full', {
80 'signature': mock.MagicMock(
81 gpgme.Signature,
82 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
83 status=gpgme.ERR_NO_ERROR,
84 summary=functools.reduce(operator.ior, [
85 gpgme.SIGSUM_VALID, gpgme.SIGSUM_GREEN]),
86 validity=gpgme.VALIDITY_FULL),
87 'expected_character': "valid",
88 'expected_description': (
89 "Valid signature from F00DBEEFDECAFBAD"),
90 }),
91 ('signature-bad', {
92 'signature': mock.MagicMock(
93 gpgme.Signature,
94 fpr="BADBEEF2FACEDCADF00DBEEFDECAFBAD",
95 status=gpgme.ERR_BAD_SIGNATURE,
96 summary=functools.reduce(
97 operator.ior, [gpgme.SIGSUM_RED]),
98 validity=gpgme.VALIDITY_FULL),
99 'expected_character': "bad",
100 'expected_description': (
101 "Bad signature from F00DBEEFDECAFBAD"),
105 return scenarios
108 class characterise_signature_TestCase(
109 testscenarios.WithScenarios,
110 testtools.TestCase):
111 """ Test cases for function `characterise_signature`. """
113 scenarios = make_gpgme_signature_scenarios()
115 def test_returns_expected_character(self):
116 """ Should return expected character for signature. """
117 result = dput.crypto.characterise_signature(self.signature)
118 self.assertEqual(result, self.expected_character)
121 class describe_signature_TestCase(
122 testscenarios.WithScenarios,
123 testtools.TestCase):
124 """ Test cases for function `describe_signature`. """
126 scenarios = make_gpgme_signature_scenarios()
128 def test_returns_expected_character(self):
129 """ Should return expected character for signature. """
130 result = dput.crypto.describe_signature(self.signature)
131 self.assertEqual(result, self.expected_description)
134 def make_gpgme_verify_scenarios():
135 """ Make a collection of scenarios for ‘Context.verify’ method.
137 :return: A collection of scenarios for tests.
139 The collection is a mapping from scenario name to a dictionary of
140 scenario attributes.
144 signatures_by_name = {
145 name: scenario['signature']
146 for (name, scenario) in make_gpgme_signature_scenarios()}
148 scenarios_by_name = {
149 'goodsig': {
150 'result': [
151 signatures_by_name['signature-good validity-unknown'],
154 'validsig': {
155 'result': [
156 signatures_by_name['signature-good validity-full'],
159 'badsig': {
160 'exception': gpgme.GpgmeError(
161 gpgme.ERR_SOURCE_GPGME, gpgme.ERR_BAD_SIGNATURE,
162 "Bad signature"),
164 'errsig': {
165 'exception': gpgme.GpgmeError(
166 gpgme.ERR_SOURCE_GPGME, gpgme.ERR_SIG_EXPIRED,
167 "Signature expired"),
169 'nodata': {
170 'exception': gpgme.GpgmeError(
171 gpgme.ERR_SOURCE_GPGME, gpgme.ERR_NO_DATA,
172 "No data"),
174 'bogus': {
175 'exception': ValueError,
179 scenarios = {
180 'default': scenarios_by_name['goodsig'],
182 scenarios.update(
183 (name, scenario)
184 for (name, scenario) in scenarios_by_name.items())
186 return scenarios
189 def setup_gpgme_verify_fixtures(testcase):
190 """ Set up fixtures for GPGME interaction behaviour. """
191 scenarios = make_gpgme_verify_scenarios()
192 testcase.gpgme_verify_scenarios = scenarios
195 class check_file_signature_TestCase(testtools.TestCase):
196 """ Test cases for `check_file_signature` function. """
198 def setUp(self):
199 """ Set up test fixtures. """
200 super(check_file_signature_TestCase, self).setUp()
201 patch_system_interfaces(self)
203 setup_fake_file_fixtures(self)
204 set_fake_file_scenario(self, 'exist-minimal')
206 self.set_test_args()
208 self.patch_gpgme_context()
210 setup_gpgme_verify_fixtures(self)
211 self.set_gpgme_verify_scenario('default')
213 def set_test_args(self):
214 """ Set the arguments for the test call to the function. """
215 self.test_args = dict(
216 infile=self.file_double.fake_file,
219 def patch_gpgme_context(self):
220 """ Patch the ‘gpgme.Context’ class for this test case. """
221 class_patcher = mock.patch.object(gpgme, 'Context')
222 class_patcher.start()
223 self.addCleanup(class_patcher.stop)
225 def set_gpgme_verify_scenario(self, name):
226 """ Set the status scenario for the ‘Context.verify’ call. """
227 scenario = self.gpgme_verify_scenarios[name]
228 mock_class = gpgme.Context
229 self.mock_gpgme_context = mock_class.return_value
230 mock_func = self.mock_gpgme_context.verify
231 if 'exception' in scenario:
232 mock_func.side_effect = scenario['exception']
233 else:
234 mock_func.return_value = scenario['result']
236 def assert_stderr_contains_gpgme_error(self, code):
237 """ Assert the `stderr` content contains the GPGME message. """
238 expected_output = textwrap.dedent("""\
239 gpgme: {path}: error {code}: ...
240 """).format(
241 path=self.file_double.path, code=code)
242 self.assertThat(
243 sys.stderr.getvalue(),
244 testtools.matchers.DocTestMatches(
245 expected_output, doctest.ELLIPSIS))
247 def test_calls_gpgme_verify_with_expected_args(self):
248 """ Should call `gpgme.Context.verify` with expected args. """
249 dput.crypto.check_file_signature(**self.test_args)
250 gpgme.Context.return_value.verify.assert_called_with(
251 self.file_double.fake_file, None, None)
253 def test_calls_sys_exit_if_gnupg_reports_bad_signature(self):
254 """ Should call `sys.exit` if GnuPG reports bad signature. """
255 self.set_gpgme_verify_scenario('badsig')
256 with testtools.ExpectedException(gpgme.GpgmeError):
257 dput.crypto.check_file_signature(**self.test_args)
258 self.assert_stderr_contains_gpgme_error(gpgme.ERR_BAD_SIGNATURE)
260 def test_calls_sys_exit_if_gnupg_reports_sig_expired(self):
261 """ Should call `sys.exit` if GnuPG reports signature expired. """
262 self.set_gpgme_verify_scenario('errsig')
263 with testtools.ExpectedException(gpgme.GpgmeError):
264 dput.crypto.check_file_signature(**self.test_args)
265 self.assert_stderr_contains_gpgme_error(gpgme.ERR_SIG_EXPIRED)
267 def test_calls_sys_exit_if_gnupg_reports_nodata(self):
268 """ Should call `sys.exit` if GnuPG reports no data. """
269 self.set_gpgme_verify_scenario('nodata')
270 with testtools.ExpectedException(gpgme.GpgmeError):
271 dput.crypto.check_file_signature(**self.test_args)
272 self.assert_stderr_contains_gpgme_error(gpgme.ERR_NO_DATA)
274 def test_outputs_message_if_gnupg_reports_goodsig(self):
275 """ Should output a message if GnuPG reports a good signature. """
276 self.set_gpgme_verify_scenario('goodsig')
277 dput.crypto.check_file_signature(**self.test_args)
278 expected_output = textwrap.dedent("""\
279 gpgme: {path}: Good signature from ...
280 """).format(path=self.file_double.path)
281 self.assertThat(
282 sys.stderr.getvalue(),
283 testtools.matchers.DocTestMatches(
284 expected_output, doctest.ELLIPSIS))
286 def test_outputs_message_if_gnupg_reports_validsig(self):
287 """ Should output a message if GnuPG reports a valid signature. """
288 self.set_gpgme_verify_scenario('validsig')
289 dput.crypto.check_file_signature(**self.test_args)
290 expected_output = textwrap.dedent("""\
291 gpgme: {path}: Valid signature from ...
292 """).format(path=self.file_double.path)
293 self.assertThat(
294 sys.stderr.getvalue(),
295 testtools.matchers.DocTestMatches(
296 expected_output, doctest.ELLIPSIS))
299 # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
301 # This is free software: you may copy, modify, and/or distribute this work
302 # under the terms of the GNU General Public License as published by the
303 # Free Software Foundation; version 3 of that license or any later version.
304 # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details.
307 # Local variables:
308 # coding: utf-8
309 # mode: python
310 # End:
311 # vim: fileencoding=utf-8 filetype=python :