1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-01-23 05:55:44 -05:00

Added --output-json for aprsd sample-config

This adds the ability to output the sample config
as json for non-human processing.
This commit is contained in:
Walter Boring 2026-01-21 15:29:24 -05:00
parent e3fda752f6
commit 24bc86424e
4 changed files with 182 additions and 7 deletions

View File

@ -1 +1 @@
waboring@hemna.com : 1
waboring@hemna.com : 1

View File

@ -34,6 +34,7 @@ from oslo_config import cfg, generator
import aprsd
from aprsd import cli_helper, packets, threads, utils
from aprsd.stats import collector
from aprsd.utils import config_converter
# setup the global logger
# log.basicConfig(level=log.DEBUG) # level=10
@ -108,8 +109,14 @@ def check_version(ctx):
@cli.command()
@click.option(
'--output-json',
default=False,
is_flag=True,
help='Output the sample config in JSON format instead of INI.',
)
@click.pass_context
def sample_config(ctx):
def sample_config(ctx, output_json):
"""Generate a sample Config file from aprsd and all installed plugins."""
def _get_selected_entry_points():
@ -151,8 +158,34 @@ def sample_config(ctx):
if not sys.argv[1:]:
raise SystemExit from ex
raise
generator.generate(conf)
return
if not output_json:
generator.generate(conf)
return
import io
import json
from contextlib import redirect_stdout
from rich.console import Console
f = io.StringIO()
with redirect_stdout(f):
conf.format_ = 'json'
generator.generate(conf)
s = f.getvalue()
c = Console()
c.print_json(data=json.loads(s))
@cli.command()
@cli_helper.add_options(cli_helper.common_options)
@click.pass_context
@cli_helper.process_standard_options
def json_config(ctx):
"""Output the current loaded configuration in JSON format."""
click.echo(config_converter.conf_to_json(CONF))
@cli.command()

View File

@ -8,7 +8,6 @@ click==8.3.1 # via pip-tools
colorama==0.4.6 # via tox
coverage==7.13.1 # via pytest-cov
distlib==0.4.0 # via virtualenv
exceptiongroup==1.3.1 # via pytest
filelock==3.20.0 # via tox, virtualenv
identify==2.6.15 # via pre-commit
iniconfig==2.3.0 # via pytest
@ -32,13 +31,12 @@ pytest-cov==7.0.0 # via -r requirements-dev.in
pyyaml==6.0.3 # via pre-commit
ruff==0.14.13 # via -r requirements-dev.in
setuptools==80.9.0 # via pip-tools
tomli==2.4.0 # via build, coverage, mypy, pip-tools, pyproject-api, pytest, tox, tox-uv
tox==4.32.0 # via tox-uv, -r requirements-dev.in
tox-uv==1.29.0 # via -r requirements-dev.in
types-pytz==2025.2.0.20251108 # via types-tzlocal, -r requirements-dev.in
types-requests==2.32.4.20260107 # via -r requirements-dev.in
types-tzlocal==5.1.0.1 # via -r requirements-dev.in
typing-extensions==4.15.0 # via exceptiongroup, mypy, tox, virtualenv
typing-extensions==4.15.0 # via mypy
urllib3==2.6.2 # via types-requests
uv==0.9.26 # via pre-commit-uv, tox-uv
virtualenv==20.35.4 # via pre-commit, tox

View File

