diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a4f8a2965..b27433bf1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -254,27 +254,8 @@ jobs: path: /tmp/**/*.log retention-days: 5 - docker-build-postgres-container: - runs-on: blacksmith-4vcpu-ubuntu-2404 - name: Docker build postgres container - steps: - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ inputs.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Setup Docker Builder - uses: useblacksmith/setup-docker-builder@v1 - - name: Build and push - uses: useblacksmith/build-push-action@v2 - with: - push: true - tags: speckle/speckle-postgres:${{ inputs.IMAGE_VERSION_TAG }} - file: ./utils/postgres/Dockerfile - test-server: name: Server - needs: [docker-build-postgres-container] runs-on: blacksmith-8vcpu-ubuntu-2404 continue-on-error: ${{ inputs.CONTINUE_ON_ERROR }} services: @@ -288,11 +269,12 @@ jobs: ports: - 6379:6379 postgres: - image: speckle/speckle-postgres:${{ inputs.IMAGE_VERSION_TAG }} + image: postgres:16.4-alpine3.20 env: POSTGRES_DB: speckle2_test POSTGRES_PASSWORD: speckle POSTGRES_USER: speckle + POSTGRES_INITDB_ARGS: -c max_prepared_transactions=150 ports: - 5432:5432 options: >- @@ -369,7 +351,6 @@ jobs: test-server-no-ff: name: Server no ff - needs: [docker-build-postgres-container] runs-on: blacksmith-8vcpu-ubuntu-2404 continue-on-error: ${{ inputs.CONTINUE_ON_ERROR }} services: @@ -383,11 +364,12 @@ jobs: ports: - 6379:6379 postgres: - image: speckle/speckle-postgres:${{ inputs.IMAGE_VERSION_TAG }} + image: postgres:16.4-alpine3.20 env: POSTGRES_DB: speckle2_test POSTGRES_PASSWORD: speckle POSTGRES_USER: speckle + POSTGRES_INITDB_ARGS: -c max_prepared_transactions=150 ports: - 5432:5432 options: >- @@ -454,9 +436,7 @@ jobs: test-server-multiregion: name: Server multiregion - needs: [docker-build-postgres-container] continue-on-error: ${{ inputs.CONTINUE_ON_ERROR }} - if: false # disabled runs-on: blacksmith-4vcpu-ubuntu-2404 services: redis: @@ -470,11 +450,12 @@ jobs: - 6379:6379 postgres0: - image: speckle/speckle-postgres:${{ inputs.IMAGE_VERSION_TAG }} + image: postgres:16.4-alpine3.20 env: POSTGRES_DB: speckle2_test POSTGRES_PASSWORD: speckle POSTGRES_USER: speckle + POSTGRES_INITDB_ARGS: -c max_prepared_transactions=150 ports: - 5432:5432 options: >- @@ -482,13 +463,13 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - postgres1: - image: speckle/speckle-postgres:${{ inputs.IMAGE_VERSION_TAG }} + image: postgres:16.4-alpine3.20 env: POSTGRES_DB: speckle2_test POSTGRES_PASSWORD: speckle POSTGRES_USER: speckle + POSTGRES_INITDB_ARGS: -c max_prepared_transactions=150 ports: - 5433:5432 options: >- @@ -496,13 +477,13 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - postgres2: - image: speckle/speckle-postgres:${{ inputs.IMAGE_VERSION_TAG }} + image: postgres:16.4-alpine3.20 env: POSTGRES_DB: speckle2_test POSTGRES_PASSWORD: speckle POSTGRES_USER: speckle + POSTGRES_INITDB_ARGS: -c max_prepared_transactions=150 ports: - 5434:5432 options: >- @@ -510,7 +491,6 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - minio0: image: bitnami/minio env: @@ -583,7 +563,7 @@ jobs: - run: cp .env.test-example .env.test working-directory: 'packages/server' - name: 'Run test' - run: yarn test:report + run: yarn test:multiregion working-directory: 'packages/server' timeout-minutes: 30 - uses: codecov/codecov-action@v5 diff --git a/packages/server/modules/cli/commands/db/migrate/rollback.ts b/packages/server/modules/cli/commands/db/migrate/rollback.ts index 5b868e1b6..2429d8978 100644 --- a/packages/server/modules/cli/commands/db/migrate/rollback.ts +++ b/packages/server/modules/cli/commands/db/migrate/rollback.ts @@ -1,7 +1,6 @@ import { cliLogger as logger } from '@/observability/logging' import type { CommonDbArgs } from '@/modules/cli/commands/db/helpers' import { getTargettedDbClients } from '@/modules/cli/commands/db/helpers' -import { resetPubSubFactory } from '@/test/hooks' import type { CommandModule } from 'yargs' const command: CommandModule = { @@ -15,8 +14,6 @@ const command: CommandModule = { const dbs = await getTargettedDbClients({ regionKey }) for (const db of dbs) { logger.info(`Rolling back DB ${db.regionKey}...`) - const resetPubSub = resetPubSubFactory({ db: db.client }) - await resetPubSub() await db.client.migrate.rollback(undefined, true) } diff --git a/packages/server/modules/cli/commands/db/purge-test-dbs.ts b/packages/server/modules/cli/commands/db/purge-test-dbs.ts index 0097fe940..47ef3f64c 100644 --- a/packages/server/modules/cli/commands/db/purge-test-dbs.ts +++ b/packages/server/modules/cli/commands/db/purge-test-dbs.ts @@ -7,7 +7,6 @@ import type { CommandModule } from 'yargs' import { isTestEnv } from '@/modules/shared/helpers/envHelper' import { BaseError } from '@/modules/shared/errors' import { ensureError } from '@speckle/shared' -import { resetPubSubFactory } from '@/test/hooks' import { mainDb } from '@/db/knex' const command: CommandModule = { @@ -54,13 +53,6 @@ const command: CommandModule = { for (const db of dbs) { logger.info(`Purging test DB ${db.regionKey}...`) try { - // Attempt to reset pubsub, swallowing issues - await resetPubSubFactory({ db: db.client })().catch((err) => { - logger.warn(`Failed to reset pubsub for ${db.regionKey}`, { - cause: ensureError(err) - }) - }) - // Find and drop all tables const tables = await db.client.raw( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ?', diff --git a/packages/server/modules/shared/test/dbHelper.spec.ts b/packages/server/modules/shared/test/dbHelper.spec.ts index c35093b1a..5384cab1f 100644 --- a/packages/server/modules/shared/test/dbHelper.spec.ts +++ b/packages/server/modules/shared/test/dbHelper.spec.ts @@ -170,13 +170,13 @@ isMultiRegionTestMode() } const manyParallelCreates = async () => { - await Promise.allSettled(Array.from({ length: 1000 }, oneKnexInstanceCall)) + await Promise.allSettled(Array.from({ length: 500 }, oneKnexInstanceCall)) } await manyParallelCreates() const [{ count }] = await db('users').count() - expect(count).to.eql('1000') + expect(count).to.eql('500') await sleep(50) diff --git a/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts b/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts index 78d64c11c..76f10ce87 100644 --- a/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts +++ b/packages/server/modules/workspaces/tests/integration/projects.graph.spec.ts @@ -988,28 +988,34 @@ describe('Workspace project GQL CRUD', () => { isMultiRegionTestMode() ? describe('when the default server db region is not the main db @multiregion', () => { - const regionalProject: StreamRecord = { - id: cryptoRandomString({ length: 9 }), - name: 'My Special Project', - description: null, - clonedFrom: null, - createdAt: new Date(), - updatedAt: new Date(), - allowPublicComments: false, - workspaceId: null, - regionKey: 'region1', - visibility: ProjectRecordVisibility.Public - } + let regionalProject: BasicTestStream - beforeEach(async () => { + before(async () => { // Simulate non-main default db region + regionalProject = await createTestStream( + { + name: 'My Special Project', + description: null, + clonedFrom: null, + createdAt: new Date(), + updatedAt: new Date(), + allowPublicComments: false, + workspaceId: null, + regionKey: 'region1', + visibility: ProjectRecordVisibility.Public + }, + serverAdminUser + ) + }) + + it('should be located in the correct region', async () => { const regionDb = await getRegionDb({ regionKey: 'region1' }) - await tables.streams(regionDb).insert(regionalProject) - await grantStreamPermissions({ - streamId: regionalProject.id, - userId: serverAdminUser.id, - role: Roles.Stream.Owner - }) + + const [res] = await tables + .streams(regionDb) + .where({ id: regionalProject.id }) + + expect(res).to.exist }) it('should update project without removing workspace association @multiregion', async () => { diff --git a/packages/server/package.json b/packages/server/package.json index 29cab51f1..9a43a009c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -28,7 +28,7 @@ "ts-gqlgen": "tsx --import ./esmLoader.js ./bin/gqlgen", "test": "cross-env TSX=true NODE_ENV=test LOG_FILTER=test LOG_PRETTY=true yarn ts-mocha", "test:all-ff": "cross-env ENABLE_ALL_FFS=true yarn test", - "test:multiregion": "cross-env RUN_TESTS_IN_MULTIREGION_MODE=true FF_WORKSPACES_MODULE_ENABLED=true FF_WORKSPACES_MULTI_REGION_ENABLED=true FF_MOVE_PROJECT_REGION_ENABLED=true yarn test --grep @multiregion", + "test:multiregion": "cross-env RUN_TESTS_IN_MULTIREGION_MODE=true FF_WORKSPACES_MODULE_ENABLED=true FF_WORKSPACES_MULTI_REGION_ENABLED=true FF_MOVE_PROJECT_REGION_ENABLED=true yarn test:report -g '@multiregion'", "test:no-ff": "cross-env DISABLE_ALL_FFS=true yarn test", "test:coverage": "cross-env NODE_ENV=test LOG_FILTER=test LOG_PRETTY=true c8 yarn test", "test:report": "MOCHA_FILE=reports/test-results.xml yarn test:coverage -- --reporter mocha-multi --reporter-options spec=-,mocha-junit-reporter=reports/test-results.xml", diff --git a/packages/server/test/hooks.ts b/packages/server/test/hooks.ts index e0d870ab2..f6b88ca8d 100644 --- a/packages/server/test/hooks.ts +++ b/packages/server/test/hooks.ts @@ -18,7 +18,7 @@ import type http from 'http' import type express from 'express' import type net from 'net' import type { MaybeAsync, MaybeNullOrUndefined, Nullable } from '@speckle/shared' -import { ensureError, retry, TIME_MS, wait } from '@speckle/shared' +import { ensureError, retry } from '@speckle/shared' import { getAvailableRegionKeysFactory, getFreeRegionKeysFactory @@ -37,7 +37,6 @@ import { } from '@/modules/multiregion/utils/dbSelector' import type { Knex } from 'knex' import { isMultiRegionTestMode } from '@/test/speckle-helpers/regions' -import { isMultiRegionEnabled } from '@/modules/multiregion/helpers' import type { GraphQLContext } from '@/modules/shared/helpers/typeHelper' import type { ApolloServer } from '@apollo/server' import type { ReadinessHandler } from '@/healthchecks/types' @@ -103,10 +102,6 @@ const inEachDb = async (fn: (db: Knex) => MaybeAsync) => { } } -const ensureAivenExtrasFactory = (deps: { db: Knex }) => async () => { - await deps.db.raw('CREATE EXTENSION IF NOT EXISTS "aiven_extras";') -} - const setupDatabases = async () => { // First reset main db const db = mainDb @@ -164,60 +159,6 @@ const unlockFactory = (deps: { db: Knex }) => async () => { export const getRegionKeys = () => Object.keys(regionClients) -export const resetPubSubFactory = (deps: { db: Knex }) => async () => { - // We wanna reset even outside of multiregion test mode, as long as multi region is generally enabled - if (!isMultiRegionEnabled()) { - return { drop: async () => {}, reenable: async () => {} } - } - - const ensureAivenExtras = ensureAivenExtrasFactory(deps) - await ensureAivenExtras() - - type SubInfo = { - subname: string - subconninfo: string - subpublications: string[] - subslotname: string - } - - const subscriptions = (await deps.db.raw( - `SELECT subname, subconninfo, subpublications, subslotname FROM aiven_extras.pg_list_all_subscriptions() WHERE subname ILIKE 'test_%';` - )) as { - rows: Array - } - const publications = (await deps.db.raw( - `SELECT pubname FROM pg_publication WHERE pubname ILIKE 'test_%';` - )) as { - rows: Array<{ pubname: string }> - } - - // If we do not wait, the following call occasionally fails because a replication slot is still in use. - const dropSubs = async (info: SubInfo) => { - await wait(TIME_MS.second) - await deps.db.raw( - `SELECT * FROM aiven_extras.pg_alter_subscription_disable('${info.subname}');` - ) - await wait(TIME_MS.second) - await deps.db.raw( - `SELECT * FROM aiven_extras.pg_drop_subscription('${info.subname}');` - ) - await wait(TIME_MS.second) - await deps.db.raw( - `SELECT * FROM aiven_extras.dblink_slot_create_or_drop('${info.subconninfo}', '${info.subslotname}', 'drop');` - ) - } - - // Drop all subs - for (const sub of subscriptions.rows) { - await dropSubs(sub) - } - - // Drop all pubs - for (const pub of publications.rows) { - await deps.db.raw(`DROP PUBLICATION ${pub.pubname};`) - } -} - const truncateTablesFactory = (deps: { db: Knex }) => async (tableNames?: string[]) => { if (!tableNames?.length) { tableNames = ( @@ -257,7 +198,6 @@ const resetSchemaFactory = (deps: { db: Knex; regionKey: Nullable }) => async () => { const { regionKey } = deps - const resetPubSub = resetPubSubFactory(deps) const truncate = truncateTablesFactory(deps) const pendingTransactions = await getStalePreparedTransactionsFactory({ @@ -268,7 +208,6 @@ const resetSchemaFactory = ) await unlockFactory(deps)() - await resetPubSub() await truncate() // otherwise some rollbacks will fail // Reset schema @@ -287,28 +226,9 @@ const resetSchemaFactory = } } -export const truncateTables = async ( - tableNames?: string[], - options?: Partial<{ - /** - * Whether to also reset pubsub before truncate. Pubsub only gets re-initialized on app - * init so don't do this if not needed! - * Defaults to: false - */ - resetPubSub: boolean - }> -) => { - const { resetPubSub = false } = options || {} +export const truncateTables = async (tableNames?: string[]) => { const dbs = [mainDb, ...Object.values(regionClients)] - // First reset pubsubs, if needed - if (resetPubSub) { - for (const db of dbs) { - const resetPubSub = resetPubSubFactory({ db }) - await resetPubSub() - } - } - // Now truncate for (const db of dbs) { const truncate = truncateTablesFactory({ db }) @@ -360,7 +280,7 @@ export const buildApp = async () => { } export const beforeEachContext = async () => { - await truncateTables(undefined, { resetPubSub: true }) + await truncateTables(undefined) return await buildApp() }