Skip to content

Conversation

@Devasy
Copy link
Owner

@Devasy Devasy commented Aug 8, 2025

Introduce group settings features, including new APIs for managing groups and a dedicated settings screen. Enhance the group details screen with improved member display and navigation to the settings.

Summary by CodeRabbit

  • New Features

    • Introduced a dedicated Group Settings screen for admins to update group details, manage members, share invite codes, leave, or delete groups.
    • Added dynamic avatar rendering for groups and members, supporting both image and text avatars.
    • Added new group management actions: updating group info, removing members, leaving groups, and deleting groups with appropriate checks.
    • Added profile image selection and upload in the Edit Profile screen.
  • Improvements

    • Simplified API usage by removing manual token handling; authentication is now managed automatically.
    • Enhanced navigation with a new route to Group Settings from group details.
    • Added confirmation dialogs and error alerts for critical group actions.
    • Added checks to prevent leaving or removing group members when unsettled balances exist.
    • Improved handling of group member and expense data fetching without explicit token parameters.
    • Enhanced friends list with group metadata and avatar images.
    • Added skeleton loading UI for friends list.
    • Improved avatar rendering in Account and Home screens.
    • Updated app permissions and dependencies to support image picking for group icons.
    • Added currency formatting utility for consistent display across the app.
  • Authentication

    • Implemented centralized token management and automatic token refresh for seamless user sessions.
    • Added support for refresh tokens in authentication context with persistent storage.
  • Bug Fixes

    • Minor UI consistency updates and error handling improvements across screens.
  • Chores

    • Applied consistent code formatting and style across multiple files.

@Devasy Devasy requested a review from vrajpatelll as a code owner August 8, 2025 16:51
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

Walkthrough

This update refactors API calls to use a centralized client handling authentication internally, removing explicit token parameters. It adds new group management API functions and a dedicated group settings screen. Navigation and UI components for group details and avatars are enhanced, enabling admins to manage group info, members, and settings with improved avatar rendering and seamless navigation. Backend logic now enforces checks for unsettled balances before allowing member removal or leaving groups, with corresponding tests added.

Changes

Cohort / File(s) Change Summary
API Refactor and Extensions
frontend/api/groups.js, frontend/api/auth.js, frontend/api/client.js
Refactored API calls to remove explicit token parameters, using a centralized apiClient that manages authentication and token refresh internally. Added new group management API functions for fetching, updating, deleting groups, managing members, and leaving groups. Added token refresh API and centralized client with automatic token refresh and retry logic.
Authentication Context Enhancements
frontend/context/AuthContext.js
Extended AuthContext to manage both access and refresh tokens, integrating with the new API client. Added token persistence, update listeners, and logout handling for refresh tokens.
Navigation Updates
frontend/navigation/GroupsStackNavigator.js
Added GroupSettingsScreen to the navigation stack with a new route and screen title.
Group Details UI Enhancements
frontend/screens/GroupDetailsScreen.js
Added a settings icon in the header to navigate to group settings. Removed member names from header. Updated API calls to omit token parameters. Minor formatting and styling improvements.
New Group Settings Screen
frontend/screens/GroupSettingsScreen.js
Introduced a new screen for managing group settings. Includes group info editing, member management, invite sharing, leave/delete group actions, and role-based UI. Handles loading, errors, and confirmation dialogs.
Home Screen Updates
frontend/screens/HomeScreen.js
Updated API calls to omit token parameters. Enhanced group avatar rendering to support image avatars with fallback to text avatars. Reformatted code for consistent style.
Expense Screen Updates
frontend/screens/AddExpenseScreen.js
Removed token parameters from API calls. Reformatted code for readability and consistent style without changing logic.
Friends Screen Updates
frontend/screens/FriendsScreen.js
Removed token parameter from API calls. Extended friend data with user images by fetching group members. Reformatted code for consistent style without functional changes.
Join Group Screen Updates
frontend/screens/JoinGroupScreen.js
Removed token parameter from joinGroup API call. Reformatted imports, state initialization, and alert formatting for consistency.
Edit Profile Screen Updates
frontend/screens/EditProfileScreen.js
Added image picking and upload support for profile pictures. Removed token parameter from updateUser API call. Reformatted imports and alert messages for consistency.
Account Screen Updates
frontend/screens/AccountScreen.js
Added conditional avatar rendering based on user image URL presence and format. Reformatted imports and JSX for consistent style.
Backend Group Service Logic
backend/app/groups/service.py
Added checks to prevent leaving a group or removing a member if there are unsettled balances (pending settlements). Raises HTTP 400 errors with appropriate messages if unsettled balances exist. Exception handling added for database query failures.
Backend Group Service Tests
backend/tests/groups/test_groups_service.py
Added tests verifying that member removal and leaving group operations are blocked when unsettled balances exist and allowed otherwise. Tests mock database collections and simulate group membership and roles.
App Configuration and Dependencies
frontend/app.json, frontend/package.json
Added iOS and Android permissions for photo library access. Added expo-image-picker dependency for image selection functionality.
Currency Utility Addition
frontend/utils/currency.js
Added utility functions for consistent currency symbol retrieval and formatting amounts with currency symbol and two decimal places.
Backend Expenses Service Update
backend/app/expenses/service.py
Enhanced friends balance summary to include user profile images alongside user names, avoiding extra client requests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant HomeScreen
    participant GroupDetailsScreen
    participant GroupSettingsScreen
    participant API
    participant BackendService

    User->>HomeScreen: Selects a group
    HomeScreen->>GroupDetailsScreen: Navigates with groupId
    GroupDetailsScreen->>User: Displays group details & member list
    User->>GroupDetailsScreen: Taps settings icon
    GroupDetailsScreen->>GroupSettingsScreen: Navigates with groupId
    GroupSettingsScreen->>API: Fetch group details & members
    API-->>GroupSettingsScreen: Returns group and member data
    GroupSettingsScreen->>User: Displays group info, members, settings
    User->>GroupSettingsScreen: Edits group info / removes member / leaves group
    GroupSettingsScreen->>API: Sends update/delete/leave requests
    API->>BackendService: Validates requests, checks unsettled balances
    BackendService-->>API: Returns success or error (e.g., unsettled balances)
    API-->>GroupSettingsScreen: Returns success/failure
    GroupSettingsScreen->>User: Updates UI and shows confirmation or error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

