@@ -7,9 +7,10 @@ import {
77 user ,
88 workspace ,
99} from '@sim/db/schema'
10- import { eq , inArray } from 'drizzle-orm'
11- import { afterAll , beforeAll , describe , expect , it } from 'vitest'
10+ import { and , eq , inArray } from 'drizzle-orm'
11+ import { afterAll , afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
1212import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture'
13+ import type { GoogleCompanyWorkUpdate } from '@/lib/knowledge/connectors/google-company-scheduler'
1314import {
1415 commitGoogleCompanyWork ,
1516 googleCompanyWorkStore ,
@@ -35,7 +36,7 @@ describe('Google company user checkpoint storage', () => {
3536 startedAt : generationStartedAt ,
3637 } )
3738
38- beforeAll ( async ( ) => {
39+ beforeEach ( async ( ) => {
3940 owner = await seedKnowledgeAclFixture ( undefined , { connectorType : 'google_drive' } )
4041 await db . transaction ( async ( tx ) => {
4142 await commitGoogleCompanyWork (
@@ -51,12 +52,13 @@ describe('Google company user checkpoint storage', () => {
5152 . where ( eq ( knowledgeConnector . id , owner . connectorId ) )
5253 } )
5354 } )
54- afterAll ( async ( ) => {
55+ afterEach ( async ( ) => {
56+ vi . unstubAllEnvs ( )
5557 await db . delete ( workspace ) . where ( eq ( workspace . id , owner . workspaceId ) )
5658 await db . delete ( organization ) . where ( eq ( organization . id , owner . organizationId ) )
5759 await db . delete ( user ) . where ( inArray ( user . id , [ owner . aliceId , owner . bobId ] ) )
58- await db . $client . end ( )
5960 } )
61+ afterAll ( ( ) => db . $client . end ( ) )
6062
6163 it ( 'rolls back user progress and the owning checkpoint together' , async ( ) => {
6264 const store = googleCompanyWorkStore ( owner . connectorId , generationId )
@@ -208,6 +210,23 @@ describe('Google company user checkpoint storage', () => {
208210 } )
209211
210212 it ( 'does not rescan completed manual work or use its past retry time to wake blocked work' , async ( ) => {
213+ await commitGoogleCompanyWork (
214+ db ,
215+ owner . connectorId ,
216+ generationId ,
217+ {
218+ update : {
219+ userId : 'first' ,
220+ kind : 'content' ,
221+ cursor : 'blocked-user-page' ,
222+ completed : false ,
223+ retryAt : new Date ( Date . now ( ) + 60 * 60_000 ) ,
224+ attempts : 1 ,
225+ failure : { scope : first . user . email , operation : 'drive.files.list' , reasons : [ ] } ,
226+ } ,
227+ } ,
228+ generationStartedAt
229+ )
211230 await commitGoogleCompanyWork (
212231 db ,
213232 owner . connectorId ,
@@ -252,7 +271,80 @@ describe('Google company user checkpoint storage', () => {
252271 expect ( ( await automatic . remaining ( ) ) . count ) . toBe ( 0 )
253272 } )
254273
255- it ( 'resets both provider continuations on a new configuration generation and cascades on connector deletion' , async ( ) => {
274+ it . each ( [ 'continuation' , 'failure' ] as const ) (
275+ 'retains a deferred permission %s after all content completes without keeping future refreshes open' ,
276+ async ( pending ) => {
277+ vi . stubEnv ( 'TZ' , 'Pacific/Honolulu' )
278+ const now = new Date ( )
279+ const retryAt = new Date ( now . getTime ( ) + 30 * 60_000 )
280+ const nextPeriodicRefresh = new Date ( now . getTime ( ) + 12 * 60 * 60_000 )
281+ const update = (
282+ userId : string ,
283+ kind : GoogleCompanyWorkUpdate [ 'kind' ] ,
284+ changes : Partial < GoogleCompanyWorkUpdate > = { }
285+ ) =>
286+ commitGoogleCompanyWork (
287+ db ,
288+ owner . connectorId ,
289+ generationId ,
290+ {
291+ update : {
292+ userId,
293+ kind,
294+ cursor : null ,
295+ completed : true ,
296+ retryAt : nextPeriodicRefresh ,
297+ attempts : 0 ,
298+ failure : null ,
299+ permissionStartedAt : null ,
300+ ...changes ,
301+ } ,
302+ } ,
303+ generationStartedAt
304+ )
305+ for ( const userId of [ 'first' , 'second' ] ) {
306+ await update ( userId , 'permissions' )
307+ await update ( userId , 'content' , { retryAt : new Date ( now . getTime ( ) - 60_000 ) } )
308+ }
309+ await update ( 'first' , 'permissions' , {
310+ cursor : pending === 'continuation' ? 'permission-page-9' : null ,
311+ completed : false ,
312+ retryAt,
313+ attempts : pending === 'failure' ? 1 : 0 ,
314+ failure :
315+ pending === 'failure'
316+ ? {
317+ scope : first . user . email ,
318+ operation : 'drive.files.list' ,
319+ status : 403 ,
320+ reasons : [ ] ,
321+ }
322+ : null ,
323+ } )
324+ const automatic = googleCompanyWorkStore ( owner . connectorId , generationId )
325+ const manual = googleCompanyWorkStore ( owner . connectorId , generationId , false )
326+ expect ( await manual . remaining ( ) ) . toMatchObject ( { count : 1 , retryAt } )
327+ expect ( await manual . next ( 'content' , now ) ) . toBeNull ( )
328+ expect ( await automatic . next ( 'content' , now ) ) . not . toBeNull ( )
329+ expect ( await automatic . next ( 'permissions' , now ) ) . toBeNull ( )
330+ expect ( ( await automatic . next ( 'permissions' , retryAt ) ) ?. user . id ) . toBe ( 'first' )
331+ for ( const userId of [ 'first' , 'second' ] ) {
332+ await update ( userId , 'content' , { retryAt : new Date ( now . getTime ( ) + 2 * 60 * 60_000 ) } )
333+ }
334+ expect ( await automatic . remaining ( ) ) . toMatchObject ( { count : 1 , retryAt } )
335+ await update ( 'first' , 'permissions' )
336+ expect ( await automatic . remaining ( ) ) . toEqual ( { count : 0 , retryAt : null } )
337+ expect ( await manual . remaining ( ) ) . toEqual ( { count : 0 , retryAt : null } )
338+ expect ( await automatic . next ( 'content' , new Date ( now . getTime ( ) + 3 * 60 * 60_000 ) ) ) . toBeNull ( )
339+ }
340+ )
341+
342+ it ( 'resets both provider continuations while preserving due permissions, and cascades on connector deletion' , async ( ) => {
343+ const permissionDueAt = new Date ( Date . now ( ) - 60_000 )
344+ await db
345+ . update ( knowledgeConnectorGoogleUser )
346+ . set ( { permissionRetryAt : permissionDueAt } )
347+ . where ( eq ( knowledgeConnectorGoogleUser . connectorId , owner . connectorId ) )
256348 await db . transaction ( async ( tx ) => {
257349 await commitGoogleCompanyWork (
258350 tx ,
@@ -285,6 +377,16 @@ describe('Google company user checkpoint storage', () => {
285377 cursor : undefined ,
286378 permissionStartedAt : undefined ,
287379 } )
380+ const [ progress ] = await db
381+ . select ( { permissionRetryAt : knowledgeConnectorGoogleUser . permissionRetryAt } )
382+ . from ( knowledgeConnectorGoogleUser )
383+ . where (
384+ and (
385+ eq ( knowledgeConnectorGoogleUser . connectorId , owner . connectorId ) ,
386+ eq ( knowledgeConnectorGoogleUser . userId , 'first' )
387+ )
388+ )
389+ expect ( progress . permissionRetryAt ) . toEqual ( permissionDueAt )
288390 await db . delete ( knowledgeConnector ) . where ( eq ( knowledgeConnector . id , owner . connectorId ) )
289391 expect (
290392 await db
0 commit comments