Auth refresh, stable router tests, and Household Settings members list; spec + cleanup tasks
This commit is contained in:
parent
f78cc31479
commit
aed7ea44e9
2 changed files with 78 additions and 15 deletions
|
|
@ -9,25 +9,42 @@ setAuthTokenProvider(() => authToken)
|
|||
export async function currentUser(): Promise<User | null> {
|
||||
if (cachedUser) return cachedUser
|
||||
try {
|
||||
const res = await api.POST('/api/v1/auth/refresh', { params: { cookie: { user_id: 0 } } })
|
||||
if (!res.response.ok) return null
|
||||
// refresh may return legacy Person; adapt minimally to User shape without type assertions
|
||||
const p = res.data
|
||||
if (typeof p === 'object' && p !== null) {
|
||||
const idDesc = Object.getOwnPropertyDescriptor(p, 'id')
|
||||
const nameDesc = Object.getOwnPropertyDescriptor(p, 'name')
|
||||
const id = idDesc?.value
|
||||
const name = nameDesc?.value
|
||||
if (typeof id === 'number' && typeof name === 'string') {
|
||||
cachedUser = { id, email: '', displayName: name }
|
||||
} else {
|
||||
cachedUser = null
|
||||
}
|
||||
// Provide minimal params to satisfy current OpenAPI shape; backend ignores cookie in JWT mode
|
||||
const res = await api.POST('/api/v1/auth/refresh', { params: { cookie: { user_id: 0 } } })
|
||||
if (!res.response.ok) {
|
||||
authToken = null
|
||||
cachedUser = null
|
||||
return null
|
||||
}
|
||||
const data: unknown = res.data
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
authToken = null
|
||||
cachedUser = null
|
||||
return null
|
||||
}
|
||||
const tokenVal = (data as Record<string, unknown>)['accessToken']
|
||||
const userVal = (data as Record<string, unknown>)['user']
|
||||
if (typeof tokenVal !== 'string' || typeof userVal !== 'object' || userVal === null) {
|
||||
authToken = null
|
||||
cachedUser = null
|
||||
return null
|
||||
}
|
||||
const uid = (userVal as Record<string, unknown>)['id']
|
||||
const email = (userVal as Record<string, unknown>)['email']
|
||||
const displayName = (userVal as Record<string, unknown>)['displayName']
|
||||
if (typeof uid !== 'number') {
|
||||
authToken = null
|
||||
cachedUser = null
|
||||
return null
|
||||
}
|
||||
authToken = tokenVal
|
||||
cachedUser = { id: uid, email: typeof email === 'string' ? email : '', displayName: typeof displayName === 'string' ? displayName : '' }
|
||||
return cachedUser
|
||||
} catch (_) {
|
||||
authToken = null
|
||||
cachedUser = null
|
||||
return null
|
||||
}
|
||||
return cachedUser
|
||||
}
|
||||
|
||||
export async function login(username: string): Promise<User> {
|
||||
|
|
|
|||
46
tests/auth.refresh.api.test.ts
Normal file
46
tests/auth.refresh.api.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { server, http, HttpResponse } from './test-setup'
|
||||
import { currentUser, logout } from '@/api/auth'
|
||||
import { api } from '@/api/client'
|
||||
|
||||
describe('auth refresh (currentUser)', () => {
|
||||
beforeEach(async () => {
|
||||
await logout()
|
||||
})
|
||||
it('currentUser refresh returns user and sets auth token', async () => {
|
||||
server.use(
|
||||
http.post('*/api/v1/auth/refresh', () =>
|
||||
HttpResponse.json({ accessToken: 'ref-123', tokenType: 'bearer', user: { id: 9, email: 'ref@example.com', displayName: 'Refreshed' } })
|
||||
)
|
||||
)
|
||||
const user = await currentUser()
|
||||
expect(user?.id).toBe(9)
|
||||
expect(user?.displayName).toBe('Refreshed')
|
||||
|
||||
// Subsequent API call should include Authorization header
|
||||
server.use(
|
||||
http.get('*/api/v1/users/me/households', ({ request }) => {
|
||||
const auth = request.headers.get('authorization')
|
||||
expect(auth?.toLowerCase()).toBe('bearer ref-123')
|
||||
return HttpResponse.json([])
|
||||
})
|
||||
)
|
||||
const res = await api.GET('/api/v1/users/me/households', { params: {} })
|
||||
expect(res.response.ok).toBe(true)
|
||||
})
|
||||
|
||||
it('returns null if refresh fails', async () => {
|
||||
server.use(http.post('*/api/v1/auth/refresh', () => HttpResponse.json({ message: 'not logged in' }, { status: 401 })))
|
||||
const user = await currentUser()
|
||||
expect(user).toBeNull()
|
||||
server.use(
|
||||
http.get('*/api/v1/users/me/households', ({ request }) => {
|
||||
const auth = request.headers.get('authorization')
|
||||
expect(auth).toBeFalsy()
|
||||
return HttpResponse.json([])
|
||||
})
|
||||
)
|
||||
const res = await api.GET('/api/v1/users/me/households', { params: {} })
|
||||
expect(res.response.ok).toBe(true)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue