Skip to content

fix: Reconcile local authData on anonymous-to-email conversion - #1138

Open
chadpav wants to merge 7 commits into
parse-community:masterfrom
chadpav:pr/anonymous-link-cleanup
Open

chadpav wants to merge 7 commits into
parse-community:masterfrom
chadpav:pr/anonymous-link-cleanup

Conversation

@chadpav

@chadpav chadpav commented May 17, 2026 •

Copy link
Copy Markdown
Contributor

Issue

No existing issue.

Approach

Split out from #1136 at maintainer request. This PR contains only the
original concern of that PR — reconciling local authData when an
anonymous user is converted to email/password.

When a persisted anonymous user has its username set and is saved,
parseUser.save() issues a PUT /classes/_User/:objectId carrying
only the dirty fields. The Dart SDK treated this as a generic
property update — it neither emitted an authData unlink signal in
the request body nor cleaned up the local cache after the save.

The iOS Parse SDK handles this via two helpers in PFUser.m:

  1. stripAnonymity (PFUser.m:593-607): the username setter
    writes authData[anonymous] = NSNull into the local user. The
    PUT body re-injects the map, so the request carries
    authData: { anonymous: null } — Parse Server's documented
    unlink signal.
  2. cleanUpAuthData (PFUser.m:300-313): after a successful
    save, the local authData has any null entries stripped.

Ported to ParseUser as two private helpers:

  • _stripAnonymity() invoked from the username setter. On a
    persisted user it sets authData['anonymous'] = null locally; on
    a lazy user (no objectId) it removes the entry outright.
  • _cleanUpAuthData() invoked from save() and update() between
    the response merge and _onResponseSuccess().

A dartdoc paragraph was added to loginAnonymous documenting the
conversion path. The loginAnonymous request body was also updated
to use the new _keyAuthAnonymous constant rather than the literal
'anonymous'.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)

Behavior change disclosure

  1. Wire — anonymous conversion save: when objectId is set
    and authData contains an anonymous entry, setting username
    and saving now produces a PUT body that includes
    authData: { anonymous: null }. Previously no authData field
    was sent for that save.
  2. Local cache — anonymous conversion: setting username on
    an anonymous-with-objectId ParseUser now mutates the local
    authData map. After a successful save, the anonymous entry
    is removed entirely.

Tests

packages/dart/test/src/objects/parse_user/parse_user_anonymous_link_test.dart
— 6 tests covering:

  • save() reconciles authData on conversion (anonymous-with-objectId)
  • update() reconciles authData on conversion
  • _cleanUpAuthData removes the local anonymous: null marker after save
  • lazy (no-objectId) path removes the entry outright instead of leaving a null
  • empty authData map is not serialized into the lazy POST body
  • non-anonymous users do not synthesize an authData field

All 213 tests in packages/dart/test/ pass.

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling when anonymous accounts transition to email/password accounts.
    • Anonymous authentication markers are now correctly linked during conversion and cleaned up after successful saves or updates.
    • Preserved other authentication providers during conversion and prevented unnecessary authentication data from being sent for non-anonymous accounts.
  • Documentation

    • Expanded documentation for anonymous login behavior.
  • Tests

    • Added coverage for account conversion workflows, request payloads, and authentication state cleanup.

chadpav added 4 commits May 17, 2026 11:30
Setting username on a persisted anonymous user and calling save() now
unlinks the anonymous provider server-side and leaves the local
authData consistent with the cleaned server record — without requiring
a follow-up GET.

Previously, the save's PUT response carried only the dirty fields
written by the client (no authData), so the additive response merge
in _handleSingleResult could not reconcile the local authData copy.
Apps had to follow up with GET /users/me to refresh local state.

This mirrors iOS PFUser's stripAnonymity + cleanUpAuthData pattern.
No public API changes; the new helpers are private.
save() and update() both run _cleanUpAuthData() in their success branch
but were patched independently. Add a parallel test that invokes
update() instead of save() so a future regression in either path is
caught.

Also tightened test descriptions to focus on client-side behavior.
On the lazy (no objectId) path of _stripAnonymity, removing the
anonymous provider could leave an empty map in _unsavedChanges
under keyVarAuthData. The empty map then serialized into the POST
body via toJson(forApiRQ: true) on signUp, sending wire noise the
SDK shouldn't emit. Mirrors the isEmpty handling already present
in _cleanUpAuthData.
Addresses upstream PR parse-community#1136 review feedback. The loginAnonymous
request body literally wrote 'anonymous' while the rest of the file
(stripAnonymity branches at lines 537/541/543) references the
`_keyAuthAnonymous` constant defined at line 46. One-line change
brings the body into alignment.
@parse-github-assistant

Copy link
Copy Markdown

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai

coderabbitai Bot commented May 17, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: c700d921-3ea8-4ad9-a2e2-ceea40dd0c65

📥 Commits

Reviewing files that changed from the base of the PR and between fd0ddf3 and bb9132c.

📒 Files selected for processing (3)
  • packages/dart/test/src/network/parse_client_retry_integration_test.mocks.dart
  • packages/dart/test/src/network/parse_query_test.mocks.dart
  • packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

This PR updates ParseUser to convert anonymous users to permanent accounts. It reconciles anonymous auth data, cleans null entries after persistence, standardizes the provider key, and adds conversion tests.

Changes

Anonymous Account Conversion for ParseUser

