22 * @vitest -environment node
33 */
44
5- import { beforeEach , describe , expect , it , vi } from 'vitest'
5+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
66
77const mocks = vi . hoisted ( ( ) => ( {
88 requestJson : vi . fn ( ) ,
@@ -21,9 +21,37 @@ vi.mock('@/lib/api/client/request', () => ({
2121 requestJson : mocks . requestJson ,
2222} ) )
2323
24- import { listKnowledgeConnectorDocumentsContract } from '@/lib/api/contracts/knowledge'
24+ import {
25+ type ConnectorData ,
26+ listKnowledgeConnectorDocumentsContract ,
27+ } from '@/lib/api/contracts/knowledge'
2528import { MAX_KNOWLEDGE_CONNECTOR_DOCUMENT_PAGE_SIZE } from '@/lib/knowledge/constants'
26- import { useConnectorDocuments } from '@/hooks/queries/kb/connectors'
29+ import { isConnectorSyncingOrPending , useConnectorDocuments } from '@/hooks/queries/kb/connectors'
30+
31+ const NOW_MS = new Date ( '2026-08-21T12:00:00.000Z' ) . getTime ( )
32+
33+ function makeConnector ( overrides : Partial < ConnectorData > = { } ) : ConnectorData {
34+ const createdAt = new Date ( NOW_MS - 60_000 ) . toISOString ( )
35+
36+ return {
37+ id : 'connector-1' ,
38+ knowledgeBaseId : 'knowledge-1' ,
39+ connectorType : 'hubspot' ,
40+ credentialId : 'credential-1' ,
41+ sourceConfig : { } ,
42+ syncMode : 'full' ,
43+ syncIntervalMinutes : 1440 ,
44+ status : 'active' ,
45+ lastSyncAt : null ,
46+ lastSyncError : null ,
47+ lastSyncDocCount : null ,
48+ nextSyncAt : null ,
49+ consecutiveFailures : 0 ,
50+ createdAt,
51+ updatedAt : createdAt ,
52+ ...overrides ,
53+ }
54+ }
2755
2856interface ConnectorDocumentsPage {
2957 documents : Array < { id : string } >
@@ -39,6 +67,53 @@ interface ConnectorDocumentsQueryOptions {
3967 ) => number | undefined
4068}
4169
70+ describe ( 'isConnectorSyncingOrPending' , ( ) => {
71+ beforeEach ( ( ) => {
72+ vi . spyOn ( Date , 'now' ) . mockReturnValue ( NOW_MS )
73+ } )
74+
75+ afterEach ( ( ) => {
76+ vi . restoreAllMocks ( )
77+ } )
78+
79+ it ( 'treats a recently created active connector without a completed sync as pending' , ( ) => {
80+ expect ( isConnectorSyncingOrPending ( makeConnector ( ) ) ) . toBe ( true )
81+ } )
82+
83+ it ( 'treats a syncing connector as syncing regardless of its age or sync history' , ( ) => {
84+ const connector = makeConnector ( {
85+ status : 'syncing' ,
86+ createdAt : new Date ( NOW_MS - 24 * 60 * 60 * 1000 ) . toISOString ( ) ,
87+ lastSyncAt : new Date ( NOW_MS - 60 * 60 * 1000 ) . toISOString ( ) ,
88+ } )
89+
90+ expect ( isConnectorSyncingOrPending ( connector ) ) . toBe ( true )
91+ } )
92+
93+ it ( 'does not treat an active connector with a completed sync as pending' , ( ) => {
94+ const connector = makeConnector ( {
95+ lastSyncAt : new Date ( NOW_MS - 30_000 ) . toISOString ( ) ,
96+ } )
97+
98+ expect ( isConnectorSyncingOrPending ( connector ) ) . toBe ( false )
99+ } )
100+
101+ it ( 'stops treating an active connector as pending at the two-minute boundary' , ( ) => {
102+ const connector = makeConnector ( {
103+ createdAt : new Date ( NOW_MS - 2 * 60 * 1000 ) . toISOString ( ) ,
104+ } )
105+
106+ expect ( isConnectorSyncingOrPending ( connector ) ) . toBe ( false )
107+ } )
108+
109+ it . each ( [ 'error' , 'paused' , 'disabled' ] as const ) (
110+ 'does not treat a recent %s connector as pending' ,
111+ ( status ) => {
112+ expect ( isConnectorSyncingOrPending ( makeConnector ( { status } ) ) ) . toBe ( false )
113+ }
114+ )
115+ } )
116+
42117describe ( 'useConnectorDocuments' , ( ) => {
43118 beforeEach ( ( ) => {
44119 vi . clearAllMocks ( )
0 commit comments