Skip to content

feat(test): add unit tests for config, output, and dubbing modules#24

Merged
KYBvWHxW merged 1 commit into
mainfrom
feat/add-unit-tests
Apr 29, 2026
Merged

feat(test): add unit tests for config, output, and dubbing modules#24
KYBvWHxW merged 1 commit into
mainfrom
feat/add-unit-tests

Conversation

@KYBvWHxW
Copy link
Copy Markdown
Contributor

Related Issue

Closes #23

Summary

  • test_config.py: 10 tests — config load/save, env vars, defaults, error paths
  • test_output.py: 7 tests — JSON output, error/success/info formatting
  • test_dubbing.py: 7 tests — voice list, filtering by lang/tag, JSON output
  • Coverage: 30% → 37% (28 tests total, all pass)

Test Plan

  • All 28 tests pass locally
  • Coverage above 20% threshold (37%)

🤖 Generated with Claude Code

- test_config.py: 10 tests — load/save, env vars, defaults, error cases
- test_output.py: 7 tests — JSON output, error/success/info formatting
- test_dubbing.py: 7 tests — voice list filtering, JSON output, fields

Coverage: 30% → 37% (28 tests, all pass)

Closes #22

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 29, 2026 16:51
@KYBvWHxW KYBvWHxW merged commit 130bb92 into main Apr 29, 2026
8 of 9 checks passed
@KYBvWHxW KYBvWHxW deleted the feat/add-unit-tests branch April 29, 2026 16:51
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new unit-test suite to improve coverage for core CLI modules (config management, output formatting, and dubbing voice listing/filtering), addressing Issue #23’s request to expand beyond smoke tests.

Changes:

  • Adds tests for narrator_ai.output JSON and message output helpers.
  • Adds tests for narrator_ai.commands.dubbing CLI JSON output and basic filtering behavior.
  • Adds tests for narrator_ai.config load/save behavior and env var overrides.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
tests/test_output.py New tests covering JSON stdout output and error/success/info stderr output.
tests/test_dubbing.py New CLI tests for dubbing list/languages/tags JSON output and filter options.
tests/test_config.py New tests for default config, load/save roundtrip, and env-var overrides.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_dubbing.py
assert result.exit_code == 0
import json
data = json.loads(result.output)
assert all(v["type"] == "英语" for v in data)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CLI filter logic uses substring matching (lang in d["type"]), but this test asserts strict equality. To match the implemented behavior and avoid brittle failures if type ever contains additional descriptors, assert lang in v["type"] (or otherwise align the assertion with the command’s semantics).

Suggested change
assert all(v["type"] == "英语" for v in data)
assert all("英语" in v["type"] for v in data)

Copilot uses AI. Check for mistakes.
Comment thread tests/test_dubbing.py
assert result.exit_code == 0
import json
data = json.loads(result.output)
assert all(v["tag"] == "通用男声" for v in data)
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command filters tags with substring matching (tag in d["tag"]), but this test asserts strict equality. Align the assertion with the actual filter semantics (e.g., tag in v["tag"]) to reduce brittleness if tag strings expand in the future.

Suggested change
assert all(v["tag"] == "通用男声" for v in data)
assert all("通用男声" in v["tag"] for v in data)

Copilot uses AI. Check for mistakes.
Comment thread tests/test_output.py
Comment on lines +4 to +5
from io import StringIO
from unittest.mock import patch
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringIO and patch are imported but never used. With Ruff linting enabled (F401), this will fail CI; remove the unused imports or use them in the tests.

Suggested change
from io import StringIO
from unittest.mock import patch

Copilot uses AI. Check for mistakes.
Comment thread tests/test_config.py
Comment on lines +44 to +50
def test_get_server_from_env():
with patch.dict(os.environ, {"NARRATOR_SERVER": "https://env.example.com"}):
assert get_server() == "https://env.example.com"


def test_get_server_strips_trailing_slash():
with patch.dict(os.environ, {"NARRATOR_SERVER": "https://example.com/"}):
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests call get_server() without isolating the on-disk config. If a developer/CI machine has an existing ~/.narrator-ai/config.yaml (or invalid YAML), load_config() can affect or break this test even though the env var is set. Patch narrator_ai.config.CONFIG_FILE to a nonexistent path (or tmp_path) within this test to make it hermetic.

