Hybrid approach: reuse LocalChunker to chunk the DoclingDocument JSON returned by Serve, so chunking works identically in both local and remote modes without calling Serve's chunk endpoint. Backend: - _build_chunker() always returns LocalChunker (remove engine guard) - Use docling-core[chunking] extra for required dependencies - Skip client-side batching in remote mode (Serve manages its own resources, and batching discards document_json needed for chunking) - Fix Serve form fields: remove generate_page_images (not a Serve field), use repeated form keys for to_formats and page_range - Log Serve error response body on 4xx/5xx for diagnosis - Fix FastAPI 204 DELETE routes missing response_model=None Frontend: - Update chunking feature flag to enable Prepare UI in remote mode Closes #51
126 lines
4.1 KiB
TypeScript
126 lines
4.1 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { setActivePinia, createPinia } from 'pinia'
|
|
import { useFeatureFlagStore } from './store'
|
|
|
|
const mockApiFetch = vi.fn()
|
|
vi.mock('../../shared/api/http', () => ({
|
|
apiFetch: (...args: unknown[]) => mockApiFetch(...args),
|
|
}))
|
|
|
|
describe('useFeatureFlagStore', () => {
|
|
beforeEach(() => {
|
|
setActivePinia(createPinia())
|
|
mockApiFetch.mockReset()
|
|
})
|
|
|
|
it('starts unloaded with flags disabled', () => {
|
|
const store = useFeatureFlagStore()
|
|
expect(store.loaded).toBe(false)
|
|
expect(store.isEnabled('chunking')).toBe(false)
|
|
expect(store.isEnabled('disclaimer')).toBe(false)
|
|
})
|
|
|
|
it('enables chunking when engine is local', async () => {
|
|
mockApiFetch.mockResolvedValue({ status: 'ok', engine: 'local' })
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.engine).toBe('local')
|
|
expect(store.loaded).toBe(true)
|
|
expect(store.isEnabled('chunking')).toBe(true)
|
|
})
|
|
|
|
it('enables chunking when engine is remote', async () => {
|
|
mockApiFetch.mockResolvedValue({ status: 'ok', engine: 'remote' })
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.engine).toBe('remote')
|
|
expect(store.isEnabled('chunking')).toBe(true)
|
|
})
|
|
|
|
it('enables disclaimer when deploymentMode is huggingface', async () => {
|
|
mockApiFetch.mockResolvedValue({
|
|
status: 'ok',
|
|
engine: 'local',
|
|
deploymentMode: 'huggingface',
|
|
})
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.deploymentMode).toBe('huggingface')
|
|
expect(store.isEnabled('disclaimer')).toBe(true)
|
|
})
|
|
|
|
it('disables disclaimer when deploymentMode is self-hosted', async () => {
|
|
mockApiFetch.mockResolvedValue({
|
|
status: 'ok',
|
|
engine: 'local',
|
|
deploymentMode: 'self-hosted',
|
|
})
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.isEnabled('disclaimer')).toBe(false)
|
|
})
|
|
|
|
it('defaults deploymentMode to self-hosted when missing', async () => {
|
|
mockApiFetch.mockResolvedValue({ status: 'ok', engine: 'local' })
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.deploymentMode).toBe('self-hosted')
|
|
expect(store.isEnabled('disclaimer')).toBe(false)
|
|
})
|
|
|
|
it('reads maxFileSizeMb from health response', async () => {
|
|
mockApiFetch.mockResolvedValue({ status: 'ok', engine: 'local', maxFileSizeMb: 100 })
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.maxFileSizeMb).toBe(100)
|
|
})
|
|
|
|
it('defaults maxFileSizeMb to 0 when missing', async () => {
|
|
mockApiFetch.mockResolvedValue({ status: 'ok', engine: 'local' })
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.maxFileSizeMb).toBe(0)
|
|
})
|
|
|
|
it('enables ingestion when ingestionAvailable is true', async () => {
|
|
mockApiFetch.mockResolvedValue({
|
|
status: 'ok',
|
|
engine: 'local',
|
|
ingestionAvailable: true,
|
|
})
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.ingestionAvailable).toBe(true)
|
|
expect(store.isEnabled('ingestion')).toBe(true)
|
|
})
|
|
|
|
it('disables ingestion when ingestionAvailable is false', async () => {
|
|
mockApiFetch.mockResolvedValue({
|
|
status: 'ok',
|
|
engine: 'local',
|
|
ingestionAvailable: false,
|
|
})
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.ingestionAvailable).toBe(false)
|
|
expect(store.isEnabled('ingestion')).toBe(false)
|
|
})
|
|
|
|
it('defaults ingestionAvailable to false when missing', async () => {
|
|
mockApiFetch.mockResolvedValue({ status: 'ok', engine: 'local' })
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.ingestionAvailable).toBe(false)
|
|
expect(store.isEnabled('ingestion')).toBe(false)
|
|
})
|
|
|
|
it('handles health endpoint failure gracefully', async () => {
|
|
mockApiFetch.mockRejectedValue(new Error('Network error'))
|
|
const store = useFeatureFlagStore()
|
|
await store.load()
|
|
expect(store.loaded).toBe(true)
|
|
expect(store.error).toBe('Network error')
|
|
expect(store.isEnabled('chunking')).toBe(false)
|
|
expect(store.isEnabled('disclaimer')).toBe(false)
|
|
})
|
|
})
|