mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-10 22:20:25 +00:00
test(web): standardize frontend tests on Vitest (#6569)
* test(web): standardize frontend tests on Vitest - configure Vitest, jsdom, and React Testing Library with shared test scripts. - migrate existing node:test suites to the Vitest runner. - rewrite JsonCodeEditor component tests with RTL and remove the direct happy-dom dependency. * fix(ci): run frontend tests with Vitest - invoke the configured Vitest script so browser test setup loads in CI. - migrate remaining node:test suites to Vitest lifecycle APIs. * test(web): use shared jsdom environment for component tests - migrate usage cost and tool price tests to React Testing Library. - remove duplicate happy-dom globals and rely on the configured Vitest setup. * test(web): verify behavior with shared vitest setup - replace Node test assertions with Vitest expect across frontend suites. - migrate Keys component tests to React Testing Library interactions. - centralize jsdom browser mocks for consistent component execution. * fix(web): unblock frozen installs and Vitest CI - sync dompurify 3.4.13 metadata into the Bun lockfile. - replace the bun:test and happy-dom redemption harness with Vitest and RTL. - preserve quota conversion, error feedback, and stale-response coverage in jsdom.
This commit is contained in:
@@ -16,10 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { afterEach, describe, test } from 'node:test'
|
||||
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { afterEach, describe, expect, test } from 'vitest'
|
||||
|
||||
import { useAuthStore, type AuthBundle } from '../stores/auth-store'
|
||||
import {
|
||||
@@ -59,10 +57,10 @@ afterEach(() => {
|
||||
describe('authentication session coordination', () => {
|
||||
test('bootstrap distinguishes a completed anonymous check from an active session', async () => {
|
||||
useAuthStore.getState().auth.reset('complete')
|
||||
assert.deepEqual(await bootstrapAuthentication(), { kind: 'anonymous' })
|
||||
expect(await bootstrapAuthentication()).toEqual({ kind: 'anonymous' })
|
||||
|
||||
useAuthStore.getState().auth.setBundle(bundle)
|
||||
assert.deepEqual(await bootstrapAuthentication(), {
|
||||
expect(await bootstrapAuthentication()).toEqual({
|
||||
kind: 'authenticated',
|
||||
bundle,
|
||||
})
|
||||
@@ -97,10 +95,10 @@ describe('authentication session coordination', () => {
|
||||
|
||||
const outcome = await createRefreshRunner(runtime)()
|
||||
|
||||
assert.equal(outcome.kind, 'authenticated')
|
||||
assert.deepEqual(requestedSIDs, [bundle.session.sid, undefined])
|
||||
assert.deepEqual(clears, [[false, 'idle']])
|
||||
assert.deepEqual(accepted, [bundle])
|
||||
expect(outcome.kind).toBe('authenticated')
|
||||
expect(requestedSIDs).toEqual([bundle.session.sid, undefined])
|
||||
expect(clears).toEqual([[false, 'idle']])
|
||||
expect(accepted).toEqual([bundle])
|
||||
})
|
||||
|
||||
test('a rejected refresh confirms anonymous state and synchronizes sign-out', async () => {
|
||||
@@ -117,10 +115,10 @@ describe('authentication session coordination', () => {
|
||||
wait: async () => undefined,
|
||||
}
|
||||
|
||||
assert.deepEqual(await createRefreshRunner(runtime)(), {
|
||||
expect(await createRefreshRunner(runtime)()).toEqual({
|
||||
kind: 'anonymous',
|
||||
})
|
||||
assert.deepEqual(clears, [[true, undefined]])
|
||||
expect(clears).toEqual([[true, undefined]])
|
||||
})
|
||||
|
||||
test('a temporary refresh failure remains retryable without clearing the session', async () => {
|
||||
@@ -142,9 +140,9 @@ describe('authentication session coordination', () => {
|
||||
|
||||
const outcome = await createRefreshRunner(runtime)()
|
||||
|
||||
assert.equal(outcome.kind, 'transient_error')
|
||||
assert.equal(clearCount, 0)
|
||||
assert.equal(transientCount, 1)
|
||||
expect(outcome.kind).toBe('transient_error')
|
||||
expect(clearCount).toBe(0)
|
||||
expect(transientCount).toBe(1)
|
||||
})
|
||||
|
||||
test('a rate limited refresh remains retryable without clearing the session', async () => {
|
||||
@@ -166,9 +164,9 @@ describe('authentication session coordination', () => {
|
||||
|
||||
const outcome = await createRefreshRunner(runtime)()
|
||||
|
||||
assert.equal(outcome.kind, 'transient_error')
|
||||
assert.equal(clearCount, 0)
|
||||
assert.equal(transientCount, 1)
|
||||
expect(outcome.kind).toBe('transient_error')
|
||||
expect(clearCount).toBe(0)
|
||||
expect(transientCount).toBe(1)
|
||||
})
|
||||
|
||||
test('an exhausted refresh race clears the unusable local session', async () => {
|
||||
@@ -191,12 +189,12 @@ describe('authentication session coordination', () => {
|
||||
},
|
||||
}
|
||||
|
||||
assert.deepEqual(await createRefreshRunner(runtime)(), {
|
||||
expect(await createRefreshRunner(runtime)()).toEqual({
|
||||
kind: 'out_of_sync',
|
||||
code: 'AUTH_REFRESH_RACE',
|
||||
})
|
||||
assert.deepEqual(requestedDelays, [80, 200, 500])
|
||||
assert.deepEqual(clears, [[false, undefined]])
|
||||
expect(requestedDelays).toEqual([80, 200, 500])
|
||||
expect(clears).toEqual([[false, undefined]])
|
||||
})
|
||||
|
||||
test('an unexpected successful response is treated as out of sync', async () => {
|
||||
@@ -213,11 +211,11 @@ describe('authentication session coordination', () => {
|
||||
wait: async () => undefined,
|
||||
}
|
||||
|
||||
assert.deepEqual(await createRefreshRunner(runtime)(), {
|
||||
expect(await createRefreshRunner(runtime)()).toEqual({
|
||||
kind: 'out_of_sync',
|
||||
code: 'AUTH_INVALID_REFRESH_RESPONSE',
|
||||
})
|
||||
assert.equal(cleared, true)
|
||||
expect(cleared).toBe(true)
|
||||
})
|
||||
|
||||
test('a refresh response cannot restore credentials after a newer auth operation', async () => {
|
||||
@@ -241,8 +239,8 @@ describe('authentication session coordination', () => {
|
||||
|
||||
const outcome = await createRefreshRunner(runtime)()
|
||||
|
||||
assert.equal(outcome.kind, 'transient_error')
|
||||
assert.equal(accepted, false)
|
||||
expect(outcome.kind).toBe('transient_error')
|
||||
expect(accepted).toBe(false)
|
||||
})
|
||||
|
||||
test('explicit rotations update only the current session', () => {
|
||||
@@ -254,41 +252,35 @@ describe('authentication session coordination', () => {
|
||||
session: { ...bundle.session, last_active_at: 200 },
|
||||
})
|
||||
|
||||
assert.equal(useAuthStore.getState().auth.accessToken, 'rotated-token')
|
||||
assert.strictEqual(useAuthStore.getState().auth.user, bundle.user)
|
||||
expect(useAuthStore.getState().auth.accessToken).toBe('rotated-token')
|
||||
expect(useAuthStore.getState().auth.user).toBe(bundle.user)
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
applyAuthRotation({
|
||||
access_token: 'non-bearer-token',
|
||||
token_type: 'Custom',
|
||||
access_expires_at: bundle.access_expires_at + 120,
|
||||
session: bundle.session,
|
||||
}),
|
||||
/Invalid authentication rotation response/
|
||||
)
|
||||
assert.throws(
|
||||
() =>
|
||||
applyAuthRotation({
|
||||
access_token: 'non-current-token',
|
||||
token_type: 'Bearer',
|
||||
access_expires_at: bundle.access_expires_at + 120,
|
||||
session: { ...bundle.session, current: false },
|
||||
}),
|
||||
/Invalid authentication rotation response/
|
||||
)
|
||||
expect(() =>
|
||||
applyAuthRotation({
|
||||
access_token: 'non-bearer-token',
|
||||
token_type: 'Custom',
|
||||
access_expires_at: bundle.access_expires_at + 120,
|
||||
session: bundle.session,
|
||||
})
|
||||
).toThrow(/Invalid authentication rotation response/)
|
||||
expect(() =>
|
||||
applyAuthRotation({
|
||||
access_token: 'non-current-token',
|
||||
token_type: 'Bearer',
|
||||
access_expires_at: bundle.access_expires_at + 120,
|
||||
session: { ...bundle.session, current: false },
|
||||
})
|
||||
).toThrow(/Invalid authentication rotation response/)
|
||||
|
||||
assert.throws(
|
||||
() =>
|
||||
applyAuthRotation({
|
||||
access_token: 'wrong-session-token',
|
||||
token_type: 'Bearer',
|
||||
access_expires_at: bundle.access_expires_at + 120,
|
||||
session: { ...bundle.session, sid: 'session-b' },
|
||||
}),
|
||||
/session mismatch/
|
||||
)
|
||||
assert.equal(useAuthStore.getState().auth.accessToken, 'rotated-token')
|
||||
expect(() =>
|
||||
applyAuthRotation({
|
||||
access_token: 'wrong-session-token',
|
||||
token_type: 'Bearer',
|
||||
access_expires_at: bundle.access_expires_at + 120,
|
||||
session: { ...bundle.session, sid: 'session-b' },
|
||||
})
|
||||
).toThrow(/session mismatch/)
|
||||
expect(useAuthStore.getState().auth.accessToken).toBe('rotated-token')
|
||||
})
|
||||
|
||||
test('sign-out clears user-scoped query, mutation, and authentication state', () => {
|
||||
@@ -305,13 +297,13 @@ describe('authentication session coordination', () => {
|
||||
|
||||
clearAuthenticatedClientState(queryClient, false)
|
||||
|
||||
assert.equal(queryClient.getQueryCache().getAll().length, 0)
|
||||
assert.equal(queryClient.getMutationCache().getAll().length, 0)
|
||||
assert.equal(useAuthStore.getState().auth.user, null)
|
||||
assert.equal(useAuthStore.getState().auth.accessToken, null)
|
||||
assert.equal(useAuthStore.getState().auth.session, null)
|
||||
assert.equal(useAuthStore.getState().auth.pending2FAFlowToken, null)
|
||||
assert.equal(useAuthStore.getState().auth.bootstrapState, 'complete')
|
||||
expect(queryClient.getQueryCache().getAll().length).toBe(0)
|
||||
expect(queryClient.getMutationCache().getAll().length).toBe(0)
|
||||
expect(useAuthStore.getState().auth.user).toBe(null)
|
||||
expect(useAuthStore.getState().auth.accessToken).toBe(null)
|
||||
expect(useAuthStore.getState().auth.session).toBe(null)
|
||||
expect(useAuthStore.getState().auth.pending2FAFlowToken).toBe(null)
|
||||
expect(useAuthStore.getState().auth.bootstrapState).toBe('complete')
|
||||
|
||||
const nextBundle: AuthBundle = {
|
||||
...bundle,
|
||||
@@ -320,8 +312,7 @@ describe('authentication session coordination', () => {
|
||||
session: { ...bundle.session, sid: 'session-b' },
|
||||
}
|
||||
useAuthStore.getState().auth.setBundle(nextBundle)
|
||||
assert.equal(
|
||||
queryClient.getQueryData(['account', bundle.user.id]),
|
||||
expect(queryClient.getQueryData(['account', bundle.user.id])).toBe(
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
@@ -16,8 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { describe, test } from 'node:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { resolveLegacyRoute } from './legacy-route'
|
||||
|
||||
@@ -43,17 +42,15 @@ describe('legacy frontend route migration', () => {
|
||||
}
|
||||
|
||||
for (const [source, target] of Object.entries(routes)) {
|
||||
assert.equal(resolveLegacyRoute(source), target)
|
||||
expect(resolveLegacyRoute(source)).toBe(target)
|
||||
}
|
||||
})
|
||||
|
||||
test('preserves search and hash while applying route-specific behavior', () => {
|
||||
assert.equal(
|
||||
resolveLegacyRoute('/login?redirect=%2Fkeys#continue'),
|
||||
expect(resolveLegacyRoute('/login?redirect=%2Fkeys#continue')).toBe(
|
||||
'/sign-in?redirect=%2Fkeys#continue'
|
||||
)
|
||||
assert.equal(
|
||||
resolveLegacyRoute('/console/topup?source=email#orders'),
|
||||
expect(resolveLegacyRoute('/console/topup?source=email#orders')).toBe(
|
||||
'/wallet?source=email#orders'
|
||||
)
|
||||
})
|
||||
@@ -75,23 +72,20 @@ describe('legacy frontend route migration', () => {
|
||||
}
|
||||
|
||||
for (const [tab, target] of Object.entries(settingsTabs)) {
|
||||
assert.equal(
|
||||
resolveLegacyRoute(`/console/setting?tab=${tab}&from=bookmark#form`),
|
||||
`${target}?tab=${tab}&from=bookmark#form`
|
||||
)
|
||||
expect(
|
||||
resolveLegacyRoute(`/console/setting?tab=${tab}&from=bookmark#form`)
|
||||
).toBe(`${target}?tab=${tab}&from=bookmark#form`)
|
||||
}
|
||||
assert.equal(
|
||||
resolveLegacyRoute('/console/setting?tab=unknown'),
|
||||
expect(resolveLegacyRoute('/console/setting?tab=unknown')).toBe(
|
||||
'/system-settings?tab=unknown'
|
||||
)
|
||||
})
|
||||
|
||||
test('safely redirects unknown console locations without touching new routes', () => {
|
||||
assert.equal(
|
||||
resolveLegacyRoute('/console/removed?page=2#old'),
|
||||
expect(resolveLegacyRoute('/console/removed?page=2#old')).toBe(
|
||||
'/dashboard?page=2#old'
|
||||
)
|
||||
assert.equal(resolveLegacyRoute('/dashboard'), null)
|
||||
assert.equal(resolveLegacyRoute('/api/status'), null)
|
||||
expect(resolveLegacyRoute('/dashboard')).toBe(null)
|
||||
expect(resolveLegacyRoute('/api/status')).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,8 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import assert from 'node:assert/strict'
|
||||
import { describe, test } from 'node:test'
|
||||
import { describe, expect, test } from 'vitest'
|
||||
|
||||
import { getServerErrorMessageKey } from './server-error-message'
|
||||
|
||||
@@ -25,8 +24,8 @@ describe('server error message mapping', () => {
|
||||
test('maps the active-session limit to recovery instructions', () => {
|
||||
const message = getServerErrorMessageKey({ code: 'AUTH_SESSION_LIMIT' })
|
||||
|
||||
assert.match(message ?? '', /Sign out other sessions/)
|
||||
assert.match(message ?? '', /reset your password/)
|
||||
expect(message ?? '').toMatch(/Sign out other sessions/)
|
||||
expect(message ?? '').toMatch(/reset your password/)
|
||||
})
|
||||
|
||||
test('maps an Axios-shaped issuance limit to rolling-window guidance', () => {
|
||||
@@ -34,8 +33,8 @@ describe('server error message mapping', () => {
|
||||
response: { data: { code: 'AUTH_SESSION_ISSUANCE_LIMIT' } },
|
||||
})
|
||||
|
||||
assert.match(message ?? '', /rolling window/)
|
||||
assert.equal(getServerErrorMessageKey({ code: 'UNKNOWN_CODE' }), null)
|
||||
expect(message ?? '').toMatch(/rolling window/)
|
||||
expect(getServerErrorMessageKey({ code: 'UNKNOWN_CODE' })).toBe(null)
|
||||
})
|
||||
|
||||
test('maps stable Telegram bind errors without exposing server text', () => {
|
||||
@@ -55,16 +54,15 @@ describe('server error message mapping', () => {
|
||||
}
|
||||
|
||||
for (const [code, message] of Object.entries(expected)) {
|
||||
assert.equal(getServerErrorMessageKey({ code }), message)
|
||||
expect(getServerErrorMessageKey({ code })).toBe(message)
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
getServerErrorMessageKey({
|
||||
response: {
|
||||
data: { code: 'TELEGRAM_BIND_INTERNAL_ERROR', message: 'raw detail' },
|
||||
},
|
||||
}),
|
||||
expected.TELEGRAM_BIND_INTERNAL_ERROR
|
||||
)
|
||||
})
|
||||
).toBe(expected.TELEGRAM_BIND_INTERNAL_ERROR)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user