Files
Starlight_Lancher/apps/app-frontend/src/composables/skin-site-session.test.ts
T
2026-10-21 09:16:55 +08:00

266 lines
9.5 KiB
TypeScript

import assert from 'node:assert/strict'
import { registerHooks } from 'node:module'
import test from 'node:test'
type FetchHandler = (url: string, options: RequestInit) => Promise<Response>
const testGlobal = globalThis as unknown as {
skinSiteTestFetch: FetchHandler
skinSiteTestHead: (texture: string) => Promise<string>
}
const hooks = registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier === '@/helpers/skin-site-render') {
const renderer = new URL('../helpers/skin-site-render.ts', import.meta.url).href
return {
url: `data:text/javascript,${encodeURIComponent(`export { fetchImageAsDataUrl } from ${JSON.stringify(renderer)}; export const renderSkinHead=texture=>globalThis.skinSiteTestHead(texture)`)}`,
shortCircuit: true,
}
}
if (specifier === '@tauri-apps/plugin-http') {
return {
url: 'data:text/javascript,export const fetch=(...args)=>globalThis.skinSiteTestFetch(...args)',
shortCircuit: true,
}
}
if (specifier.startsWith('@/')) {
return nextResolve(new URL(`../${specifier.slice(2)}.ts`, import.meta.url).href, context)
}
return nextResolve(specifier, context)
},
})
const session = await import('./skin-site-session.ts')
hooks.deregister()
const user = { uuid: 'owner', username: 'Player', permissions: [] }
const ok = (payload: unknown) => Response.json({ payload })
const storage = new Map<string, string>()
const tokenKey = 'starlight-skin-site-token'
let calls: Array<{ path: string; token: string | undefined }> = []
let respond: FetchHandler
test.beforeEach(() => {
storage.clear()
Object.defineProperty(globalThis, 'localStorage', {
configurable: true,
value: {
getItem: (key: string) => storage.get(key) ?? null,
setItem: (key: string, value: string) => storage.set(key, value),
removeItem: (key: string) => storage.delete(key),
},
})
calls = []
respond = async (url) => {
if (url.endsWith('/user/refresh')) return ok({ username: 'Player', jwt: 'fresh-token' })
if (url.endsWith('/user')) return ok(user)
return ok([])
}
testGlobal.skinSiteTestFetch = async (url, options = {}) => {
calls.push({
path: new URL(url).pathname,
token: (options.headers as Record<string, string>)?.Authorization,
})
return respond(url, options)
}
session.resetSkinSiteSession()
})
test.afterEach(async () => {
session.signOutSkinSite()
await new Promise((resolve) => setImmediate(resolve))
Reflect.deleteProperty(globalThis, 'localStorage')
})
test('startup renews the stored JWT before loading the user with the new token', async () => {
storage.set(tokenKey, 'old-token')
await session.initSkinSiteSession()
assert.deepEqual(calls.slice(0, 2), [
{ path: '/starlight/user/refresh', token: 'Bearer old-token' },
{ path: '/starlight/user', token: 'Bearer fresh-token' },
])
assert.equal(storage.get(tokenKey), 'fresh-token')
assert.equal(session.skinSiteStatus.value, 'signed-in')
})
test('simultaneous startup consumers share one refresh', async () => {
storage.set(tokenKey, 'old-token')
await Promise.all([
session.initSkinSiteSession(),
session.initSkinSiteSession(),
session.waitForSkinSiteSession(),
])
assert.equal(calls.filter((call) => call.path.endsWith('/refresh')).length, 1)
})
test('startup without a stored token does not make authenticated requests', async () => {
await session.initSkinSiteSession()
assert.equal(session.skinSiteStatus.value, 'signed-out')
assert.deepEqual(calls, [])
})
test('an expired token is cleared when renewal is rejected', async () => {
storage.set(tokenKey, 'expired-token')
respond = async () => Response.json({ errorMessage: 'Expired' }, { status: 401 })
await session.initSkinSiteSession()
assert.equal(storage.has(tokenKey), false)
assert.equal(session.skinSiteStatus.value, 'signed-out')
assert.equal(calls.length, 1)
})
test('temporary renewal failure preserves the token and allows startup retry', async () => {
storage.set(tokenKey, 'old-token')
respond = async () => Response.json({ errorMessage: 'Unavailable' }, { status: 503 })
await session.initSkinSiteSession()
assert.equal(session.skinSiteStatus.value, 'error')
assert.equal(storage.get(tokenKey), 'old-token')
respond = async (url) =>
url.endsWith('/refresh')
? ok({ jwt: 'fresh-token' })
: url.endsWith('/user')
? ok(user)
: ok([])
await session.initSkinSiteSession()
assert.equal(session.skinSiteStatus.value, 'signed-in')
})
test('a late refresh cannot restore a session after sign-out', async () => {
storage.set(tokenKey, 'old-token')
let finish!: (response: Response) => void
respond = () =>
new Promise((resolve) => {
finish = resolve
})
const initializing = session.initSkinSiteSession()
await new Promise((resolve) => setImmediate(resolve))
session.signOutSkinSite()
finish(ok({ username: 'Player', jwt: 'late-token' }))
await initializing
assert.equal(storage.has(tokenKey), false)
assert.equal(session.skinSiteStatus.value, 'signed-out')
assert.equal(session.skinSiteUser.value, null)
})
test('a malformed refresh response never replaces the saved token', async () => {
storage.set(tokenKey, 'old-token')
respond = async () => ok({ username: 'Player' })
await session.initSkinSiteSession()
assert.equal(storage.get(tokenKey), 'old-token')
assert.equal(session.skinSiteStatus.value, 'error')
})
test('a failed sign-in attempt does not invalidate a pending startup renewal', async () => {
storage.set(tokenKey, 'old-token')
let finish!: (response: Response) => void
respond = async (url) => {
if (url.endsWith('/refresh'))
return new Promise((resolve) => {
finish = resolve
})
if (url.endsWith('/login'))
return Response.json({ errorMessage: 'Invalid password' }, { status: 401 })
return url.endsWith('/user') ? ok(user) : ok([])
}
const initializing = session.initSkinSiteSession()
await new Promise((resolve) => setImmediate(resolve))
await session.signInSkinSite({ username: 'Player', password: 'wrong', clientId: 'captcha' })
finish(ok({ username: 'Player', jwt: 'fresh-token' }))
await initializing
assert.equal(session.skinSiteStatus.value, 'signed-in')
assert.equal(storage.get(tokenKey), 'fresh-token')
})
test('rejection of an old startup token does not discard a new successful login', async () => {
storage.set(tokenKey, 'expired-token')
let finishRefresh!: (response: Response) => void
let finishLogin!: (response: Response) => void
respond = async (url) => {
if (url.endsWith('/refresh'))
return new Promise((resolve) => {
finishRefresh = resolve
})
if (url.endsWith('/login'))
return new Promise((resolve) => {
finishLogin = resolve
})
return url.endsWith('/user') ? ok(user) : ok([])
}
const initializing = session.initSkinSiteSession()
const signingIn = session.signInSkinSite({
username: 'Player',
password: 'valid',
clientId: 'captcha',
})
await new Promise((resolve) => setImmediate(resolve))
finishRefresh(Response.json({ errorMessage: 'Expired' }, { status: 401 }))
await initializing
finishLogin(ok({ username: 'Player', jwt: 'login-token' }))
await signingIn
assert.equal(session.skinSiteStatus.value, 'signed-in')
assert.equal(storage.get(tokenKey), 'login-token')
})
test('explicit sign-out discards a late successful login', async () => {
let finishLogin!: (response: Response) => void
respond = () =>
new Promise((resolve) => {
finishLogin = resolve
})
const signingIn = session.signInSkinSite({
username: 'Player',
password: 'valid',
clientId: 'captcha',
})
await new Promise((resolve) => setImmediate(resolve))
session.signOutSkinSite()
finishLogin(ok({ username: 'Player', jwt: 'late-login-token' }))
await signingIn
assert.equal(session.skinSiteStatus.value, 'signed-out')
assert.equal(storage.has(tokenKey), false)
})
test('player heads are cropped from one downloaded local texture rather than a cross-origin image', async (t) => {
const textureUrl = 'https://skin.starlight.cool/yggdrasil/textures/test'
const textureData = 'data:image/png;base64,iVBORw=='
const previousReader = Object.getOwnPropertyDescriptor(globalThis, 'FileReader')
Object.defineProperty(globalThis, 'FileReader', {
configurable: true,
value: class {
result: string | null = null
onload?: () => void
readAsDataURL(blob: Blob) {
void blob.arrayBuffer().then((bytes) => {
this.result = `data:${blob.type};base64,${Buffer.from(bytes).toString('base64')}`
this.onload?.()
})
}
},
})
t.after(() => {
if (previousReader) Object.defineProperty(globalThis, 'FileReader', previousReader)
else Reflect.deleteProperty(globalThis, 'FileReader')
})
t.mock.method(globalThis, 'fetch', async () => {
throw new TypeError('Cross-origin image request blocked')
})
testGlobal.skinSiteTestHead = async (source) => {
if (source !== textureData) throw new Error('Cross-origin canvas image blocked')
return 'data:image/png;base64,local-head'
}
respond = async (url) => {
if (url === textureUrl)
return new Response(new Uint8Array([137, 80, 78, 71]), {
headers: { 'Content-Type': 'image/png' },
})
if (url.endsWith('/player/skin/player')) return ok({ skin: textureUrl, model: 'slim' })
if (url.endsWith('/player')) return ok([{ uuid: 'player', name: 'Player', isMojang: false }])
return url.endsWith('/user') ? ok(user) : ok({ jwt: 'token' })
}
storage.set(tokenKey, 'old-token')
await session.initSkinSiteSession()
const [player] = await session.requestSkinSitePlayers()
assert.equal(player.skinState, 'ready')
assert.equal(player.skinDataUrl, textureData)
assert.equal(player.headDataUrl, 'data:image/png;base64,local-head')
assert.equal(player.model, 'slim')
assert.equal(calls.filter((call) => call.path === '/yggdrasil/textures/test').length, 1)
})