frontend, level2

Suggested reviewers

  • vrajpatelll

Poem

🐇 In code’s cozy burrow, the changes take flight,
Groups now have settings, all shiny and bright.
Avatars gleam with images or text,
Admins hold the reins, keeping order next.
Balances settled before hopping away,
This rabbit’s work makes the app a safe play! 🥕✨🐰


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42723b8 and a184309.

📒 Files selected for processing (1)
  • frontend/screens/GroupDetailsScreen.js (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/screens/GroupDetailsScreen.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: bundle-analysis
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/group-settings-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Devasy Devasy self-assigned this Aug 8, 2025
@Devasy Devasy added enhancement New feature or request groups level2 labels Aug 8, 2025
@codecov
Copy link

codecov bot commented Aug 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.35%. Comparing base (bff8bd5) to head (a184309).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #128      +/-   ##
==========================================
+ Coverage   71.86%   72.35%   +0.48%     
==========================================
  Files          17       17              
  Lines        1667     1682      +15     
  Branches      153      155       +2     
==========================================
+ Hits         1198     1217      +19     
+ Misses        416      411       -5     
- Partials       53       54       +1     
Components Coverage Δ
Authentication System 75.45% <ø> (ø)
Expense Management 68.98% <100.00%> (+0.03%) ⬆️
Group Management 69.49% <100.00%> (+2.72%) ⬆️
User Management 97.16% <ø> (ø)
Backend Core 69.41% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link

github-actions bot commented Aug 8, 2025

🚀 Expo preview is ready!

  • Project → frontend
  • Platforms → android, ios
  • Runtime Version → 1.0.0
  • More info

Learn more about 𝝠 Expo Github Action

- Updated import statements to use consistent formatting across GroupDetailsScreen, GroupSettingsScreen, HomeScreen, and JoinGroupScreen.
- Changed string literals to use double quotes for consistency.
- Simplified API calls by removing unnecessary token parameters in group-related API functions.
- Enhanced error handling and user feedback with clearer alert messages.
- Improved layout and styling for better user experience in various components.
- Refactored settlement status calculations and rendering logic for clarity.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
frontend/screens/GroupDetailsScreen.js (1)

230-241: Style block duplicates unsupported gap

Same concern as above – swap gap for explicit spacing.

frontend/screens/GroupSettingsScreen.js (1)

207-213: gap not portable across RN versions

Same styling issue – replace with flexbox spacing utilities for reliability.

🧹 Nitpick comments (3)
frontend/api/groups.js (1)

94-141: Reduce header duplication & validate updates payload

Every new helper repeats the same Authorization header block. Extract a small authConfig(token) helper (or reuse the central apiClient with an interceptor) to DRY this file and cut maintenance overhead.
Also make sure the updates object passed to updateGroup is whitelisted on the server-side; for safety you may want to pre-filter unexpected keys on the client as well.

frontend/screens/GroupDetailsScreen.js (1)

41-47: Header callback will re-create on every render

navigation.setOptions({...}) is inside useEffect that depends on [token, groupId], but the callback you pass (headerRight) is re-created each render, forcing React Navigation to re-compute options. Wrap the callback in useCallback or move the whole setOptions into useLayoutEffect with an empty dependency array to avoid unnecessary work.

frontend/screens/HomeScreen.js (1)

142-153: Avoid passing full image URL as groupIcon param

When isImage is true you pass the URL string as groupIcon. Down-stream screens expecting an emoji/letter might attempt to render that in Avatar.Text. Pass a dedicated iconLabel param (letter/emoji) and a separate imageUrl to keep semantics clear.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bff8bd5 and 5da289e.

📒 Files selected for processing (5)
  • frontend/api/groups.js (1 hunks)
  • frontend/navigation/GroupsStackNavigator.js (2 hunks)
  • frontend/screens/GroupDetailsScreen.js (4 hunks)
  • frontend/screens/GroupSettingsScreen.js (1 hunks)
  • frontend/screens/HomeScreen.js (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
frontend/navigation/GroupsStackNavigator.js (1)
frontend/screens/GroupSettingsScreen.js (1)
  • GroupSettingsScreen (16-258)
frontend/api/groups.js (1)
frontend/api/auth.js (2)
  • apiClient (5-10)
  • token (20-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: bundle-analysis
  • GitHub Check: preview
🔇 Additional comments (2)
frontend/navigation/GroupsStackNavigator.js (1)

3-5: Route addition looks good

Importing the screen and registering the "GroupSettings" route is straightforward and matches the rest of the stack. No issues spotted.

Also applies to: 17-17

frontend/screens/GroupSettingsScreen.js (1)

26-30: Missing dependency: user?._id won’t update

useMemo depends on user?._id but the optional-chain expression is evaluated immediately, so React will treat it as a constant value. Depend on user (or user && user._id) instead to keep admin status in sync if context changes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
frontend/screens/JoinGroupScreen.js (1)

8-10: Drop unused token from context destructuring

const { token } = useContext(AuthContext); is now dead code and will trigger ESLint‘s no-unused-vars. Remove the destructuring (or extract only the values you actually use) to keep the component clean.

frontend/context/AuthContext.js (1)

45-53: Register the token-update listener only once

Because the effect depends on token and refresh, a fresh listener is assigned on every token change, replacing the previous one mid-stream. Use useCallback + empty-deps (or move listener assignment outside a hook) so the reference is stable and you avoid missing rapid consecutive refresh events.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5da289e and df20f03.

📒 Files selected for processing (10)
  • frontend/api/auth.js (1 hunks)
  • frontend/api/client.js (1 hunks)
  • frontend/api/groups.js (1 hunks)
  • frontend/context/AuthContext.js (4 hunks)
  • frontend/screens/AddExpenseScreen.js (6 hunks)
  • frontend/screens/FriendsScreen.js (4 hunks)
  • frontend/screens/GroupDetailsScreen.js (9 hunks)
  • frontend/screens/GroupSettingsScreen.js (1 hunks)
  • frontend/screens/HomeScreen.js (9 hunks)
  • frontend/screens/JoinGroupScreen.js (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • frontend/screens/GroupSettingsScreen.js
  • frontend/screens/AddExpenseScreen.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/screens/HomeScreen.js
  • frontend/screens/GroupDetailsScreen.js
  • frontend/api/groups.js
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-26T09:41:01.332Z
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to backend/app/auth/**/*.py : JWT authentication with refresh token rotation must be implemented in the backend.

Applied to files:

  • frontend/context/AuthContext.js
🧬 Code Graph Analysis (3)
frontend/screens/JoinGroupScreen.js (2)
frontend/context/AuthContext.js (2)
  • AuthContext (10-10)
  • AuthContext (10-10)
frontend/api/groups.js (2)
  • joinGroup (23-24)
  • joinGroup (23-24)
frontend/context/AuthContext.js (3)
frontend/api/auth.js (2)
  • refresh (15-17)
  • refresh (15-17)
frontend/api/client.js (8)
  • setAuthTokens (15-18)
  • setAuthTokens (15-18)
  • setTokenUpdateListener (11-13)
  • setTokenUpdateListener (11-13)
  • accessToken (5-5)
  • refreshToken (6-6)
  • clearAuthTokens (20-23)
  • clearAuthTokens (20-23)
backend/app/auth/routes.py (1)
  • refresh_token (172-212)
frontend/api/client.js (1)
backend/app/auth/routes.py (1)
  • refresh_token (172-212)
🪛 Biome (2.1.2)
frontend/api/client.js

[error] 66-66: Unsafe usage of 'return'.

'return' in 'finally' overwrites the control flow statements inside 'try' and 'catch'.

(lint/correctness/noUnsafeFinally)

🔇 Additional comments (1)
frontend/screens/FriendsScreen.js (1)

26-39: Looks good – API call correctly updated to new client
No issues spotted in the fetch/transform block; logic matches the new getFriendsBalance signature.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
backend/app/groups/service.py (1)

75-78: Inconsistent member user image key: 'avatar' vs 'imageUrl'

Elsewhere you use imageUrl, but here the fallback uses avatar. This breaks consumer expectations and UI.

Apply this diff:

-                                "avatar": None,
+                                "imageUrl": None,
🧹 Nitpick comments (2)
backend/tests/groups/test_groups_service.py (2)

191-219: Good coverage for removal-blocked path; flatten nested context managers (SIM117)

Use a single with-statement for multiple contexts.

Apply this diff:

-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
+        with patch.object(self.service, "get_db", return_value=mock_db), \
+             pytest.raises(HTTPException) as exc:
             await self.service.remove_member(group_id, member_id, admin_id)

250-277: Solid leave-blocked test; also flatten nested context managers (SIM117)

Same nit as above; combine contexts.

Apply this diff:

-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
+        with patch.object(self.service, "get_db", return_value=mock_db), \
+             pytest.raises(HTTPException) as exc:
             await self.service.leave_group(group_id, user_id)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df20f03 and e9824a1.

📒 Files selected for processing (3)
  • backend/app/groups/service.py (2 hunks)
  • backend/tests/groups/test_groups_service.py (1 hunks)
  • frontend/screens/GroupSettingsScreen.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/screens/GroupSettingsScreen.js
🧰 Additional context used
🪛 Ruff (0.12.2)
backend/tests/groups/test_groups_service.py

213-214: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


271-272: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

🔇 Additional comments (2)
backend/tests/groups/test_groups_service.py (2)

220-249: LGTM: happy-path remove_member when settled is covered well

Test logic and mocks look correct; asserts validate the intended success path.


278-306: LGTM: happy-path leave when settled is covered

Mocks and assertions reflect the intended behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

✅ Actions performed

Comments resolved.

3 similar comments
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

✅ Actions performed

Comments resolved.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

✅ Actions performed

Comments resolved.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 8, 2025

✅ Actions performed

Comments resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
backend/app/groups/service.py (1)

447-469: Same unsettled-check duplicated – centralise logic

Replicate fix from leave_group: use raise … from e and move the existence query into a shared helper to cut duplication and keep behaviour consistent.

🧹 Nitpick comments (5)
frontend/screens/EditProfileScreen.js (3)

14-14: Trim user input before validation and submission

Avoid accepting whitespace-only names and send a normalized value to the API.

-    if (!name) {
+    if (!name?.trim()) {
       Alert.alert("Error", "Name cannot be empty.");
       return;
     }
@@
-      const response = await updateUser({ name });
+      const response = await updateUser({ name: name.trim() });

Also applies to: 19-19


24-25: Surface server error messages and improve logging context

Bubble up API-provided messages when available and log the meaningful detail.

-      console.error("Failed to update profile:", error);
-      Alert.alert("Error", "Failed to update profile.");
+      console.error(
+        "Failed to update profile:",
+        error?.response?.data ?? error?.message ?? error
+      );
+      Alert.alert(
+        "Error",
+        error?.response?.data?.message ?? error?.message ?? "Failed to update profile."
+      );

34-35: Prevent navigation while submitting to avoid state updates on unmounted component

Disable the back action during submission to reduce UI churn and potential setState-on-unmounted warnings.

-        <Appbar.BackAction onPress={() => navigation.goBack()} />
+        <Appbar.BackAction
+          onPress={() => {
+            if (!isSubmitting) navigation.goBack();
+          }}
+          disabled={isSubmitting}
+        />
backend/app/groups/service.py (1)

315-337: Preserve traceback & DRY the unsettled-check

Catch only Exception but re-raise without context. Use raise HTTPException(... ) from e to keep the original stack (Ruff B904).
Also, this exact block is duplicated in remove_member; extract into a private helper _has_pending_settlements to avoid copy-paste.

backend/tests/groups/test_groups_service.py (1)

216-219: Flatten nested with blocks for readability

Combine patching & exception assertions in a single with to silence Ruff SIM117 and reduce indentation:

with patch.object(self.service, "get_db", return_value=mock_db), \
     pytest.raises(HTTPException) as exc:
    await self.service.remove_member(group_id, member_id, admin_id)

Also applies to: 274-277

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9824a1 and 070321b.

📒 Files selected for processing (9)
  • backend/app/groups/service.py (2 hunks)
  • backend/tests/groups/test_groups_service.py (3 hunks)
  • frontend/api/auth.js (1 hunks)
  • frontend/api/client.js (1 hunks)
  • frontend/app.json (1 hunks)
  • frontend/package.json (1 hunks)
  • frontend/screens/EditProfileScreen.js (1 hunks)
  • frontend/screens/GroupDetailsScreen.js (6 hunks)
  • frontend/screens/GroupSettingsScreen.js (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/api/auth.js
  • frontend/screens/GroupDetailsScreen.js
  • frontend/api/client.js
🧰 Additional context used
📓 Path-based instructions (3)
backend/**/*.py

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Backend code must be implemented using FastAPI with Python 3.9+ and organized under the /backend/ directory.

Files:

  • backend/app/groups/service.py
  • backend/tests/groups/test_groups_service.py
backend/app/{auth,user,groups,expenses}/**/*.py

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

backend/app/{auth,user,groups,expenses}/**/*.py: Backend services should be modular, with authentication and user registration in app/auth/, user profile management in app/user/, group management in app/groups/, and expense tracking in app/expenses/.
When adding a new API endpoint, add the route to the appropriate service router file in the backend.

Files:

  • backend/app/groups/service.py
backend/tests/**/*.py

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Backend tests must be placed in the /backend/tests/ directory and run using pytest.

Files:

  • backend/tests/groups/test_groups_service.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.
📚 Learning: 2025-07-26T09:41:01.332Z
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to backend/app/expenses/**/*.py : Settlements must track debt resolution between users in the expense tracking logic.

Applied to files:

  • backend/app/groups/service.py
🧬 Code Graph Analysis (1)
backend/tests/groups/test_groups_service.py (4)
backend/tests/conftest.py (1)
  • mock_db (65-96)
backend/app/groups/service.py (2)
  • remove_member (409-474)
  • leave_group (286-342)
backend/app/groups/routes.py (1)
  • leave_group (100-107)
backend/tests/groups/test_groups_routes.py (1)
  • test_leave_group (234-247)
🪛 Ruff (0.12.2)
backend/app/groups/service.py

329-332: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


461-464: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

backend/tests/groups/test_groups_service.py

216-217: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


274-275: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

🪛 GitHub Check: codecov/patch
backend/app/groups/service.py

[warning] 325-325: backend/app/groups/service.py#L325
Added line #L325 was not covered by tests


[warning] 457-457: backend/app/groups/service.py#L457
Added line #L457 was not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: bundle-analysis
  • GitHub Check: preview
🔇 Additional comments (3)
frontend/screens/EditProfileScreen.js (3)

19-19: Tokenless update via centralized client looks good

Calling updateUser without a token aligns with the centralized apiClient approach. Cleaner and reduces prop/threading of auth state.


9-9: Good defaulting of editable state

Initializing from user?.name with a safe fallback is appropriate.


19-19: Verified: updateUser is wired to apiClient with retry logic
The updateUser call in EditProfileScreen uses the shared apiClient (frontend/api/auth.js:11), and apiClient is configured in frontend/api/client.js with:

  • A request interceptor to inject auth headers (line 34)
  • A response interceptor that automatically refreshes expired tokens and retries the original request (lines 72–89)

No further action needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
frontend/screens/AccountScreen.js (1)

24-28: Extract repeated avatar-selection logic into a shared utility

This regex-based branching now exists in several screens (Account, Friends, Home, Edit Profile). Centralising it (e.g. getAvatarSource(imageUrl, fallbackLabel)) eliminates copy-paste, keeps behaviour consistent, and makes future fixes (e.g. more URI schemes) one-liner updates.

frontend/screens/FriendsScreen.js (1)

95-105: Regex check may still yield hasImage but imageUri === null

If the string starts with an unsupported scheme (e.g. file://), hasImage is truthy yet no imageUri is produced, but the UI falls back on Avatar.Text only when imageUri is falsy. Consider merging the two checks or simply deriving imageUri first and branching on that variable.

frontend/screens/EditProfileScreen.js (1)

9-12: Remove unused token variable

token is extracted from context but never used, leading to lint noise.

frontend/context/AuthContext.js (1)

52-58: Clean up the token-update listener in useEffect

setTokenUpdateListener is invoked on every render where token or refresh changes, potentially stacking multiple listeners if the helper doesn’t replace the previous one. Return a cleanup function:

useEffect(() => {
  const unsub = setTokenUpdateListener(({ accessToken, refreshToken }) => {
    if (accessToken && accessToken !== token) setToken(accessToken);
    if (refreshToken && refreshToken !== refresh) setRefresh(refreshToken);
  });
  return () => unsub?.();
}, [token, refresh]);

Prevents memory leaks and duplicate state updates.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b142c1 and 2cc7991.

📒 Files selected for processing (5)
  • frontend/api/auth.js (1 hunks)
  • frontend/context/AuthContext.js (4 hunks)
  • frontend/screens/AccountScreen.js (3 hunks)
  • frontend/screens/EditProfileScreen.js (3 hunks)
  • frontend/screens/FriendsScreen.js (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/api/auth.js
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.
📚 Learning: 2025-07-26T09:41:01.332Z
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to backend/app/auth/**/*.py : JWT authentication with refresh token rotation must be implemented in the backend.

Applied to files:

  • frontend/context/AuthContext.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: preview

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
frontend/screens/FriendsScreen.js (3)

19-27: Fetch both APIs in parallel to cut wait time

getFriendsBalance() and getGroups() are awaited one after the other, adding unnecessary latency. Kick them off together:

-const friendsResponse = await getFriendsBalance();
-const friendsData = friendsResponse.data.friendsBalance || [];
-const groupsResponse  = await getGroups();
-const groups = groupsResponse?.data?.groups || [];
+const [friendsResponse, groupsResponse] = await Promise.all([
+  getFriendsBalance(),
+  getGroups(),
+]);
+const friendsData = friendsResponse.data.friendsBalance || [];
+const groups      = groupsResponse?.data?.groups || [];

Small win, especially on mobile networks.


62-71: Factor out image-URI normalisation

The identical base64/URL detection logic appears twice. Extract to a helper (e.g., normalizeImageUri(src)) to avoid duplication and keep the component lean.

Also applies to: 100-107


15-53: Guard against state updates after unmount

If the user navigates away before the fetch resolves, setIsLoading / setFriends will run on an unmounted component. Add an isActive flag or AbortController cleanup inside the effect’s return to prevent noisy warnings.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc7991 and 2c4505f.

📒 Files selected for processing (2)
  • backend/app/expenses/service.py (1 hunks)
  • frontend/screens/FriendsScreen.js (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • backend/app/expenses/service.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.
📚 Learning: 2025-07-26T09:41:01.332Z
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.

Applied to files:

  • frontend/screens/FriendsScreen.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: bundle-analysis

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/screens/FriendsScreen.js (1)

36-47: Nice defensive fallback on breakdown array

Using (friend.breakdown || []) prevents crashes. Good fix.

🧹 Nitpick comments (6)
frontend/utils/currency.js (1)

4-7: Optional: Use Intl.NumberFormat and make currency/locale configurable

Improves thousands separators, negative formatting, and future-proofs for multi-currency users while keeping current API intact.

Add an Intl-based formatter alongside current function:

+export const formatCurrencyIntl = (
+  amount,
+  { currency = "INR", locale = "en-IN", minimumFractionDigits = 2, maximumFractionDigits = 2 } = {}
+) => {
+  const num = Number(amount);
+  if (!Number.isFinite(num)) return new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits, maximumFractionDigits }).format(0);
+  return new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits, maximumFractionDigits }).format(num);
+};

And export it:

 export default {
   getCurrencySymbol,
   formatCurrency,
+  formatCurrencyIntl,
 };

Also applies to: 8-13

frontend/screens/FriendsScreen.js (5)

105-111: Handle zero group balances consistently ("Settled up" + gray)

Align group-level display with friend-level: show "Settled up" and gray color when balance is 0.

-          const groupBalanceColor = group.balance < 0 ? "red" : "green";
-          const groupBalanceText =
-            group.balance < 0
-              ? `You owe ${formatCurrency(Math.abs(group.balance))}`
-              : `Owes you ${formatCurrency(group.balance)}`;
+          const isZero = group.balance === 0;
+          const groupBalanceColor = isZero ? "gray" : group.balance < 0 ? "red" : "green";
+          const groupBalanceText = isZero
+            ? "Settled up"
+            : group.balance < 0
+              ? `You owe ${formatCurrency(Math.abs(group.balance))}`
+              : `Owes you ${formatCurrency(group.balance)}`;

70-82: Deduplicate image URI detection with a small helper

Same logic is implemented twice for friend and group avatars. Extract it to a helper to reduce bugs and keep behavior consistent.

Replace the blocks with:

-    let imageUri = null;
-    if (hasImage) {
-      // If it's a raw base64 string without prefix, add a default MIME prefix
-      if (
-        /^data:image/.test(item.imageUrl) ||
-        /^https?:\/\//.test(item.imageUrl)
-      ) {
-        imageUri = item.imageUrl;
-      } else if (/^[A-Za-z0-9+/=]+$/.test(item.imageUrl.substring(0, 50))) {
-        imageUri = `data:image/jpeg;base64,${item.imageUrl}`;
-      }
-    }
+    const imageUri = toImageUri(item.imageUrl);
-          let groupImageUri = null;
-          if (group.imageUrl) {
-            if (
-              /^data:image/.test(group.imageUrl) ||
-              /^https?:\/\//.test(group.imageUrl)
-            ) {
-              groupImageUri = group.imageUrl;
-            } else if (
-              /^[A-Za-z0-9+/=]+$/.test(group.imageUrl.substring(0, 50))
-            ) {
-              groupImageUri = `data:image/jpeg;base64,${group.imageUrl}`;
-            }
-          }
+          const groupImageUri = toImageUri(group.imageUrl);

Add this helper (outside the selected ranges, e.g., above renderFriend):

const toImageUri = (src) => {
  if (!src) return null;
  if (/^data:image/.test(src) || /^https?:\/\//.test(src)) return src;
  const head = src.slice(0, 50);
  if (/^[A-Za-z0-9+/=]+$/.test(head)) return `data:image/jpeg;base64,${src}`;
  return null;
};

Also applies to: 110-123


64-68: Prefer theme colors over raw "red"/"green" for accessibility and theming

Use react-native-paper theme colors so it adapts to light/dark themes and improves contrast.

Example:

-import {
-  Appbar,
-  Avatar,
-  Divider,
-  IconButton,
-  List,
-  Text,
-} from "react-native-paper";
+import {
+  Appbar,
+  Avatar,
+  Divider,
+  IconButton,
+  List,
+  Text,
+  useTheme,
+} from "react-native-paper";
 const FriendsScreen = () => {
+  const { colors } = useTheme();
   ...
-  const balanceColor = item.netBalance < 0 ? "red" : "green";
+  const balanceColor = item.netBalance < 0 ? colors.error : colors.primary;

Apply similar for groupBalanceColor.


233-233: Ensure FlatList keys are strings

Minor nit: coerce id to string to avoid warnings when ids are numeric.

-        keyExtractor={(item) => item.id}
+        keyExtractor={(item) => String(item.id)}

247-251: Remove unused style (loaderContainer) or repurpose

loaderContainer seems unused after skeleton adoption; dead code.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4505f and a2ea885.

📒 Files selected for processing (4)
  • frontend/screens/AccountScreen.js (3 hunks)
  • frontend/screens/FriendsScreen.js (4 hunks)
  • frontend/screens/HomeScreen.js (9 hunks)
  • frontend/utils/currency.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/screens/AccountScreen.js
  • frontend/screens/HomeScreen.js
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.
📚 Learning: 2025-07-26T09:41:01.332Z
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.

Applied to files:

  • frontend/screens/FriendsScreen.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: preview
🔇 Additional comments (2)
frontend/screens/FriendsScreen.js (2)

27-34: Fetch friends and groups truly in parallel (AI summary says concurrent; code is sequential)

Do both requests concurrently to reduce total wait time.

-        // Fetch friends balance + groups concurrently for group icons
-        const friendsResponse = await getFriendsBalance();
-        const friendsData = friendsResponse.data.friendsBalance || [];
-        const groupsResponse = await getGroups();
-        const groups = groupsResponse?.data?.groups || [];
+        // Fetch friends balance + groups concurrently for group icons
+        const [friendsResponse, groupsResponse] = await Promise.all([
+          getFriendsBalance(),
+          getGroups(),
+        ]);
+        const friendsData = friendsResponse?.data?.friendsBalance || [];
+        const groups = groupsResponse?.data?.groups || [];

Likely an incorrect or invalid review comment.


193-206: Shimmer skeleton is well-implemented

Smooth looped animation with proper cleanup; good UX while fetching.

…prove image MIME type handling in profile updates
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/screens/EditProfileScreen.js (1)

24-31: Resolved: Use actual MIME type for data URL (matches prior bot suggestion).

Good fix deriving MIME dynamically and falling back safely. This addresses the earlier comment about hardcoded "image/jpeg".

🧹 Nitpick comments (6)
frontend/screens/EditProfileScreen.js (6)

11-11: Update state shape comment to reflect mimeType.

The comment is stale. It should include mimeType to match the actual state shape.

-  const [pickedImage, setPickedImage] = useState(null); // { uri, base64 }
+  const [pickedImage, setPickedImage] = useState(null); // { uri, base64, mimeType }

15-17: Trim name before validating and sending to API.

Prevents whitespace-only names and keeps payload normalized.

-  const handleUpdateProfile = async () => {
-    if (!name) {
+  const handleUpdateProfile = async () => {
+    const trimmedName = name.trim();
+    if (!trimmedName) {
       Alert.alert("Error", "Name cannot be empty.");
       return;
     }
     setIsSubmitting(true);
     try {
-      const updates = { name };
+      const updates = { name: trimmedName };

Also applies to: 21-21


45-55: Handle iOS “limited” photo access and use granted boolean.

Expo returns a granted boolean and may set accessPrivileges to "limited" on iOS. Treat both as allowed.

-    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
-    if (status !== "granted") {
+    const { status, granted, accessPrivileges } =
+      await ImagePicker.requestMediaLibraryPermissionsAsync();
+    const allowed = granted || status === "granted" || accessPrivileges === "limited";
+    if (!allowed) {
       Alert.alert(
         "Permission required",
         "We need media library permission to select an image."
       );
       return;
     }

62-80: Improve MIME inference for HEIC/HEIF and a few common formats.

Many iOS photos are HEIC/HEIF. Add those mappings to avoid mislabeling as JPEG.

-      let mimeType = asset.mimeType || asset.type; // expo sometimes supplies type like 'image'
+      let mimeType = asset.mimeType || asset.type; // expo sometimes supplies type like 'image'
       if (mimeType && !/image\//.test(mimeType)) {
         // if it's just 'image', normalize
-        if (mimeType === 'image') mimeType = 'image/jpeg';
+        if (mimeType === "image") mimeType = "image/jpeg";
       }
       if (!mimeType || !/image\//.test(mimeType)) {
         // Attempt to infer from file extension as a lightweight fallback
         const ext = (asset.uri || "").split(".").pop()?.toLowerCase();
-        if (ext === "png") mimeType = "image/png";
+        if (ext === "png") mimeType = "image/png";
+        else if (ext === "heic") mimeType = "image/heic";
+        else if (ext === "heif") mimeType = "image/heif";
         else if (ext === "webp") mimeType = "image/webp";
         else if (ext === "gif") mimeType = "image/gif";
         else if (ext === "jpg" || ext === "jpeg") mimeType = "image/jpeg";
         else mimeType = "image/jpeg"; // safe default
       }

45-81: Avoid duplication: extract a shared image-picking helper.

This pickImage logic overlaps with GroupSettingsScreen (frontend/screens/GroupSettingsScreen.js lines 121-143). Consider a small utility (e.g., frontend/utils/imagePicking.js) that:

  • Requests permission (handling “limited”).
  • Launches picker with consistent options.
  • Normalizes/infer mimeType as done here.
  • Returns { uri, base64, mimeType }.

Reduces drift and centralizes future fixes (e.g., HEIC support).


56-61: Optional: downscale before base64 to avoid large payloads.

High-res images can bloat requests and hit 413s. Consider resizing to a sensible bound (e.g., 512–1024 px) with expo-image-manipulator.

Add import:

+import * as ImageManipulator from "expo-image-manipulator";

Inside pickImage, after selecting asset:

// Example: bound longest side to 768, keep aspect ratio
const bound = 768;
const { width = bound, height = bound } = asset;
const scale = Math.min(bound / Math.max(width, height), 1);
const target = scale < 1 ? { width: Math.round(width * scale), height: Math.round(height * scale) } : undefined;

let processed = asset;
if (target) {
  const manipulated = await ImageManipulator.manipulateAsync(
    asset.uri,
    [{ resize: target }],
    { compress: 0.8, base64: true } // keep original format; RN will honor mime when constructing data URL later
  );
  processed = { ...asset, uri: manipulated.uri, base64: manipulated.base64 };
}

Then use processed.uri and processed.base64 for setPickedImage and MIME inference.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2ea885 and 6177ceb.

📒 Files selected for processing (3)
  • frontend/api/client.js (1 hunks)
  • frontend/screens/EditProfileScreen.js (3 hunks)
  • frontend/utils/currency.js (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/utils/currency.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/api/client.js
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.
🧬 Code Graph Analysis (1)
frontend/screens/EditProfileScreen.js (3)
frontend/screens/GroupSettingsScreen.js (1)
  • pickImage (122-144)
frontend/context/AuthContext.js (5)
  • AuthContext (10-10)
  • AuthContext (10-10)
  • user (13-13)
  • updateUserInContext (165-173)
  • updatedUser (103-105)
frontend/api/auth.js (2)
  • updateUser (11-11)
  • updateUser (11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: preview
🔇 Additional comments (1)
frontend/screens/EditProfileScreen.js (1)

33-35: No additional retry logic needed for updateUser

The updateUser call in frontend/api/auth.js uses the shared apiClient from frontend/api/client.js, which already includes:

  • Automatic token refresh on 401 responses
  • Exponential-backoff retries (up to 3 attempts) for network errors and 5xx responses

All update calls therefore benefit from the standardized retry and interceptor behavior.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

✅ Actions performed

Comments resolved.

3 similar comments
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

✅ Actions performed

Comments resolved.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

✅ Actions performed

Comments resolved.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

✅ Actions performed

Comments resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
backend/tests/groups/test_groups_service.py (7)

191-222: Combine nested contexts (SIM117) and assert no writes on blocked removal

Flatten the nested with blocks and assert DB write is not attempted when blocked. Also assert the settlements lookup happened.

-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
-                await self.service.remove_member(group_id, member_id, admin_id)
+        with patch.object(self.service, "get_db", return_value=mock_db), pytest.raises(HTTPException) as exc:
+            await self.service.remove_member(group_id, member_id, admin_id)
 
         assert exc.value.status_code == 400
         assert "unsettled balances" in str(exc.value.detail)
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_not_called()

223-252: Stabilize mocks and strengthen success assertions

  • Use return_value instead of a single-item side_effect to avoid StopIteration if the service calls find_one more than once.
  • Assert the expected DB interactions on the happy path.
-        groups.find_one.side_effect = [
-            {
-                "_id": ObjectId(group_id),
-                "members": [
-                    {"userId": admin_id, "role": "admin"},
-                    {"userId": member_id, "role": "member"},
-                ],
-            }
-        ]
+        groups.find_one.return_value = {
+            "_id": ObjectId(group_id),
+            "members": [
+                {"userId": admin_id, "role": "admin"},
+                {"userId": member_id, "role": "member"},
+            ],
+        }
         settlements.find_one.return_value = None  # No pending settlements
         groups.update_one.return_value = MagicMock(modified_count=1)
 
         with patch.object(self.service, "get_db", return_value=mock_db):
             ok = await self.service.remove_member(group_id, member_id, admin_id)
 
         assert ok is True
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_called_once()

253-280: Combine nested contexts (SIM117) and assert no writes on blocked leave

Flatten the nested with blocks and assert no write occurs when leave is blocked. Also verify settlement lookup.

-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
-                await self.service.leave_group(group_id, user_id)
+        with patch.object(self.service, "get_db", return_value=mock_db), pytest.raises(HTTPException) as exc:
+            await self.service.leave_group(group_id, user_id)
 
         assert exc.value.status_code == 400
         assert "unsettled balances" in str(exc.value.detail)
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_not_called()

281-309: Strengthen success-path assertions

On allowed leave, assert lookups and writes occur exactly once.

         with patch.object(self.service, "get_db", return_value=mock_db):
             ok = await self.service.leave_group(group_id, user_id)
 
         assert ok is True
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_called_once()

528-566: Minor: verify settlements lookup performed in this happy-path test

This improves intent coverage with minimal cost.

         with patch.object(self.service, "get_db", return_value=mock_db):
             result = await self.service.leave_group(
                 "642f1e4a9b3c2d1f6a1b2c3d", "user456"
             )
 
             assert result is True
+            mock_settlements.find_one.assert_called_once()
+            mock_collection.update_one.assert_called_once()

603-673: Apply SIM117 and assert no writes on blocked/exceptional leave paths

  • Flatten nested contexts per Ruff (SIM117).
  • Ensure no write occurs when leave is blocked or when lookup fails.
-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
-                await self.service.leave_group(str(group["_id"]), "member1")
+        with patch.object(self.service, "get_db", return_value=mock_db), pytest.raises(HTTPException) as exc:
+            await self.service.leave_group(str(group["_id"]), "member1")
 
         assert exc.value.status_code == 400
         assert "Cannot leave group with unsettled balances" in exc.value.detail
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_not_called()
-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
-                await self.service.leave_group(str(group["_id"]), "member1")
+        with patch.object(self.service, "get_db", return_value=mock_db), pytest.raises(HTTPException) as exc:
+            await self.service.leave_group(str(group["_id"]), "member1")
 
         assert exc.value.status_code == 503
         assert "Unable to verify unsettled balances" in exc.value.detail
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_not_called()

675-743: Apply SIM117 and assert no writes on blocked/exceptional removal paths

  • Flatten nested contexts per Ruff (SIM117).
  • Ensure no write occurs when removal is blocked or lookup fails.
-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
-                await self.service.remove_member(str(group["_id"]), "member1", "admin1")
+        with patch.object(self.service, "get_db", return_value=mock_db), pytest.raises(HTTPException) as exc:
+            await self.service.remove_member(str(group["_id"]), "member1", "admin1")
 
         assert exc.value.status_code == 400
         assert "Cannot remove member with unsettled balances" in exc.value.detail
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_not_called()
-        with patch.object(self.service, "get_db", return_value=mock_db):
-            with pytest.raises(HTTPException) as exc:
-                await self.service.remove_member(str(group["_id"]), "member1", "admin1")
+        with patch.object(self.service, "get_db", return_value=mock_db), pytest.raises(HTTPException) as exc:
+            await self.service.remove_member(str(group["_id"]), "member1", "admin1")
 
         assert exc.value.status_code == 503
         assert "Unable to verify unsettled balances" in exc.value.detail
+        settlements.find_one.assert_called_once()
+        groups.update_one.assert_not_called()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6177ceb and 42723b8.

📒 Files selected for processing (1)
  • backend/tests/groups/test_groups_service.py (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
backend/**/*.py

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Backend code must be implemented using FastAPI with Python 3.9+ and organized under the /backend/ directory.

Files:

  • backend/tests/groups/test_groups_service.py
backend/tests/**/*.py

📄 CodeRabbit Inference Engine (.github/copilot-instructions.md)

Backend tests must be placed in the /backend/tests/ directory and run using pytest.

Files:

  • backend/tests/groups/test_groups_service.py
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: Devasy23/splitwiser#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-26T09:41:01.332Z
Learning: Applies to ui-poc/pages/Groups.py : API calls from the frontend should use retry logic as demonstrated in Groups.py.
🪛 Ruff (0.12.2)
backend/tests/groups/test_groups_service.py

216-217: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


274-275: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


632-633: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


667-668: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


702-703: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)


737-738: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: bundle-analysis

@Devasy Devasy added level3 and removed level2 labels Aug 9, 2025
@Devasy Devasy added this to Splitwiser Aug 9, 2025
@github-project-automation github-project-automation bot moved this to Backlog in Splitwiser Aug 9, 2025
@Devasy Devasy moved this from Backlog to In review in Splitwiser Aug 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request groups level3

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants