Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 1 addition & 13 deletions apps/tradinggoose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
# - Configure system-managed integration OAuth apps in Admin Integrations instead:
# google-drive, google-email, github-repo, microsoft, slack, reddit, etc.
# - Platform-managed vars like NODE_ENV, NEXT_RUNTIME, and VERCEL are omitted on purpose.
# - Internal React Email preview vars like EMAILS_DIR_ABSOLUTE_PATH and
# PREVIEW_SERVER_LOCATION are also omitted on purpose.
# - Internal React Email preview vars are omitted on purpose.
#
# Notes:
# - Boolean-like flags should use true/false unless noted otherwise.
Expand Down Expand Up @@ -232,14 +231,6 @@ KB_CONFIG_DELAY_BETWEEN_DOCUMENTS="50"
# NEXT_PUBLIC_POSTHOG_KEY=""
NEXT_PUBLIC_POSTHOG_DISABLED="1"

###############################################################################
# Email preview / local tooling
###############################################################################

# Optional: only used by React Email preview and similar local tooling when
# NEXT_PUBLIC_APP_URL is not available in that process.
# EMAILS_PREVIEW_BASE_URL="http://localhost:3000"

###############################################################################
# Deployment and local infrastructure helpers
###############################################################################
Expand All @@ -255,6 +246,3 @@ NEXT_PUBLIC_POSTHOG_DISABLED="1"

# Optional: tell the app it is running inside a Docker image build.
DOCKER_BUILD="false"

# Optional: landing-page preview/dev helper used by getBaseUrl().
# NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT="false"
110 changes: 95 additions & 15 deletions apps/tradinggoose/app/(auth)/auth-locale-redirects.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { VerifyContent } from './verify/verify-content'
const mockPush = vi.hoisted(() => vi.fn())
const mockSignUpEmail = vi.hoisted(() => vi.fn())
const mockSignInEmail = vi.hoisted(() => vi.fn())
const mockSignOut = vi.hoisted(() => vi.fn())
const mockSendVerificationOtp = vi.hoisted(() => vi.fn())
const mockRefetchSession = vi.hoisted(() => vi.fn())
const mockUseVerification = vi.hoisted(() => vi.fn())
Expand Down Expand Up @@ -45,6 +46,7 @@ vi.mock('@/i18n/navigation', () => ({
useRouter: () => ({
push: mockPush,
}),
usePathname: () => '/login',
}))

