Files
what-to-eat/test/yunlefunAuth.test.ts
T
toor 76a5d71252 refactor: 只保留 Web,移除 Capacitor 原生壳
删除 android/、ios/ 两个 Capacitor 原生工程与 capacitor.config.ts、
ionic.config.json,以及 scripts/android、scripts/ios.sh 构建脚本;
根与 apps/web 的 @capacitor/* 依赖和相关 npm scripts 一并移除。

Web 侧去掉原生分支:登录只走云乐坊容器授权面板或顶层重定向,
暗色模式不再改原生状态栏,移除 Android 物理返回键处理。

保留 @nuxtjs/ionic(Web UI 框架)、云乐坊 window.ylf 桥接与 PWA。

决策记录见 docs/decisions/0004-web-only.md。
2026-10-21 12:27:50 +08:00

153 lines
3.9 KiB
TypeScript

import type { SsoAuthorizationResult } from '@yunlefun/sso'
import type {
YunlefunAccount,
YunlefunAuthorizationAdapter,
YunlefunIdentityAdapter,
} from '../apps/web/app/utils/yunlefunAuth'
import { describe, expect, it } from 'vitest'
import {
createYunlefunAuthController,
} from '../apps/web/app/utils/yunlefunAuth'
const authorization: SsoAuthorizationResult = {
ok: true,
clientId: 'cook-web',
code: 'c'.repeat(43),
codeVerifier: 'v'.repeat(43),
issuer: 'https://www.yunle.fun',
nonce: 'n'.repeat(43),
redirectUri: 'https://cook.yunyoujun.cn/auth/callback',
scope: ['identity:bootstrap'],
}
const account: YunlefunAccount = {
displayName: '云游君',
uid: 'user-1',
}
describe('yunlefun auth controller', () => {
it('adopts an initial authorization result through its public state', async () => {
const web = fakeAuthorizationAdapter(authorization)
const identity = fakeIdentityAdapter()
const controller = createYunlefunAuthController({
identity,
web,
})
await controller.initialize()
expect(controller.snapshot()).toEqual({
account,
errorMessage: '',
status: 'signed-in',
})
expect(identity.adopted).toEqual([authorization])
})
it('opens the web authorization surface', async () => {
const web = fakeAuthorizationAdapter()
const controller = createYunlefunAuthController({
identity: fakeIdentityAdapter(),
web,
})
await controller.signIn()
expect(web.started).toBe(1)
expect(controller.snapshot().status).toBe('signing-in')
})
it('adopts a host authorization without leaving the current page', async () => {
const web = fakeAuthorizationAdapter(null, authorization)
const identity = fakeIdentityAdapter()
const controller = createYunlefunAuthController({
identity,
web,
})
await controller.signIn()
expect(identity.adopted).toEqual([authorization])
expect(controller.snapshot()).toEqual({
account,
errorMessage: '',
status: 'signed-in',
})
})
it('clears both the identity session and public account on sign-out', async () => {
const identity = fakeIdentityAdapter(account)
const controller = createYunlefunAuthController({
identity,
web: fakeAuthorizationAdapter(),
})
await controller.initialize()
await controller.signOut()
expect(identity.signOutCount).toBe(1)
expect(controller.snapshot()).toEqual({
account: null,
errorMessage: '',
status: 'signed-out',
})
})
it('keeps the authenticated account visible when sign-out fails', async () => {
const identity = fakeIdentityAdapter(account)
identity.signOut = async () => {
identity.signOutCount += 1
throw new Error('remote sign-out failed')
}
const controller = createYunlefunAuthController({
identity,
web: fakeAuthorizationAdapter(),
})
await controller.initialize()
await expect(controller.signOut()).resolves.toBeUndefined()
expect(controller.snapshot()).toEqual({
account,
errorMessage: '退出登录失败,请稍后重试',
status: 'error',
})
})
})
function fakeAuthorizationAdapter(
initial: SsoAuthorizationResult | null = null,
startedResult: SsoAuthorizationResult | null = null,
): YunlefunAuthorizationAdapter & { started: number } {
return {
started: 0,
async consumeInitial() {
return initial
},
async start() {
this.started += 1
return startedResult
},
}
}
function fakeIdentityAdapter(restored: YunlefunAccount | null = null): YunlefunIdentityAdapter & {
adopted: SsoAuthorizationResult[]
signOutCount: number
} {
return {
adopted: [],
signOutCount: 0,
async adopt(nextAuthorization) {
this.adopted.push(nextAuthorization)
return account
},
async restore() {
return restored
},
async signOut() {
this.signOutCount += 1
},
}
}