@ -0,0 +1,144 @@
import sys
import unittest
from unittest import mock
from click.testing import CliRunner
from aprsd.main import cli
class TestSampleConfigCommand(unittest.TestCase):
"""Unit tests for the sample_config command."""
def _create_mock_entry_point(self, name):
"""Create a mock entry point object."""
mock_entry = mock.Mock()
mock_entry.name = name
mock_entry.group = 'oslo.config.opts'
return mock_entry
@mock.patch('aprsd.main.generator.generate')
@mock.patch('aprsd.main.imp.entry_points')
@mock.patch('aprsd.main.metadata_version')
def test_sample_config_default_ini_output(
self, mock_version, mock_entry_points, mock_generate
):
"""Test sample_config command outputs INI format by default."""
mock_version.return_value = '1.0.0'
# Mock entry_points to return at least one aprsd entry point
# so that get_namespaces() returns a non-empty list
if sys.version_info >= (3, 10):
mock_entry_points.return_value = [
self._create_mock_entry_point('aprsd.conf')
]
else:
# For Python < 3.10, entry_points() returns a dict-like object
mock_entry = self._create_mock_entry_point('aprsd.conf')
mock_dict = {'oslo.config.opts': [mock_entry]}
mock_entry_points.return_value = mock_dict
runner = CliRunner()
result = runner.invoke(
cli,
['sample-config'],
catch_exceptions=False,
)
assert result.exit_code == 0
# Verify generator.generate was called
mock_generate.assert_called_once()
# The conf object passed should not have format_ set to 'json'
call_args = mock_generate.call_args
conf_obj = call_args[0][0]
# When output_json is False, format_ should not be set to 'json'
assert not hasattr(conf_obj, 'format_') or conf_obj.format_ != 'json'
@mock.patch('rich.console.Console')
@mock.patch('aprsd.main.generator.generate')
@mock.patch('aprsd.main.imp.entry_points')
@mock.patch('aprsd.main.metadata_version')
def test_sample_config_json_output(
self, mock_version, mock_entry_points, mock_generate, mock_console
):
"""Test sample_config command with --output-json flag outputs JSON format."""
mock_version.return_value = '1.0.0'
# Mock entry_points to return at least one aprsd entry point
if sys.version_info >= (3, 10):
mock_entry_points.return_value = [
self._create_mock_entry_point('aprsd.conf')
]
else:
# For Python < 3.10, entry_points() returns a dict-like object
mock_entry = self._create_mock_entry_point('aprsd.conf')
mock_dict = {'oslo.config.opts': [mock_entry]}
mock_entry_points.return_value = mock_dict
# Mock generator.generate to write JSON to stdout
# This simulates what oslo.config generator does when format_='json'
def generate_side_effect(conf):
import sys
json_output = '{"test": "config", "version": "1.0"}'
sys.stdout.write(json_output)
mock_generate.side_effect = generate_side_effect
# Mock the Console
mock_console_instance = mock.Mock()
mock_console.return_value = mock_console_instance
runner = CliRunner()
result = runner.invoke(
cli,
['sample-config', '--output-json'],
catch_exceptions=False,
)
assert result.exit_code == 0
# Verify generator.generate was called
mock_generate.assert_called_once()
# Verify Console was instantiated
mock_console.assert_called_once()
# Verify print_json was called with parsed JSON
mock_console_instance.print_json.assert_called_once()
call_args = mock_console_instance.print_json.call_args
# The data argument should be a dict (parsed JSON)
assert isinstance(call_args[1]['data'], dict)
assert call_args[1]['data'] == {'test': 'config', 'version': '1.0'}
# Verify that conf.format_ was set to 'json' before generate was called
generate_call_conf = mock_generate.call_args[0][0]
assert generate_call_conf.format_ == 'json'
@mock.patch('aprsd.main.generator.generate')
@mock.patch('aprsd.main.imp.entry_points')
@mock.patch('aprsd.main.metadata_version')
def test_sample_config_without_flag(
self, mock_version, mock_entry_points, mock_generate
):
"""Test sample_config command without --output-json flag (explicit default)."""
mock_version.return_value = '1.0.0'
# Mock entry_points to return at least one aprsd entry point
if sys.version_info >= (3, 10):
mock_entry_points.return_value = [
self._create_mock_entry_point('aprsd.conf')
]
else:
# For Python < 3.10, entry_points() returns a dict-like object
mock_entry = self._create_mock_entry_point('aprsd.conf')
mock_dict = {'oslo.config.opts': [mock_entry]}
mock_entry_points.return_value = mock_dict
runner = CliRunner()
result = runner.invoke(
cli,
['sample-config'],
catch_exceptions=False,
)
assert result.exit_code == 0
# Verify generator.generate was called
mock_generate.assert_called_once()
# Verify format_ was not set to 'json'
call_args = mock_generate.call_args
conf_obj = call_args[0][0]
assert not hasattr(conf_obj, 'format_') or conf_obj.format_ != 'json'