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> {
|
export async function currentUser(): Promise<User | null> {
|
||||||
if (cachedUser) return cachedUser
|
if (cachedUser) return cachedUser
|
||||||
try {
|
try {
|
||||||
const res = await api.POST('/api/v1/auth/refresh', { params: { cookie: { user_id: 0 } } })
|
// Provide minimal params to satisfy current OpenAPI shape; backend ignores cookie in JWT mode
|
||||||
if (!res.response.ok) return null
|
const res = await api.POST('/api/v1/auth/refresh', { params: { cookie: { user_id: 0 } } })
|
||||||
// refresh may return legacy Person; adapt minimally to User shape without type assertions
|
if (!res.response.ok) {
|
||||||
const p = res.data
|
authToken = null
|
||||||
if (typeof p === 'object' && p !== null) {
|
cachedUser = null
|
||||||
const idDesc = Object.getOwnPropertyDescriptor(p, 'id')
|
return null
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 (_) {
|
} catch (_) {
|
||||||
|
authToken = null
|
||||||
cachedUser = null
|
cachedUser = null
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return cachedUser
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function login(username: string): Promise<User> {
|
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