wrolpi/modules/zim/test/test_read_config_value.py

85 lines
2.9 KiB
Python
Raw Permalink Normal View History

zim: ship a default fallback Zim and honor the configured Zim directory Two related gaps in how WROLPi serves Kiwix content: 1. A fresh WROLPi (or one whose zims are all corrupt/absent) showed a blank Kiwix. Every image now ships a small built-in default Zim that Kiwix falls back to, telling the user to download real Zim files. 2. The startup scripts hardcoded the `/zims` directory, ignoring wrolpi.yaml's `zims_destination` (which the Python side already honors). Changes: - New wrolpi/scripts/read_config_value.sh: a zero-dependency (awk) reader so non-Python scripts can reliably read top-level wrolpi.yaml scalars. Also a config_value() wrapper in wrolpi/scripts/lib.sh. - New wrolpi/scripts/default_zim/: HTML source + a reproducible builder (ghcr.io/openzim/zim-tools) that outputs the committed wrolpi/blobs/default.zim. - New wrolpi/blobs/: any committed blob here is copied to /opt/wrolpi-blobs by the pi-gen and debian-live builds; the Docker zim image bakes it in. - start_kiwix_serve.sh honors zims_destination and falls back to the default Zim when no functioning zim is found. - docker/zim/entrypoint.sh gains the same default-Zim fallback. It needs no config read: it already scans the whole mounted media dir, and zims_destination is always a subdirectory of that mount. - Changing zims_destination via the settings API now restarts Kiwix so it serves the new directory without a reboot. Tests: new test_read_config_value.py, extended test_start_kiwix_serve.py (configured dir + default-Zim fallback), and a root_api test for the restart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:24:03 -06:00
"""Tests for wrolpi/scripts/read_config_value.sh.
The reader extracts a top-level scalar from wrolpi.yaml with zero dependencies
so non-Python scripts (e.g. start_kiwix_serve.sh, the Docker Zim entrypoint) can
read config values. Exercised directly via bash.
"""
import os
import pathlib
import subprocess
import pytest
REPO_ROOT = pathlib.Path(__file__).parents[3]
READER = REPO_ROOT / 'wrolpi' / 'scripts' / 'read_config_value.sh'
def _read(media: pathlib.Path, key: str, default: str = '') -> str:
env = {**os.environ, 'MEDIA_DIRECTORY': str(media)}
result = subprocess.run(['bash', str(READER), key, default], env=env,
capture_output=True, text=True, timeout=30)
assert result.returncode == 0, result.stderr
return result.stdout.strip('\n')
@pytest.fixture
def media_with_config(tmp_path):
media = tmp_path / 'media'
(media / 'config').mkdir(parents=True)
def write(text: str):
(media / 'config' / 'wrolpi.yaml').write_text(text)
return media, write
def test_reads_scalar_value(media_with_config):
media, write = media_with_config
write('zims_destination: mycustomzims\nvideos_destination: videos\n')
assert _read(media, 'zims_destination', 'zims') == 'mycustomzims'
def test_strips_surrounding_quotes(media_with_config):
media, write = media_with_config
write('videos_destination: "quoted videos"\n')
assert _read(media, 'videos_destination', 'x') == 'quoted videos'
def test_missing_key_returns_default(media_with_config):
media, write = media_with_config
write('videos_destination: videos\n')
assert _read(media, 'zims_destination', 'zims') == 'zims'
def test_empty_value_returns_default(media_with_config):
media, write = media_with_config
write('zims_destination:\n')
assert _read(media, 'zims_destination', 'zims') == 'zims'
@pytest.mark.parametrize('null_repr', ['null', 'Null', 'NULL', '~'])
def test_yaml_null_returns_default(media_with_config, null_repr):
"""An unquoted YAML null means "unset" -> default, matching the Python config layer."""
media, write = media_with_config
write(f'zims_destination: {null_repr}\n')
assert _read(media, 'zims_destination', 'zims') == 'zims'
def test_quoted_null_is_literal_string(media_with_config):
"""A quoted "null" is the string null, not YAML null."""
media, write = media_with_config
write('zims_destination: "null"\n')
assert _read(media, 'zims_destination', 'zims') == 'null'
zim: ship a default fallback Zim and honor the configured Zim directory Two related gaps in how WROLPi serves Kiwix content: 1. A fresh WROLPi (or one whose zims are all corrupt/absent) showed a blank Kiwix. Every image now ships a small built-in default Zim that Kiwix falls back to, telling the user to download real Zim files. 2. The startup scripts hardcoded the `/zims` directory, ignoring wrolpi.yaml's `zims_destination` (which the Python side already honors). Changes: - New wrolpi/scripts/read_config_value.sh: a zero-dependency (awk) reader so non-Python scripts can reliably read top-level wrolpi.yaml scalars. Also a config_value() wrapper in wrolpi/scripts/lib.sh. - New wrolpi/scripts/default_zim/: HTML source + a reproducible builder (ghcr.io/openzim/zim-tools) that outputs the committed wrolpi/blobs/default.zim. - New wrolpi/blobs/: any committed blob here is copied to /opt/wrolpi-blobs by the pi-gen and debian-live builds; the Docker zim image bakes it in. - start_kiwix_serve.sh honors zims_destination and falls back to the default Zim when no functioning zim is found. - docker/zim/entrypoint.sh gains the same default-Zim fallback. It needs no config read: it already scans the whole mounted media dir, and zims_destination is always a subdirectory of that mount. - Changing zims_destination via the settings API now restarts Kiwix so it serves the new directory without a reboot. Tests: new test_read_config_value.py, extended test_start_kiwix_serve.py (configured dir + default-Zim fallback), and a root_api test for the restart. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:24:03 -06:00
def test_missing_config_file_returns_default(tmp_path):
# No config directory/file at all (fresh install) -> default, no error.
assert _read(tmp_path / 'media', 'zims_destination', 'zims') == 'zims'
def test_ignores_indented_nested_keys(media_with_config):
media, write = media_with_config
# A nested key with the same name must not be matched at the top level.
write('some_section:\n zims_destination: nested\ntop: value\n')
assert _read(media, 'zims_destination', 'zims') == 'zims'