diff options
-rw-r--r-- | pyproject.toml | 2 | ||||
-rw-r--r-- | src/scadere/check.py | 10 | ||||
-rw-r--r-- | src/scadere/listen.py | 9 | ||||
-rw-r--r-- | tst/test_help.py | 69 |
4 files changed, 80 insertions, 10 deletions
diff --git a/pyproject.toml b/pyproject.toml index 3e2d251..60291a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = 'flit_core.buildapi' name = 'scadere' description = 'TLS certificate renewal reminder' readme = 'README.md' -requires-python = '>=3.9' +requires-python = '>=3.10' license = { file = 'COPYING' } authors = [ { name = 'Nguyễn Gia Phong', email = 'cnx@loang.net' } ] maintainers = [ { name = 'Nguyễn Gia Phong', email = 'chung@loa.loang.net' } ] diff --git a/src/scadere/check.py b/src/scadere/check.py index 7ef2d4a..5764382 100644 --- a/src/scadere/check.py +++ b/src/scadere/check.py @@ -23,7 +23,7 @@ from email.utils import parsedate_to_datetime as parsedate from itertools import chain from socket import AF_INET, socket from ssl import create_default_context as tls_context -from sys import stderr, stdout +from sys import argv, stderr, stdout from . import __version__, GNUHelpFormatter, NetLoc @@ -66,7 +66,7 @@ def check(netlocs, after, output, fake_ca=None): file=output) -def main(): +def main(arguments=argv[1:]): """Run TLS checker.""" description = ('Check TLS certificate expiration of HOST,' ' where PORT defaults to 443.') @@ -82,11 +82,11 @@ def main(): parser.add_argument('-o', '--output', metavar='PATH', type=FileType('w'), default=stdout, help='output file (default to stdout)') - args = parser.parse_args() - with args.output: + args = parser.parse_args(arguments) + with args.output: # pragma: no cover after = datetime.now(tz=timezone.utc) + timedelta(days=args.days) check(args.netloc, after, args.output) -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover main() diff --git a/src/scadere/listen.py b/src/scadere/listen.py index 4870e2f..80929ea 100644 --- a/src/scadere/listen.py +++ b/src/scadere/listen.py @@ -26,6 +26,7 @@ from urllib.parse import parse_qs, urljoin, urlsplit from xml.etree.ElementTree import (Element as xml_element, SubElement as xml_subelement, indent, tostring as str_from_xml) +from sys import argv from . import __version__, GNUHelpFormatter, NetLoc @@ -168,7 +169,7 @@ async def listen(certs, base_url, host, port): # pragma: no cover await server.serve_forever() -def main(): +def main(arguments=argv[1:]): """Launch server.""" description = ('Serve the TLS certificate expiration feed' ' from INPUT file for base URL at HOST:PORT,' @@ -183,9 +184,9 @@ def main(): parser.add_argument('base_url', metavar='URL') parser.add_argument('netloc', metavar='[HOST][:PORT]', nargs='?', type=NetLoc(None), default=('localhost', None)) - args = parser.parse_args() - run(listen(args.certs, args.base_url, *args.netloc)) + args = parser.parse_args(arguments) + run(listen(args.certs, args.base_url, *args.netloc)) # pragma: no cover -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover main() diff --git a/tst/test_help.py b/tst/test_help.py new file mode 100644 index 0000000..429d7c4 --- /dev/null +++ b/tst/test_help.py @@ -0,0 +1,69 @@ +# Tests for CLI help formatting +# Copyright (C) 2025 Nguyễn Gia Phong +# +# This file is part of scadere. +# +# Scadere is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Scadere 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with scadere. If not, see <https://www.gnu.org/licenses/>. + +from contextlib import redirect_stdout, suppress +from io import StringIO + +from hypothesis import given +from pytest import fixture, mark, raises + +from scadere import NetLoc +from scadere.check import main as check +from scadere.listen import main as listen + + +@fixture(scope='session') +def help_string(request): + string = StringIO() + with suppress(SystemExit), redirect_stdout(string): + request.param(['--help']) + return string.getvalue() + + +@mark.parametrize('help_string', [check, listen], indirect=True) +def test_usage_prefix(help_string): + assert help_string.startswith('Usage: ') + + +@mark.parametrize('help_string', [check, listen], indirect=True) +def test_options_heading(help_string): + assert '\n\nOptions:\n' in help_string + + +@mark.parametrize('help_string', [check], indirect=True) +def test_one_or_more(help_string): + assert ' HOST[:PORT]...\n' in help_string + + +@mark.parametrize('help_string', [check], indirect=True) +@mark.parametrize('short,long,metavar', [('-d', '--days', 'DAYS'), + ('-o', '--output', 'PATH')]) +def test_long_option(help_string, short, long, metavar): + assert f'{short} {metavar}, {long}={metavar}' in help_string + + +@given(...) +def test_netloc(hostname: str, port: int | None, default_port: int | None): + netloc = NetLoc(default_port) + if port is not None: + assert netloc(f'{hostname}:{port}') == (hostname, port) + elif default_port is None: + with raises(ValueError): + netloc(f'{hostname}:{port}') + else: + assert netloc(hostname) == (hostname, default_port) |