Layer / File(s) Summary
Anonymous auth key and conversion contract
packages/dart/lib/src/objects/parse_user.dart
Defines _keyAuthAnonymous, updates anonymous login documentation, and uses the constant in the login payload.
Anonymous auth reconciliation
packages/dart/lib/src/objects/parse_user.dart
The username setter calls _stripAnonymity(). The helper removes or nullifies anonymous auth data based on persistence state and updates _unsavedChanges.
Persistence cleanup
packages/dart/lib/src/objects/parse_user.dart
Successful save() and update() operations call _cleanUpAuthData(). The helper removes null provider values and empty authData maps.
Conversion validation and generated mocks
packages/dart/test/src/objects/parse_user/parse_user_anonymous_link_test.dart, packages/dart/test/src/network/*.mocks.dart, packages/dart/test/src/objects/parse_object/parse_object_test.mocks.dart
Tests validate persisted and unpersisted conversion, null-marker serialization, provider preservation, cleanup after save and update, and non-anonymous behavior. Generated mock files also receive whitespace-only changes.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant ParseUser
  participant ParseServer

  App->>ParseUser: Set username
  ParseUser->>ParseUser: Reconcile anonymous authData
  App->>ParseUser: Call save() or update()
  ParseUser->>ParseServer: Send authData.anonymous = null for persisted conversion
  ParseServer-->>ParseUser: Return successful response
  ParseUser->>ParseUser: Remove null authData entries
  ParseUser-->>App: Return updated ParseUser
Loading

Merge Risk: ⚪ Minimal · up to a2e4b

The anonymous-account conversion behavior is covered by the supplied tests, and the remaining investigated concern is not introduced by this PR.

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the required fix: prefix, starts the summary with a capitalized letter, and accurately describes the anonymous-to-email authentication change.
Description check ✅ Passed The description includes the required Issue, Approach, and Tasks sections. It also documents the implementation, behavior changes, tests, and documentation updates. The omitted repository boilerplate …
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed No security vulnerability is introduced by the pull request. The production diff only adds a fixed private anonymous key, reconciles the current user's local authData, and sends the documented `au…
Engage In Review Feedback ✅ Passed The PR satisfies the review-engagement requirement. GitHub review data shows the author discussed the null-guard concern (comments 3255062090 and 3255064566); CodeRabbit then withdrew the suggestion a…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented May 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.42%. Comparing base (8055316) to head (a2e4b70).

Files with missing lines Patch % Lines
packages/dart/lib/src/objects/parse_user.dart 83.33% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1138      +/-   ##
==========================================
+ Coverage   44.42%   46.42%   +2.00%     
==========================================
  Files          62       62              
  Lines        3730     3752      +22     
==========================================
+ Hits         1657     1742      +85     
+ Misses       2073     2010      -63     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/dart/test/src/objects/parse_user/parse_user_anonymous_link_test.dart (1)

218-239: ⚡ Quick win

Exercise the lazy sign-up request body too.

This branch only checks local state. The behavior the new helper is actually protecting here is dropping authData from the lazy-user POST once the anonymous map becomes empty, so a client.post(...) assertion would catch regressions that still serialize authData: {}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/dart/test/src/objects/parse_user/parse_user_anonymous_link_test.dart`
around lines 218 - 239, The test only checks local state after setting
ParseUser.username but doesn't assert the lazy sign-up HTTP body; extend the
test to perform the lazy sign-up (e.g., call the code path that triggers
client.post for unpersisted users) and add an assertion on the mocked
client.post invocation to ensure the posted JSON does not include an empty
authData (i.e., no "authData" key or no empty map) after the anonymous map was
dropped; locate the test around ParseUser creation and user.username assignment
in parse_user_anonymous_link_test.dart and add the client.post verification
referencing client.post, ParseUser, and the lazy sign-up call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dart/lib/src/objects/parse_user.dart`:
- Around line 74-77: The username setter currently calls _stripAnonymity() even
when username is being cleared, which causes anonymous auth to be nulled; update
the setter (the set username(String? username) method) to only call
_stripAnonymity() when the incoming username is non-null (i.e., guard
_stripAnonymity() behind a username != null check) before calling
set<String?>(keyVarUsername, username) so clearing username does not queue
anonymous unlinking.

---

Nitpick comments:
In
`@packages/dart/test/src/objects/parse_user/parse_user_anonymous_link_test.dart`:
- Around line 218-239: The test only checks local state after setting
ParseUser.username but doesn't assert the lazy sign-up HTTP body; extend the
test to perform the lazy sign-up (e.g., call the code path that triggers
client.post for unpersisted users) and add an assertion on the mocked
client.post invocation to ensure the posted JSON does not include an empty
authData (i.e., no "authData" key or no empty map) after the anonymous map was
dropped; locate the test around ParseUser creation and user.username assignment
in parse_user_anonymous_link_test.dart and add the client.post verification
referencing client.post, ParseUser, and the lazy sign-up call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d3fdeb84-d5c1-48b9-b3af-a00aa85093bd

📥 Commits

Reviewing files that changed from the base of the PR and between 785f76b and 0df6c12.

📒 Files selected for processing (2)
  • packages/dart/lib/src/objects/parse_user.dart
  • packages/dart/test/src/objects/parse_user/parse_user_anonymous_link_test.dart

Comment thread packages/dart/lib/src/objects/parse_user.dart
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 19, 2026
The lazy-user cleanup exists to keep an emptied anonymous map out of
the sign-up POST; the test previously checked only local state. It now
captures the mocked sign-up request and asserts no authData key is
serialized.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chadpav

chadpav commented Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review nitpick in fd0ddf3 — the lazy-user test now performs the sign-up and asserts the captured POST body contains no authData key once the anonymous map was dropped.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 24, 2026
… clean

CI resolves fresh (no lockfile) and its build_runner/source_gen emit a
blank line after the ignore_for_file header; the committed mocks lacked
it, so CI's own regeneration dirtied the tree and 'dart pub publish
--dry-run' failed with exit 65 on the stable Dart legs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant