2022-12-30 20:37:38 +01:00
# pytest for extract_otp_secrets.py
2022-09-03 14:31:09 +02:00
# Run tests:
2022-09-03 15:38:47 +02:00
# pytest
2022-09-03 14:31:09 +02:00
# Author: Scito (https://scito.ch)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2023-01-01 17:25:24 +01:00
from __future__ import annotations # workaround for PYTHON <= 3.10
2023-01-02 13:40:03 +01:00
2022-12-25 11:00:15 +01:00
import io
import os
2022-12-29 21:29:20 +01:00
import pathlib
2023-01-02 13:40:03 +01:00
import re
2022-12-29 21:34:14 +01:00
import sys
2023-01-02 13:40:03 +01:00
import time
2023-01-02 17:29:59 +01:00
from typing import Optional
2022-12-25 11:00:15 +01:00
2023-01-02 13:40:03 +01:00
import colorama
2022-12-26 18:31:09 +01:00
import pytest
2022-12-29 21:29:20 +01:00
from pytest_mock import MockerFixture
2023-01-02 20:27:41 +01:00
from utils import ( count_files_in_dir , file_exits , read_binary_file_as_stream ,
read_csv , read_csv_str , read_file_to_str , read_json ,
read_json_str , replace_escaped_octal_utf8_bytes_with_str )
2023-01-02 13:40:03 +01:00
import extract_otp_secrets
2022-09-03 14:31:09 +02:00
2022-12-30 20:37:38 +01:00
qreader_available : bool = extract_otp_secrets . qreader_available
2022-12-26 23:39:13 +01:00
2022-09-03 16:12:28 +02:00
2022-12-31 21:24:01 +01:00
# Quickfix comment
2023-01-01 00:41:11 +01:00
# @pytest.mark.skipif(sys.platform.startswith("win") or not qreader_available or sys.implementation.name == 'pypy' or sys.version_info >= (3, 10), reason="Quickfix")
2022-12-31 21:24:01 +01:00
2022-12-29 21:29:20 +01:00
def test_extract_stdout ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-18 17:41:35 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ 'example_export.txt' ])
2022-12-18 17:41:35 +01:00
# Assert
captured = capsys . readouterr ()
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_non_existent_file ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-24 01:59:35 +01:00
# Act
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'non_existent_file.txt' ])
2022-12-24 01:59:35 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-30 12:37:05 +01:00
expected_stderr = ' \n ERROR: Input file provided is non-existent or not a file. \n input file: non_existent_file.txt \n '
2022-12-24 01:59:35 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-12-29 21:29:20 +01:00
def test_extract_stdin_stdout ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-18 21:45:32 +01:00
# Arrange
2022-12-25 11:00:15 +01:00
monkeypatch . setattr ( 'sys.stdin' , io . StringIO ( read_file_to_str ( 'example_export.txt' )))
2022-12-18 17:41:35 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-' ])
2022-12-18 17:41:35 +01:00
# Assert
captured = capsys . readouterr ()
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_stdin_empty ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-26 18:31:09 +01:00
# Arrange
monkeypatch . setattr ( 'sys.stdin' , io . StringIO ())
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , '-' ])
2022-12-26 18:31:09 +01:00
# Assert
captured = capsys . readouterr ()
assert captured . out == ''
2023-01-01 00:13:34 +01:00
assert captured . err == ' \n WARN: stdin is empty \n '
2022-12-26 18:31:09 +01:00
2022-12-29 21:29:20 +01:00
def test_extract_empty_file_no_qreader ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-26 23:39:13 +01:00
if qreader_available :
# Act
with pytest . raises ( SystemExit ) as e :
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/empty_file.txt' ])
2022-12-26 23:39:13 +01:00
# Assert
captured = capsys . readouterr ()
2023-01-01 00:13:34 +01:00
expected_stderr = ' \n WARN: tests/data/empty_file.txt is empty \n\n ERROR: Unable to open file for reading. \n input file: tests/data/empty_file.txt \n '
2022-12-26 23:39:13 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
else :
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ 'tests/data/empty_file.txt' ])
2022-12-26 18:31:09 +01:00
2022-12-26 23:39:13 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-26 18:31:09 +01:00
2022-12-26 23:39:13 +01:00
assert captured . err == ''
assert captured . out == ''
2022-12-26 18:31:09 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_extract_stdin_img_empty ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-26 18:31:09 +01:00
# Arrange
monkeypatch . setattr ( 'sys.stdin' , io . BytesIO ())
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , '=' ])
2022-12-26 18:31:09 +01:00
# Assert
captured = capsys . readouterr ()
assert captured . out == ''
2023-01-01 00:13:34 +01:00
assert captured . err == ' \n WARN: stdin is empty \n '
2022-12-26 18:31:09 +01:00
2022-12-29 21:29:20 +01:00
def test_extract_csv ( capsys : pytest . CaptureFixture [ str ], tmp_path : pathlib . Path ) -> None :
2022-09-03 15:38:47 +02:00
# Arrange
2022-12-29 13:44:19 +01:00
output_file = str ( tmp_path / 'test_example_output.csv' )
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-q' , '-c' , output_file , 'example_export.txt' ])
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
# Assert
expected_csv = read_csv ( 'example_output.csv' )
2022-12-29 13:44:19 +01:00
actual_csv = read_csv ( output_file )
2022-09-03 14:31:09 +02:00
2022-09-03 16:12:28 +02:00
assert actual_csv == expected_csv
2022-09-03 14:31:09 +02:00
2022-09-09 13:13:13 +02:00
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-09-03 16:12:28 +02:00
2022-12-29 21:29:20 +01:00
def test_extract_csv_stdout ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-18 19:24:07 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-c' , '-' , 'example_export.txt' ])
2022-12-18 21:45:32 +01:00
# Assert
assert not file_exits ( 'test_example_output.csv' )
captured = capsys . readouterr ()
expected_csv = read_csv ( 'example_output.csv' )
actual_csv = read_csv_str ( captured . out )
assert actual_csv == expected_csv
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_stdin_and_csv_stdout ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-18 21:45:32 +01:00
# Arrange
2022-12-25 11:00:15 +01:00
monkeypatch . setattr ( 'sys.stdin' , io . StringIO ( read_file_to_str ( 'example_export.txt' )))
2022-12-18 21:45:32 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-c' , '-' , '-' ])
2022-12-18 19:24:07 +01:00
# Assert
assert not file_exits ( 'test_example_output.csv' )
captured = capsys . readouterr ()
expected_csv = read_csv ( 'example_output.csv' )
actual_csv = read_csv_str ( captured . out )
assert actual_csv == expected_csv
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_keepass_csv ( capsys : pytest . CaptureFixture [ str ], tmp_path : pathlib . Path ) -> None :
2022-12-04 12:23:39 +01:00
'''Two csv files .totp and .htop are generated.'''
# Arrange
2022-12-29 13:44:19 +01:00
file_name = str ( tmp_path / 'test_example_keepass_output.csv' )
2022-12-04 12:23:39 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-q' , '-k' , file_name , 'example_export.txt' ])
2022-12-04 12:23:39 +01:00
# Assert
expected_totp_csv = read_csv ( 'example_keepass_output.totp.csv' )
expected_hotp_csv = read_csv ( 'example_keepass_output.hotp.csv' )
2022-12-29 13:44:19 +01:00
actual_totp_csv = read_csv ( str ( tmp_path / 'test_example_keepass_output.totp.csv' ))
actual_hotp_csv = read_csv ( str ( tmp_path / 'test_example_keepass_output.hotp.csv' ))
2022-12-04 12:23:39 +01:00
assert actual_totp_csv == expected_totp_csv
assert actual_hotp_csv == expected_hotp_csv
2022-12-29 13:44:19 +01:00
assert not file_exits ( file_name )
2022-12-04 12:23:39 +01:00
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_keepass_csv_stdout ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-18 19:24:07 +01:00
'''Two csv files .totp and .htop are generated.'''
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-k' , '-' , 'tests/data/example_export_only_totp.txt' ])
2022-12-18 19:24:07 +01:00
# Assert
expected_totp_csv = read_csv ( 'example_keepass_output.totp.csv' )
assert not file_exits ( 'test_example_keepass_output.totp.csv' )
assert not file_exits ( 'test_example_keepass_output.hotp.csv' )
assert not file_exits ( 'test_example_keepass_output.csv' )
captured = capsys . readouterr ()
actual_totp_csv = read_csv_str ( captured . out )
assert actual_totp_csv == expected_totp_csv
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_single_keepass_csv ( capsys : pytest . CaptureFixture [ str ], tmp_path : pathlib . Path ) -> None :
2022-12-04 12:23:39 +01:00
'''Does not add .totp or .hotp pre-suffix'''
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-q' , '-k' , str ( tmp_path / 'test_example_keepass_output.csv' ), 'tests/data/example_export_only_totp.txt' ])
2022-12-04 12:23:39 +01:00
# Assert
expected_totp_csv = read_csv ( 'example_keepass_output.totp.csv' )
2022-12-29 13:44:19 +01:00
actual_totp_csv = read_csv ( str ( tmp_path / 'test_example_keepass_output.csv' ))
2022-12-04 12:23:39 +01:00
assert actual_totp_csv == expected_totp_csv
2022-12-29 13:44:19 +01:00
assert not file_exits ( tmp_path / 'test_example_keepass_output.totp.csv' )
assert not file_exits ( tmp_path / 'test_example_keepass_output.hotp.csv' )
2022-12-04 12:23:39 +01:00
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_json ( capsys : pytest . CaptureFixture [ str ], tmp_path : pathlib . Path ) -> None :
2022-09-03 15:38:47 +02:00
# Arrange
2022-12-29 13:44:19 +01:00
output_file = str ( tmp_path / 'test_example_output.json' )
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-q' , '-j' , output_file , 'example_export.txt' ])
2022-09-03 14:31:09 +02:00
2022-09-03 23:47:43 +02:00
# Assert
2022-09-03 15:38:47 +02:00
expected_json = read_json ( 'example_output.json' )
2022-12-29 13:44:19 +01:00
actual_json = read_json ( output_file )
2022-09-03 14:31:09 +02:00
2022-09-03 15:38:47 +02:00
assert actual_json == expected_json
2022-09-03 14:31:09 +02:00
2022-09-09 13:13:13 +02:00
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-09-03 16:12:28 +02:00
2022-12-29 21:29:20 +01:00
def test_extract_json_stdout ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-18 19:24:07 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-j' , '-' , 'example_export.txt' ])
2022-12-18 19:24:07 +01:00
# Assert
expected_json = read_json ( 'example_output.json' )
assert not file_exits ( 'test_example_output.json' )
captured = capsys . readouterr ()
actual_json = read_json_str ( captured . out )
assert actual_json == expected_json
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_not_encoded_plus ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-09-07 21:58:03 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ 'tests/data/test_plus_problem_export.txt' ])
2022-09-07 21:58:03 +02:00
# Assert
captured = capsys . readouterr ()
2022-12-04 12:23:39 +01:00
expected_stdout = '''Name: SerenityLabs:test1@serenitylabs.co.uk
Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
2022-12-04 12:23:39 +01:00
Name: SerenityLabs:test2@serenitylabs.co.uk
Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
2022-12-04 12:23:39 +01:00
Name: SerenityLabs:test3@serenitylabs.co.uk
Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
2022-12-04 12:23:39 +01:00
Name: SerenityLabs:test4@serenitylabs.co.uk
Secret: N2ILWSXSJUQUB7S6NONPJSC62NPG7EXN
Issuer: SerenityLabs
Type: totp
2022-09-07 21:58:03 +02:00
'''
assert captured . out == expected_stdout
assert captured . err == ''
2022-12-19 16:39:28 +01:00
2022-12-29 21:29:20 +01:00
def test_extract_printqr ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-09-03 23:47:43 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-p' , 'example_export.txt' ])
2022-09-03 23:47:43 +02:00
# Assert
captured = capsys . readouterr ()
2022-12-30 12:37:05 +01:00
expected_stdout = read_file_to_str ( 'tests/data/printqr_output.txt' )
2022-09-03 23:47:43 +02:00
assert captured . out == expected_stdout
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_saveqr ( capsys : pytest . CaptureFixture [ str ], tmp_path : pathlib . Path ) -> None :
2022-09-09 13:13:13 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-q' , '-s' , str ( tmp_path ), 'example_export.txt' ])
2022-09-09 13:13:13 +02:00
# Assert
captured = capsys . readouterr ()
assert captured . out == ''
assert captured . err == ''
2022-12-29 13:44:19 +01:00
assert os . path . isfile ( tmp_path / '1-piraspberrypi-raspberrypi.png' )
assert os . path . isfile ( tmp_path / '2-piraspberrypi.png' )
assert os . path . isfile ( tmp_path / '3-piraspberrypi.png' )
assert os . path . isfile ( tmp_path / '4-piraspberrypi-raspberrypi.png' )
2022-12-31 11:32:07 +01:00
assert os . path . isfile ( tmp_path / '5-hotpdemo.png' )
assert os . path . isfile ( tmp_path / '6-encodingäÄéÉdemo.png' )
assert count_files_in_dir ( tmp_path ) == 6
2022-09-09 13:13:13 +02:00
2023-01-02 17:29:59 +01:00
def test_extract_ignored_duplicates ( capsys : pytest . CaptureFixture [ str ]) -> None :
# Act
extract_otp_secrets . main ([ '-i' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
expected_stdout = '''Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
'''
expected_stderr = '''Ignored duplicate otp: pi@raspberrypi
Ignored duplicate otp: pi@raspberrypi / raspberrypi
'''
assert captured . out == expected_stdout
assert captured . err == expected_stderr
2022-12-29 21:29:20 +01:00
def test_normalize_bytes () -> None :
2022-12-29 15:52:17 +01:00
assert replace_escaped_octal_utf8_bytes_with_str (
'Before \\\\ 302 \\\\ 277 \\\\ 303 \n name: enc: \\ 302 \\ 277 \\ 303 \\ 244 \\ 303 \\ 204 \\ 303 \\ 251 \\ 303 \\ 211? \n After' ) == 'Before \\\\ 302 \\\\ 277 \\\\ 303 \n name: enc: ¿äÄéÉ? \n After'
2022-12-26 18:31:09 +01:00
2023-01-02 13:40:03 +01:00
# Generate verbose output:
# for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do python3.11 src/extract_otp_secrets.py example_export.txt $color $level > tests/data/print_verbose_output$color$level.txt; done; done
2023-01-01 17:25:24 +01:00
# workaround for PYTHON <= 3.10
2023-01-01 00:41:11 +01:00
@pytest.mark.skipif ( sys . version_info < ( 3 , 10 ), reason = "fileinput.input encoding exists since PYTHON 3.10" )
2023-01-02 13:40:03 +01:00
@pytest.mark.parametrize ( "verbose_level" , [ '' , '-v' , '-vv' , '-vvv' ])
@pytest.mark.parametrize ( "color" , [ '' , '-n' ])
def test_extract_verbose ( verbose_level : str , color : str , capsys : pytest . CaptureFixture [ str ], relaxed : bool ) -> None :
args = [ 'example_export.txt' ]
if verbose_level :
args . append ( verbose_level )
if color :
args . append ( color )
2022-09-04 07:42:27 +02:00
# Act
2023-01-02 13:40:03 +01:00
extract_otp_secrets . main ( args )
2022-09-04 07:42:27 +02:00
# Assert
captured = capsys . readouterr ()
2023-01-02 22:41:30 +01:00
expected_stdout = normalize_verbose_text ( read_file_to_str ( f 'tests/data/print_verbose_output { color }{ verbose_level } .txt' ), relaxed or sys . implementation . name == 'pypy' )
actual_stdout = normalize_verbose_text ( captured . out , relaxed or sys . implementation . name == 'pypy' )
2022-09-04 07:42:27 +02:00
2023-01-02 20:27:41 +01:00
assert actual_stdout == expected_stdout
2022-09-04 07:42:27 +02:00
assert captured . err == ''
2023-01-02 14:16:30 +01:00
2023-01-02 20:27:41 +01:00
def normalize_verbose_text ( text : str , relaxed : bool ) -> str :
normalized = re . sub ( '^.+ version: .+$' , '' , text , flags = re . MULTILINE | re . IGNORECASE )
if not qreader_available :
normalized = normalized \
. replace ( 'QReader installed: True' , 'QReader installed: False' ) \
. replace ( ' \n QR reading mode: ZBAR \n\n ' , '' )
if relaxed :
print ( ' \n Relaxed mode \n ' )
normalized = replace_escaped_octal_utf8_bytes_with_str ( normalized )
return normalized
2023-01-02 13:40:03 +01:00
2022-09-04 07:42:27 +02:00
2022-12-29 21:29:20 +01:00
def test_extract_debug ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-09-09 13:13:13 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-vvv' , 'example_export.txt' ])
2022-09-09 13:13:13 +02:00
# Assert
captured = capsys . readouterr ()
2022-12-30 12:37:05 +01:00
expected_stdout = read_file_to_str ( 'tests/data/print_verbose_output.txt' )
2022-09-09 13:13:13 +02:00
assert len ( captured . out ) > len ( expected_stdout )
assert "DEBUG: " in captured . out
assert captured . err == ''
2022-12-29 21:29:20 +01:00
def test_extract_help ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2022-09-09 13:13:13 +02:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-h' ])
2022-09-09 13:13:13 +02:00
# Assert
captured = capsys . readouterr ()
assert len ( captured . out ) > 0
2023-01-02 17:29:59 +01:00
assert "-h, --help" in captured . out and "-v, --verbose" in captured . out
2022-09-09 13:13:13 +02:00
assert captured . err == ''
2022-12-24 01:59:35 +01:00
assert e . type == SystemExit
assert e . value . code == 0
2022-09-09 13:13:13 +02:00
2022-12-29 15:52:17 +01:00
2022-12-29 21:29:20 +01:00
def test_extract_no_arguments ( capsys : pytest . CaptureFixture [ str ], mocker : MockerFixture ) -> None :
2022-12-28 23:37:21 +01:00
if qreader_available :
# Arrange
otps = read_json ( 'example_output.json' )
2022-12-30 20:37:38 +01:00
mocker . patch ( 'extract_otp_secrets.extract_otps_from_camera' , return_value = otps )
2022-12-18 21:45:32 +01:00
2022-12-28 23:37:21 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '-c' , '-' ])
2022-12-18 21:45:32 +01:00
2022-12-28 23:37:21 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-18 21:45:32 +01:00
2022-12-28 23:37:21 +01:00
expected_csv = read_csv ( 'example_output.csv' )
actual_csv = read_csv_str ( captured . out )
assert actual_csv == expected_csv
assert captured . err == ''
else :
# Act
with pytest . raises ( SystemExit ) as e :
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([])
2022-12-28 23:37:21 +01:00
# Assert
captured = capsys . readouterr ()
expected_err_msg = 'error: the following arguments are required: infile'
assert expected_err_msg in captured . err
assert captured . out == ''
assert e . value . code == 2
assert e . type == SystemExit
2022-12-18 21:45:32 +01:00
2022-12-29 21:29:20 +01:00
def test_verbose_and_quiet ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2022-11-29 04:19:35 +08:00
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , '-v' , '-q' , 'example_export.txt' ])
2022-11-29 04:19:35 +08:00
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
assert len ( captured . err ) > 0
2023-01-02 17:29:59 +01:00
assert 'error: argument -q/--quiet: not allowed with argument -v/--verbose' in captured . err
2022-12-18 19:24:07 +01:00
assert captured . out == ''
2022-12-24 01:59:35 +01:00
assert e . value . code == 2
assert e . type == SystemExit
2022-09-09 13:13:13 +02:00
2022-12-04 12:23:39 +01:00
2023-01-02 17:29:59 +01:00
@pytest.mark.parametrize ( "parameter,parameter_value,stdout_expected,stderr_expected" , [
( '-c' , 'outfile' , False , False ),
( '-c' , '-' , True , False ),
( '-k' , 'outfile' , False , False ),
( '-k' , '-' , True , False ),
( '-j' , 'outfile' , False , False ),
( '-s' , 'outfile' , False , False ),
( '-j' , '-' , True , False ),
( '-i' , None , False , False ),
( '-p' , None , True , False ),
( '-Q' , 'CV2' , False , False ),
2023-01-02 20:27:41 +01:00
( '-C' , '0' , False , False ),
2023-01-02 17:29:59 +01:00
( '-n' , None , False , False ),
])
def test_quiet ( parameter : str , parameter_value : Optional [ str ], stdout_expected : bool , stderr_expected : bool , capsys : pytest . CaptureFixture [ str ], tmp_path : pathlib . Path ) -> None :
2023-01-02 20:27:41 +01:00
if parameter in [ '-Q' , '-C' ] and not qreader_available :
return
# Arrange
2023-01-02 17:29:59 +01:00
args = [ '-q' , 'example_export.txt' , 'example_export.png' , parameter ]
if parameter_value == 'outfile' :
args . append ( str ( tmp_path / parameter_value ))
elif parameter_value :
args . append ( parameter_value )
2023-01-02 20:27:41 +01:00
2023-01-02 17:29:59 +01:00
# Act
extract_otp_secrets . main ( args )
# Assert
captured = capsys . readouterr ()
assert ( captured . out == '' and not stdout_expected ) or ( len ( captured . out ) > 0 and stdout_expected )
assert ( captured . err == '' and not stderr_expected ) or ( len ( captured . err ) > 0 and stderr_expected )
2022-12-29 21:29:20 +01:00
def test_wrong_data ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2022-12-16 12:43:32 +01:00
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/test_export_wrong_data.txt' ])
2022-12-16 12:43:32 +01:00
# Assert
captured = capsys . readouterr ()
2023-01-01 19:19:41 +01:00
first_expected_stderr = '''
2022-12-16 12:43:32 +01:00
ERROR: Cannot decode otpauth-migration migration payload.
data=XXXX
2023-01-01 15:12:24 +01:00
Exception: Error parsing message
2022-12-16 12:43:32 +01:00
'''
2023-01-01 19:19:41 +01:00
# Alpine Linux prints this exception message
second_expected_stderr = '''
ERROR: Cannot decode otpauth-migration migration payload.
data=XXXX
Exception: unpack requires a buffer of 4 bytes
'''
assert captured . err == first_expected_stderr or captured . err == second_expected_stderr
2022-12-18 19:24:07 +01:00
assert captured . out == ''
2022-12-24 01:59:35 +01:00
assert e . value . code == 1
assert e . type == SystemExit
2022-12-16 12:43:32 +01:00
2022-12-29 21:29:20 +01:00
def test_wrong_content ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-31 11:32:07 +01:00
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/test_export_wrong_content.txt' ])
2022-12-16 12:43:32 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
expected_stderr = '''
2022-12-31 11:32:07 +01:00
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
2022-12-16 12:43:32 +01:00
2022-12-31 11:32:07 +01:00
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
2022-12-16 12:43:32 +01:00
'''
2022-12-18 19:24:07 +01:00
assert captured . out == ''
assert captured . err == expected_stderr
2022-12-31 11:32:07 +01:00
def test_one_wrong_file ( capsys : pytest . CaptureFixture [ str ]) -> None :
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/test_export_wrong_content.txt' , 'example_export.txt' ])
2022-12-31 11:32:07 +01:00
# Assert
captured = capsys . readouterr ()
expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == expected_stderr
2023-01-01 00:13:34 +01:00
def test_one_wrong_file_colored ( capsys : pytest . CaptureFixture [ str ]) -> None :
# Act
extract_otp_secrets . main ([ 'tests/data/test_export_wrong_content.txt' , 'example_export.txt' ])
# Assert
captured = capsys . readouterr ()
expected_stderr = f ''' { colorama . Fore . RED }
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given { colorama . Fore . RESET }
{ colorama . Fore . RED }
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. { colorama . Fore . RESET }
'''
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == expected_stderr
2022-12-31 11:32:07 +01:00
def test_one_wrong_line ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
# Arrange
monkeypatch . setattr ( 'sys.stdin' ,
io . StringIO ( read_file_to_str ( 'tests/data/test_export_wrong_content.txt' ) + read_file_to_str ( 'example_export.txt' )))
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , '-' ])
2022-12-31 11:32:07 +01:00
# Assert
captured = capsys . readouterr ()
expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: -
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: -
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured . err == expected_stderr
2022-12-16 12:43:32 +01:00
2022-12-29 21:29:20 +01:00
def test_wrong_prefix ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-16 12:43:32 +01:00
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/test_export_wrong_prefix.txt' ])
2022-12-16 12:43:32 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-18 19:24:07 +01:00
expected_stderr = '''
2022-12-31 11:32:07 +01:00
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_prefix.txt
input: QR-Code:otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B %2F%2F%2F%2F%2F 8B
Maybe a wrong file was given
2022-12-18 19:24:07 +01:00
'''
expected_stdout = '''Name: pi@raspberrypi
2022-12-16 12:43:32 +01:00
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
'''
assert captured . out == expected_stdout
2022-12-18 19:24:07 +01:00
assert captured . err == expected_stderr
2022-12-16 12:43:32 +01:00
2022-12-29 21:29:20 +01:00
def test_add_pre_suffix ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-30 20:37:38 +01:00
assert extract_otp_secrets . add_pre_suffix ( "name.csv" , "totp" ) == "name.totp.csv"
assert extract_otp_secrets . add_pre_suffix ( "name.csv" , "" ) == "name..csv"
assert extract_otp_secrets . add_pre_suffix ( "name" , "totp" ) == "name.totp"
2022-12-04 12:23:39 +01:00
2022-12-26 18:31:09 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_img_qr_reader_from_file_happy_path ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-24 01:59:35 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ 'tests/data/test_googleauth_export.png' ])
2022-12-24 01:59:35 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-24 04:48:12 +01:00
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
2022-12-24 01:59:35 +01:00
assert captured . err == ''
2022-12-28 22:28:54 +01:00
2022-12-31 15:41:37 +01:00
@pytest.mark.qreader
def test_img_qr_reader_by_parameter ( capsys : pytest . CaptureFixture [ str ], qr_mode : str ) -> None :
# Act
2023-01-02 13:40:03 +01:00
start_s = time . process_time ()
2022-12-31 15:41:37 +01:00
extract_otp_secrets . main ([ '--qr' , qr_mode , 'tests/data/test_googleauth_export.png' ])
2023-01-02 13:40:03 +01:00
elapsed_s = time . process_time () - start_s
2022-12-31 15:41:37 +01:00
# Assert
captured = capsys . readouterr ()
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert captured . err == ''
2023-01-02 13:40:03 +01:00
print ( f "Elapsed time for { qr_mode } : { elapsed_s : .2f } s" )
2022-12-31 15:41:37 +01:00
2022-12-28 22:28:54 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_extract_multiple_files_and_mixed ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-28 22:28:54 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([
2022-12-28 22:28:54 +01:00
'example_export.txt' ,
2022-12-30 12:37:05 +01:00
'tests/data/test_googleauth_export.png' ,
2022-12-28 22:28:54 +01:00
'example_export.txt' ,
2022-12-30 12:37:05 +01:00
'tests/data/test_googleauth_export.png' ])
2022-12-28 22:28:54 +01:00
# Assert
captured = capsys . readouterr ()
assert captured . out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT + EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG + EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT + EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert captured . err == ''
2022-12-26 18:31:09 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_img_qr_reader_from_stdin ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-24 01:59:35 +01:00
# Arrange
# sys.stdin.buffer should be monkey patched, but it does not work
2022-12-30 12:37:05 +01:00
monkeypatch . setattr ( 'sys.stdin' , read_binary_file_as_stream ( 'tests/data/test_googleauth_export.png' ))
2022-12-24 01:59:35 +01:00
# Act
2022-12-30 20:37:38 +01:00
extract_otp_secrets . main ([ '=' ])
2022-12-24 01:59:35 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-29 15:52:17 +01:00
expected_stdout = '''Name: Test1:test1@example1.com
2022-12-24 01:59:35 +01:00
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:test2@example2.com
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:test3@example3.com
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''
assert captured . out == expected_stdout
assert captured . err == ''
2022-12-26 18:31:09 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_img_qr_reader_from_stdin_wrong_symbol ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-24 04:19:43 +01:00
# Arrange
# sys.stdin.buffer should be monkey patched, but it does not work
2022-12-30 12:37:05 +01:00
monkeypatch . setattr ( 'sys.stdin' , read_binary_file_as_stream ( 'tests/data/test_googleauth_export.png' ))
2022-12-24 04:19:43 +01:00
# Act
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , '-' ])
2022-12-24 04:19:43 +01:00
# Assert
captured = capsys . readouterr ()
2023-01-01 00:13:34 +01:00
expected_stderr = ' \n ERROR: Binary input was given in stdin, please use = instead of - as infile argument for images. \n '
2022-12-24 04:19:43 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-12-28 22:28:54 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_extract_stdin_stdout_wrong_symbol ( capsys : pytest . CaptureFixture [ str ], monkeypatch : pytest . MonkeyPatch ) -> None :
2022-12-28 22:28:54 +01:00
# Arrange
monkeypatch . setattr ( 'sys.stdin' , io . StringIO ( read_file_to_str ( 'example_export.txt' )))
# Act
with pytest . raises ( SystemExit ) as e :
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , '=' ])
2022-12-28 22:28:54 +01:00
# Assert
captured = capsys . readouterr ()
2023-01-01 15:12:24 +01:00
expected_stderr = " \n ERROR: Cannot read binary stdin buffer. \n Exception: a bytes-like object is required, not 'str' \n "
2022-12-28 22:28:54 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-12-26 18:31:09 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_img_qr_reader_no_qr_code_in_image ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-24 01:59:35 +01:00
# Act
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/lena_std.tif' ])
2022-12-24 01:59:35 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-30 12:37:05 +01:00
expected_stderr = ' \n ERROR: Unable to read QR Code from file. \n input file: tests/data/lena_std.tif \n '
2022-12-24 01:59:35 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-12-26 18:31:09 +01:00
@pytest.mark.qreader
2022-12-29 21:29:20 +01:00
def test_img_qr_reader_nonexistent_file ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-24 01:59:35 +01:00
# Act
2022-12-26 18:31:09 +01:00
with pytest . raises ( SystemExit ) as e :
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'nonexistent.bmp' ])
2022-12-24 01:59:35 +01:00
# Assert
captured = capsys . readouterr ()
2022-12-30 12:37:05 +01:00
expected_stderr = ' \n ERROR: Input file provided is non-existent or not a file. \n input file: nonexistent.bmp \n '
2022-12-24 01:59:35 +01:00
assert captured . err == expected_stderr
assert captured . out == ''
assert e . value . code == 1
assert e . type == SystemExit
2022-12-29 21:29:20 +01:00
def test_non_image_file ( capsys : pytest . CaptureFixture [ str ]) -> None :
2022-12-24 01:59:35 +01:00
# Act
2023-01-01 00:13:34 +01:00
extract_otp_secrets . main ([ '-n' , 'tests/data/text_masquerading_as_image.jpeg' ])
2022-12-24 01:59:35 +01:00
# Assert
captured = capsys . readouterr ()
expected_stderr = '''
2022-12-31 11:32:07 +01:00
WARN: input is not a otpauth-migration:// url
source: tests/data/text_masquerading_as_image.jpeg
input: This is just a text file masquerading as an image file.
Maybe a wrong file was given
2022-12-24 01:59:35 +01:00
2022-12-31 11:32:07 +01:00
ERROR: could not parse query parameter in input url
source: tests/data/text_masquerading_as_image.jpeg
url: This is just a text file masquerading as an image file.
2022-12-24 01:59:35 +01:00
'''
assert captured . err == expected_stderr
assert captured . out == ''
2022-12-18 17:41:35 +01:00
EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT = '''Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
2022-12-19 22:01:08 +01:00
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
2022-12-18 17:41:35 +01:00
'''
2022-12-24 04:48:12 +01:00
2022-12-29 15:52:17 +01:00
EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG = '''Name: Test1:test1@example1.com
2022-12-24 04:48:12 +01:00
Secret: JBSWY3DPEHPK3PXP
Issuer: Test1
Type: totp
Name: Test2:test2@example2.com
Secret: JBSWY3DPEHPK3PXQ
Issuer: Test2
Type: totp
Name: Test3:test3@example3.com
Secret: JBSWY3DPEHPK3PXR
Issuer: Test3
Type: totp
'''