"""CLI 入口 main.py 单元测试""" import os import pytest from unittest.mock import patch, MagicMock from main import derive_output_path, build_parser, main from exceptions import ParseError, UnsupportedFormatError, ApiError class TestDeriveOutputPath: """默认输出路径推导测试""" def test_pdf_to_md(self): assert derive_output_path("report.pdf") == "report.md" def test_xlsx_to_md(self): assert derive_output_path("data.xlsx") == "data.md" def test_with_directory(self): assert derive_output_path("/home/user/docs/file.docx") == "/home/user/docs/file.md" def test_txt_to_md(self): assert derive_output_path("notes.txt") == "notes.md" def test_no_extension(self): assert derive_output_path("README") == "README.md" def test_multiple_dots(self): assert derive_output_path("my.report.v2.pdf") == "my.report.v2.md" class TestBuildParser: """argparse 参数解析测试""" def test_all_args(self): parser = build_parser() args = parser.parse_args(["input.pdf", "-k", "sk-abc", "-o", "out.md", "-d", "==="]) assert args.input_file == "input.pdf" assert args.api_key == "sk-abc" assert args.output == "out.md" assert args.delimiter == "===" def test_required_args_only(self): parser = build_parser() args = parser.parse_args(["input.pdf", "-k", "sk-abc"]) assert args.input_file == "input.pdf" assert args.api_key == "sk-abc" assert args.output is None assert args.delimiter == "---" def test_long_option_names(self): parser = build_parser() args = parser.parse_args(["input.pdf", "--api-key", "sk-abc", "--output", "out.md", "--delimiter", "***"]) assert args.api_key == "sk-abc" assert args.output == "out.md" assert args.delimiter == "***" def test_missing_input_file(self): parser = build_parser() with pytest.raises(SystemExit) as exc_info: parser.parse_args(["-k", "sk-abc"]) assert exc_info.value.code != 0 @patch.dict(os.environ, {}, clear=True) def test_missing_api_key(self): """无 -k 且无环境变量时,api_key 应为 None""" parser = build_parser() args = parser.parse_args(["input.pdf"]) assert args.api_key is None class TestMainFunction: """main() 函数集成测试""" @patch("main.Splitter") def test_success(self, mock_splitter_cls): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter with patch("sys.argv", ["main.py", "input.pdf", "-k", "sk-abc"]): main() mock_splitter_cls.assert_called_once_with( api_key="sk-abc", delimiter="---", pre_split_size=None, vision_prompt=None, output_format="markdown", ) mock_splitter.process.assert_called_once_with("input.pdf", "input.md") @patch("main.Splitter") def test_custom_output(self, mock_splitter_cls): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter with patch("sys.argv", ["main.py", "input.pdf", "-k", "sk-abc", "-o", "custom.md"]): main() mock_splitter.process.assert_called_once_with("input.pdf", "custom.md") @patch("main.Splitter") def test_custom_delimiter(self, mock_splitter_cls): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter with patch("sys.argv", ["main.py", "input.pdf", "-k", "sk-abc", "-d", "==="]): main() mock_splitter_cls.assert_called_once_with( api_key="sk-abc", delimiter="===", pre_split_size=None, vision_prompt=None, output_format="markdown", ) @patch("main.Splitter") def test_file_not_found_error(self, mock_splitter_cls, capsys): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter mock_splitter.process.side_effect = FileNotFoundError("输入文件不存在: missing.pdf") with patch("sys.argv", ["main.py", "missing.pdf", "-k", "sk-abc"]): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 captured = capsys.readouterr() assert "missing.pdf" in captured.err @patch("main.Splitter") def test_unsupported_format_error(self, mock_splitter_cls, capsys): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter mock_splitter.process.side_effect = UnsupportedFormatError("file.xyz", ".xyz") with patch("sys.argv", ["main.py", "file.xyz", "-k", "sk-abc"]): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 captured = capsys.readouterr() assert ".xyz" in captured.err @patch("main.Splitter") def test_parse_error(self, mock_splitter_cls, capsys): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter mock_splitter.process.side_effect = ParseError("bad.pdf", "文件损坏") with patch("sys.argv", ["main.py", "bad.pdf", "-k", "sk-abc"]): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 captured = capsys.readouterr() assert "bad.pdf" in captured.err @patch("main.Splitter") def test_api_error(self, mock_splitter_cls, capsys): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter mock_splitter.process.side_effect = ApiError("认证失败", status_code=401) with patch("sys.argv", ["main.py", "input.pdf", "-k", "bad-key"]): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 captured = capsys.readouterr() assert "API" in captured.err @patch("main.Splitter") def test_generic_exception(self, mock_splitter_cls, capsys): mock_splitter = MagicMock() mock_splitter_cls.return_value = mock_splitter mock_splitter.process.side_effect = RuntimeError("意外错误") with patch("sys.argv", ["main.py", "input.pdf", "-k", "sk-abc"]): with pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 captured = capsys.readouterr() assert "意外错误" in captured.err