Files
speckle-server/packages/server/modules/multiregion/tests/integration/sync.spec.ts
T
Daniel Gak Anagrov 1f5003bd32 feat(multiregion): cron job to detect regions out of sync (#5392)
* feat: basic structure

* fix: detect regions out of sync

* feat: added sync logic

* fix: tests

* feat: merge all strategy

* fix: mr comments

* fix: updatedAt in updates

* feat: project updatedAt no longer is a hidden operation

* feat: project region sync

* feat: delete project on cross-region-move

* removal: unnecesary permisions

* fix: caching issue

* fix: saved views on project cross region move
2025-09-10 16:35:48 +01:00

190 lines
6.8 KiB
TypeScript

import { db } from '@/db/knex'
import {
buildBasicTestWorkspace,
createTestWorkspace
} from '@/modules/workspaces/tests/helpers/creation'
import {
buildBasicTestUser,
createTestUser,
type BasicTestUser
} from '@/test/authHelper'
import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions'
import { getDb } from '@/modules/multiregion/utils/dbSelector'
import { expect } from 'chai'
import {
copyAllProjectsAcrossRegionsFactory,
copyAllUsersAcrossRegionsFactory,
copyAllWorkspacesAcrossRegionsFactory
} from '@/modules/multiregion/tasks/regionSync'
import {
bulkUpsertWorkspacesFactory,
getAllWorkspaceChecksumFactory,
getAllWorkspacesFactory
} from '@/modules/workspaces/repositories/workspaces'
import type { Knex } from 'knex'
import { logger } from '@/observability/logging'
import {
bulkUpsertUsersFactory,
getAllUsersChecksumFactory,
getAllUsersFactory
} from '@/modules/core/repositories/users'
import { truncateTables } from '@/test/hooks'
import { Workspaces } from '@/modules/workspacesCore/helpers/db'
import { Streams, Users } from '@/modules/core/dbSchema'
import { createTestStream } from '@/test/speckle-helpers/streamHelper'
import { buildTestProject } from '@/modules/core/tests/helpers/creation'
import {
bulkUpsertProjectsFactory,
getAllProjectsChecksumFactory,
getAllProjectsFactory
} from '@/modules/core/repositories/projects'
isMultiRegionTestMode()
? describe('Testing mechanism to solve workspace or user cross region inconsistencies @multiregion', () => {
const mainDb = db
let region1Db: Knex
let region2Db: Knex
let testUser: BasicTestUser
before(async () => {
await truncateTables([Workspaces.name, Users.name, Streams.name])
region1Db = await getDb({ regionKey: 'region1' })
region2Db = await getDb({ regionKey: 'region2' })
testUser = await createTestUser(buildBasicTestUser())
for (let i = 0; i < 30; i++) {
await createTestWorkspace(buildBasicTestWorkspace(), testUser)
}
for (let i = 0; i < 34; i++) {
await createTestUser(buildBasicTestUser())
}
// no workspaces or users in the target region
await region1Db('workspaces').delete()
await region1Db('users').delete()
const workspaces = await region1Db('workspaces').select()
const users = await region1Db('users').select()
expect(workspaces.length).to.eq(0)
expect(users.length).to.eq(0)
})
it('backfills all workspaces from region1 so checksums are identical', async () => {
const copyAllWorkspacesAcrossRegions = copyAllWorkspacesAcrossRegionsFactory({
getAllWorkspaces: getAllWorkspacesFactory({ db: mainDb }),
bulkUpsertWorkspaces: bulkUpsertWorkspacesFactory({ db: region1Db })
})
await copyAllWorkspacesAcrossRegions({ logger })
const checksumMain = await getAllWorkspaceChecksumFactory({ db: mainDb })()
const checksumRegion = await getAllWorkspaceChecksumFactory({ db: region1Db })()
const workspaces = await region1Db('workspaces').select()
expect(workspaces.length).to.eq(30)
expect(checksumMain).to.deep.eq(checksumRegion)
})
it('backfills all users from region1 so checksums are identical', async () => {
const copyAllUsersAcressRegions = copyAllUsersAcrossRegionsFactory({
getAllUsers: getAllUsersFactory({ db: mainDb }),
bulkUpsertUsers: bulkUpsertUsersFactory({ db: region1Db })
})
await copyAllUsersAcressRegions({ logger })
const checksumMain = await getAllUsersChecksumFactory({ db: mainDb })()
const checksumRegion = await getAllUsersChecksumFactory({ db: region1Db })()
const users = await region1Db('users').select()
expect(users.length).to.eq(35)
expect(checksumMain).to.deep.eq(checksumRegion)
})
it('does not backfill projects that are not targeted ', async () => {
await copyAllProjectsAcrossRegionsFactory({
getAllProjects: getAllProjectsFactory({ db: region1Db }),
bulkUpsertProjects: bulkUpsertProjectsFactory({ db: mainDb })
})({ logger, regionKey: 'region2' }) // selecting only region2 keys
const projects = await mainDb('streams').select()
expect(projects.length).to.eq(0)
})
describe('how projects are backfilled', () => {
before(async () => {
const user = await createTestUser()
for (let i = 0; i < 20; i++) {
await createTestStream(
buildTestProject({
regionKey: 'region1',
workspaceId: null
}),
user
)
}
for (let i = 0; i < 20; i++) {
await createTestStream(
buildTestProject({
regionKey: 'region2',
workspaceId: null
}),
user
)
}
await mainDb('streams').delete()
// no projects in main but only in region1 and region2
const projects = await mainDb('streams').select()
expect(projects.length).to.eq(0)
})
it('backfills all projects from region1 and region2 so checksums are identical', async () => {
// region 1 -> main
await copyAllProjectsAcrossRegionsFactory({
getAllProjects: getAllProjectsFactory({ db: region1Db }),
bulkUpsertProjects: bulkUpsertProjectsFactory({ db: mainDb })
})({ logger, regionKey: 'region1' })
// region 2 -> main
await copyAllProjectsAcrossRegionsFactory({
getAllProjects: getAllProjectsFactory({ db: region2Db }),
bulkUpsertProjects: bulkUpsertProjectsFactory({ db: mainDb })
})({ logger, regionKey: 'region2' })
const mainDbregion1Checksum = await getAllProjectsChecksumFactory({
db: mainDb
})({
regionKey: 'region1'
})
const mainDbregion2Checksum = await getAllProjectsChecksumFactory({
db: mainDb
})({
regionKey: 'region2'
})
const checksumRegion1 = await getAllProjectsChecksumFactory({
db: region1Db
})({
regionKey: 'region1'
})
const checksumRegion2 = await getAllProjectsChecksumFactory({
db: region2Db
})({
regionKey: 'region2'
})
// check by counts and checksums
const projects = await mainDb('streams').select()
expect(projects.length).to.eq(20 + 20)
expect(checksumRegion1).to.deep.eq(mainDbregion1Checksum)
expect(checksumRegion2).to.deep.eq(mainDbregion2Checksum)
expect(checksumRegion1).not.to.eq(checksumRegion2)
})
})
})
: {}