Coverage for tests/test_cli_whoami.py: 100%

127 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-11-09 00:25 +0000

1import json 1abcdefg

2from pathlib import Path 1abcdefg

3 

4import pytest 1abcdefg

5import respx 1abcdefg

6from httpx import ReadTimeout, Response 1abcdefg

7from typer.testing import CliRunner 1abcdefg

8 

9from fastapi_cloud_cli.cli import app as root_app 1abcdefg

10from fastapi_cloud_cli.cli import cloud_app as app 1abcdefg

11from fastapi_cloud_cli.commands.whoami import WhoAmIOutput 1abcdefg

12from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit 1abcdefg

13 

14runner = CliRunner() 1abcdefg

15 

16assets_path = Path(__file__).parent / "assets" 1abcdefg

17 

18 

19@pytest.mark.respx 1abcdefg

20def test_shows_a_message_if_something_is_wrong( 1abcdefg

21 logged_in_cli: None, respx_mock: respx.MockRouter 

22) -> None: 

23 respx_mock.get("/users/me").mock(return_value=Response(500)) 1456789!

24 

25 result = runner.invoke(app, ["whoami"]) 1456789!

26 

27 assert ( 1456789!

28 "Something went wrong while contacting the FastAPI Cloud server." 

29 in result.output 

30 ) 

31 assert result.exit_code == 1 1456789!

32 

33 

34@pytest.mark.respx 1abcdefg

35def test_shows_a_message_when_token_is_invalid( 1abcdefg

36 logged_in_cli: None, respx_mock: respx.MockRouter 

37) -> None: 

38 respx_mock.get("/users/me").mock(return_value=Response(401)) 1#$%'()*

39 

40 result = runner.invoke(app, ["whoami"]) 1#$%'()*

41 

42 assert result.exit_code == 1 1#$%'()*

43 assert "The specified token is not valid" in result.output 1#$%'()*

44 

45 

46@pytest.mark.respx 1abcdefg

47def test_shows_a_message_when_user_has_no_permissions( 1abcdefg

48 logged_in_cli: None, respx_mock: respx.MockRouter 

49) -> None: 

50 respx_mock.get("/users/me").mock(return_value=Response(403)) 1+,-./:;

51 

52 result = runner.invoke(app, ["whoami"]) 1+,-./:;

53 

54 assert result.exit_code == 1 1+,-./:;

55 assert "You don't have permissions for this resource" in result.output 1+,-./:;

56 

57 

58@pytest.mark.respx 1abcdefg

59def test_shows_email(logged_in_cli: None, respx_mock: respx.MockRouter) -> None: 1abcdefg

60 respx_mock.get("/users/me").mock( 1=?@[]^_

61 return_value=Response(200, json={"email": "email@fastapi.com"}) 

62 ) 

63 

64 result = runner.invoke(app, ["whoami"]) 1=?@[]^_

65 

66 assert result.exit_code == 0 1=?@[]^_

67 assert "email@fastapi.com" in result.output 1=?@[]^_

68 

69 

70@pytest.mark.respx 1abcdefg

71def test_uses_toolkit_output_renderer_for_logged_in_user( 1abcdefg

72 logged_in_cli: None, 

73 monkeypatch: pytest.MonkeyPatch, 

74 respx_mock: respx.MockRouter, 

75) -> None: 

76 output_calls: list[tuple[object, object]] = [] 1hijklmn

77 

78 def record_output( 1hijklmn

79 self: FastAPIRichToolkit, 

80 data: object, 

81 render_output: object = None, 

82 ) -> None: 

83 output_calls.append((data, render_output)) 1hijklmn

84 

85 monkeypatch.setattr(FastAPIRichToolkit, "output", record_output) 1hijklmn

86 respx_mock.get("/users/me").mock( 1hijklmn

87 return_value=Response(200, json={"email": "email@fastapi.com"}) 

88 ) 

89 

90 result = runner.invoke(app, ["whoami"]) 1hijklmn

91 

92 assert result.exit_code == 0 1hijklmn

93 assert len(output_calls) == 1 1hijklmn

94 output_data, render_output = output_calls[0] 1hijklmn

