test: add focused tests for session sharing utilities and API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
72
frontend/src/api/sessions.test.ts
Normal file
72
frontend/src/api/sessions.test.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
||||||
|
import { sessionsApi } from './sessions'
|
||||||
|
import apiClient from './client'
|
||||||
|
|
||||||
|
vi.mock('./client', () => ({
|
||||||
|
default: {
|
||||||
|
get: vi.fn(),
|
||||||
|
post: vi.fn(),
|
||||||
|
put: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
patch: vi.fn(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockClient = apiClient as unknown as {
|
||||||
|
get: ReturnType<typeof vi.fn>
|
||||||
|
post: ReturnType<typeof vi.fn>
|
||||||
|
delete: ReturnType<typeof vi.fn>
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('sessionsApi sharing methods', () => {
|
||||||
|
it('createShare hits POST /sessions/{id}/shares with correct payload', async () => {
|
||||||
|
const mockShare = { id: 'share-1', share_token: 'tok-123', visibility: 'public' }
|
||||||
|
mockClient.post.mockResolvedValue({ data: mockShare })
|
||||||
|
|
||||||
|
const payload = { visibility: 'public' as const, share_name: 'My Share' }
|
||||||
|
const result = await sessionsApi.createShare('session-42', payload)
|
||||||
|
|
||||||
|
expect(mockClient.post).toHaveBeenCalledWith('/sessions/session-42/shares', payload)
|
||||||
|
expect(result).toEqual(mockShare)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('listMyShares hits GET /shares/my-shares', async () => {
|
||||||
|
const mockShares = [
|
||||||
|
{ id: 'share-1', share_token: 'tok-1' },
|
||||||
|
{ id: 'share-2', share_token: 'tok-2' },
|
||||||
|
]
|
||||||
|
mockClient.get.mockResolvedValue({ data: mockShares })
|
||||||
|
|
||||||
|
const result = await sessionsApi.listMyShares()
|
||||||
|
|
||||||
|
expect(mockClient.get).toHaveBeenCalledWith('/shares/my-shares')
|
||||||
|
expect(result).toEqual(mockShares)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('revokeShare hits DELETE /shares/{id}', async () => {
|
||||||
|
mockClient.delete.mockResolvedValue({})
|
||||||
|
|
||||||
|
await sessionsApi.revokeShare('share-99')
|
||||||
|
|
||||||
|
expect(mockClient.delete).toHaveBeenCalledWith('/shares/share-99')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getSharedSession hits GET /share/{token}', async () => {
|
||||||
|
const mockView = {
|
||||||
|
session_id: 'sess-1',
|
||||||
|
tree_name: 'DNS Troubleshooting',
|
||||||
|
path_taken: ['root', 'node-1'],
|
||||||
|
decisions: [],
|
||||||
|
}
|
||||||
|
mockClient.get.mockResolvedValue({ data: mockView })
|
||||||
|
|
||||||
|
const result = await sessionsApi.getSharedSession('tok-abc')
|
||||||
|
|
||||||
|
expect(mockClient.get).toHaveBeenCalledWith('/share/tok-abc')
|
||||||
|
expect(result).toEqual(mockView)
|
||||||
|
})
|
||||||
|
})
|
||||||
85
frontend/src/lib/sessionShare.test.ts
Normal file
85
frontend/src/lib/sessionShare.test.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||||
|
import type { SessionShare } from '@/types'
|
||||||
|
import { buildSessionShareUrl, filterSharesForSession, getLatestActiveShareForSession } from './sessionShare'
|
||||||
|
|
||||||
|
function makeMockShare(overrides: Partial<SessionShare> = {}): SessionShare {
|
||||||
|
return {
|
||||||
|
id: 'share-1',
|
||||||
|
session_id: 'session-1',
|
||||||
|
account_id: 'account-1',
|
||||||
|
share_token: 'abc123',
|
||||||
|
share_name: null,
|
||||||
|
visibility: 'public',
|
||||||
|
created_by: 'user-1',
|
||||||
|
created_at: '2026-02-14T10:00:00Z',
|
||||||
|
updated_at: '2026-02-14T10:00:00Z',
|
||||||
|
expires_at: null,
|
||||||
|
view_count: 0,
|
||||||
|
last_viewed_at: null,
|
||||||
|
is_active: true,
|
||||||
|
share_url: null,
|
||||||
|
...overrides,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllGlobals()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('buildSessionShareUrl', () => {
|
||||||
|
it('returns share_url when present', () => {
|
||||||
|
const share = makeMockShare({ share_url: 'https://resolutionflow.com/share/abc123' })
|
||||||
|
expect(buildSessionShareUrl(share)).toBe('https://resolutionflow.com/share/abc123')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('constructs URL from token when share_url is null', () => {
|
||||||
|
vi.stubGlobal('location', { origin: 'http://localhost:5173' })
|
||||||
|
const share = makeMockShare({ share_token: 'tok-xyz', share_url: null })
|
||||||
|
expect(buildSessionShareUrl(share)).toBe('http://localhost:5173/share/tok-xyz')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('filterSharesForSession', () => {
|
||||||
|
it('filters to matching session_id and active shares', () => {
|
||||||
|
const shares = [
|
||||||
|
makeMockShare({ id: 's1', session_id: 'sess-A', is_active: true }),
|
||||||
|
makeMockShare({ id: 's2', session_id: 'sess-B', is_active: true }),
|
||||||
|
makeMockShare({ id: 's3', session_id: 'sess-A', is_active: true }),
|
||||||
|
]
|
||||||
|
const result = filterSharesForSession(shares, 'sess-A')
|
||||||
|
expect(result).toHaveLength(2)
|
||||||
|
expect(result.map(s => s.id)).toEqual(['s1', 's3'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('excludes inactive shares', () => {
|
||||||
|
const shares = [
|
||||||
|
makeMockShare({ id: 's1', session_id: 'sess-A', is_active: true }),
|
||||||
|
makeMockShare({ id: 's2', session_id: 'sess-A', is_active: false }),
|
||||||
|
]
|
||||||
|
const result = filterSharesForSession(shares, 'sess-A')
|
||||||
|
expect(result).toHaveLength(1)
|
||||||
|
expect(result[0].id).toBe('s1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getLatestActiveShareForSession', () => {
|
||||||
|
it('returns the most recently created share', () => {
|
||||||
|
const shares = [
|
||||||
|
makeMockShare({ id: 'old', session_id: 'sess-A', created_at: '2026-02-10T10:00:00Z', is_active: true }),
|
||||||
|
makeMockShare({ id: 'newest', session_id: 'sess-A', created_at: '2026-02-14T12:00:00Z', is_active: true }),
|
||||||
|
makeMockShare({ id: 'mid', session_id: 'sess-A', created_at: '2026-02-12T10:00:00Z', is_active: true }),
|
||||||
|
]
|
||||||
|
const result = getLatestActiveShareForSession(shares, 'sess-A')
|
||||||
|
expect(result).not.toBeNull()
|
||||||
|
expect(result!.id).toBe('newest')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null when no shares match', () => {
|
||||||
|
const shares = [
|
||||||
|
makeMockShare({ session_id: 'sess-B', is_active: true }),
|
||||||
|
makeMockShare({ session_id: 'sess-A', is_active: false }),
|
||||||
|
]
|
||||||
|
const result = getLatestActiveShareForSession(shares, 'sess-A')
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user