Files
Daniel Gak Anagrov 2c122a138d feat(workspaces): apply prepared transactions to workspaces (#5383)
* feat(multiregion): replace user replication

* chore(multiregion): optimise replication

* maybe it's this

* postgres is fun

* once more

* chore(multiregion): only replicate test user creation during multiregion tests

* feat: improved replicate_query logic

* fix: minor

* fix: starting issue

* feat: included user create and delete specs to multiregion

* feat: removed console logs

* fix: user defaults

* fix: multiregion test helper

* fix: update scenarios for users

* refactor(multiregion): swap replicateQuery concept to asMultiregionOperation (#5301)

feat(multiregion): introduced asMultregionOperator, refactor test to user builder classes

* chore: renamings

* fix: remove comments

* feat: remove user replication

* refactor: simplified spec usages

* chore: comments

* chore: branches and favs

* chore: more tests

* chore: more tests

* fix linting

* fix tests

* feat: dropping replication

* refactor: moved project delete to service

* fix: comment

* feat: updateStreamFactory and updateProjectFacotry

* deleteProjectFactory + replicateFactory

* deleteWorkspaceFactory

* fix: selector

* fix: tests

* fix tests, finished createStreamFactory

* feat: simplify changes

* fix: remove comment

* fix: minor strucutres

* fix: moveProjectToRegion

* fix: moved branch creation outside of multiregion scope

* fix: branch creation

* fix: tests

* fix: ci tests

* fix: removed log form test

* fix: on specs, no random regionKeys

* review fixes

* feat: workspace replciation

* fix: mr comments

* feat: removed test

* fix: worksapce test creation

* fix: mr issues

* updated mutations

* feat: drop workspace random defaults

---------

Co-authored-by: Charles Driesler <chuck@speckle.systems>
2025-09-11 10:08:26 +02:00

144 lines
5.5 KiB
TypeScript

import { db } from '@/db/knex'
import type { BasicTestUser } from '@/test/authHelper'
import { buildBasicTestUser, createTestUser } from '@/test/authHelper'
import type { BasicTestWorkspace } from '@/modules/workspaces/tests/helpers/creation'
import {
buildBasicTestWorkspace,
createTestWorkspace
} from '@/modules/workspaces/tests/helpers/creation'
import { Workspaces } from '@/modules/workspacesCore/helpers/db'
import {
getWorkspaceFactory,
getWorkspacesNonCompleteFactory
} from '@/modules/workspaces/repositories/workspaces'
import { expect } from 'chai'
import dayjs from 'dayjs'
import { deleteWorkspacesNonCompleteFactory } from '@/modules/workspaces/services/workspaceCreationState'
import type { Logger } from '@/observability/logging'
import { logger } from '@/observability/logging'
import { getExplicitProjects } from '@/modules/core/repositories/streams'
import { deleteSsoProviderFactory } from '@/modules/workspaces/repositories/sso'
import { deleteAllResourceInvitesFactory } from '@/modules/serverinvites/repositories/serverInvites'
import { deleteWorkspaceFactory as repoDeleteWorkspaceFactory } from '@/modules/workspaces/repositories/workspaces'
import { deleteWorkspaceFactory } from '@/modules/workspaces/services/management'
import {
deleteProjectAndCommitsFactory,
queryAllProjectsFactory
} from '@/modules/core/services/projects'
import { deleteProjectFactory } from '@/modules/core/repositories/projects'
import { deleteProjectCommitsFactory } from '@/modules/core/repositories/commits'
import { asMultiregionalOperation, replicateFactory } from '@/modules/shared/command'
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
const updateAWorkspaceCreatedAt = async (
workspaceId: string,
createdAt: dayjs.Dayjs = dayjs()
) => {
await db
.table(Workspaces.name)
.where({ [Workspaces.col.id]: workspaceId })
.update({ createdAt })
}
describe('WorkspaceCreationState services', () => {
const getWorkspacesNonComplete = getWorkspacesNonCompleteFactory({ db })
const getWorkspace = getWorkspaceFactory({ db })
const deleteWorkspacesNonComplete = async ({ logger }: { logger: Logger }) =>
asMultiregionalOperation(
({ allDbs, mainDb, emit }) => {
const deleteWorkspacesNonComplete = deleteWorkspacesNonCompleteFactory({
getWorkspacesNonComplete: getWorkspacesNonCompleteFactory({ db: mainDb }),
deleteWorkspace: deleteWorkspaceFactory({
deleteWorkspace: replicateFactory(allDbs, repoDeleteWorkspaceFactory),
deleteProjectAndCommits: deleteProjectAndCommitsFactory({
deleteProject: replicateFactory(allDbs, deleteProjectFactory),
deleteProjectCommits: replicateFactory(
allDbs,
deleteProjectCommitsFactory
)
}),
deleteAllResourceInvites: deleteAllResourceInvitesFactory({
db: mainDb
}),
queryAllProjects: queryAllProjectsFactory({
getExplicitProjects: getExplicitProjects({ db: mainDb })
}),
deleteSsoProvider: deleteSsoProviderFactory({ db: mainDb }),
emitWorkspaceEvent: emit
})
})
return deleteWorkspacesNonComplete({ logger })
},
{
logger,
name: 'deleteWorkspacesNonComplete',
dbs: await getAllRegisteredDbs()
}
)
let adminUser: BasicTestUser
let completeWorkspace: BasicTestWorkspace
let nonCompleteWorkspace: BasicTestWorkspace
before(async () => {
adminUser = buildBasicTestUser()
completeWorkspace = buildBasicTestWorkspace()
nonCompleteWorkspace = buildBasicTestWorkspace()
await createTestUser(adminUser)
await createTestWorkspace(completeWorkspace, adminUser, {
addCreationState: {
state: {},
completed: true
}
})
await createTestWorkspace(nonCompleteWorkspace, adminUser, {
addCreationState: {
state: {},
completed: false
}
})
})
it('does not show completed/impcompleted workspaces when they are recent', async () => {
await updateAWorkspaceCreatedAt(completeWorkspace.id, dayjs())
await updateAWorkspaceCreatedAt(nonCompleteWorkspace.id, dayjs())
const thirtyMinutesAgo = dayjs().subtract(30, 'minutes')
const workspaces = await getWorkspacesNonComplete({
createdAtBefore: thirtyMinutesAgo.toDate()
})
expect(workspaces).to.have.lengthOf(0)
})
it('hits workspaces with complete false when they are 30 mins older', async () => {
const fortyMinutesAgo = dayjs().subtract(40, 'minutes')
const thirtyMinutesAgo = dayjs().subtract(30, 'minutes')
await updateAWorkspaceCreatedAt(completeWorkspace.id, fortyMinutesAgo)
await updateAWorkspaceCreatedAt(nonCompleteWorkspace.id, fortyMinutesAgo)
const workspaces = await getWorkspacesNonComplete({
createdAtBefore: thirtyMinutesAgo.toDate()
})
expect(workspaces).to.have.lengthOf(1)
expect(workspaces).to.deep.eq([{ workspaceId: nonCompleteWorkspace.id }])
})
it('deletes only those workspaces that are not completed', async () => {
const fortyMinutesAgo = dayjs().subtract(40, 'minutes')
await updateAWorkspaceCreatedAt(completeWorkspace.id, fortyMinutesAgo)
await updateAWorkspaceCreatedAt(nonCompleteWorkspace.id, fortyMinutesAgo)
await deleteWorkspacesNonComplete({ logger })
const workspace1 = await getWorkspace({ workspaceId: completeWorkspace.id })
const workspace2 = await getWorkspace({ workspaceId: nonCompleteWorkspace.id })
expect(workspace1).to.exist
expect(workspace2).to.be.null
})
})