import type { DocumentProcessingTask, LogicalDocument, } from '@dify/contracts/knowledge-fs/types.gen' import { documentDisplayStatus, newestTaskByDocument, taskCanRetry, taskIsActive, taskNeedsAttention, taskVersionIsAfter, } from '../document-model' const document = (overrides: Partial = {}): LogicalDocument => ({ active: { contentHash: 'hash', createdAt: '2026-07-20T10:00:00Z', documentAssetId: 'asset-1', documentAssetVersion: 1, documentId: 'document-1', knowledgeSpaceId: 'space-1', mimeType: 'text/plain', revision: 2, sizeBytes: 10, state: 'active', }, activeRevision: 2, createdAt: '2026-07-20T10:00:00Z', id: 'document-1', knowledgeSpaceId: 'space-1', rowVersion: 1, status: 'ready', title: 'Document', updatedAt: '2026-07-20T10:00:00Z', userMetadata: {}, ...overrides, }) const task = (overrides: Partial = {}): DocumentProcessingTask => ({ createdAt: '2026-07-20T10:00:00Z', documentId: 'document-1', documentRevision: 3, id: 'task-1', knowledgeSpaceId: 'space-1', progressPercent: 10, stage: 'queued', state: 'queued', updatedAt: '2026-07-20T10:00:00Z', ...overrides, }) describe('new Knowledge document model', () => { it('chooses the highest revision before a more recently updated stale task', () => { const result = newestTaskByDocument([ task({ documentRevision: 2, id: 'stale', updatedAt: '2026-07-20T10:10:00Z' }), task({ documentRevision: 3, id: 'current', updatedAt: '2026-07-20T10:01:00Z' }), ]) expect(result.get('document-1')?.id).toBe('current') }) it('compares task versions without losing sub-millisecond precision', () => { expect(taskVersionIsAfter('2026-07-20T10:00:00.123789Z', '2026-07-20T10:00:00.123456Z')).toBe( true, ) expect(taskVersionIsAfter('2026-07-20T10:00:00.000001Z', '2026-07-20T10:00:00Z')).toBe(true) expect(taskVersionIsAfter('2026-07-20T10:00:00Z', '2026-07-20T10:00:00.000001Z')).toBe(false) }) it('selects the newest task when versions differ only after milliseconds', () => { const result = newestTaskByDocument([ task({ id: 'older', updatedAt: '2026-07-20T10:00:00.123456Z' }), task({ id: 'newer', updatedAt: '2026-07-20T10:00:00.123789Z' }), ]) expect(result.get('document-1')?.id).toBe('newer') }) it.each([ ['dispatch_pending', 'queued'], ['queued', 'queued'], ['retry_wait', 'queued'], ['running', 'processing'], ['failed', 'failed'], ] as const)('maps a latest %s task to %s', (state, expected) => { expect(documentDisplayStatus(document(), task({ state }))).toBe(expected) }) it('keeps a last ready revision available after a canceled re-index', () => { expect(documentDisplayStatus(document(), task({ state: 'canceled' }))).toBe('ready') expect( documentDisplayStatus( document({ active: null, activeRevision: undefined, status: 'pending' }), task({ state: 'canceled' }), ), ).toBe('failed') }) it('does not let an older failed task override the active revision', () => { expect(documentDisplayStatus(document(), task({ documentRevision: 1, state: 'failed' }))).toBe( 'ready', ) }) it('maps a disabled source and deleting lifecycle to the designed disabled state', () => { expect(documentDisplayStatus(document(), undefined, true)).toBe('disabled') expect(documentDisplayStatus(document({ status: 'deleting' }))).toBe('disabled') }) it('keeps task badge and actions aligned with the contract states', () => { expect(taskIsActive(task({ state: 'running' }))).toBe(true) expect(taskNeedsAttention(task({ state: 'failed' }))).toBe(true) expect(taskCanRetry(task({ state: 'canceled' }))).toBe(false) expect(taskNeedsAttention(task({ state: 'canceled' }))).toBe(false) expect(taskNeedsAttention(task({ state: 'succeeded' }))).toBe(false) expect(taskCanRetry(task({ state: 'superseded' }))).toBe(false) }) })