@@ -27,6 +27,7 @@ import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors'
2727import { sleep } from '@sim/utils/helpers'
2828import { generateId } from '@sim/utils/id'
2929import { and , eq , inArray , sql } from 'drizzle-orm'
30+ import { NextRequest } from 'next/server'
3031import { afterAll , beforeEach , describe , expect , it , vi } from 'vitest'
3132import { removeUserFromOrganization } from '@/lib/billing/organizations/membership'
3233import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion'
@@ -55,9 +56,14 @@ import { archiveWorkspace } from '@/lib/workspaces/lifecycle'
5556import { detachOrganizationWorkspacesTx } from '@/lib/workspaces/organization-workspaces'
5657import { getWorkspaceWithOwner } from '@/lib/workspaces/permissions/utils'
5758import { getWorkspaceCreationPolicy } from '@/lib/workspaces/policy'
59+ import { POST as importAdminWorkflow } from '@/app/api/v1/admin/workflows/import/route'
5860import { createFork } from '@/ee/workspace-forking/lib/create-fork'
5961import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink'
6062
63+ vi . hoisted ( ( ) => {
64+ process . env . ADMIN_API_KEY = 'project-fixture-admin-key'
65+ } )
66+
6167beforeEach ( ( ) => {
6268 vi . stubEnv ( 'PROJECT_API_ENABLED' , 'true' )
6369} )
@@ -842,6 +848,55 @@ describe('Project foundation at the database and application boundary', () => {
842848 }
843849 )
844850
851+ check (
852+ 'admin import returns not found when a concurrent archive wins the workspace lock' ,
853+ async ( ) => {
854+ const f = await fixture ( false , 1 )
855+ const held = createDeferred < number > ( )
856+ const release = createDeferred < void > ( )
857+ const archive = db . transaction ( async ( tx ) => {
858+ const [ connection ] = await tx . execute < { pid : number } > ( sql `SELECT pg_backend_pid() AS pid` )
859+ await archiveProjectInTransaction ( tx , f . projectId )
860+ held . resolve ( connection . pid )
861+ await release . promise
862+ } )
863+ const blocker = await held . promise
864+ const imported = importAdminWorkflow (
865+ new NextRequest ( 'http://localhost:3000/api/v1/admin/workflows/import' , {
866+ method : 'POST' ,
867+ headers : {
868+ 'content-type' : 'application/json' ,
869+ 'x-admin-key' : 'project-fixture-admin-key' ,
870+ } ,
871+ body : JSON . stringify ( {
872+ workspaceId : f . ids [ 0 ] ,
873+ workflow : { blocks : { } , edges : [ ] , loops : { } , parallels : { } } ,
874+ } ) ,
875+ } ) ,
876+ { }
877+ )
878+ try {
879+ let waiting = false
880+ for ( let attempt = 0 ; attempt < 100 ; attempt ++ ) {
881+ const rows = await db . execute (
882+ sql `SELECT 1 FROM pg_stat_activity WHERE ${ blocker } = ANY(pg_blocking_pids(pid))`
883+ )
884+ if ( rows . length ) {
885+ waiting = true
886+ break
887+ }
888+ await sleep ( 10 )
889+ }
890+ expect ( waiting ) . toBe ( true )
891+ } finally {
892+ release . resolve ( )
893+ await archive
894+ }
895+ expect ( ( await imported ) . status ) . toBe ( 404 )
896+ expect ( await db . select ( ) . from ( workflow ) . where ( eq ( workflow . workspaceId , f . ids [ 0 ] ) ) ) . toEqual ( [ ] )
897+ }
898+ )
899+
845900 check ( 'workflow restore cannot overtake a concurrent Project archive' , async ( ) => {
846901 const f = await fixture ( )
847902 const workflowId = await addWorkflow ( f . ids [ 0 ] , f . ownerId )
0 commit comments