From b245cbcc4a86359ca30cd8707a7bf4c3d4716836 Mon Sep 17 00:00:00 2001 From: Taesu Date: Fri, 16 Oct 2026 09:47:19 +0900 Subject: [PATCH] test(notion): verify stored image preservation --- .changeset/mellow-notion-owner.md | 2 +- .../src/oauth2/link-account.test.ts | 89 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/.changeset/mellow-notion-owner.md b/.changeset/mellow-notion-owner.md index d448d5592d..37ec21a3bb 100644 --- a/.changeset/mellow-notion-owner.md +++ b/.changeset/mellow-notion-owner.md @@ -2,4 +2,4 @@ "@better-auth/core": minor --- -Allow partial Notion owner profiles and nullable display fields in `NotionProfile`, and leave the local image unset when no owner avatar is returned. +Allow partial Notion owner profiles and nullable display fields in `NotionProfile`, and leave the local image unset when no owner avatar is returned. Existing user images are preserved when profile updates are enabled and the provider returns no image. diff --git a/packages/better-auth/src/oauth2/link-account.test.ts b/packages/better-auth/src/oauth2/link-account.test.ts index 3c43697ec6..9f2e2009c1 100644 --- a/packages/better-auth/src/oauth2/link-account.test.ts +++ b/packages/better-auth/src/oauth2/link-account.test.ts @@ -4,6 +4,7 @@ import type { GoogleProfile, } from "@better-auth/core/social-providers"; import { NodeSqliteDialect } from "@better-auth/kysely-adapter/node-sqlite-dialect"; +import { getTestInstance } from "better-auth/test"; import { Kysely } from "kysely"; import { HttpResponse, http } from "msw"; import { setupServer } from "msw/node"; @@ -23,7 +24,6 @@ import { createAuthClient } from "../client"; import { parseSetCookieHeader, setCookieToHeader } from "../cookies"; import { signJWT } from "../crypto"; import { getMigrations } from "../db/get-migration"; -import { getTestInstance } from "../test-utils/test-instance"; import type { User } from "../types"; import { DEFAULT_SECRET } from "../utils/constants"; @@ -2710,3 +2710,90 @@ describe("oauth2 - account-linking logs use the configured logger", async () => ); }); }); + +/** + * @see https://developers.notion.com/reference/user + * @see https://github.com/better-auth/better-auth/issues/8742 + */ +it.each(["sign-in", "link"] as const)( + "preserves the stored image when Notion has no avatar during %s", + async (flow) => { + const storedImage = "https://example.com/stored-avatar.png"; + const { auth, client, testUser, signInWithTestUser, cookieSetter } = + await getTestInstance( + { + socialProviders: { + notion: { + clientId: "notion-app", + clientSecret: "notion-secret", + overrideUserInfoOnSignIn: true, + }, + }, + account: { + accountLinking: { + trustedProviders: ["notion"], + updateUserInfoOnLink: true, + }, + }, + }, + { testUser: { image: storedImage } }, + ); + const { user, headers } = await signInWithTestUser(); + const ctx = await auth.$context; + if (flow === "sign-in") { + await ctx.internalAdapter.createAccount({ + userId: user.id, + providerId: "notion", + accountId: "notion-owner", + }); + } + server.use( + http.post("https://api.notion.com/v1/oauth/token", () => + HttpResponse.json({ + access_token: "notion-token", + token_type: "bearer", + }), + ), + http.get("https://api.notion.com/v1/users/me", () => + HttpResponse.json({ + bot: { + owner: { + user: { + object: "user", + id: "notion-owner", + type: "person", + name: "Notion User", + avatar_url: null, + person: { email: testUser.email }, + }, + }, + }, + }), + ), + ); + const request = { provider: "notion" as const, callbackURL: "/" }; + const options = { headers, onSuccess: cookieSetter(headers) }; + const response = + flow === "link" + ? await client.linkSocial(request, options) + : await client.signIn.social(request, options); + expect(response.error).toBeNull(); + const state = new URL(response.data!.url!).searchParams.get("state"); + await client.$fetch("/callback/notion", { + query: { state, code: "notion-code" }, + headers, + onError({ response }) { + expect(response.status).toBe(302); + expect( + new URL( + response.headers.get("location")!, + "http://localhost:3000", + ).searchParams.get("error"), + ).toBeNull(); + }, + }); + const updated = await ctx.internalAdapter.findUserById(user.id); + expect(updated?.name).toBe("Notion User"); + expect(updated?.image).toBe(storedImage); + }, +);