mirror of
https://github.com/RSSNext/Folo.git
synced 2026-10-17 05:08:13 +08:00
fix(store): roll back profile and email changes the auth server rejects
updateProfile, updateEmail and sendVerificationEmail awaited the better-auth client, which resolves with an error instead of throwing. On mobile a rejected handle or email kept the new value in the local user and database and showed a success toast, and a failed verification email reported as sent. They now throw on an auth error, so the optimistic change rolls back and callers show the error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import type { AuthClient } from "@follow/shared/auth"
|
||||
import { FollowAPIError, FollowAuthError, FollowTimeoutError } from "@follow-app/client-sdk"
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||
|
||||
import { apiContext } from "../../context"
|
||||
import { apiContext, authClientContext } from "../../context"
|
||||
import type { FollowAPI } from "../../types"
|
||||
import type { MeModel } from "./store"
|
||||
import { userSyncService, useUserStore } from "./store"
|
||||
@@ -57,3 +58,32 @@ describe("userSyncService.whoami", () => {
|
||||
expect(useUserStore.getState().whoami).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("userSyncService.updateProfile", () => {
|
||||
const updateUserMock = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
useUserStore.setState({ users: {}, whoami: me, role: null, roleEndAt: null })
|
||||
authClientContext.provide({ updateUser: updateUserMock } as unknown as AuthClient)
|
||||
})
|
||||
|
||||
it("rolls the change back when the auth server rejects it", async () => {
|
||||
updateUserMock.mockResolvedValue({
|
||||
data: null,
|
||||
error: { message: "Handle is already taken", status: 400, statusText: "Bad Request" },
|
||||
})
|
||||
|
||||
await expect(userSyncService.updateProfile({ handle: "taken" })).rejects.toThrow(
|
||||
"Handle is already taken",
|
||||
)
|
||||
expect(useUserStore.getState().whoami).toEqual(me)
|
||||
})
|
||||
|
||||
it("keeps the change when the auth server accepts it", async () => {
|
||||
updateUserMock.mockResolvedValue({ data: { status: true }, error: null })
|
||||
|
||||
await userSyncService.updateProfile({ handle: "mine" })
|
||||
expect(useUserStore.getState().whoami).toEqual({ ...me, handle: "mine" })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -119,10 +119,14 @@ class UserSyncService {
|
||||
})
|
||||
|
||||
tx.request(async () => {
|
||||
await authClient().updateUser({
|
||||
const res = await authClient().updateUser({
|
||||
...data,
|
||||
socialLinks: (data.socialLinks || null) as any,
|
||||
})
|
||||
// The auth client resolves with the error instead of throwing; throw so the change rolls back
|
||||
if (res.error) {
|
||||
throw new Error(res.error.message || res.error.statusText)
|
||||
}
|
||||
})
|
||||
tx.persist(async () => {
|
||||
const { whoami } = get()
|
||||
@@ -145,7 +149,10 @@ class UserSyncService {
|
||||
async sendVerificationEmail() {
|
||||
const me = get().whoami
|
||||
if (!me?.email) return
|
||||
await authClient().sendVerificationEmail({ email: me.email! })
|
||||
const res = await authClient().sendVerificationEmail({ email: me.email! })
|
||||
if (res.error) {
|
||||
throw new Error(res.error.message || res.error.statusText)
|
||||
}
|
||||
}
|
||||
|
||||
async updateTwoFactor(enabled: boolean, password: string) {
|
||||
@@ -182,7 +189,10 @@ class UserSyncService {
|
||||
tx.request(async () => {
|
||||
const { whoami } = get()
|
||||
if (!whoami) return
|
||||
await authClient().changeEmail({ newEmail: email })
|
||||
const res = await authClient().changeEmail({ newEmail: email })
|
||||
if (res.error) {
|
||||
throw new Error(res.error.message || res.error.statusText)
|
||||
}
|
||||
})
|
||||
tx.rollback(() => {
|
||||
immerSet((state) => {
|
||||
|
||||
Reference in New Issue
Block a user