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,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 type { RefreshOutcome } from '@/lib/api'
|
||||
import type { AuthBundle } from '@/stores/auth-store'
|
||||
@@ -63,8 +62,8 @@ describe('logout coordination', () => {
|
||||
},
|
||||
})
|
||||
|
||||
assert.deepEqual(result, { success: false, message: 'not revoked' })
|
||||
assert.equal(refreshCount, 0)
|
||||
expect(result).toEqual({ success: false, message: 'not revoked' })
|
||||
expect(refreshCount).toBe(0)
|
||||
})
|
||||
|
||||
test('recovers a cookie mismatch and retries with the refreshed SID', async () => {
|
||||
@@ -83,8 +82,8 @@ describe('logout coordination', () => {
|
||||
},
|
||||
})
|
||||
|
||||
assert.deepEqual(result, { success: true, message: '' })
|
||||
assert.deepEqual(requestedSIDs, ['session-a', 'session-b'])
|
||||
expect(result).toEqual({ success: true, message: '' })
|
||||
expect(requestedSIDs).toEqual(['session-a', 'session-b'])
|
||||
})
|
||||
|
||||
test('treats a mismatch that refresh confirms anonymous as signed out', async () => {
|
||||
@@ -96,7 +95,7 @@ describe('logout coordination', () => {
|
||||
refresh: async () => ({ kind: 'anonymous' }),
|
||||
})
|
||||
|
||||
assert.deepEqual(result, { success: true, message: '' })
|
||||
expect(result).toEqual({ success: true, message: '' })
|
||||
})
|
||||
|
||||
test('preserves the active session when mismatch recovery is temporary', async () => {
|
||||
@@ -106,15 +105,14 @@ describe('logout coordination', () => {
|
||||
error: new Error('offline'),
|
||||
}
|
||||
|
||||
await assert.rejects(
|
||||
await expect(
|
||||
executeLogout({
|
||||
getExpectedSID: () => 'session-a',
|
||||
request: async () => {
|
||||
throw originalError
|
||||
},
|
||||
refresh: async () => transient,
|
||||
}),
|
||||
(error) => error === originalError
|
||||
)
|
||||
})
|
||||
).rejects.toBe(originalError)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 {
|
||||
getOAuthSessionStorage,
|
||||
@@ -40,15 +39,14 @@ const bindState = 'bind-state'
|
||||
describe('resolveOAuthCallbackMode', () => {
|
||||
test('matching provider and state mark is treated as a bind flow', () => {
|
||||
const storage = fakeStorage()
|
||||
assert.equal(markOAuthBindPopup(storage, 'oidc', bindState), true)
|
||||
expect(markOAuthBindPopup(storage, 'oidc', bindState)).toBe(true)
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: openOpener,
|
||||
storage,
|
||||
}),
|
||||
'bind'
|
||||
)
|
||||
})
|
||||
).toBe('bind')
|
||||
})
|
||||
|
||||
// Regression: a tab opened from an external link (Slack, e-mail, another
|
||||
@@ -58,75 +56,69 @@ describe('resolveOAuthCallbackMode', () => {
|
||||
test('login redirect in a tab with a foreign opener stays a login flow', () => {
|
||||
const storage = fakeStorage()
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: openOpener,
|
||||
storage,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
|
||||
test('bind marker for another provider does not hijack this callback', () => {
|
||||
const storage = fakeStorage()
|
||||
markOAuthBindPopup(storage, 'github', bindState)
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: openOpener,
|
||||
storage,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
|
||||
test('stale bind marker does not hijack a later callback', () => {
|
||||
const storage = fakeStorage()
|
||||
markOAuthBindPopup(storage, 'oidc', 'previous-state')
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: openOpener,
|
||||
storage,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
|
||||
test('bind marker without an opener falls back to login', () => {
|
||||
const storage = fakeStorage()
|
||||
markOAuthBindPopup(storage, 'oidc', bindState)
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: null,
|
||||
storage,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
|
||||
test('closed opener falls back to login', () => {
|
||||
const storage = fakeStorage()
|
||||
markOAuthBindPopup(storage, 'oidc', bindState)
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: { closed: true },
|
||||
storage,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
|
||||
test('missing storage degrades to login instead of throwing', () => {
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: openOpener,
|
||||
storage: null,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
|
||||
test('storage read failure degrades to login instead of throwing', () => {
|
||||
@@ -137,13 +129,12 @@ describe('resolveOAuthCallbackMode', () => {
|
||||
setItem: () => undefined,
|
||||
}
|
||||
|
||||
assert.equal(
|
||||
expect(
|
||||
resolveOAuthCallbackMode('oidc', bindState, {
|
||||
opener: openOpener,
|
||||
storage,
|
||||
}),
|
||||
'login'
|
||||
)
|
||||
})
|
||||
).toBe('login')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -155,7 +146,7 @@ describe('OAuth bind popup storage', () => {
|
||||
},
|
||||
}
|
||||
|
||||
assert.equal(getOAuthSessionStorage(owner), null)
|
||||
expect(getOAuthSessionStorage(owner)).toBe(null)
|
||||
})
|
||||
|
||||
test('marking reports unavailable or unwritable storage', () => {
|
||||
@@ -166,9 +157,9 @@ describe('OAuth bind popup storage', () => {
|
||||
},
|
||||
}
|
||||
|
||||
assert.equal(markOAuthBindPopup(null, 'oidc', bindState), false)
|
||||
assert.equal(markOAuthBindPopup(storage, 'oidc', bindState), false)
|
||||
assert.equal(
|
||||
expect(markOAuthBindPopup(null, 'oidc', bindState)).toBe(false)
|
||||
expect(markOAuthBindPopup(storage, 'oidc', bindState)).toBe(false)
|
||||
expect(
|
||||
markOAuthBindPopup(
|
||||
{
|
||||
getItem: () => null,
|
||||
@@ -176,8 +167,7 @@ describe('OAuth bind popup storage', () => {
|
||||
},
|
||||
'oidc',
|
||||
bindState
|
||||
),
|
||||
false
|
||||
)
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 type { AuthUser } from '@/stores/auth-store'
|
||||
|
||||
@@ -27,17 +26,15 @@ const origin = 'https://dashboard.example.com'
|
||||
|
||||
describe('authentication redirect validation', () => {
|
||||
test('preserves safe internal paths, search parameters, and fragments', () => {
|
||||
assert.equal(
|
||||
sanitizeAuthRedirect('/console?tab=usage#recent', origin),
|
||||
expect(sanitizeAuthRedirect('/console?tab=usage#recent', origin)).toBe(
|
||||
'/console?tab=usage#recent'
|
||||
)
|
||||
assert.equal(
|
||||
expect(
|
||||
sanitizeAuthRedirect(
|
||||
'https://dashboard.example.com/dashboard?tab=quota#daily',
|
||||
origin
|
||||
),
|
||||
'/dashboard?tab=quota#daily'
|
||||
)
|
||||
)
|
||||
).toBe('/dashboard?tab=quota#daily')
|
||||
})
|
||||
|
||||
test('rejects external and ambiguously parsed redirect targets', () => {
|
||||
@@ -53,13 +50,13 @@ describe('authentication redirect validation', () => {
|
||||
]
|
||||
|
||||
for (const target of unsafeTargets) {
|
||||
assert.equal(sanitizeAuthRedirect(target, origin), null)
|
||||
expect(sanitizeAuthRedirect(target, origin)).toBe(null)
|
||||
}
|
||||
})
|
||||
|
||||
test('rejects invalid or non-HTTP application origins', () => {
|
||||
assert.equal(sanitizeAuthRedirect('/dashboard', 'not-an-origin'), null)
|
||||
assert.equal(sanitizeAuthRedirect('/dashboard', 'file:///tmp/app'), null)
|
||||
expect(sanitizeAuthRedirect('/dashboard', 'not-an-origin')).toBe(null)
|
||||
expect(sanitizeAuthRedirect('/dashboard', 'file:///tmp/app')).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -67,31 +64,27 @@ describe('saved authentication language', () => {
|
||||
const user: AuthUser = { id: 1, username: 'user', role: 1 }
|
||||
|
||||
test('prefers the explicit user language', () => {
|
||||
assert.equal(
|
||||
expect(
|
||||
getSavedLanguage({
|
||||
...user,
|
||||
language: 'ja',
|
||||
setting: { language: 'fr' },
|
||||
}),
|
||||
'ja'
|
||||
)
|
||||
})
|
||||
).toBe('ja')
|
||||
})
|
||||
|
||||
test('reads object and JSON string settings', () => {
|
||||
assert.equal(
|
||||
getSavedLanguage({ ...user, setting: { language: 'fr' } }),
|
||||
expect(getSavedLanguage({ ...user, setting: { language: 'fr' } })).toBe(
|
||||
'fr'
|
||||
)
|
||||
assert.equal(
|
||||
getSavedLanguage({ ...user, setting: '{"language":"ru"}' }),
|
||||
expect(getSavedLanguage({ ...user, setting: '{"language":"ru"}' })).toBe(
|
||||
'ru'
|
||||
)
|
||||
})
|
||||
|
||||
test('ignores malformed and non-string setting languages', () => {
|
||||
assert.equal(getSavedLanguage({ ...user, setting: '{' }), undefined)
|
||||
assert.equal(
|
||||
getSavedLanguage({ ...user, setting: { language: 123 } }),
|
||||
expect(getSavedLanguage({ ...user, setting: '{' })).toBe(undefined)
|
||||
expect(getSavedLanguage({ ...user, setting: { language: 123 } })).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 {
|
||||
parseTelegramBindCallback,
|
||||
@@ -51,51 +50,48 @@ function fakeTimerRuntime() {
|
||||
|
||||
describe('OAuth bind popup lifecycle', () => {
|
||||
test('parses Telegram success and stable error callbacks', () => {
|
||||
assert.deepEqual(
|
||||
expect(
|
||||
parseTelegramBindCallback({
|
||||
telegram_bind: 'success',
|
||||
flow_token: 'flow-success',
|
||||
}),
|
||||
{
|
||||
kind: 'result',
|
||||
flowToken: 'flow-success',
|
||||
success: true,
|
||||
}
|
||||
)
|
||||
assert.deepEqual(
|
||||
})
|
||||
).toEqual({
|
||||
kind: 'result',
|
||||
flowToken: 'flow-success',
|
||||
success: true,
|
||||
})
|
||||
expect(
|
||||
parseTelegramBindCallback({
|
||||
telegram_bind: 'error',
|
||||
flow_token: 'flow-error',
|
||||
error_code: 'TELEGRAM_BIND_ALREADY_BOUND',
|
||||
}),
|
||||
{
|
||||
kind: 'result',
|
||||
flowToken: 'flow-error',
|
||||
success: false,
|
||||
code: 'TELEGRAM_BIND_ALREADY_BOUND',
|
||||
}
|
||||
)
|
||||
})
|
||||
).toEqual({
|
||||
kind: 'result',
|
||||
flowToken: 'flow-error',
|
||||
success: false,
|
||||
code: 'TELEGRAM_BIND_ALREADY_BOUND',
|
||||
})
|
||||
})
|
||||
|
||||
test('rejects Telegram callbacks without a flow token and ignores descriptions', () => {
|
||||
assert.deepEqual(parseTelegramBindCallback({ telegram_bind: 'error' }), {
|
||||
expect(parseTelegramBindCallback({ telegram_bind: 'error' })).toEqual({
|
||||
kind: 'invalid',
|
||||
})
|
||||
assert.deepEqual(
|
||||
expect(
|
||||
parseTelegramBindCallback({
|
||||
telegram_bind: 'error',
|
||||
flow_token: 'flow-error',
|
||||
error_code: 'UNKNOWN_CODE',
|
||||
error_description: 'untrusted message',
|
||||
} as Parameters<typeof parseTelegramBindCallback>[0]),
|
||||
{
|
||||
kind: 'result',
|
||||
flowToken: 'flow-error',
|
||||
success: false,
|
||||
code: 'UNKNOWN_CODE',
|
||||
}
|
||||
)
|
||||
assert.equal(parseTelegramBindCallback({}), null)
|
||||
} as Parameters<typeof parseTelegramBindCallback>[0])
|
||||
).toEqual({
|
||||
kind: 'result',
|
||||
flowToken: 'flow-error',
|
||||
success: false,
|
||||
code: 'UNKNOWN_CODE',
|
||||
})
|
||||
expect(parseTelegramBindCallback({})).toBe(null)
|
||||
})
|
||||
|
||||
test('posts only complete Telegram bind results to an available opener', () => {
|
||||
@@ -112,11 +108,10 @@ describe('OAuth bind popup lifecycle', () => {
|
||||
error_code: 'UNKNOWN_CODE',
|
||||
})
|
||||
|
||||
assert.equal(
|
||||
postTelegramBindResult(callback, opener, 'https://dashboard.example.com'),
|
||||
true
|
||||
)
|
||||
assert.deepEqual(messages, [
|
||||
expect(
|
||||
postTelegramBindResult(callback, opener, 'https://dashboard.example.com')
|
||||
).toBe(true)
|
||||
expect(messages).toEqual([
|
||||
{
|
||||
message: {
|
||||
type: 'telegram:binding:result',
|
||||
@@ -128,23 +123,17 @@ describe('OAuth bind popup lifecycle', () => {
|
||||
},
|
||||
])
|
||||
|
||||
assert.equal(
|
||||
postTelegramBindResult(
|
||||
{ kind: 'invalid' },
|
||||
opener,
|
||||
'https://example.com'
|
||||
),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
expect(
|
||||
postTelegramBindResult({ kind: 'invalid' }, opener, 'https://example.com')
|
||||
).toBe(false)
|
||||
expect(
|
||||
postTelegramBindResult(
|
||||
callback,
|
||||
{ ...opener, closed: true },
|
||||
'https://example.com'
|
||||
),
|
||||
false
|
||||
)
|
||||
assert.equal(messages.length, 1)
|
||||
)
|
||||
).toBe(false)
|
||||
expect(messages.length).toBe(1)
|
||||
})
|
||||
|
||||
test('waits 30 seconds for the opener response and can be cancelled', () => {
|
||||
@@ -158,11 +147,11 @@ describe('OAuth bind popup lifecycle', () => {
|
||||
timer.runtime
|
||||
)
|
||||
|
||||
assert.equal(timer.delay, 30_000)
|
||||
expect(timer.delay).toBe(30_000)
|
||||
cancel()
|
||||
timer.fire()
|
||||
assert.equal(timedOut, false)
|
||||
assert.deepEqual(timer.cancelled, [timer.handle])
|
||||
expect(timedOut).toBe(false)
|
||||
expect(timer.cancelled).toEqual([timer.handle])
|
||||
})
|
||||
|
||||
test('reports a closed popup once and clears its poller', () => {
|
||||
@@ -178,13 +167,13 @@ describe('OAuth bind popup lifecycle', () => {
|
||||
timer.runtime
|
||||
)
|
||||
|
||||
assert.equal(timer.delay, 500)
|
||||
expect(timer.delay).toBe(500)
|
||||
timer.fire()
|
||||
assert.equal(closedCount, 0)
|
||||
expect(closedCount).toBe(0)
|
||||
popup.closed = true
|
||||
timer.fire()
|
||||
timer.fire()
|
||||
assert.equal(closedCount, 1)
|
||||
assert.deepEqual(timer.cancelled, [timer.handle])
|
||||
expect(closedCount).toBe(1)
|
||||
expect(timer.cancelled).toEqual([timer.handle])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,14 +16,13 @@ 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 { pickTelegramAuthorization } from './telegram-login'
|
||||
|
||||
describe('Telegram login authorization', () => {
|
||||
test('keeps only fields signed by the Telegram login contract', () => {
|
||||
assert.deepEqual(
|
||||
expect(
|
||||
pickTelegramAuthorization({
|
||||
id: 12345,
|
||||
first_name: 'Test',
|
||||
@@ -35,33 +34,27 @@ describe('Telegram login authorization', () => {
|
||||
lang: 'en',
|
||||
admin: true,
|
||||
redirect: 'https://attacker.example',
|
||||
}),
|
||||
{
|
||||
id: 12345,
|
||||
first_name: 'Test',
|
||||
last_name: 'User',
|
||||
username: 'test_user',
|
||||
photo_url: 'https://t.me/i/userpic/320/test.jpg',
|
||||
auth_date: 1_900_000_000,
|
||||
hash: 'signed-hash',
|
||||
lang: 'en',
|
||||
}
|
||||
)
|
||||
})
|
||||
).toEqual({
|
||||
id: 12345,
|
||||
first_name: 'Test',
|
||||
last_name: 'User',
|
||||
username: 'test_user',
|
||||
photo_url: 'https://t.me/i/userpic/320/test.jpg',
|
||||
auth_date: 1_900_000_000,
|
||||
hash: 'signed-hash',
|
||||
lang: 'en',
|
||||
})
|
||||
})
|
||||
|
||||
test('rejects incomplete or structurally invalid callbacks', () => {
|
||||
assert.equal(pickTelegramAuthorization(null), null)
|
||||
assert.equal(
|
||||
pickTelegramAuthorization({ auth_date: 1, hash: 'hash' }),
|
||||
null
|
||||
)
|
||||
assert.equal(
|
||||
pickTelegramAuthorization({ id: 1, auth_date: 1, hash: '' }),
|
||||
null
|
||||
)
|
||||
assert.equal(
|
||||
pickTelegramAuthorization({ id: {}, auth_date: 1, hash: 'hash' }),
|
||||
expect(pickTelegramAuthorization(null)).toBe(null)
|
||||
expect(pickTelegramAuthorization({ auth_date: 1, hash: 'hash' })).toBe(null)
|
||||
expect(pickTelegramAuthorization({ id: 1, auth_date: 1, hash: '' })).toBe(
|
||||
null
|
||||
)
|
||||
expect(
|
||||
pickTelegramAuthorization({ id: {}, auth_date: 1, hash: 'hash' })
|
||||
).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user