summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_generator.py280
-rw-r--r--tests/test_main.py265
-rw-r--r--tests/test_server.py128
-rw-r--r--tests/test_static.py96
4 files changed, 0 insertions, 769 deletions
diff --git a/tests/test_generator.py b/tests/test_generator.py
deleted file mode 100644
index b4bb961..0000000
--- a/tests/test_generator.py
+++ /dev/null
@@ -1,280 +0,0 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3#
4# Copyright (c) 2020-2023, Simeon Simeonov
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""Tests for otp2289.generator"""
27import pytest
28
29import otp2289
30
31
32def test_caller_exceptions():
33 """Tests the exceptions when calling an initialized object"""
34 gen = otp2289.OTPGenerator(
35 'This is a test.'.encode(),
36 'TeSt',
37 otp2289.OTP_ALGO_MD5,
38 )
39 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
40 gen.generate_otp_words('3')
41 assert exc_info.type is otp2289.OTPGeneratorException
42 assert exc_info.value.args[0] == 'Step value MUST be an int'
43 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
44 gen.generate_otp_hexdigest(-1)
45 assert exc_info.type is otp2289.OTPGeneratorException
46 assert exc_info.value.args[0] == 'Step value MUST be >= 0'
47 with pytest.raises(otp2289.OTPChallengeException) as exc_info:
48 gen.generate_otp_hexdigest_from_challenge(b'md5 fbd TeSt')
49 assert exc_info.type is otp2289.OTPChallengeException
50 assert exc_info.value.args[0] == 'Challenge must be str'
51 with pytest.raises(otp2289.OTPChallengeException) as exc_info:
52 gen.generate_otp_hexdigest_from_challenge('md5 fbd TeSt')
53 assert exc_info.type is otp2289.OTPChallengeException
54 assert exc_info.value.args[0] == 'Invalid challenge'
55 with pytest.raises(otp2289.generator.OTPChallengeException) as exc_info:
56 gen.generate_otp_hexdigest_from_challenge('otp-md5 fbd TeSt')
57 assert exc_info.type is otp2289.generator.OTPChallengeException
58 assert exc_info.value.args[0] == 'Invalid challenge'
59
60
61def test_constructor_exceptions():
62 """
63 Tests the exceptions when initializing a new object (in the constructor)
64 """
65 # test the otp2289.OTPGenerator __init__ and validators
66 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
67 otp2289.OTPGenerator(
68 'This is a test.'.encode(),
69 'TeStø'.encode(),
70 otp2289.OTP_ALGO_MD5,
71 )
72 assert exc_info.type is otp2289.OTPGeneratorException
73 assert exc_info.value.args[0] == 'Seed must be a string'
74 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
75 otp2289.OTPGenerator(
76 'This is a test.'.encode(),
77 'TeStøtEsTteSTteStTest',
78 otp2289.OTP_ALGO_SHA1,
79 )
80 assert exc_info.type is otp2289.OTPGeneratorException
81 assert exc_info.value.args[0] == (
82 'The seed MUST be of 1 to 16 characters in length'
83 )
84 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
85 otp2289.OTPGenerator(
86 'This is a test.'.encode(),
87 'TeStø',
88 otp2289.OTP_ALGO_SHA1,
89 )
90 assert exc_info.type is otp2289.OTPGeneratorException
91 assert exc_info.value.args[0] == (
92 'The seed MUST consist of purely alphanumeric characters'
93 )
94 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
95 otp2289.OTPGenerator(
96 'This is a test.'.encode(),
97 'TeSt',
98 9,
99 )
100 assert exc_info.type is otp2289.OTPGeneratorException
101 assert exc_info.value.args[0] == (
102 'hash_algo is not among the known algorithms'
103 )
104 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
105 otp2289.OTPGenerator(
106 'This is a test.'.encode(),
107 'TeSt',
108 b'md5',
109 )
110 assert exc_info.type is otp2289.OTPGeneratorException
111 assert exc_info.value.args[0] == 'hash_algo must be an int or a str'
112 # test the package structure as well
113 with pytest.raises(otp2289.generator.OTPGeneratorException) as exc_info:
114 otp2289.generator.OTPGenerator(
115 'This is a test.'.encode(),
116 'TeSt',
117 'foo',
118 )
119 assert exc_info.type is otp2289.generator.OTPGeneratorException
120 assert exc_info.value.args[0] == (
121 'foo is not supported by this version of the hashlib module'
122 )
123 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
124 otp2289.OTPGenerator('1234567', 'TeSt', otp2289.OTP_ALGO_MD5)
125 assert exc_info.type is otp2289.OTPGeneratorException
126 assert exc_info.value.args[0] == 'Password must be a byte-string'
127 with pytest.raises(otp2289.OTPGeneratorException) as exc_info:
128 otp2289.OTPGenerator(
129 '1234567'.encode(),
130 'TeSt',
131 otp2289.OTP_ALGO_MD5,
132 )
133 assert exc_info.type is otp2289.OTPGeneratorException
134 assert exc_info.value.args[0] == 'Password must be longer than 10 bytes'
135
136
137def test_md5():
138 """
139 Tests the MD5 functionality of the OTPGenerator as described in the RFC
140
141 Those are the tests from 'RFC-2289 Appendix C - OTP Verification Examples'
142 """
143 # We could run this in a loop, but I guess "Readability counts."
144 # pass='This is a test.', seed='TeSt'
145 gen = otp2289.OTPGenerator(
146 'This is a test.'.encode(),
147 'TeSt',
148 otp2289.OTP_ALGO_MD5,
149 )
150 res_words = gen.generate_otp_words(0)
151 res_hex = gen.generate_otp_hexdigest(0)
152 assert isinstance(res_words, str)
153 assert isinstance(res_hex, str)
154 assert res_hex == '0x9e876134d90499dd'
155 assert res_words == 'INCH SEA ANNE LONG AHEM TOUR'
156 # step 1
157 assert gen.generate_otp_hexdigest(1) == '0x7965e05436f5029f'
158 assert gen.generate_otp_words(1) == 'EASE OIL FUM CURE AWRY AVIS'
159 assert gen.generate_otp_hexdigest_from_challenge('otp-md5 1 TeSt') == (
160 '0x7965e05436f5029f'
161 )
162 assert gen.generate_otp_words_from_challenge('otp-md5 1 TeSt') == (
163 'EASE OIL FUM CURE AWRY AVIS'
164 )
165 # step 99
166 assert gen.generate_otp_hexdigest(99) == '0x50fe1962c4965880'
167 assert gen.generate_otp_words(99) == 'BAIL TUFT BITS GANG CHEF THY'
168 assert gen.generate_otp_hexdigest_from_challenge('otp-md5 99 TeSt') == (
169 '0x50fe1962c4965880'
170 )
171 assert gen.generate_otp_words_from_challenge('otp-md5 99 TeSt') == (
172 'BAIL TUFT BITS GANG CHEF THY'
173 )
174 # iterator test
175 hexdigests = list(gen.hexdigest_range(105)) # testing the range itself
176 words = list(gen.words_range(99))
177 hexdigests.reverse()
178 words.reverse()
179 assert hexdigests[0] == '0x9e876134d90499dd'
180 assert hexdigests[1] == '0x7965e05436f5029f'
181 assert hexdigests[99] == '0x50fe1962c4965880'
182 assert words[0] == 'INCH SEA ANNE LONG AHEM TOUR'
183 assert words[1] == 'EASE OIL FUM CURE AWRY AVIS'
184 assert words[99] == 'BAIL TUFT BITS GANG CHEF THY'
185 # pass='AbCdEfGhIjK', seed='alpha1'
186 gen = otp2289.OTPGenerator(
187 'AbCdEfGhIjK'.encode(),
188 'alpha1',
189 otp2289.OTP_ALGO_MD5,
190 )
191 assert gen.generate_otp_hexdigest(0) == '0x87066dd9644bf206'
192 assert gen.generate_otp_words(0) == 'FULL PEW DOWN ONCE MORT ARC'
193 assert gen.generate_otp_hexdigest(1) == '0x7cd34c1040add14b'
194 assert gen.generate_otp_words(1) == 'FACT HOOF AT FIST SITE KENT'
195 assert gen.generate_otp_hexdigest(99) == '0x5aa37a81f212146c'
196 assert gen.generate_otp_words(99) == 'BODE HOP JAKE STOW JUT RAP'
197 # pass="OTP's are good", seed='correct'
198 gen = otp2289.OTPGenerator(
199 "OTP's are good".encode(),
200 'correct',
201 otp2289.OTP_ALGO_MD5,
202 )
203 assert gen.generate_otp_hexdigest(0) == '0xf205753943de4cf9'
204 assert gen.generate_otp_words(0) == 'ULAN NEW ARMY FUSE SUIT EYED'
205 assert gen.generate_otp_hexdigest(1) == '0xddcdac956f234937'
206 assert gen.generate_otp_words(1) == 'SKIM CULT LOB SLAM POE HOWL'
207 assert gen.generate_otp_hexdigest(99) == '0xb203e28fa525be47'
208 assert gen.generate_otp_words(99) == 'LONG IVY JULY AJAR BOND LEE'
209
210
211def test_sha1():
212 """
213 Tests the SHA-1 functionality of the OTPGenerator as described in the RFC
214
215 Those are the tests from 'RFC-2289 Appendix C - OTP Verification Examples'
216 """
217 # pass='This is a test.', seed='TeSt'
218 gen = otp2289.OTPGenerator(
219 'This is a test.'.encode(),
220 'TeSt',
221 otp2289.OTP_ALGO_SHA1,
222 )
223 # step=0
224 res_hex = gen.generate_otp_hexdigest(0)
225 res_words = gen.generate_otp_words(0)
226 assert isinstance(res_words, str)
227 assert isinstance(res_hex, str)
228 assert res_hex == '0xbb9e6ae1979d8ff4'
229 assert res_words == 'MILT VARY MAST OK SEES WENT'
230 assert gen.generate_otp_hexdigest(1) == '0x63d936639734385b'
231 assert gen.generate_otp_words(1) == 'CART OTTO HIVE ODE VAT NUT'
232 assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 1 TeSt') == (
233 '0x63d936639734385b'
234 )
235 assert gen.generate_otp_words_from_challenge('otp-sha1 1 TeSt') == (
236 'CART OTTO HIVE ODE VAT NUT'
237 )
238 assert gen.generate_otp_hexdigest(99) == '0x87fec7768b73ccf9'
239 assert gen.generate_otp_words(99) == 'GAFF WAIT SKID GIG SKY EYED'
240 assert gen.generate_otp_hexdigest_from_challenge('otp-sha1 99 TeSt') == (
241 '0x87fec7768b73ccf9'
242 )
243 assert gen.generate_otp_words_from_challenge('otp-sha1 99 TeSt') == (
244 'GAFF WAIT SKID GIG SKY EYED'
245 )
246 # iterator test
247 hexdigests = list(gen.hexdigest_range(105))
248 words = list(gen.words_range(99))
249 hexdigests.reverse()
250 words.reverse()
251 assert hexdigests[0] == '0xbb9e6ae1979d8ff4'
252 assert hexdigests[1] == '0x63d936639734385b'
253 assert hexdigests[99] == '0x87fec7768b73ccf9'
254 assert words[0] == 'MILT VARY MAST OK SEES WENT'
255 assert words[1] == 'CART OTTO HIVE ODE VAT NUT'
256 assert words[99] == 'GAFF WAIT SKID GIG SKY EYED'
257 # pass='AbCdEfGhIjK', seed='alpha1'
258 gen = otp2289.OTPGenerator(
259 'AbCdEfGhIjK'.encode(),
260 'alpha1',
261 otp2289.OTP_ALGO_SHA1,
262 )
263 assert gen.generate_otp_hexdigest(0) == '0xad85f658ebe383c9'
264 assert gen.generate_otp_words(0) == 'LEST OR HEEL SCOT ROB SUIT'
265 assert gen.generate_otp_hexdigest(1) == '0xd07ce229b5cf119b'
266 assert gen.generate_otp_words(1) == 'RITE TAKE GELD COST TUNE RECK'
267 assert gen.generate_otp_hexdigest(99) == '0x27bc71035aaf3dc6'
268 assert gen.generate_otp_words(99) == 'MAY STAR TIN LYON VEDA STAN'
269 # pass="OTP's are good", seed='correct'
270 gen = otp2289.OTPGenerator(
271 "OTP's are good".encode(),
272 'correct',
273 otp2289.OTP_ALGO_SHA1,
274 )
275 assert gen.generate_otp_hexdigest(0) == '0xd51f3e99bf8e6f0b'
276 assert gen.generate_otp_words(0) == 'RUST WELT KICK FELL TAIL FRAU'
277 assert gen.generate_otp_hexdigest(1) == '0x82aeb52d943774e4'
278 assert gen.generate_otp_words(1) == 'FLIT DOSE ALSO MEW DRUM DEFY'
279 assert gen.generate_otp_hexdigest(99) == '0x4f296a74fe1567ec'
280 assert gen.generate_otp_words(99) == 'AURA ALOE HURL WING BERG WAIT'
diff --git a/tests/test_main.py b/tests/test_main.py
deleted file mode 100644
index 35bd2a2..0000000
--- a/tests/test_main.py
+++ /dev/null
@@ -1,265 +0,0 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3#
4# Copyright (c) 2020-2023, Simeon Simeonov
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""Tests for otp2289.__main__"""
27import os
28import unittest.mock
29
30import pytest
31
32from otp2289.__main__ import main
33
34
35def test_main_generate_otp_response(capsys):
36 """tests main"""
37 args = [
38 '--generate-otp-response',
39 '-a',
40 'sha1',
41 '-i',
42 '99',
43 '-s',
44 'TesT',
45 '-p',
46 'This is a test.',
47 ]
48 with pytest.raises(SystemExit) as exit_info:
49 main(args)
50 captured = capsys.readouterr()
51 assert captured.out == (
52 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
53 f'0x87fec7768b73ccf9{os.linesep}'
54 )
55 assert exit_info.type == SystemExit
56 assert exit_info.value.code == 0
57 args.extend(['-f', 'token'])
58 with pytest.raises(SystemExit) as exit_info:
59 main(args)
60 captured = capsys.readouterr()
61 assert captured.out == (
62 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
63 f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
64 )
65 assert exit_info.type == SystemExit
66 assert exit_info.value.code == 0
67 args.append('-q')
68 with pytest.raises(SystemExit) as exit_info:
69 main(args)
70 captured = capsys.readouterr()
71 assert captured.out == f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
72 assert exit_info.type == SystemExit
73 assert exit_info.value.code == 0
74
75
76def test_main_generate_otp_response_env_passwd(capsys):
77 """tests main by fetching password from the env. var. 'OTP2289_PASSWORD'"""
78 args = [
79 '--generate-otp-response',
80 '-a',
81 'sha1',
82 '-i',
83 '99',
84 '-s',
85 'TesT',
86 ]
87 with unittest.mock.patch.dict(
88 os.environ, {'OTP2289_PASSWORD': 'This is a test.'}
89 ):
90 with pytest.raises(SystemExit) as exit_info:
91 main(args)
92 captured = capsys.readouterr()
93 assert captured.out == (
94 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
95 f'0x87fec7768b73ccf9{os.linesep}'
96 )
97 assert exit_info.type == SystemExit
98 assert exit_info.value.code == 0
99 args.extend(['-f', 'token'])
100 with pytest.raises(SystemExit) as exit_info:
101 main(args)
102 captured = capsys.readouterr()
103 assert captured.out == (
104 f'Seed: TesT, Step: 99, Hash: sha1{os.linesep}'
105 f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
106 )
107 assert exit_info.type == SystemExit
108 assert exit_info.value.code == 0
109 args.append('-q')
110 with pytest.raises(SystemExit) as exit_info:
111 main(args)
112 captured = capsys.readouterr()
113 assert captured.out == f'GAFF WAIT SKID GIG SKY EYED{os.linesep}'
114 assert exit_info.type == SystemExit
115 assert exit_info.value.code == 0
116
117
118def test_main_generate_otp_range(capsys):
119 """tests main"""
120 args = [
121 '--generate-otp-range',
122 '-i',
123 '2',
124 '-s',
125 'TesT',
126 '-r',
127 '5',
128 '-p',
129 'This is a test.',
130 ]
131 with pytest.raises(SystemExit) as exit_info:
132 main(args)
133 captured = capsys.readouterr()
134 assert captured.out == (
135 f'Seed: TesT, Step: 2, Hash: md5, Range: 3'
136 f'{os.linesep}'
137 f'2: 0x4049f8b161669b7b{os.linesep}'
138 f'1: 0x7965e05436f5029f{os.linesep}'
139 f'0: 0x9e876134d90499dd{os.linesep}'
140 )
141 assert exit_info.type == SystemExit
142 assert exit_info.value.code == 0
143 args.append('-q')
144 with pytest.raises(SystemExit) as exit_info:
145 main(args)
146 captured = capsys.readouterr()
147 assert captured.out == (
148 f'2: 0x4049f8b161669b7b{os.linesep}'
149 f'1: 0x7965e05436f5029f{os.linesep}'
150 f'0: 0x9e876134d90499dd{os.linesep}'
151 )
152 assert exit_info.type == SystemExit
153 assert exit_info.value.code == 0
154 args.extend(['-f', 'token'])
155 with pytest.raises(SystemExit) as exit_info:
156 main(args)
157 captured = capsys.readouterr()
158 assert captured.out == (
159 f'2: THY AVON NO NECK COKE MOLL{os.linesep}'
160 f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}'
161 f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}'
162 )
163 assert exit_info.type == SystemExit
164 assert exit_info.value.code == 0
165
166
167@pytest.mark.parametrize(
168 'args',
169 [
170 [
171 '--generate-otp-range',
172 '-i',
173 '2',
174 '-s',
175 'TesT',
176 '-r',
177 '5',
178 ],
179 [
180 '--generate-otp-range',
181 '-i',
182 '2',
183 '-s',
184 'TesT',
185 '-r',
186 '5',
187 '-P',
188 ],
189 ],
190)
191@unittest.mock.patch('getpass.getpass', lambda *args: 'This is a test.')
192def test_main_generate_otp_range_passwd_prompt(capsys, args):
193 """tests main by prompting for password (with or without -P)"""
194 args = [
195 '--generate-otp-range',
196 '-i',
197 '2',
198 '-s',
199 'TesT',
200 '-r',
201 '5',
202 ]
203 with pytest.raises(SystemExit) as exit_info:
204 main(args)
205 captured = capsys.readouterr()
206 assert captured.out == (
207 f'Seed: TesT, Step: 2, Hash: md5, Range: 3'
208 f'{os.linesep}'
209 f'2: 0x4049f8b161669b7b{os.linesep}'
210 f'1: 0x7965e05436f5029f{os.linesep}'
211 f'0: 0x9e876134d90499dd{os.linesep}'
212 )
213 assert exit_info.type == SystemExit
214 assert exit_info.value.code == 0
215 args.append('-q')
216 with pytest.raises(SystemExit) as exit_info:
217 main(args)
218 captured = capsys.readouterr()
219 assert captured.out == (
220 f'2: 0x4049f8b161669b7b{os.linesep}'
221 f'1: 0x7965e05436f5029f{os.linesep}'
222 f'0: 0x9e876134d90499dd{os.linesep}'
223 )
224 assert exit_info.type == SystemExit
225 assert exit_info.value.code == 0
226 args.extend(['-f', 'token'])
227 with pytest.raises(SystemExit) as exit_info:
228 main(args)
229 captured = capsys.readouterr()
230 assert captured.out == (
231 f'2: THY AVON NO NECK COKE MOLL{os.linesep}'
232 f'1: EASE OIL FUM CURE AWRY AVIS{os.linesep}'
233 f'0: INCH SEA ANNE LONG AHEM TOUR{os.linesep}'
234 )
235 assert exit_info.type == SystemExit
236 assert exit_info.value.code == 0
237
238
239def test_main_initiate(capsys):
240 """tests main"""
241 args = [
242 '--initiate-new-sequence',
243 '-i',
244 '500',
245 '-s',
246 'TesT',
247 '-p',
248 'This is a test.',
249 ]
250 with pytest.raises(SystemExit) as exit_info:
251 main(args)
252 captured = capsys.readouterr()
253 assert captured.out == (
254 f'Seed: TesT, Step: 500, Hash: md5{os.linesep}'
255 f'0x2b8d82b6ac14346c{os.linesep}'
256 )
257 assert exit_info.type == SystemExit
258 assert exit_info.value.code == 0
259 args.append('-q')
260 with pytest.raises(SystemExit) as exit_info:
261 main(args)
262 captured = capsys.readouterr()
263 assert captured.out == f'0x2b8d82b6ac14346c{os.linesep}'
264 assert exit_info.type == SystemExit
265 assert exit_info.value.code == 0
diff --git a/tests/test_server.py b/tests/test_server.py
deleted file mode 100644
index 64e7f67..0000000
--- a/tests/test_server.py
+++ /dev/null
@@ -1,128 +0,0 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3#
4# Copyright (c) 2020-2023, Simeon Simeonov
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""Tests for otp2289.server"""
27import json
28
29import pytest
30
31import otp2289
32
33
34def test_state_caller_exceptions():
35 """Tests the exceptions when calling the OTPState objects"""
36 state = otp2289.OTPState(
37 '0x7965e05436f5029f',
38 1,
39 'TeSt',
40 otp2289.OTP_ALGO_MD5,
41 )
42 with pytest.raises(otp2289.OTPInvalidResponse) as exc_info:
43 state.response_validates('bla')
44 assert exc_info.type is otp2289.OTPInvalidResponse
45 assert exc_info.value.args[0] == (
46 'The response is neither a valid token or hex'
47 )
48
49
50def test_state_constructor_exceptions():
51 """Tests the exceptions when initializing new OTPState objects"""
52 with pytest.raises(otp2289.OTPStateException) as exc_info:
53 otp2289.OTPState(
54 '0x7965e05436f5029t',
55 1,
56 'TeStø'.encode(),
57 otp2289.OTP_ALGO_MD5,
58 )
59 assert exc_info.type is otp2289.OTPStateException
60 assert exc_info.value.args[0] == 'Seed must be a string'
61 with pytest.raises(otp2289.OTPStateException) as exc_info:
62 otp2289.OTPState(
63 '0x7965e05436f5029t',
64 '1',
65 'TeSt',
66 otp2289.OTP_ALGO_MD5,
67 )
68 assert exc_info.type is otp2289.OTPStateException
69 assert exc_info.value.args[0] == 'Step value MUST be an int'
70
71
72def test_state_validation_md5():
73 """Tests the OTPState validation functionality for MD5"""
74 state = otp2289.OTPState(
75 '0x7965e05436f5029f',
76 1,
77 'TeSt',
78 otp2289.OTP_ALGO_MD5,
79 )
80 assert state.validated is False
81 assert state.response_validates('0x9e876134d90499dd') is True
82 assert state.response_validates('INCH SEA ANNE LONG AHEM TOUR') is True
83 assert state.ot_hex == '7965e05436f5029f'
84 assert state.validated is True
85
86
87def test_state_validation_sha1():
88 """Tests the OTPState validation functionality for SHA1"""
89 state = otp2289.OTPState(
90 '0x63d936639734385b',
91 1,
92 'TeSt',
93 otp2289.OTP_ALGO_SHA1,
94 )
95 assert state.validated is False
96 assert state.response_validates('0xbb9e6ae1979d8ff4') is True
97 assert state.response_validates('MILT VARY MAST OK SEES WENT') is True
98 assert state.ot_hex == '63d936639734385b'
99 assert state.validated is True
100
101
102def test_store():
103 """Tests the OTPStore functionality"""
104 store_data = {
105 'sgs': {
106 'ot_hex': '0x7965e05436f5029f',
107 'current_step': 1,
108 'seed': 'TeSt',
109 'hash_algo': 'md5',
110 },
111 'blackmore': {
112 'ot_hex': '0x63d936639734385b',
113 'current_step': 1,
114 'seed': 'TeSt',
115 'hash_algo': 'sha1',
116 },
117 }
118 store = otp2289.OTPStore(store_data)
119 assert len(store) == 2
120 assert isinstance(json.dumps(store.to_dict()), str) # serializable?
121 assert store.response_validates('sgs', '0x9e876134d90499dd') is True
122 assert store.response_validates('sgs', '0x9e876134d90499dd') is False
123 sgs_state = store.get('sgs')
124 assert sgs_state in store
125 store.pop_state('sgs')
126 assert bool(store) is True
127 store.pop_state('blackmore')
128 assert bool(store) is False
diff --git a/tests/test_static.py b/tests/test_static.py
deleted file mode 100644
index 7635e5e..0000000
--- a/tests/test_static.py
+++ /dev/null
@@ -1,96 +0,0 @@
1# -*- coding: utf-8 -*-
2# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3#
4# Copyright (c) 2020-2023, Simeon Simeonov
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation
14# and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26"""Tests for the static methods and basic bit, byte, token functionality"""
27import binascii
28import os
29
30import otp2289
31
32
33def test_bytes_and_tokens():
34 """Tests the official hex and tokens defined in RFC2289"""
35 assert binascii.unhexlify('9e876134d90499dd') == (
36 otp2289.OTPGenerator.tokens_to_bytes('INCH SEA ANNE LONG AHEM TOUR')
37 )
38 assert binascii.unhexlify('7965e05436f5029f') == (
39 otp2289.OTPGenerator.tokens_to_bytes('EASE OIL FUM CURE AWRY AVIS')
40 )
41 assert binascii.unhexlify('50fe1962c4965880') == (
42 otp2289.OTPGenerator.tokens_to_bytes('BAIL TUFT BITS GANG CHEF THY')
43 )
44 assert binascii.unhexlify('87066dd9644bf206') == (
45 otp2289.OTPGenerator.tokens_to_bytes('FULL PEW DOWN ONCE MORT ARC')
46 )
47 assert binascii.unhexlify('7cd34c1040add14b') == (
48 otp2289.OTPGenerator.tokens_to_bytes('FACT HOOF AT FIST SITE KENT')
49 )
50 assert binascii.unhexlify('5aa37a81f212146c') == (
51 otp2289.OTPGenerator.tokens_to_bytes('BODE HOP JAKE STOW JUT RAP')
52 )
53 assert binascii.unhexlify('f205753943de4cf9') == (
54 otp2289.OTPGenerator.tokens_to_bytes('ULAN NEW ARMY FUSE SUIT EYED')
55 )
56 assert binascii.unhexlify('ddcdac956f234937') == (
57 otp2289.OTPGenerator.tokens_to_bytes('SKIM CULT LOB SLAM POE HOWL')
58 )
59 assert binascii.unhexlify('b203e28fa525be47') == (
60 otp2289.OTPGenerator.tokens_to_bytes('LONG IVY JULY AJAR BOND LEE')
61 )
62 assert binascii.unhexlify('bb9e6ae1979d8ff4') == (
63 otp2289.OTPGenerator.tokens_to_bytes('MILT VARY MAST OK SEES WENT')
64 )
65 assert binascii.unhexlify('63d936639734385b') == (
66 otp2289.OTPGenerator.tokens_to_bytes('CART OTTO HIVE ODE VAT NUT')
67 )
68 assert binascii.unhexlify('87fec7768b73ccf9') == (
69 otp2289.OTPGenerator.tokens_to_bytes('GAFF WAIT SKID GIG SKY EYED')
70 )
71 assert binascii.unhexlify('ad85f658ebe383c9') == (
72 otp2289.OTPGenerator.tokens_to_bytes('LEST OR HEEL SCOT ROB SUIT')
73 )
74 assert binascii.unhexlify('d07ce229b5cf119b') == (
75 otp2289.OTPGenerator.tokens_to_bytes('RITE TAKE GELD COST TUNE RECK')
76 )
77 assert binascii.unhexlify('27bc71035aaf3dc6') == (
78 otp2289.OTPGenerator.tokens_to_bytes('MAY STAR TIN LYON VEDA STAN')
79 )
80 assert binascii.unhexlify('d51f3e99bf8e6f0b') == (
81 otp2289.OTPGenerator.tokens_to_bytes('RUST WELT KICK FELL TAIL FRAU')
82 )
83 assert binascii.unhexlify('82aeb52d943774e4') == (
84 otp2289.OTPGenerator.tokens_to_bytes('FLIT DOSE ALSO MEW DRUM DEFY')
85 )
86 assert binascii.unhexlify('4f296a74fe1567ec') == (
87 otp2289.OTPGenerator.tokens_to_bytes('AURA ALOE HURL WING BERG WAIT')
88 )
89
90
91def test_random_bytes():
92 """Implement a few tests with random bytes"""
93 for _ in range(10):
94 rnd_bytes = os.urandom(8) # 64 bits
95 tokens = otp2289.OTPGenerator.bytes_to_tokens(rnd_bytes)
96 assert rnd_bytes == otp2289.OTPGenerator.tokens_to_bytes(tokens)