Coverage for /home/runner/work/bijux-cli/bijux-cli/src/bijux_cli/commands/doctor.py: 100%
43 statements
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-19 23:36 +0000
« prev ^ index » next coverage.py v7.10.4, created at 2025-08-19 23:36 +0000
1# SPDX-License-Identifier: MIT
2# Copyright © 2025 Bijan Mousavi
4"""Implements the `doctor` command for the Bijux CLI.
6This module provides the functionality for the `bijux doctor` command, which runs
7a series of health diagnostics on the CLI's operating environment. It checks for
8common configuration issues and reports a summary of its findings in a
9structured, machine-readable format suitable for automation.
11Output Contract:
12 * Success: `{"status": str, "summary": list[str]}`
13 * Verbose: Adds `{"python": str, "platform": str}` to the payload.
14 * Error: `{"error": str, "code": int}`
16Exit Codes:
17 * `0`: Success (command ran without errors, regardless of health status).
18 * `1`: Internal or fatal error (e.g., dependency injection failure).
19 * `2`: CLI argument or flag error.
20"""
22from __future__ import annotations
24from collections.abc import Mapping
25import os
26import platform
28import typer
30from bijux_cli.commands.utilities import (
31 ascii_safe,
32 emit_error_and_exit,
33 new_run_command,
34 validate_common_flags,
35)
36from bijux_cli.contracts import EmitterProtocol, TelemetryProtocol
37from bijux_cli.core.constants import (
38 HELP_DEBUG,
39 HELP_FORMAT,
40 HELP_NO_PRETTY,
41 HELP_QUIET,
42 HELP_VERBOSE,
43)
44from bijux_cli.core.di import DIContainer
46typer.core.rich = None # type: ignore[attr-defined,assignment]
48doctor_app = typer.Typer( # pytype: skip-file
49 name="doctor",
50 help="Run CLI health diagnostics and environment checks.",
51 rich_markup_mode=None,
52 context_settings={"help_option_names": ["-h", "--help"]},
53 no_args_is_help=False,
54)
57def _build_payload(include_runtime: bool) -> Mapping[str, object]:
58 """Builds the payload summarizing CLI environment health.
60 This function performs a series of checks on the environment and aggregates
61 the findings into a structured payload.
63 Args:
64 include_runtime (bool): If True, appends Python and platform version
65 information to the payload.
67 Returns:
68 Mapping[str, object]: A dictionary containing the health status, a
69 summary of findings, and optional runtime details.
70 """
71 healthy = True
72 summary: list[str] = []
74 if not os.environ.get("PATH", ""):
75 healthy = False
76 summary.append("Environment PATH is empty")
78 if os.environ.get("BIJUXCLI_TEST_FORCE_UNHEALTHY") == "1":
79 healthy = False
80 summary.append("Forced unhealthy by test environment")
82 if not summary:
83 summary.append(
84 "All core checks passed" if healthy else "Unknown issue detected"
85 )
87 payload: dict[str, object] = {
88 "status": "healthy" if healthy else "unhealthy",
89 "summary": summary,
90 }
92 if include_runtime:
93 payload["python"] = ascii_safe(platform.python_version(), "python_version")
94 payload["platform"] = ascii_safe(platform.platform(), "platform")
96 return payload
99@doctor_app.callback(invoke_without_command=True)
100def doctor(
101 ctx: typer.Context,
102 quiet: bool = typer.Option(False, "-q", "--quiet", help=HELP_QUIET),
103 verbose: bool = typer.Option(False, "-v", "--verbose", help=HELP_VERBOSE),
104 fmt: str = typer.Option("json", "-f", "--format", help=HELP_FORMAT),
105 pretty: bool = typer.Option(True, "--pretty/--no-pretty", help=HELP_NO_PRETTY),
106 debug: bool = typer.Option(False, "-d", "--debug", help=HELP_DEBUG),
107) -> None:
108 """Defines the entrypoint and logic for the `bijux doctor` command.
110 This function orchestrates the health check process. It validates all CLI
111 flags, performs critical pre-flight checks (like dependency availability),
112 and then invokes the main run utility to build and emit the health payload.
114 Args:
115 ctx (typer.Context): The Typer context for managing command state.
116 quiet (bool): If True, suppresses all output; the exit code is the
117 primary indicator of the outcome.
118 verbose (bool): If True, includes Python and platform details in the
119 output payload.
120 fmt (str): The output format, either "json" or "yaml". Defaults to "json".
121 pretty (bool): If True, pretty-prints the output for human readability.
122 debug (bool): If True, enables debug diagnostics, implying `verbose`
123 and `pretty`.
125 Returns:
126 None:
128 Raises:
129 SystemExit: Exits the application with a contract-compliant status code
130 and payload upon any error, such as invalid arguments or an
131 internal system failure.
132 """
133 if ctx.invoked_subcommand:
134 return
136 command = "doctor"
138 fmt_lower = validate_common_flags(fmt, command, quiet)
140 if ctx.args:
141 stray = ctx.args[0]
142 msg = (
143 f"No such option: {stray}"
144 if stray.startswith("-")
145 else f"Too many arguments: {' '.join(ctx.args)}"
146 )
147 emit_error_and_exit(
148 msg,
149 code=2,
150 failure="args",
151 command=command,
152 fmt=fmt_lower,
153 quiet=quiet,
154 include_runtime=verbose,
155 debug=debug,
156 )
158 try:
159 DIContainer.current().resolve(EmitterProtocol)
160 DIContainer.current().resolve(TelemetryProtocol)
161 except Exception as exc:
162 emit_error_and_exit(
163 str(exc),
164 code=1,
165 failure="internal",
166 command=command,
167 fmt=fmt_lower,
168 quiet=quiet,
169 include_runtime=verbose,
170 debug=debug,
171 )
173 new_run_command(
174 command_name=command,
175 payload_builder=_build_payload,
176 quiet=quiet,
177 verbose=verbose,
178 fmt=fmt_lower,
179 pretty=pretty,
180 debug=debug,
181 )