fix frontend phase0 bugs
This commit is contained in:
@@ -85,6 +85,19 @@ describe('Login Page', () => {
|
|||||||
expect(html).toContain('登录')
|
expect(html).toContain('登录')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 辅助:填写表单并提交,绕过 Ant Design 表单验证
|
||||||
|
async function submitLogin(
|
||||||
|
fields: { username: string; password: string; remember?: boolean },
|
||||||
|
) {
|
||||||
|
const vm = wrapper.getCurrentComponent().setupState
|
||||||
|
vm.formState.username = fields.username
|
||||||
|
vm.formState.password = fields.password
|
||||||
|
if (fields.remember !== undefined) vm.formState.remember = fields.remember
|
||||||
|
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
||||||
|
await vm.handleSubmit()
|
||||||
|
await flushPromises()
|
||||||
|
}
|
||||||
|
|
||||||
it('calls /api/v1/login, sets token/user and redirects to /', async () => {
|
it('calls /api/v1/login, sets token/user and redirects to /', async () => {
|
||||||
const { api } = await import('@/utils/request')
|
const { api } = await import('@/utils/request')
|
||||||
const mockResponse = {
|
const mockResponse = {
|
||||||
@@ -96,17 +109,7 @@ describe('Login Page', () => {
|
|||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
await submitLogin({ username: 'admin', password: 'password' })
|
||||||
// 直接调用 handleSubmit 来绕过表单验证
|
|
||||||
// 通过组件 vm 访问 setup 暴露的方法
|
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
|
||||||
vm.formState.username = 'admin'
|
|
||||||
vm.formState.password = 'password'
|
|
||||||
vm.formRef = { value: { validate: vi.fn().mockResolvedValue(true) } }
|
|
||||||
// mock formRef.validate
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
expect(api.post).toHaveBeenCalledWith('/api/v1/login', {
|
expect(api.post).toHaveBeenCalledWith('/api/v1/login', {
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
@@ -128,13 +131,7 @@ describe('Login Page', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
await submitLogin({ username: 'user', password: 'pass', remember: false })
|
||||||
vm.formState.username = 'user'
|
|
||||||
vm.formState.password = 'pass'
|
|
||||||
vm.formState.remember = false
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
expect(localStorage.getItem('access_token')).toBeNull()
|
expect(localStorage.getItem('access_token')).toBeNull()
|
||||||
expect(localStorage.getItem('user')).toBeNull()
|
expect(localStorage.getItem('user')).toBeNull()
|
||||||
@@ -150,12 +147,7 @@ describe('Login Page', () => {
|
|||||||
mockRoute.query = { redirect: '/dashboard' }
|
mockRoute.query = { redirect: '/dashboard' }
|
||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
await submitLogin({ username: 'user', password: 'pass' })
|
||||||
vm.formState.username = 'user'
|
|
||||||
vm.formState.password = 'pass'
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
vi.advanceTimersByTime(500)
|
vi.advanceTimersByTime(500)
|
||||||
expect(mockPush).toHaveBeenCalledWith('/dashboard')
|
expect(mockPush).toHaveBeenCalledWith('/dashboard')
|
||||||
@@ -171,12 +163,7 @@ describe('Login Page', () => {
|
|||||||
mockRoute.query = { redirect: '//evil.com' }
|
mockRoute.query = { redirect: '//evil.com' }
|
||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
await submitLogin({ username: 'user', password: 'pass' })
|
||||||
vm.formState.username = 'user'
|
|
||||||
vm.formState.password = 'pass'
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
vi.advanceTimersByTime(500)
|
vi.advanceTimersByTime(500)
|
||||||
expect(mockPush).toHaveBeenCalledWith('/')
|
expect(mockPush).toHaveBeenCalledWith('/')
|
||||||
@@ -192,12 +179,7 @@ describe('Login Page', () => {
|
|||||||
mockRoute.query = { redirect: 'http://evil.com' }
|
mockRoute.query = { redirect: 'http://evil.com' }
|
||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
await submitLogin({ username: 'user', password: 'pass' })
|
||||||
vm.formState.username = 'user'
|
|
||||||
vm.formState.password = 'pass'
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
vi.advanceTimersByTime(500)
|
vi.advanceTimersByTime(500)
|
||||||
expect(mockPush).toHaveBeenCalledWith('/')
|
expect(mockPush).toHaveBeenCalledWith('/')
|
||||||
@@ -209,12 +191,7 @@ describe('Login Page', () => {
|
|||||||
const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as any)
|
const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as any)
|
||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
await submitLogin({ username: 'user', password: 'wrong' })
|
||||||
vm.formState.username = 'user'
|
|
||||||
vm.formState.password = 'wrong'
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
expect(errorSpy).toHaveBeenCalledWith('用户名或密码错误')
|
expect(errorSpy).toHaveBeenCalledWith('用户名或密码错误')
|
||||||
errorSpy.mockRestore()
|
errorSpy.mockRestore()
|
||||||
@@ -226,12 +203,7 @@ describe('Login Page', () => {
|
|||||||
const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as any)
|
const errorSpy = vi.spyOn(message, 'error').mockImplementation(() => ({}) as any)
|
||||||
|
|
||||||
await mountPage()
|
await mountPage()
|
||||||
const vm = wrapper.getCurrentComponent().setupState
|
await submitLogin({ username: 'user', password: 'pass' })
|
||||||
vm.formState.username = 'user'
|
|
||||||
vm.formState.password = 'pass'
|
|
||||||
vm.formRef = { validate: vi.fn().mockResolvedValue(true) }
|
|
||||||
await vm.handleSubmit()
|
|
||||||
await flushPromises()
|
|
||||||
|
|
||||||
expect(errorSpy).toHaveBeenCalledWith('登录失败,请检查网络连接')
|
expect(errorSpy).toHaveBeenCalledWith('登录失败,请检查网络连接')
|
||||||
errorSpy.mockRestore()
|
errorSpy.mockRestore()
|
||||||
|
|||||||
@@ -96,8 +96,9 @@ describe('useUserStore', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('setUser', () => {
|
describe('setUser', () => {
|
||||||
it('sets user info in store and localStorage', () => {
|
it('persists to localStorage when remember=true', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
|
store.setToken('h.p.s', 'r.p.s', true)
|
||||||
const userInfo = { id: 1, username: 'test', email: 't@t.com', role: 'user', status: 1 }
|
const userInfo = { id: 1, username: 'test', email: 't@t.com', role: 'user', status: 1 }
|
||||||
|
|
||||||
store.setUser(userInfo)
|
store.setUser(userInfo)
|
||||||
@@ -108,7 +109,17 @@ describe('useUserStore', () => {
|
|||||||
expect(JSON.parse(localStorage.getItem('user')!)).toEqual(userInfo)
|
expect(JSON.parse(localStorage.getItem('user')!)).toEqual(userInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('does not write to localStorage when remember=false', () => {
|
it('does not persist to localStorage by default (remember=false)', () => {
|
||||||
|
const store = useUserStore()
|
||||||
|
const userInfo = { id: 1, username: 'test', email: 't@t.com', role: 'user', status: 1 }
|
||||||
|
|
||||||
|
store.setUser(userInfo)
|
||||||
|
|
||||||
|
expect(store.user).toEqual(userInfo)
|
||||||
|
expect(localStorage.getItem('user')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not write to localStorage after setToken(remember=false)', () => {
|
||||||
const store = useUserStore()
|
const store = useUserStore()
|
||||||
store.setToken('h.p.s', 'r.p.s', false)
|
store.setToken('h.p.s', 'r.p.s', false)
|
||||||
|
|
||||||
@@ -136,6 +147,19 @@ describe('useUserStore', () => {
|
|||||||
expect(localStorage.getItem('refresh_token')).toBeNull()
|
expect(localStorage.getItem('refresh_token')).toBeNull()
|
||||||
expect(localStorage.getItem('user')).toBeNull()
|
expect(localStorage.getItem('user')).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('resets _remember so subsequent setUser does not persist', () => {
|
||||||
|
const store = useUserStore()
|
||||||
|
store.setToken('h.p.s', 'r.p.s', true)
|
||||||
|
store.setUser({ id: 1, username: 'a', email: 'a@a.com', role: 'admin', status: 1 })
|
||||||
|
expect(localStorage.getItem('user')).not.toBeNull()
|
||||||
|
|
||||||
|
store.logout()
|
||||||
|
store.setUser({ id: 2, username: 'b', email: 'b@b.com', role: 'user', status: 1 })
|
||||||
|
|
||||||
|
expect(store.user?.username).toBe('b')
|
||||||
|
expect(localStorage.getItem('user')).toBeNull()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('fetchCurrentUser', () => {
|
describe('fetchCurrentUser', () => {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
const token = ref<string | null>(localStorage.getItem('access_token'))
|
const token = ref<string | null>(localStorage.getItem('access_token'))
|
||||||
const refreshToken = ref<string | null>(localStorage.getItem('refresh_token'))
|
const refreshToken = ref<string | null>(localStorage.getItem('refresh_token'))
|
||||||
const user = ref<UserInfo | null>(null)
|
const user = ref<UserInfo | null>(null)
|
||||||
const _remember = ref(true)
|
// 默认不持久化;从 localStorage 推断:有 token 说明上次选了"记住我"
|
||||||
|
const _remember = ref(!!localStorage.getItem('access_token'))
|
||||||
|
|
||||||
// 基本 JWT 格式校验(三段式),防止垃圾值绕过路由守卫
|
// 基本 JWT 格式校验(三段式),防止垃圾值绕过路由守卫
|
||||||
const isLoggedIn = computed(() => {
|
const isLoggedIn = computed(() => {
|
||||||
@@ -30,7 +31,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
localStorage.setItem('access_token', accessToken)
|
localStorage.setItem('access_token', accessToken)
|
||||||
localStorage.setItem('refresh_token', newRefreshToken)
|
localStorage.setItem('refresh_token', newRefreshToken)
|
||||||
} else {
|
} else {
|
||||||
// 不记住:清除持久化,token 仅存于内存,关闭标签页即失效
|
// 不记住:清除全部持久化数据,token 仅存于内存,关闭标签页即失效
|
||||||
localStorage.removeItem('access_token')
|
localStorage.removeItem('access_token')
|
||||||
localStorage.removeItem('refresh_token')
|
localStorage.removeItem('refresh_token')
|
||||||
localStorage.removeItem('user')
|
localStorage.removeItem('user')
|
||||||
@@ -54,6 +55,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
token.value = null
|
token.value = null
|
||||||
refreshToken.value = null
|
refreshToken.value = null
|
||||||
user.value = null
|
user.value = null
|
||||||
|
_remember.value = false
|
||||||
localStorage.removeItem('access_token')
|
localStorage.removeItem('access_token')
|
||||||
localStorage.removeItem('refresh_token')
|
localStorage.removeItem('refresh_token')
|
||||||
localStorage.removeItem('user')
|
localStorage.removeItem('user')
|
||||||
|
|||||||
Reference in New Issue
Block a user