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
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-09 00:25 +0000
1import json 1abcdefg
2from pathlib import Path 1abcdefg
4import pytest 1abcdefg
5import respx 1abcdefg
6from httpx import ReadTimeout, Response 1abcdefg
7from typer.testing import CliRunner 1abcdefg
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
14runner = CliRunner() 1abcdefg
16assets_path = Path(__file__).parent / "assets" 1abcdefg
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!
25 result = runner.invoke(app, ["whoami"]) 1456789!
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!
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#$%'()*
40 result = runner.invoke(app, ["whoami"]) 1#$%'()*
42 assert result.exit_code == 1 1#$%'()*
43 assert "The specified token is not valid" in result.output 1#$%'()*
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+,-./:;
52 result = runner.invoke(app, ["whoami"]) 1+,-./:;
54 assert result.exit_code == 1 1+,-./:;
55 assert "You don't have permissions for this resource" in result.output 1+,-./:;
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 )
64 result = runner.invoke(app, ["whoami"]) 1=?@[]^_
66 assert result.exit_code == 0 1=?@[]^_
67 assert "email@fastapi.com" in result.output 1=?@[]^_
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
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
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 )
90 result = runner.invoke(app, ["whoami"]) 1hijklmn
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
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 )
111 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1vwxyzAB
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
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
129 assert result.exit_code == 2 2EbFbGbHbIbJbKb
130 assert "--json" in result.output 2EbFbGbHbIbJbKb
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 )
141 result = runner.invoke(root_app, ["cloud", "whoami", "--json"]) 2` { | } ~ abbb
143 assert result.exit_code == 0 2` { | } ~ abbb
144 assert json.loads(result.stdout)["data"]["email"] == "email@fastapi.com" 2` { | } ~ abbb
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
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
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
169 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1CDEFGHI
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
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
191 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1JKLMNOP
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
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
210 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1opqrstu
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
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
228 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_JSON": "1"}) 1QRSTUVW
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
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
250 result = runner.invoke(app, ["whoami"]) 2jbkblbmbnbobpb
252 assert result.exit_code == 1 2jbkblbmbnbobpb
253 assert "The request to the FastAPI Cloud server timed out" in result.output 2jbkblbmbnbobpb
256def test_prints_not_logged_in(logged_out_cli: None) -> None: 1abcdefg
257 result = runner.invoke(app, ["whoami"]) 2qbrbsbtbubvbwb
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
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
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
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 )
280 result = runner.invoke(app, ["whoami"], env={"FASTAPI_CLOUD_TOKEN": "ABC"}) 1XYZ0123
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