import datetime import jwt import pytest from fastapi.testclient import TestClient from monday_com_orca_backend.api import main @pytest.fixture(scope="module") def test_client(): """Fixture to create a TestClient for the FastAPI app. This client does NOT have overridden authentication, so it can be used to test the app's behavior with and without authentication. """ client = TestClient(main.app) yield client client.close() @pytest.fixture(scope="class") def non_existing_route(): """Find a non-existing route in the FastAPI app.""" non_existing_route = "/non-existing-endpoint-1234567890" assert ( next( (route for route in main.app.routes if route.path == non_existing_route), None, ) is None ), f"Non-existing endpoint should not be defined: {non_existing_route}" return non_existing_route @pytest.fixture(scope="class") def protected_route(): """Find a protected route in the FastAPI app.""" try: return next( route for route in main.app.routes if route.path.startswith("/api/data/") ) except StopIteration: raise AssertionError( "No protected route found in the API. " "Ensure that the app has protected routes." ) from None class TestProtectedRoutes: """Verify that a correct token allows access to protected routes. We use a realistic HS256 token for testing purposes (HS256 is the algorithm used by Monday.com to sign JWT sessionTokens). """ mock_secret = "mock_secret" @pytest.fixture(scope="class") def mock_hs256_token(self): """Generates a realistic HS256 token for testing purposes, following the structure of a Monday.com JWT token. """ now = datetime.datetime.now(datetime.UTC) payload = { "sub": "test_user", "exp": now + datetime.timedelta(hours=1), "iat": now, "scope": "user:read", } token = jwt.encode(payload, self.mock_secret, algorithm="HS256") return token if isinstance(token, str) else token.decode("utf-8") @pytest.fixture(autouse=True) def patch_monday_app_client_secret(self, mocker): """Patch the MONDAY_APP_CLIENT_SECRET to use the mock secret.""" mocker.patch( "monday_com_orca_backend.env_vars.MONDAY_APP_CLIENT_SECRET", self.mock_secret, ) yield @pytest.mark.skip(reason="Skipping test until fixed") def test_accessing_a_protected_route_with_authentication( self, test_client, protected_route, mock_hs256_token ): """Test that accessing a protected route with authentication returns a 200 OK response. """ headers = {"Authorization": f"Bearer {mock_hs256_token}"} response = test_client.get(protected_route.path, headers=headers) assert ( response.status_code == 200 ), f"Expected 200 OK for {protected_route.path}, got {response.status_code}" class TestExceptionHandlers: def test_obfuscate_unauthorized_requests( self, test_client, non_existing_route, protected_route ): """Test that attempts to access unauthorized endpoints are returned the same response as a non-existing endpoint, to obfuscate the existence of protected endpoints to potential attackers. The returned response object must match exactly the response of a non-existing endpoint. """ # Get a response from a real non-existing endpoint, to compare it later response_non_existing = test_client.get(non_existing_route) assert response_non_existing.status_code == 404 # Attempt to access the protected route without authentication and # compare the responses response_protected = test_client.get(protected_route.path) assert ( response_protected.json() == response_non_existing.json() ), "Response for non-existing endpoint should match protected endpoint response"