1 # -*- coding: utf-8; -*-
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
)
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. """
38 ('signature-good validity-unknown', {
39 'signature': mock
.MagicMock(
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"),
50 ('signature-good validity-never', {
51 'signature': mock
.MagicMock(
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"),
62 ('signature-good validity-full key-expired', {
63 'signature': mock
.MagicMock(
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"),
74 ('signature-good validity-full', {
75 'signature': mock
.MagicMock(
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"),
87 'signature': mock
.MagicMock(
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"),
103 class characterise_signature_TestCase(
104 testscenarios
.WithScenarios
,
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
,
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
139 signatures_by_name
= {
140 name
: scenario
['signature']
141 for (name
, scenario
) in make_gpgme_signature_scenarios()}
143 scenarios_by_name
= {
146 signatures_by_name
['signature-good validity-unknown'],
151 signatures_by_name
['signature-good validity-full'],
155 'exception': gpgme
.GpgmeError(
156 gpgme
.ERR_SOURCE_GPGME
, gpgme
.ERR_BAD_SIGNATURE
,
160 'exception': gpgme
.GpgmeError(
161 gpgme
.ERR_SOURCE_GPGME
, gpgme
.ERR_SIG_EXPIRED
,
162 "Signature expired"),
165 'exception': gpgme
.GpgmeError(
166 gpgme
.ERR_SOURCE_GPGME
, gpgme
.ERR_NO_DATA
,
170 'exception': ValueError,
175 'default': scenarios_by_name
['goodsig'],
179 for (name
, scenario
) in scenarios_by_name
.items())
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. """
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')
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']
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}: ...
236 path
=self
.file_double
.path
, code
=code
)
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
)
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
)
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.
306 # vim: fileencoding=utf-8 filetype=python :