vi.mock('@/lib/auth-client', () => ({
Expand All @@ -55,6 +57,7 @@ vi.mock('@/lib/auth-client', () => ({
signIn: {
email: mockSignInEmail,
},
signOut: mockSignOut,
emailOtp: {
sendVerificationOtp: mockSendVerificationOtp,
},
Expand Down Expand Up @@ -107,7 +110,11 @@ vi.mock('@/components/ui/label', () => ({
...props
}: React.LabelHTMLAttributes<HTMLLabelElement> & {
children?: React.ReactNode
}) => <label {...props}>{children}</label>,
}) => (
<label {...props} htmlFor={props.htmlFor ?? 'test-field'}>
{children}
</label>
),
}))

vi.mock('@/components/ui/dialog', () => ({
Expand Down Expand Up @@ -149,6 +156,7 @@ describe('auth locale redirects', () => {
mockPush.mockReset()
mockSignUpEmail.mockReset()
mockSignInEmail.mockReset()
mockSignOut.mockReset()
mockSendVerificationOtp.mockReset()
mockRefetchSession.mockReset()
mockUseVerification.mockReset()
Expand All @@ -162,6 +170,7 @@ describe('auth locale redirects', () => {
root.unmount()
})
container.remove()
vi.useRealTimers()
vi.restoreAllMocks()
reactActEnvironment.IS_REACT_ACT_ENVIRONMENT = false
})
Expand Down Expand Up @@ -204,6 +213,18 @@ describe('auth locale redirects', () => {
})
}

async function renderLogin(locale: 'en' | 'es' | 'zh' = 'en') {
await renderWithLocale(
locale,
<LoginPage
githubAvailable={false}
googleAvailable={false}
isProduction={false}
registrationMode='open'
/>
)
}

it.each(['es', 'zh'] as const)(
'pushes the canonical verify path after signup for %s',
async (locale) => {
Expand All @@ -226,6 +247,8 @@ describe('auth locale redirects', () => {
await setInputValue('#password', 'Password1!')
await submitRenderedForm()

expect(mockRefetchSession).not.toHaveBeenCalled()
expect(mockFetch).not.toHaveBeenCalled()
expect(mockPush).toHaveBeenCalledWith('/verify?fromSignup=true')
}
)
Expand All @@ -235,15 +258,7 @@ describe('auth locale redirects', () => {
async (locale) => {
mockSignInEmail.mockRejectedValue({ code: 'EMAIL_NOT_VERIFIED' })

await renderWithLocale(
locale,
<LoginPage
githubAvailable={false}
googleAvailable={false}
isProduction={false}
registrationMode='open'
/>
)
await renderLogin(locale)

await setInputValue('#email', 'ada@example.com')
await setInputValue('#password', 'Password1!')
Expand All @@ -253,6 +268,75 @@ describe('auth locale redirects', () => {
}
)

it('runs reauth cleanup on arrival and waits before direct login starts', async () => {
vi.useFakeTimers()
testState.searchParams = new URLSearchParams('reauth=1&callbackUrl=%2Fworkspace')
const cleanupSignalRef: { current: AbortSignal | null } = { current: null }
mockSignOut.mockImplementation((options) => {
cleanupSignalRef.current = options?.fetchOptions?.signal ?? null
return new Promise(() => {})
})
mockSignInEmail.mockResolvedValue({})

await renderLogin()

expect(mockSignOut).toHaveBeenCalledTimes(1)
expect(container.querySelector('form')).toBeInstanceOf(HTMLFormElement)

await setInputValue('#email', 'ada@example.com')
await setInputValue('#password', 'Password1!')

await submitRenderedForm()

expect(mockSignInEmail).not.toHaveBeenCalled()

await act(async () => {
await vi.runOnlyPendingTimersAsync()
await Promise.resolve()
})

expect(cleanupSignalRef.current?.aborted).toBe(true)
expect(mockSignInEmail).toHaveBeenCalledTimes(1)
})

it.each([
'FAILED_TO_CREATE_SESSION',
'UNABLE_TO_CREATE_SESSION',
'FAILED_TO_GET_SESSION',
'SESSION_EXPIRED',
])('runs reauth cleanup when direct login returns %s', async (errorCode) => {
mockSignInEmail.mockResolvedValue({ error: { code: errorCode } })
mockSignOut.mockReturnValue(new Promise(() => {}))

await renderLogin()

await setInputValue('#email', 'ada@example.com')
await setInputValue('#password', 'Password1!')
await submitRenderedForm()

expect(mockSignInEmail).toHaveBeenCalledTimes(1)
expect(mockSignOut).toHaveBeenCalledTimes(1)
expect(container.textContent).toContain(getPublicCopy('en').auth.login.errors.unableToSignInNow)
})

it('keeps invalid credential failures on the login form', async () => {
mockSignInEmail.mockResolvedValue({
error: { code: 'INVALID_CREDENTIALS', status: 401 },
})

await renderLogin()

await setInputValue('#email', 'ada@example.com')
await setInputValue('#password', 'wrong-password')
await submitRenderedForm()

expect(mockSignOut).not.toHaveBeenCalled()
expect(container.querySelector('form')).toBeInstanceOf(HTMLFormElement)
expect(container.textContent).toContain(
getPublicCopy('en').auth.login.errors.invalidCredentials
)
})

it('pushes the canonical signup path from the verify screen back action', async () => {
mockUseVerification.mockReturnValue({
otp: '',
Expand All @@ -272,11 +356,7 @@ describe('auth locale redirects', () => {

await renderWithLocale(
'en',
<VerifyContent
hasEmailService
isProduction={false}
isEmailVerificationEnabled
/>
<VerifyContent hasEmailService isProduction={false} isEmailVerificationEnabled />
)

const backButton = Array.from(container.querySelectorAll('button')).find(
Expand Down
Loading
Loading