Suggested change
def test_get_server_from_env():
with patch.dict(os.environ, {"NARRATOR_SERVER": "https://env.example.com"}):
assert get_server() == "https://env.example.com"
def test_get_server_strips_trailing_slash():
with patch.dict(os.environ, {"NARRATOR_SERVER": "https://example.com/"}):
def test_get_server_from_env(tmp_path):
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {"NARRATOR_SERVER": "https://env.example.com"}):
assert get_server() == "https://env.example.com"
def test_get_server_strips_trailing_slash(tmp_path):
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {"NARRATOR_SERVER": "https://example.com/"}):

Copilot uses AI. Check for mistakes.
Comment thread tests/test_config.py
Comment on lines +49 to +50
def test_get_server_strips_trailing_slash():
with patch.dict(os.environ, {"NARRATOR_SERVER": "https://example.com/"}):
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same isolation issue as above: get_server() loads from CONFIG_FILE before reading env vars. Patch narrator_ai.config.CONFIG_FILE to a temp/nonexistent file in this test so it doesn't depend on the user's real config.

Suggested change
def test_get_server_strips_trailing_slash():
with patch.dict(os.environ, {"NARRATOR_SERVER": "https://example.com/"}):
def test_get_server_strips_trailing_slash(tmp_path):
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {"NARRATOR_SERVER": "https://example.com/"}):

Copilot uses AI. Check for mistakes.
Comment thread tests/test_config.py
Comment on lines +55 to +59
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {}, clear=True):
# DEFAULT_CONFIG has server set, so this won't raise
server = get_server()
assert server == DEFAULT_CONFIG["server"].rstrip("/")
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test name says it "raises", but it currently asserts the default server is returned and explicitly notes it won't raise. Either rename it to reflect the behavior being tested, or adjust the setup to exercise the actual error path (e.g., create a config file with server: "" and clear env so get_server() raises SystemExit).

Suggested change
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {}, clear=True):
# DEFAULT_CONFIG has server set, so this won't raise
server = get_server()
assert server == DEFAULT_CONFIG["server"].rstrip("/")
config_file = tmp_path / "config.yaml"
config_dir = tmp_path
with patch("narrator_ai.config.CONFIG_FILE", config_file), \
patch("narrator_ai.config.CONFIG_DIR", config_dir):
save_config({"server": ""})
with patch.dict(os.environ, {}, clear=True):
with pytest.raises(SystemExit):
get_server()

Copilot uses AI. Check for mistakes.
Comment thread tests/test_config.py
Comment on lines +62 to +63
def test_get_app_key_from_env():
with patch.dict(os.environ, {"NARRATOR_APP_KEY": "env-key-123"}):
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test calls get_app_key() without isolating CONFIG_FILE. If a local config file exists, it can change behavior (or fail parsing) even though the env var is set. Patch narrator_ai.config.CONFIG_FILE to a tmp/nonexistent path here for hermetic tests.

Suggested change
def test_get_app_key_from_env():
with patch.dict(os.environ, {"NARRATOR_APP_KEY": "env-key-123"}):
def test_get_app_key_from_env(tmp_path):
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {"NARRATOR_APP_KEY": "env-key-123"}):

Copilot uses AI. Check for mistakes.
Comment thread tests/test_config.py
Comment on lines +74 to +75
def test_get_timeout_from_env():
with patch.dict(os.environ, {"NARRATOR_TIMEOUT": "60"}):
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test calls get_timeout() without isolating CONFIG_FILE. load_config() may read a real user config (or invalid YAML) before applying the env override, which makes the test non-hermetic. Patch narrator_ai.config.CONFIG_FILE to a tmp/nonexistent path in this test.

Suggested change
def test_get_timeout_from_env():
with patch.dict(os.environ, {"NARRATOR_TIMEOUT": "60"}):
def test_get_timeout_from_env(tmp_path):
with patch("narrator_ai.config.CONFIG_FILE", tmp_path / "nonexistent.yaml"), \
patch.dict(os.environ, {"NARRATOR_TIMEOUT": "60"}):

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(test): add unit tests for config, output, and dubbing modules

3 participants