""" Authentication integration tests. """ import pytest class TestBootstrap: """Tests for the bootstrap/owner setup flow.""" def test_bootstrap_status_initial(self, client): """Bootstrap status should show setup required when no users exist.""" response = client.get("/auth/bootstrap/status") assert response.status_code == 200 data = response.get_json() assert "setup_completed" in data def test_bootstrap_owner_creates_first_user(self, client): """Bootstrap owner should create the first admin user.""" response = client.post("/auth/bootstrap/owner", json={ "username": "owner", "password": "securepassword123", "root_dirs": [], }) # Should succeed (201) or fail if already exists (400) assert response.status_code in [200, 201, 400] if response.status_code in [200, 201]: data = response.get_json() assert "accesstoken" in data assert "refreshtoken" in data def test_bootstrap_owner_requires_username(self, client): """Bootstrap owner should require username.""" response = client.post("/auth/bootstrap/owner", json={ "password": "securepassword123", }) assert response.status_code == 422 # Validation error def test_bootstrap_owner_requires_password(self, client): """Bootstrap owner should require password.""" response = client.post("/auth/bootstrap/owner", json={ "username": "testowner", }) assert response.status_code == 422 # Validation error class TestLogin: """Tests for the login endpoint.""" def test_login_requires_username(self, client): """Login should require username.""" response = client.post("/auth/login", json={ "password": "testpassword", }) assert response.status_code == 422 def test_login_requires_password(self, client): """Login should require password.""" response = client.post("/auth/login", json={ "username": "testuser", }) assert response.status_code == 422 def test_login_nonexistent_user(self, client): """Login should fail for nonexistent user.""" response = client.post("/auth/login", json={ "username": "nonexistent_user_12345", "password": "testpassword", }) assert response.status_code == 404 def test_login_wrong_password(self, client): """Login should fail with wrong password.""" # First ensure owner exists client.post("/auth/bootstrap/owner", json={ "username": "owner", "password": "correctpassword123", "root_dirs": [], }) response = client.post("/auth/login", json={ "username": "owner", "password": "wrongpassword", }) assert response.status_code == 401 class TestPairCode: """Tests for the pairing code flow (mobile app login).""" def test_get_pair_code_requires_auth(self, client): """Pair code generation should require authentication.""" response = client.get("/auth/getpaircode") assert response.status_code in [401, 422] # Unauthorized or validation error def test_pair_with_invalid_code(self, client): """Pairing with invalid code should fail.""" response = client.get("/auth/pair?code=INVALID") assert response.status_code == 400 def test_pair_with_empty_code(self, client): """Pairing with empty code should fail.""" response = client.get("/auth/pair?code=") assert response.status_code == 400 class TestInviteFlow: """Tests for the invite/accept user onboarding flow.""" def test_create_invite_requires_admin(self, client): """Create invite should require admin role.""" response = client.post("/auth/invite/create", json={ "roles": ["user"], }) assert response.status_code in [401, 403] def test_accept_invite_requires_token(self, client): """Accept invite should require a token.""" response = client.post("/auth/invite/accept", json={ "username": "newuser", "password": "newpassword123", }) assert response.status_code == 422 def test_accept_invite_invalid_token(self, client): """Accept invite should fail with invalid token.""" response = client.post("/auth/invite/accept", json={ "token": "invalid_token_12345", "username": "newuser", "password": "newpassword123", }) assert response.status_code == 400 class TestTokenRefresh: """Tests for token refresh functionality.""" def test_refresh_requires_token(self, client): """Refresh should require a refresh token.""" response = client.post("/auth/refresh") assert response.status_code in [401, 422] def test_refresh_with_invalid_token(self, client): """Refresh should fail with invalid token.""" response = client.post( "/auth/refresh", headers={"Authorization": "Bearer invalid_token"}, ) assert response.status_code in [401, 422] class TestAuthContract: """Contract tests for authentication API.""" def test_login_response_schema(self, client): """Login response should match expected schema.""" # Create owner first client.post("/auth/bootstrap/owner", json={ "username": "contractowner", "password": "contractpass123", "root_dirs": [], }) response = client.post("/auth/login", json={ "username": "contractowner", "password": "contractpass123", }) if response.status_code == 200: data = response.get_json() assert "accesstoken" in data assert "refreshtoken" in data assert "msg" in data assert isinstance(data["accesstoken"], str) assert isinstance(data["refreshtoken"], str) def test_pair_code_response_schema(self, client): """Pair code response should match expected schema.""" # Create owner and login client.post("/auth/bootstrap/owner", json={ "username": "paircodeowner", "password": "paircodepass123", "root_dirs": [], }) login_response = client.post("/auth/login", json={ "username": "paircodeowner", "password": "paircodepass123", }) if login_response.status_code == 200: token = login_response.get_json().get("accesstoken") response = client.get( "/auth/getpaircode", headers={"Authorization": f"Bearer {token}"}, ) if response.status_code == 200: data = response.get_json() assert "code" in data assert "expires_at" in data assert "server_url" in data assert isinstance(data["code"], str) assert len(data["code"]) == 6 # 6-character code