@@ -15,14 +15,15 @@ import {
1515 workspace ,
1616} from '@sim/db/schema'
1717import { generateId } from '@sim/utils/id'
18- import { eq , inArray } from 'drizzle-orm'
18+ import { eq , inArray , sql } from 'drizzle-orm'
1919import { afterAll , beforeAll , beforeEach , describe , expect , it } from 'vitest'
2020import {
2121 createKnowledgeAclFixtureIds ,
2222 seedKnowledgeAclFixture ,
2323} from '@/lib/knowledge/__integration__/seed-source-access-fixture'
2424import { readOrganizationSearchOverview } from '@/lib/knowledge/application/organization-search-overview'
2525import { listSearchSources } from '@/lib/knowledge/application/search-sources'
26+ import { SOURCE_PERMISSION_ERROR } from '@/lib/knowledge/connectors/sync-limits'
2627
2728const ids = createKnowledgeAclFixtureIds ( )
2829const indexId = generateId ( )
@@ -177,6 +178,44 @@ async function provider(connectorType: string) {
177178}
178179
179180describe ( 'organization operational overview with real SQL' , ( ) => {
181+ it . each ( [
182+ SOURCE_PERMISSION_ERROR ,
183+ `Directory refresh incomplete: fixture\n${ SOURCE_PERMISSION_ERROR } ` ,
184+ `${ SOURCE_PERMISSION_ERROR } \nSource listing failed for a fixture account` ,
185+ `Directory refresh incomplete: fixture\n${ SOURCE_PERMISSION_ERROR } \nSource listing failed for a fixture account` ,
186+ ] ) (
187+ 'recognizes a complete permission notice within composed diagnostics: %s' ,
188+ async ( lastSyncError ) => {
189+ await db
190+ . update ( knowledgeConnector )
191+ . set ( { lastSyncError } )
192+ . where ( eq ( knowledgeConnector . id , driveId ) )
193+ expect ( await provider ( 'google_drive' ) ) . toMatchObject ( {
194+ status : 'needs_attention' ,
195+ issue : 'permission_sync_incomplete' ,
196+ } )
197+ }
198+ )
199+
200+ it ( 'does not classify a provider message containing the permission text as its own notice' , async ( ) => {
201+ await db
202+ . update ( knowledgeConnector )
203+ . set ( { lastSyncError : `Provider message: ${ SOURCE_PERMISSION_ERROR } ` } )
204+ . where ( eq ( knowledgeConnector . id , driveId ) )
205+ expect ( await provider ( 'google_drive' ) ) . toMatchObject ( {
206+ status : 'needs_attention' ,
207+ issue : 'sync_failed' ,
208+ } )
209+ } )
210+
211+ it ( 'ignores permission notices on paused sources' , async ( ) => {
212+ await db
213+ . update ( knowledgeConnector )
214+ . set ( { lastSyncError : `Directory refresh incomplete: fixture\n${ SOURCE_PERMISSION_ERROR } ` } )
215+ . where ( eq ( knowledgeConnector . id , pausedDriveId ) )
216+ expect ( await provider ( 'google_drive' ) ) . toMatchObject ( { status : 'active' , issue : null } )
217+ } )
218+
180219 it ( 'counts configured sources independently of viewer ACLs, and excludes workspace and untouched providers' , async ( ) => {
181220 const result = await readOrganizationSearchOverview . execute ( { principal, input } )
182221 expect ( result . providers ) . toEqual (
@@ -188,6 +227,7 @@ describe('organization operational overview with real SQL', () => {
188227 status : 'active' ,
189228 issue : null ,
190229 isSyncing : false ,
230+ hasPendingSync : false ,
191231 } ,
192232 {
193233 connectorType : 'gmail' ,
@@ -196,6 +236,7 @@ describe('organization operational overview with real SQL', () => {
196236 status : 'active' ,
197237 issue : null ,
198238 isSyncing : false ,
239+ hasPendingSync : false ,
199240 } ,
200241 ] )
201242 )
@@ -237,7 +278,7 @@ describe('organization operational overview with real SQL', () => {
237278 . where ( eq ( knowledgeConnector . id , gmailId ) )
238279 expect ( await provider ( 'gmail' ) ) . toMatchObject ( { status : 'waiting_for_connections' } )
239280 } )
240- it ( 'distinguishes normal member continuation from partial failure without treating idle as success ' , async ( ) => {
281+ it ( 'distinguishes queued member continuation from active indexing and partial failure ' , async ( ) => {
241282 await db
242283 . update ( knowledgeConnectorMember )
243284 . set ( { listingCheckpoint : { cursor : 'fixture' } } )
@@ -248,9 +289,15 @@ describe('organization operational overview with real SQL', () => {
248289 connectorId : gmailId ,
249290 status : 'partial' ,
250291 membersIncomplete : 1 ,
292+ docsFailed : 0 ,
293+ processingDispatchFailed : 0 ,
251294 completedAt : new Date ( ) ,
252295 } )
253- expect ( await provider ( 'gmail' ) ) . toMatchObject ( { status : 'indexing' } )
296+ expect ( await provider ( 'gmail' ) ) . toMatchObject ( {
297+ status : 'active' ,
298+ isSyncing : false ,
299+ hasPendingSync : true ,
300+ } )
254301 await db
255302 . update ( knowledgeConnectorMemberSyncLog )
256303 . set ( { membersFailed : 1 } )
@@ -267,9 +314,13 @@ describe('organization operational overview with real SQL', () => {
267314 expect ( await provider ( 'gmail' ) ) . toMatchObject ( { status : 'needs_attention' } )
268315 await db
269316 . update ( knowledgeConnector )
270- . set ( { nextMemberSyncAt : new Date ( ) } )
317+ . set ( { nextMemberSyncAt : sql `statement_timestamp() - interval '1 second'` } )
271318 . where ( eq ( knowledgeConnector . id , gmailId ) )
272- expect ( await provider ( 'gmail' ) ) . toMatchObject ( { status : 'indexing' } )
319+ expect ( await provider ( 'gmail' ) ) . toMatchObject ( {
320+ status : 'active' ,
321+ isSyncing : false ,
322+ hasPendingSync : true ,
323+ } )
273324 await db
274325 . update ( knowledgeConnector )
275326 . set ( { nextMemberSyncAt : null } )
@@ -278,6 +329,8 @@ describe('organization operational overview with real SQL', () => {
278329 id : generateId ( ) ,
279330 connectorId : gmailId ,
280331 status : 'completed' ,
332+ docsFailed : 0 ,
333+ processingDispatchFailed : 0 ,
281334 startedAt : new Date ( Date . now ( ) + 1000 ) ,
282335 completedAt : new Date ( ) ,
283336 } )
0 commit comments