95 assert isinstance(output_data, WhoAmIOutput) 1hijklmn

96 assert output_data.model_dump(mode="json") == { 1hijklmn

97 "email": "email@fastapi.com", 

98 "has_deploy_token": False, 

99 } 

100 assert callable(render_output) 1hijklmn

101 

102 

103@pytest.mark.respx 1abcdefg

104def test_prints_json_when_json_env_is_enabled( 1abcdefg

105 logged_in_cli: None, respx_mock: respx.MockRouter 

106) -> None: 

107 respx_mock.get("/users/me").mock( 1vwxyzAB

108 return_value=Response(200, json={"email": "email@fastapi.com"}) 

109 ) 

110 

111 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1vwxyzAB

112 

113 assert result.exit_code == 0 1vwxyzAB

114 assert json.loads(result.stdout) == { 1vwxyzAB

115 "data": { 

116 "email": "email@fastapi.com", 

117 "has_deploy_token": False, 

118 } 

119 } 

120 assert "[bold]" not in result.stdout 1vwxyzAB

121 assert result.stderr == "" 1vwxyzAB

122 

123 

124def test_json_option_is_not_enabled_before_command( 1abcdefg

125 logged_in_cli: None, 

126) -> None: 

127 result = runner.invoke(root_app, ["cloud", "--json", "whoami"]) 2EbFbGbHbIbJbKb

128 

129 assert result.exit_code == 2 2EbFbGbHbIbJbKb

130 assert "--json" in result.output 2EbFbGbHbIbJbKb

131 

132 

133@pytest.mark.respx 1abcdefg

134def test_prints_json_when_json_option_is_enabled_after_command( 1abcdefg

135 logged_in_cli: None, respx_mock: respx.MockRouter 

136) -> None: 

