|
| 1 | +""" |
| 2 | +Final integration tests to ensure all Google OAuth features work together. |
| 3 | +Tests the complete flow from login to account linking to feature access. |
| 4 | +""" |
| 5 | +import pytest |
| 6 | +from bson import ObjectId |
| 7 | +from datetime import datetime, timezone |
| 8 | + |
| 9 | +from papers2code_app2.schemas.minimal import UserSchema, UserUpdateProfile |
| 10 | +from papers2code_app2.routers.implementation_progress_router import require_github_account |
| 11 | + |
| 12 | + |
| 13 | +class TestFinalIntegration: |
| 14 | + """Final integration tests for Google OAuth implementation""" |
| 15 | + |
| 16 | + def test_user_schema_with_all_camelcase_fields(self): |
| 17 | + """Test that UserSchema accepts all camelCase fields from MongoDB""" |
| 18 | + user_data = { |
| 19 | + "_id": ObjectId(), |
| 20 | + "githubId": 12345, |
| 21 | + "googleId": "google-67890", |
| 22 | + "username": "testuser", |
| 23 | + "email": "test@example.com", |
| 24 | + "avatarUrl": "https://avatars.githubusercontent.com/u/12345", |
| 25 | + "githubAvatarUrl": "https://avatars.githubusercontent.com/u/12345", |
| 26 | + "googleAvatarUrl": "https://lh3.googleusercontent.com/test", |
| 27 | + "preferredAvatarSource": "github", |
| 28 | + "name": "Test User", |
| 29 | + "bio": "Test bio", |
| 30 | + "websiteUrl": "https://example.com", |
| 31 | + "twitterProfileUrl": "https://twitter.com/test", |
| 32 | + "linkedinProfileUrl": "https://linkedin.com/in/test", |
| 33 | + "blueskyUsername": "test.bsky.social", |
| 34 | + "huggingfaceUsername": "test", |
| 35 | + "isAdmin": False, |
| 36 | + "isOwner": False, |
| 37 | + "createdAt": datetime.now(timezone.utc), |
| 38 | + "updatedAt": datetime.now(timezone.utc), |
| 39 | + "lastLoginAt": datetime.now(timezone.utc), |
| 40 | + "profileUpdatedAt": datetime.now(timezone.utc), |
| 41 | + "showEmail": True, |
| 42 | + "showGithub": True, |
| 43 | + } |
| 44 | + |
| 45 | + # Should create successfully with camelCase fields |
| 46 | + user = UserSchema(**user_data) |
| 47 | + |
| 48 | + # Verify fields are accessible via snake_case in Python |
| 49 | + assert user.github_id == 12345 |
| 50 | + assert user.google_id == "google-67890" |
| 51 | + assert user.avatar_url == "https://avatars.githubusercontent.com/u/12345" |
| 52 | + assert user.preferred_avatar_source == "github" |
| 53 | + assert user.show_email == True |
| 54 | + assert user.show_github == True |
| 55 | + assert user.created_at is not None |
| 56 | + assert user.updated_at is not None |
| 57 | + |
| 58 | + def test_github_only_user_has_full_access(self): |
| 59 | + """Test GitHub-only user has all permissions""" |
| 60 | + user = UserSchema( |
| 61 | + id=ObjectId(), |
| 62 | + username="githubuser", |
| 63 | + github_id=12345, |
| 64 | + email="github@example.com", |
| 65 | + avatarUrl="https://avatars.githubusercontent.com/u/12345" |
| 66 | + ) |
| 67 | + |
| 68 | + assert user.github_id is not None |
| 69 | + assert user.google_id is None |
| 70 | + # User should pass GitHub requirement check |
| 71 | + |
| 72 | + def test_google_only_user_lacks_github_access(self): |
| 73 | + """Test Google-only user doesn't have GitHub ID""" |
| 74 | + user = UserSchema( |
| 75 | + id=ObjectId(), |
| 76 | + username="googleuser", |
| 77 | + google_id="google-123", |
| 78 | + email="google@example.com", |
| 79 | + avatarUrl="https://lh3.googleusercontent.com/test" |
| 80 | + ) |
| 81 | + |
| 82 | + assert user.google_id is not None |
| 83 | + assert user.github_id is None |
| 84 | + # User would fail GitHub requirement check |
| 85 | + |
| 86 | + def test_linked_account_has_both_ids(self): |
| 87 | + """Test linked account has both GitHub and Google IDs""" |
| 88 | + user = UserSchema( |
| 89 | + id=ObjectId(), |
| 90 | + username="linkeduser", |
| 91 | + github_id=12345, |
| 92 | + google_id="google-123", |
| 93 | + email="linked@example.com", |
| 94 | + githubAvatarUrl="https://avatars.githubusercontent.com/u/12345", |
| 95 | + googleAvatarUrl="https://lh3.googleusercontent.com/test", |
| 96 | + avatarUrl="https://avatars.githubusercontent.com/u/12345", |
| 97 | + preferredAvatarSource="github" |
| 98 | + ) |
| 99 | + |
| 100 | + assert user.github_id is not None |
| 101 | + assert user.google_id is not None |
| 102 | + assert user.github_avatar_url is not None |
| 103 | + assert user.google_avatar_url is not None |
| 104 | + assert user.preferred_avatar_source == "github" |
| 105 | + |
| 106 | + def test_privacy_settings_default_to_visible(self): |
| 107 | + """Test privacy settings default to True (visible)""" |
| 108 | + user = UserSchema( |
| 109 | + id=ObjectId(), |
| 110 | + username="testuser", |
| 111 | + github_id=12345 |
| 112 | + ) |
| 113 | + |
| 114 | + assert user.show_email == True |
| 115 | + assert user.show_github == True |
| 116 | + |
| 117 | + def test_user_update_profile_with_privacy_settings(self): |
| 118 | + """Test profile update includes privacy settings""" |
| 119 | + update = UserUpdateProfile( |
| 120 | + name="Updated Name", |
| 121 | + bio="Updated bio", |
| 122 | + showEmail=False, |
| 123 | + showGithub=False, |
| 124 | + preferredAvatarSource="google" |
| 125 | + ) |
| 126 | + |
| 127 | + assert update.name == "Updated Name" |
| 128 | + assert update.show_email == False |
| 129 | + assert update.show_github == False |
| 130 | + assert update.preferred_avatar_source == "google" |
| 131 | + |
| 132 | + @pytest.mark.asyncio |
| 133 | + async def test_require_github_account_with_github_user(self): |
| 134 | + """Test GitHub requirement passes for GitHub user""" |
| 135 | + user = UserSchema( |
| 136 | + id=ObjectId(), |
| 137 | + username="githubuser", |
| 138 | + github_id=12345 |
| 139 | + ) |
| 140 | + |
| 141 | + # Should not raise exception |
| 142 | + await require_github_account(user) |
| 143 | + |
| 144 | + @pytest.mark.asyncio |
| 145 | + async def test_require_github_account_with_linked_user(self): |
| 146 | + """Test GitHub requirement passes for linked account""" |
| 147 | + user = UserSchema( |
| 148 | + id=ObjectId(), |
| 149 | + username="linkeduser", |
| 150 | + github_id=12345, |
| 151 | + google_id="google-123" |
| 152 | + ) |
| 153 | + |
| 154 | + # Should not raise exception |
| 155 | + await require_github_account(user) |
| 156 | + |
| 157 | + @pytest.mark.asyncio |
| 158 | + async def test_require_github_account_fails_for_google_only(self): |
| 159 | + """Test GitHub requirement fails for Google-only user""" |
| 160 | + from fastapi import HTTPException |
| 161 | + |
| 162 | + user = UserSchema( |
| 163 | + id=ObjectId(), |
| 164 | + username="googleuser", |
| 165 | + google_id="google-123" |
| 166 | + ) |
| 167 | + |
| 168 | + with pytest.raises(HTTPException) as exc_info: |
| 169 | + await require_github_account(user) |
| 170 | + |
| 171 | + assert exc_info.value.status_code == 403 |
| 172 | + assert "GitHub account required" in exc_info.value.detail |
| 173 | + |
| 174 | + def test_avatar_management_for_linked_accounts(self): |
| 175 | + """Test avatar source preference works correctly""" |
| 176 | + # GitHub preference |
| 177 | + user_github_pref = UserSchema( |
| 178 | + id=ObjectId(), |
| 179 | + username="linkeduser", |
| 180 | + github_id=12345, |
| 181 | + google_id="google-123", |
| 182 | + githubAvatarUrl="https://github.com/avatar.jpg", |
| 183 | + googleAvatarUrl="https://google.com/avatar.jpg", |
| 184 | + avatarUrl="https://github.com/avatar.jpg", |
| 185 | + preferredAvatarSource="github" |
| 186 | + ) |
| 187 | + |
| 188 | + assert user_github_pref.avatar_url == "https://github.com/avatar.jpg" |
| 189 | + assert user_github_pref.preferred_avatar_source == "github" |
| 190 | + |
| 191 | + # Google preference |
| 192 | + user_google_pref = UserSchema( |
| 193 | + id=ObjectId(), |
| 194 | + username="linkeduser2", |
| 195 | + github_id=12345, |
| 196 | + google_id="google-123", |
| 197 | + githubAvatarUrl="https://github.com/avatar.jpg", |
| 198 | + googleAvatarUrl="https://google.com/avatar.jpg", |
| 199 | + avatarUrl="https://google.com/avatar.jpg", |
| 200 | + preferredAvatarSource="google" |
| 201 | + ) |
| 202 | + |
| 203 | + assert user_google_pref.avatar_url == "https://google.com/avatar.jpg" |
| 204 | + assert user_google_pref.preferred_avatar_source == "google" |
| 205 | + |
| 206 | + |
| 207 | +if __name__ == "__main__": |
| 208 | + pytest.main([__file__, "-v"]) |
0 commit comments