128 lines
4 KiB
TypeScript
128 lines
4 KiB
TypeScript
import { submitChallengeAttempt } from '@/service/challenges'
|
|
import { PUBLIC_API_PREFIX } from '@/config'
|
|
|
|
const originalFetch = globalThis.fetch
|
|
let fetchMock: jest.Mock
|
|
let ssePostMock: jest.SpyInstance
|
|
|
|
jest.mock('@/service/base', () => {
|
|
const actual = jest.requireActual('@/service/base')
|
|
return {
|
|
...actual,
|
|
ssePost: jest.fn(),
|
|
}
|
|
})
|
|
|
|
jest.mock('@/app/components/share/utils', () => ({
|
|
...jest.requireActual('@/app/components/share/utils'),
|
|
getInitialTokenV2: () => ({ version: 2 }),
|
|
isTokenV1: () => false,
|
|
}))
|
|
|
|
describe('submitChallengeAttempt', () => {
|
|
beforeEach(() => {
|
|
fetchMock = jest.fn()
|
|
globalThis.fetch = fetchMock as unknown as typeof fetch
|
|
|
|
ssePostMock = jest.spyOn(require('@/service/base'), 'ssePost').mockImplementation((_url: string, _options: any, handlers: any) => {
|
|
handlers.onCompleted?.()
|
|
return Promise.resolve()
|
|
})
|
|
|
|
localStorage.clear()
|
|
})
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks()
|
|
})
|
|
|
|
afterAll(() => {
|
|
globalThis.fetch = originalFetch
|
|
})
|
|
|
|
it('throws when challenge web app is not published', async () => {
|
|
await expect(
|
|
submitChallengeAttempt('challenge-id', 'app-id', undefined, 'chat', 'hello'),
|
|
).rejects.toThrow('Challenge app is not published')
|
|
|
|
expect(fetchMock).not.toHaveBeenCalled()
|
|
expect(ssePostMock).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('requests a passport token and submits chat attempts through /chat-messages', async () => {
|
|
const passportToken = 'chat-passport-token'
|
|
fetchMock.mockResolvedValue({
|
|
ok: true,
|
|
json: jest.fn().mockResolvedValue({ access_token: passportToken }),
|
|
})
|
|
|
|
ssePostMock.mockImplementation((_url, _options, handlers) => {
|
|
handlers.getAbortController?.(new AbortController())
|
|
handlers.onData?.('Hello', true, { messageId: 'msg-1' } as any)
|
|
handlers.onMessageEnd?.({ metadata: { outputs: { challenge_succeeded: true }, answer: 'All good' } } as any)
|
|
handlers.onCompleted?.()
|
|
return Promise.resolve()
|
|
})
|
|
|
|
await submitChallengeAttempt('challenge-123', 'app-abc', 'site-code-xyz', 'chat', 'solve this')
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(`${PUBLIC_API_PREFIX}/passport`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-App-Code': 'site-code-xyz',
|
|
},
|
|
credentials: 'include',
|
|
})
|
|
|
|
expect(ssePostMock).toHaveBeenCalledWith('/chat-messages', expect.objectContaining({
|
|
body: {
|
|
query: 'solve this',
|
|
inputs: {},
|
|
response_mode: 'streaming',
|
|
conversation_id: '',
|
|
},
|
|
}), expect.any(Object))
|
|
|
|
const storedToken = JSON.parse(localStorage.getItem('token') || '{}')
|
|
expect(storedToken.version).toBe(2)
|
|
expect(storedToken['challenge-123'].DEFAULT).toBe(passportToken)
|
|
})
|
|
|
|
it('requests a passport token and submits workflow attempts through /workflows/run', async () => {
|
|
const passportToken = 'workflow-passport-token'
|
|
fetchMock.mockResolvedValue({
|
|
ok: true,
|
|
json: jest.fn().mockResolvedValue({ access_token: passportToken }),
|
|
})
|
|
|
|
ssePostMock.mockImplementation((_url, _options, handlers) => {
|
|
handlers.getAbortController?.(new AbortController())
|
|
handlers.onTextChunk?.({ data: { text: 'partial' } } as any)
|
|
handlers.onWorkflowFinished?.({ data: { outputs: { challenge_succeeded: false, message: 'nope' } } } as any)
|
|
handlers.onCompleted?.()
|
|
return Promise.resolve()
|
|
})
|
|
|
|
await submitChallengeAttempt('challenge-456', 'app-def', 'site-code-xyz', 'workflow', 'my answer')
|
|
|
|
expect(fetchMock).toHaveBeenCalledWith(`${PUBLIC_API_PREFIX}/passport`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-App-Code': 'site-code-xyz',
|
|
},
|
|
credentials: 'include',
|
|
})
|
|
|
|
expect(ssePostMock).toHaveBeenCalledWith('/workflows/run', expect.objectContaining({
|
|
body: {
|
|
inputs: {
|
|
user_prompt: 'my answer',
|
|
},
|
|
response_mode: 'streaming',
|
|
},
|
|
}), expect.any(Object))
|
|
|
|
const storedToken = JSON.parse(localStorage.getItem('token') || '{}')
|
|
expect(storedToken['challenge-456'].DEFAULT).toBe(passportToken)
|
|
})
|
|
})
|