1- import { beforeEach, describe, expect, it, vi } from 'vitest'
1+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
22import { BROWSER_SOURCES, type BrowserSource } from '@/main/browser-import/browser-sources'
33import type { BrowserProfile } from '@/main/browser-import/types'
44import { emptySkipCounts } from '@/main/browser-import/types'
@@ -35,9 +35,33 @@ vi.mock('@/main/browser-import/chromium-crypto', async (importOriginal) => ({
3535const { openInBrowserProfile } = await import('@/main/browser-import/browser-launch')
3636const { listAllBrowserProfiles } = await import('@/main/browser-import/chromium-profiles')
3737const { readBrowserCookies } = await import('@/main/browser-import/chromium-cookies')
38- const { bringSignInBack, cancelSignIn, continueSignIn, getPendingSignIn } = await import(
39- '@/main/browser-import'
40- )
38+ const {
39+ bringSignInBack,
40+ cancelSignIn,
41+ clearSignIns,
42+ continueSignIn,
43+ getPendingSignIn,
44+ migrateSignIn,
45+ } = await import('@/main/browser-import')
46+
47+ /** The importer is macOS-only; pin the platform so the suite means the same on every runner. */
48+ const realPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
49+ beforeAll(() => Object.defineProperty(process, 'platform', { value: 'darwin' }))
50+ afterAll(() => {
51+ if (realPlatform) Object.defineProperty(process, 'platform', realPlatform)
52+ })
53+
54+ /** Holds the next browser launch open until the test releases it. */
55+ function holdLaunch(): () => void {
56+ let release: (() => void) | undefined
57+ vi.mocked(openInBrowserProfile).mockImplementationOnce(
58+ () =>
59+ new Promise<void>((resolve) => {
60+ release = resolve
61+ })
62+ )
63+ return () => release?.()
64+ }
4165
4266const ARC = BROWSER_SOURCES.find(({ id }) => id === 'arc') as BrowserSource
4367const PROFILE: BrowserProfile = {
@@ -71,12 +95,10 @@ function readsCookies(count: number) {
7195}
7296
7397beforeEach(() => {
74- vi.clearAllMocks()
7598 vi.mocked(listAllBrowserProfiles).mockResolvedValue([PROFILE])
7699 vi.mocked(openInBrowserProfile).mockResolvedValue()
77100 readsCookies(2)
78- cancelSignIn('chat-a')
79- cancelSignIn('chat-b')
101+ clearSignIns()
80102})
81103
82104describe('continueSignIn', () => {
@@ -132,6 +154,7 @@ describe('bringSignInBack', () => {
132154 await expect(bringSignInBack('chat-a')).resolves.toEqual({
133155 result: { cookiesImported: 2, cookiesSkipped: 0 },
134156 handoff: expect.objectContaining({ tabId: 'tab-1', url: SIGN_IN_URL }),
157+ current: true,
135158 })
136159 expect(readBrowserCookies).toHaveBeenCalledWith('/arc/Profile 2/Cookies', expect.any(Buffer), {
137160 scope: { domain: 'amazon.com', exact: false },
@@ -167,7 +190,10 @@ describe('bringSignInBack', () => {
167190 await vi.waitFor(() => expect(finishRead).toBeTypeOf('function'))
168191 await continueSignIn('chat-a', request('https://accounts.google.com/signin'))
169192 finishRead?.()
170- await expect(broughtBack).resolves.toMatchObject({ result: { cookiesImported: 2 } })
193+ await expect(broughtBack).resolves.toMatchObject({
194+ result: { cookiesImported: 2 },
195+ current: false,
196+ })
171197
172198 expect(getPendingSignIn('chat-a')).toEqual({ site: 'google.com', browserLabel: 'Arc' })
173199 })
@@ -189,3 +215,56 @@ describe('bringSignInBack', () => {
189215 expect(readBrowserCookies).not.toHaveBeenCalled()
190216 })
191217})
218+
219+ describe('a handoff retired or moved while its browser launches', () => {
220+ it('records nothing once a cancel lands mid-launch', async () => {
221+ const release = holdLaunch()
222+ const continuing = continueSignIn('chat-a', request())
223+ await vi.waitFor(() => expect(openInBrowserProfile).toHaveBeenCalled())
224+ cancelSignIn('chat-a')
225+ release()
226+
227+ await expect(continuing).resolves.toEqual({})
228+ expect(getPendingSignIn('chat-a')).toBeNull()
229+ })
230+
231+ it('lets a newer continue win over an older launch that finishes later', async () => {
232+ const release = holdLaunch()
233+ const older = continueSignIn('chat-a', request())
234+ await vi.waitFor(() => expect(openInBrowserProfile).toHaveBeenCalled())
235+ await continueSignIn('chat-a', request('https://accounts.google.com/signin'))
236+ release()
237+
238+ await expect(older).resolves.toEqual({})
239+ expect(getPendingSignIn('chat-a')).toEqual({ site: 'google.com', browserLabel: 'Arc' })
240+ })
241+
242+ it('follows a provisional chat to its saved id, mid-launch or after', async () => {
243+ const release = holdLaunch()
244+ const continuing = continueSignIn('pending:new-chat', request())
245+ await vi.waitFor(() => expect(openInBrowserProfile).toHaveBeenCalled())
246+ migrateSignIn('pending:new-chat', 'chat-a')
247+ release()
248+ await continuing
249+
250+ expect(getPendingSignIn('chat-a')).toEqual({ site: 'amazon.com', browserLabel: 'Arc' })
251+ expect(getPendingSignIn('pending:new-chat')).toBeNull()
252+ migrateSignIn('chat-a', 'chat-b')
253+ await expect(bringSignInBack('chat-b')).resolves.toMatchObject({
254+ result: { cookiesImported: 2 },
255+ })
256+ })
257+
258+ it('forgets every handoff on account teardown, including one still launching', async () => {
259+ await continueSignIn('chat-a', request())
260+ const release = holdLaunch()
261+ const continuing = continueSignIn('chat-b', request())
262+ await vi.waitFor(() => expect(openInBrowserProfile).toHaveBeenCalledTimes(2))
263+ clearSignIns()
264+ release()
265+
266+ await expect(continuing).resolves.toEqual({})
267+ expect(getPendingSignIn('chat-a')).toBeNull()
268+ expect(getPendingSignIn('chat-b')).toBeNull()
269+ })
270+ })
0 commit comments