Skip to content

Commit 1623ebf

Browse files
committed
Add more coverage
1 parent 528f9fa commit 1623ebf

File tree

7 files changed

+363
-0
lines changed

7 files changed

+363
-0
lines changed

apps/data-management/src/api/__tests__/api.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ const { fetchMock } = vi.hoisted(() => ({
55
}))
66

77
vi.mock('@hydroserver/client', () => {
8+
const defaultExport = {
9+
session: {
10+
getAccessToken: vi.fn().mockResolvedValue(null),
11+
},
12+
}
13+
814
class Thing {
915
id = ''
1016
workspaceId = ''
@@ -35,6 +41,7 @@ vi.mock('@hydroserver/client', () => {
3541
}
3642

3743
return {
44+
default: defaultExport,
3845
Thing,
3946
Datastream,
4047
ObservedProperty,

apps/data-management/src/api/thingMarkers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const apiBaseUrl = import.meta.env.VITE_APP_PROXY_BASE_URL || apiHost
77
export async function listThingMarkers(): Promise<ThingMarker[]> {
88
const accessToken = await hs.session.getAccessToken()
99
const response = await fetch(`${apiBaseUrl}/api/data/things/markers`, {
10+
credentials: 'include',
1011
headers: {
1112
Accept: 'application/json',
1213
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),

apps/data-management/src/api/thingSiteSummaries.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export async function listThingSiteSummaries(
1212
const response = await fetch(
1313
`${apiBaseUrl}/api/data/things/site-summaries?${query.toString()}`,
1414
{
15+
credentials: 'include',
1516
headers: {
1617
Accept: 'application/json',
1718
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),

apps/data-management/src/api/visualizationBootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export async function getVisualizationBootstrap() {
5353
const response = await fetch(
5454
`${apiBaseUrl}/api/data/datastreams/visualization-bootstrap`,
5555
{
56+
credentials: 'include',
5657
headers: {
5758
Accept: 'application/json',
5859
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),

apps/data-management/src/bootstrap/__tests__/appInitialization.spec.ts

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,4 +223,178 @@ describe('app initialization bootstrap', () => {
223223

224224
consoleErrorSpy.mockRestore()
225225
})
226+
227+
it('returns an already resolved wait promise before initialization starts', async () => {
228+
const { waitForHydroServerInitialization } = await import('../appInitialization')
229+
230+
await expect(waitForHydroServerInitialization()).resolves.toBeUndefined()
231+
})
232+
233+
it('resets the user when the authenticated user request returns 401', async () => {
234+
sessionState.isAuthenticated = true
235+
sessionState.hasAccessToken = true
236+
createHydroServerMock.mockResolvedValue(undefined)
237+
fetchAllVocabulariesMock.mockResolvedValue(undefined)
238+
userGetMock.mockResolvedValue({
239+
status: 401,
240+
data: { email: 'ignored@example.com' },
241+
})
242+
listAllItemsMock.mockResolvedValue([
243+
{ id: 'workspace-1', name: 'Workspace 1' },
244+
])
245+
246+
const { startAppInitialization } = await import('../appInitialization')
247+
248+
await startAppInitialization()
249+
250+
const userStore = useUserStore()
251+
expect(userStore.user.email).toBe('')
252+
})
253+
254+
it('records user fetch failures without breaking workspace bootstrap', async () => {
255+
const userError = new Error('user request failed')
256+
const consoleErrorSpy = vi
257+
.spyOn(console, 'error')
258+
.mockImplementation(() => undefined)
259+
260+
sessionState.isAuthenticated = true
261+
sessionState.hasAccessToken = true
262+
createHydroServerMock.mockResolvedValue(undefined)
263+
fetchAllVocabulariesMock.mockResolvedValue(undefined)
264+
userGetMock.mockRejectedValue(userError)
265+
listAllItemsMock.mockResolvedValue([
266+
{ id: 'workspace-1', name: 'Workspace 1' },
267+
])
268+
269+
const {
270+
hasBootstrappedWorkspaces,
271+
initializationError,
272+
startAppInitialization,
273+
} = await import('../appInitialization')
274+
275+
await startAppInitialization()
276+
277+
expect(initializationError.value).toBe(userError)
278+
expect(hasBootstrappedWorkspaces.value).toBe(true)
279+
expect(consoleErrorSpy).toHaveBeenCalledWith(
280+
'Error fetching user',
281+
userError
282+
)
283+
284+
consoleErrorSpy.mockRestore()
285+
})
286+
287+
it('records workspace fetch failures without breaking user bootstrap', async () => {
288+
const workspaceError = new Error('workspace request failed')
289+
const consoleErrorSpy = vi
290+
.spyOn(console, 'error')
291+
.mockImplementation(() => undefined)
292+
293+
sessionState.isAuthenticated = true
294+
sessionState.hasAccessToken = true
295+
createHydroServerMock.mockResolvedValue(undefined)
296+
fetchAllVocabulariesMock.mockResolvedValue(undefined)
297+
userGetMock.mockResolvedValue({
298+
status: 200,
299+
data: { email: 'user@example.com' },
300+
})
301+
listAllItemsMock.mockRejectedValue(workspaceError)
302+
303+
const {
304+
hasBootstrappedWorkspaces,
305+
initializationError,
306+
startAppInitialization,
307+
} = await import('../appInitialization')
308+
309+
await startAppInitialization()
310+
311+
const userStore = useUserStore()
312+
expect(userStore.user.email).toBe('user@example.com')
313+
expect(hasBootstrappedWorkspaces.value).toBe(false)
314+
expect(initializationError.value).toBe(workspaceError)
315+
expect(consoleErrorSpy).toHaveBeenCalledWith(
316+
'Error fetching workspaces',
317+
workspaceError
318+
)
319+
320+
consoleErrorSpy.mockRestore()
321+
})
322+
323+
it('records vocabulary fetch failures', async () => {
324+
const vocabularyError = new Error('vocabulary request failed')
325+
const consoleErrorSpy = vi
326+
.spyOn(console, 'error')
327+
.mockImplementation(() => undefined)
328+
329+
createHydroServerMock.mockResolvedValue(undefined)
330+
fetchAllVocabulariesMock.mockRejectedValue(vocabularyError)
331+
332+
const { initializationError, startAppInitialization } = await import(
333+
'../appInitialization'
334+
)
335+
336+
await startAppInitialization()
337+
338+
expect(initializationError.value).toBe(vocabularyError)
339+
expect(consoleErrorSpy).toHaveBeenCalledWith(
340+
'Error fetching vocabularies',
341+
vocabularyError
342+
)
343+
344+
consoleErrorSpy.mockRestore()
345+
})
346+
347+
it('reuses the initialization promise and only attaches session listeners once', async () => {
348+
sessionState.isAuthenticated = false
349+
createHydroServerMock.mockResolvedValue(undefined)
350+
fetchAllVocabulariesMock.mockResolvedValue(undefined)
351+
352+
const { startAppInitialization } = await import('../appInitialization')
353+
354+
const first = startAppInitialization()
355+
const second = startAppInitialization()
356+
357+
await first
358+
await second
359+
360+
expect(first).toBe(second)
361+
expect(createHydroServerMock).toHaveBeenCalledTimes(1)
362+
expect(onMock).toHaveBeenCalledTimes(2)
363+
})
364+
365+
it('refreshes authenticated state when session change and expiry callbacks fire', async () => {
366+
sessionState.isAuthenticated = true
367+
sessionState.hasAccessToken = true
368+
createHydroServerMock.mockResolvedValue(undefined)
369+
fetchAllVocabulariesMock.mockResolvedValue(undefined)
370+
userGetMock.mockResolvedValue({
371+
status: 200,
372+
data: { email: 'callback@example.com' },
373+
})
374+
listAllItemsMock.mockResolvedValue([
375+
{ id: 'workspace-1', name: 'Workspace 1' },
376+
])
377+
378+
const { startAppInitialization } = await import('../appInitialization')
379+
380+
await startAppInitialization()
381+
382+
const sessionChanged = onMock.mock.calls.find(
383+
([eventName]) => eventName === 'session:changed'
384+
)?.[1] as (() => void) | undefined
385+
const sessionExpired = onMock.mock.calls.find(
386+
([eventName]) => eventName === 'session:expired'
387+
)?.[1] as (() => void) | undefined
388+
389+
expect(sessionChanged).toBeTypeOf('function')
390+
expect(sessionExpired).toBeTypeOf('function')
391+
392+
sessionChanged?.()
393+
sessionExpired?.()
394+
await Promise.resolve()
395+
await Promise.resolve()
396+
397+
expect(userGetMock).toHaveBeenCalledTimes(3)
398+
expect(listAllItemsMock).toHaveBeenCalledTimes(3)
399+
})
226400
})

apps/data-management/src/composables/__tests__/useTableLogic.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,47 @@ describe('useTableLogic', () => {
158158
expect(consoleErrorSpy).toHaveBeenCalled()
159159
consoleErrorSpy.mockRestore()
160160
})
161+
162+
it('does nothing when deleting with no selected item', async () => {
163+
const apiDeleteFunction = vi.fn().mockResolvedValue(undefined)
164+
const wrapper = mount(createDummyComponent({ apiDeleteFunction }))
165+
await flushPromises()
166+
167+
const vm = wrapper.vm as Omit<typeof wrapper.vm, 'item'> & {
168+
item: Unit | null
169+
}
170+
vm.item = null
171+
await wrapper.vm.onDelete()
172+
173+
expect(apiDeleteFunction).not.toHaveBeenCalled()
174+
})
175+
176+
it('reloads items when the workspace id changes', async () => {
177+
const secondResult = [{ ...unitFixtures[0], id: 'reloaded-id' }]
178+
const apiFetchFunction = vi
179+
.fn()
180+
.mockResolvedValueOnce(unitFixtures)
181+
.mockResolvedValueOnce(secondResult)
182+
183+
const wrapper = mount(createDummyComponent({ apiFetchFunction }))
184+
await flushPromises()
185+
186+
wrapper.vm.workspaceIdRef = 'next-workspace'
187+
await flushPromises()
188+
189+
expect(apiFetchFunction).toHaveBeenCalledTimes(2)
190+
expect(apiFetchFunction).toHaveBeenNthCalledWith(2, 'next-workspace')
191+
expect(wrapper.vm.items).toEqual(secondResult)
192+
})
193+
194+
it('does not reload items when the workspace id is set to the same value', async () => {
195+
const apiFetchFunction = vi.fn().mockResolvedValue(unitFixtures)
196+
const wrapper = mount(createDummyComponent({ apiFetchFunction }))
197+
await flushPromises()
198+
199+
wrapper.vm.workspaceIdRef = 'test-workspace'
200+
await flushPromises()
201+
202+
expect(apiFetchFunction).toHaveBeenCalledTimes(1)
203+
})
161204
})

0 commit comments

Comments
 (0)