137 respx_mock.get("/users/me").mock( 2` { | } ~ abbb

138 return_value=Response(200, json={"email": "email@fastapi.com"}) 

139 ) 

140 

141 result = runner.invoke(root_app, ["cloud", "whoami", "--json"]) 2` { | } ~ abbb

142 

143 assert result.exit_code == 0 2` { | } ~ abbb

144 assert json.loads(result.stdout)["data"]["email"] == "email@fastapi.com" 2` { | } ~ abbb

145 

146 

147def test_prints_json_error_when_json_env_is_enabled_and_logged_out( 1abcdefg

148 logged_out_cli: None, 

149) -> None: 

150 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 2cbdbebfbgbhbib

151 

152 assert result.exit_code == 1 2cbdbebfbgbhbib

153 assert json.loads(result.stdout) == { 2cbdbebfbgbhbib

154 "error": { 

155 "code": "not_logged_in", 

156 "message": "No credentials found.", 

157 "hint": "Run `fastapi cloud login`.", 

158 } 

159 } 

160 assert result.stderr == "" 2cbdbebfbgbhbib

161 

162 

163@pytest.mark.respx 1abcdefg

164def test_prints_json_error_when_token_is_invalid( 1abcdefg

165 logged_in_cli: None, respx_mock: respx.MockRouter 

166) -> None: 

167 respx_mock.get("/users/me").mock(return_value=Response(401)) 1CDEFGHI

168 

169 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1CDEFGHI

170 

171 assert result.exit_code == 1 1CDEFGHI

172 assert json.loads(result.stdout) == { 1CDEFGHI

173 "error": { 

174 "code": "invalid_token", 

175 "message": ( 

176 "The specified token is not valid. " 

177 "Use `fastapi login` to generate a new token." 

178 ), 

179 "hint": "Run `fastapi cloud login` to generate a new token.", 

180 } 

181 } 

182 assert result.stderr == "" 1CDEFGHI

183 

184 

185@pytest.mark.respx 1abcdefg

186def test_prints_json_error_when_user_has_no_permissions( 1abcdefg

187 logged_in_cli: None, respx_mock: respx.MockRouter 

188) -> None: 

189 respx_mock.get("/users/me").mock(return_value=Response(403)) 1JKLMNOP

190 

191 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1JKLMNOP

192 

193 assert result.exit_code == 1 1JKLMNOP

194 assert json.loads(result.stdout) == { 1JKLMNOP

195 "error": { 

196 "code": "permission_denied", 

197 "message": "You don't have permissions for this resource", 

198 "hint": None, 

199 } 

200 } 

201 assert result.stderr == "" 1JKLMNOP

202 

203 

204@pytest.mark.respx 1abcdefg

205def test_prints_json_error_when_request_has_generic_api_error( 1abcdefg

206 logged_in_cli: None, respx_mock: respx.MockRouter 

207) -> None: 

208 respx_mock.get("/users/me").mock(return_value=Response(500)) 1opqrstu

209 

210 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1opqrstu

211 

212 assert result.exit_code == 1 1opqrstu

213 output = json.loads(result.stdout) 1opqrstu

214 assert output["error"]["code"] == "api_error" 1opqrstu

215 assert output["error"]["message"].startswith( 1opqrstu

216 "Something went wrong while contacting the FastAPI Cloud server." 

217 ) 

218 assert output["error"]["hint"] is None 1opqrstu

219 assert result.stderr == "" 1opqrstu

220 

221 

222@pytest.mark.respx 1abcdefg

223def test_prints_json_error_when_request_times_out( 1abcdefg

224 logged_in_cli: None, respx_mock: respx.MockRouter 

225) -> None: 

226 respx_mock.get("/users/me").mock(side_effect=ReadTimeout) 1QRSTUVW

227 

228 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1QRSTUVW

229 

230 assert result.exit_code == 1 1QRSTUVW

231 assert json.loads(result.stdout) == { 1QRSTUVW

232 "error": { 

233 "code": "network_error", 

234 "message": ( 

235 "The request to the FastAPI Cloud server timed out." 

236 " Please try again later." 

237 ), 

238 "hint": "Please try again later.", 

239 } 

240 } 

241 assert result.stderr == "" 1QRSTUVW

242 

243 

244@pytest.mark.respx 1abcdefg

245def test_handles_read_timeout( 1abcdefg

246 logged_in_cli: None, respx_mock: respx.MockRouter 

247) -> None: 

248 respx_mock.get("/users/me").mock(side_effect=ReadTimeout) 2jbkblbmbnbobpb

249 

250 result = runner.invoke(app, ["whoami"]) 2jbkblbmbnbobpb

251 

252 assert result.exit_code == 1 2jbkblbmbnbobpb

253 assert "The request to the FastAPI Cloud server timed out" in result.output 2jbkblbmbnbobpb

254 

255 

256def test_prints_not_logged_in(logged_out_cli: None) -> None: 1abcdefg

257 result = runner.invoke(app, ["whoami"]) 2qbrbsbtbubvbwb

258 

259 assert result.exit_code == 1 2qbrbsbtbubvbwb

260 assert "No credentials found." in result.output 2qbrbsbtbubvbwb

261 assert "Run `fastapi cloud login`." in result.output 2qbrbsbtbubvbwb

262 

263 

264def test_prints_not_logged_in_with_deploy_token(logged_out_cli: None) -> None: 1abcdefg

265 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_TOKEN": "ABC"}) 2xbybzbAbBbCbDb

266 

267 assert result.exit_code == 1 2xbybzbAbBbCbDb

268 assert "No credentials found." in result.output 2xbybzbAbBbCbDb

269 assert "Run `fastapi cloud login`." in result.output 2xbybzbAbBbCbDb

270 

271 

272@pytest.mark.respx 1abcdefg

273def test_shows_logged_in_and_has_deploy_token( 1abcdefg

274 logged_in_cli: None, respx_mock: respx.MockRouter 

275) -> None: 

276 respx_mock.get("/users/me").mock( 1XYZ0123

277 return_value=Response(200, json={"email": "email@fastapi.com"}) 

278 ) 

279 

280 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_TOKEN": "ABC"}) 1XYZ0123

281 

282 assert result.exit_code == 0 1XYZ0123

283 assert "email@fastapi.com" in result.output 1XYZ0123

284 assert "Using API token from environment variable" in result.output 1XYZ0123