from __future__ import annotations from datetime import datetime, timedelta import logging from pathlib import Path from nexus_claude_api.logging_config import configure_logging, resolve_log_file FIXED_NOW = datetime(2026, 6, 27, 10, 30, 0) def fixed_date_provider(timestamp: float | None = None) -> datetime: if timestamp is None: return FIXED_NOW return datetime.fromtimestamp(timestamp) def test_configure_logging_writes_app_logs_to_file_without_console( tmp_path, capsys, ) -> None: log_file = tmp_path / "nexus-claude-api.log" dated_log_file = tmp_path / "nexus-claude-api-2026-06-27.log" logger = logging.getLogger("nexus_claude_api.routes.messages") app_logger = logging.getLogger("nexus_claude_api") original_handlers = app_logger.handlers[:] original_level = app_logger.level original_propagate = app_logger.propagate try: returned_log_file = configure_logging( verbose=False, log_file=log_file, date_provider=fixed_date_provider, ) logger.info("detailed app request") logger.debug("debug details hidden without verbose") output = capsys.readouterr() log_text = dated_log_file.read_text(encoding="utf-8") assert returned_log_file == dated_log_file assert output.err == "" assert "detailed app request" in log_text assert "debug details hidden without verbose" not in log_text finally: app_logger.handlers.clear() app_logger.handlers.extend(original_handlers) app_logger.setLevel(original_level) app_logger.propagate = original_propagate def test_configure_logging_verbose_writes_debug_app_logs_to_file( tmp_path, ) -> None: log_file = tmp_path / "nexus-claude-api.log" dated_log_file = tmp_path / "nexus-claude-api-2026-06-27.log" logger = logging.getLogger("nexus_claude_api.routes.messages") app_logger = logging.getLogger("nexus_claude_api") original_handlers = app_logger.handlers[:] original_level = app_logger.level original_propagate = app_logger.propagate try: configure_logging( verbose=True, log_file=log_file, date_provider=fixed_date_provider, ) logger.debug("debug details stay in file") assert ( "debug details stay in file" in dated_log_file.read_text(encoding="utf-8") ) finally: app_logger.handlers.clear() app_logger.handlers.extend(original_handlers) app_logger.setLevel(original_level) app_logger.propagate = original_propagate def test_resolve_log_file_uses_user_config_dir_by_default( tmp_path, monkeypatch, ) -> None: user_config_dir = tmp_path / ".config" / "nexus-claude-api" monkeypatch.setattr("nexus_claude_api.config.USER_CONFIG_DIR", user_config_dir) monkeypatch.setattr( "nexus_claude_api.logging_config._local_datetime", lambda timestamp=None: FIXED_NOW, ) assert ( resolve_log_file() == user_config_dir / "logs" / "nexus-claude-api-2026-06-27.log" ) def test_resolve_log_file_uses_local_logs_in_dev_mode(monkeypatch) -> None: monkeypatch.setattr( "nexus_claude_api.logging_config._local_datetime", lambda timestamp=None: FIXED_NOW, ) assert resolve_log_file(dev=True) == Path("logs") / "nexus-claude-api-2026-06-27.log" def test_configure_logging_prunes_logs_older_than_recent_seven_days(tmp_path) -> None: for days_ago in range(10): log_date = FIXED_NOW - timedelta(days=days_ago) (tmp_path / f"nexus-claude-api-{log_date:%Y-%m-%d}.log").write_text( f"log {days_ago}", encoding="utf-8", ) ignored_log = tmp_path / "nexus-claude-api-debug.log" ignored_log.write_text("keep me", encoding="utf-8") app_logger = logging.getLogger("nexus_claude_api") original_handlers = app_logger.handlers[:] original_level = app_logger.level original_propagate = app_logger.propagate try: configure_logging( verbose=False, log_file=tmp_path / "nexus-claude-api.log", date_provider=fixed_date_provider, ) remaining_logs = sorted( path.name for path in tmp_path.glob("nexus-claude-api-*.log") ) assert remaining_logs == [ "nexus-claude-api-2026-06-21.log", "nexus-claude-api-2026-06-22.log", "nexus-claude-api-2026-06-23.log", "nexus-claude-api-2026-06-24.log", "nexus-claude-api-2026-06-25.log", "nexus-claude-api-2026-06-26.log", "nexus-claude-api-2026-06-27.log", "nexus-claude-api-debug.log", ] finally: app_logger.handlers.clear() app_logger.handlers.extend(original_handlers) app_logger.setLevel(original_level) app_logger.propagate = original_propagate def test_configure_logging_switches_files_when_record_date_changes(tmp_path) -> None: log_file = tmp_path / "nexus-claude-api.log" logger = logging.getLogger("nexus_claude_api.routes.messages") app_logger = logging.getLogger("nexus_claude_api") original_handlers = app_logger.handlers[:] original_level = app_logger.level original_propagate = app_logger.propagate try: configure_logging( verbose=False, log_file=log_file, date_provider=lambda timestamp=None: datetime.fromtimestamp(timestamp) if timestamp is not None else datetime(2026, 6, 27, 10, 30, 0), ) before_record = logger.makeRecord( logger.name, logging.INFO, __file__, 1, "before midnight", (), None, ) before_record.created = datetime(2026, 6, 27, 23, 59, 0).timestamp() after_record = logger.makeRecord( logger.name, logging.INFO, __file__, 1, "after midnight", (), None, ) after_record.created = datetime(2026, 6, 28, 0, 1, 0).timestamp() for handler in app_logger.handlers: handler.handle(before_record) handler.handle(after_record) assert "before midnight" in ( tmp_path / "nexus-claude-api-2026-06-27.log" ).read_text(encoding="utf-8") assert "after midnight" in ( tmp_path / "nexus-claude-api-2026-06-28.log" ).read_text(encoding="utf-8") finally: app_logger.handlers.clear() app_logger.handlers.extend(original_handlers) app_logger.setLevel(original_level) app_logger.propagate = original_propagate