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

1# SPDX-License-Identifier: MIT 

2# Copyright © 2025 Bijan Mousavi 

3 

4"""Implements the `doctor` command for the Bijux CLI. 

5 

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. 

10 

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}` 

15 

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""" 

21 

22from __future__ import annotations 

23 

24from collections.abc import Mapping 

25import os 

26import platform 

27 

28import typer 

29 

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 

45 

46typer.core.rich = None # type: ignore[attr-defined,assignment] 

47 

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) 

55 

56 

57def _build_payload(include_runtime: bool) -> Mapping[str, object]: 

58 """Builds the payload summarizing CLI environment health. 

59 

60 This function performs a series of checks on the environment and aggregates 

61 the findings into a structured payload. 

62 

63 Args: 

64 include_runtime (bool): If True, appends Python and platform version 

65 information to the payload. 

66 

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] = [] 

73 

74 if not os.environ.get("PATH", ""): 

75 healthy = False 

76 summary.append("Environment PATH is empty") 

77 

78 if os.environ.get("BIJUXCLI_TEST_FORCE_UNHEALTHY") == "1": 

79 healthy = False 

80 summary.append("Forced unhealthy by test environment") 

81 

82 if not summary: 

83 summary.append( 

84 "All core checks passed" if healthy else "Unknown issue detected" 

85 ) 

86 

87 payload: dict[str, object] = { 

88 "status": "healthy" if healthy else "unhealthy", 

89 "summary": summary, 

90 } 

91 

92 if include_runtime: 

93 payload["python"] = ascii_safe(platform.python_version(), "python_version") 

94 payload["platform"] = ascii_safe(platform.platform(), "platform") 

95 

96 return payload 

97 

98 

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. 

109 

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. 

113 

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`. 

124 

125 Returns: 

126 None: 

127 

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 

135 

136 command = "doctor" 

137 

138 fmt_lower = validate_common_flags(fmt, command, quiet) 

139 

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 ) 

157 

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 ) 

172 

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 )