diff --git a/mycli/boundary_tunnel.py b/mycli/boundary_tunnel.py index da439810c..d16dbfcd0 100644 --- a/mycli/boundary_tunnel.py +++ b/mycli/boundary_tunnel.py @@ -90,8 +90,16 @@ def start(self, *, show_expiration_warning: bool = True) -> None: raise BoundaryTunnelError('Timed out waiting for Boundary tunnel process output.') connection_details = json.loads(self.stdout) - self.username = connection_details['credentials'][0]['secret']['decoded']['username'] - self.password = connection_details['credentials'][0]['secret']['decoded']['password'] + + if 'status_code' in connection_details: + raise BoundaryTunnelError(f'Boundary tunnel CLI raised status code {connection_details["status_code"]}.') + + try: + self.username = connection_details['credentials'][0]['secret']['decoded']['username'] + self.password = connection_details['credentials'][0]['secret']['decoded']['password'] + except (IndexError, KeyError): + raise BoundaryTunnelError('Boundary tunnel CLI did not return credentials.') from None + expiry_raw = connection_details['expiration'] expiry_utc = datetime.datetime.strptime(expiry_raw, '%Y-%m-%dT%H:%M:%S.%f%z') expiry_local = datetime.datetime.fromtimestamp(expiry_utc.timestamp()) diff --git a/test/pytests/test_boundary_tunnel.py b/test/pytests/test_boundary_tunnel.py index 278752b44..fcb7cc646 100644 --- a/test/pytests/test_boundary_tunnel.py +++ b/test/pytests/test_boundary_tunnel.py @@ -160,6 +160,41 @@ def fake_sleep(seconds: float) -> None: assert sleeps == [0.05] +def test_boundary_tunnel_start_reports_cli_status_code(monkeypatch: pytest.MonkeyPatch) -> None: + tunnel = BoundaryTunnel(target_id='ttcp_123', local_port=4406) + tunnel.stdout = '{"status_code":403}' + + def fake_run() -> None: + tunnel._started.set() + tunnel._output_ready.set() + + monkeypatch.setattr(tunnel, '_run', fake_run) + + with pytest.raises(BoundaryTunnelError, match='Boundary tunnel CLI raised status code 403'): + tunnel.start() + + +@pytest.mark.parametrize( + 'connection_details', + [ + '{"credentials":[]}', + '{"credentials":[{}]}', + ], +) +def test_boundary_tunnel_start_reports_missing_credentials(monkeypatch: pytest.MonkeyPatch, connection_details: str) -> None: + tunnel = BoundaryTunnel(target_id='ttcp_123', local_port=4406) + tunnel.stdout = connection_details + + def fake_run() -> None: + tunnel._started.set() + tunnel._output_ready.set() + + monkeypatch.setattr(tunnel, '_run', fake_run) + + with pytest.raises(BoundaryTunnelError, match='Boundary tunnel CLI did not return credentials'): + tunnel.start() + + def test_boundary_tunnel_start_reports_process_exit_before_ready(monkeypatch: pytest.MonkeyPatch) -> None: class FakeStdout: def readline(self) -> bytes: