Auth refresh, stable router tests, and Household Settings members list; spec + cleanup tasks

This commit is contained in:
jableader 2025-11-01 15:30:44 +11:00
parent f78cc31479
commit aed7ea44e9
2 changed files with 78 additions and 15 deletions

View file

@ -9,25 +9,42 @@ setAuthTokenProvider(() => authToken)
export async function currentUser(): Promise<User | null> {
if (cachedUser) return cachedUser
try {
// 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) 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 {
if (!res.response.ok) {
authToken = null
cachedUser = null
return null
}
}
} catch (_) {
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
}
}
export async function login(username: string): Promise<User> {

View 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)
})
})