fix(server): shard the server-test unit job to stop the intermittent crash (#23009)

## Problem

`server-test` fails intermittently: exit 1 with **no `FAIL` line and no
`Test Suites:/Tests:` summary** — the jest run is aborted mid-way,
before the reporter's `onRunComplete`.

## Root cause

The `test` target runs the entire unit suite (~6,600 tests) in **one
in-band jest process on a single runner VM** (`nx.json` sets
`maxWorkers: 1` for the `ci` configuration). A few minutes in, that
process is killed by an **external `SIGKILL`** — confirmed *not* OOM
(~15 GB free at kill time, no cgroup `oom_kill`) and *not* an in-process
crash (a Node diagnostic report armed with `--report-on-fatalerror` +
`--report-uncaught-exception` writes nothing). The whole-run kill is why
it fails intermittently with no summary. `maxWorkers=2` on one VM still
dies, so the threshold is **per-VM**, not per-process.

## Fix

Shard the unit suite across VMs, the same way `server-integration-test`
already does:

- A `twenty-server` `test:ci` target runs jest directly (so `--shard`
forwards) with `dependsOn: ["^build"]` so the workspace deps are built.
- `server-test` becomes a 4-way matrix; each shard runs a quarter of the
suite, well under the kill threshold.
- `ci-server-status-check` already aggregates `server-test`, so required
checks are unchanged.

Also provides two mocks a completed run needs but the SIGKILL had been
masking in `ApplicationRegistrationService.upsertFromCatalog` unit
tests: the `MetricsService` provider and
`applicationRegistrationRepository.createQueryBuilder`.
This commit is contained in:
Félix Malfait
2026-07-20 06:39:13 +02:00
committed by GitHub
parent cdb7e56720
commit 6e1e98f4ab
3 changed files with 27 additions and 5 deletions
+6 -5
View File
@@ -376,6 +376,10 @@ jobs:
needs: server-build
timeout-minutes: 30
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
@@ -389,11 +393,8 @@ jobs:
key: ${{ env.SERVER_BUILD_CACHE_KEY }}
- name: Build twenty-shared
run: npx nx build twenty-shared
- name: Server / Run Tests
uses: ./.github/actions/nx-affected
with:
tag: scope:backend
tasks: test
- name: Server / Run Tests (shard ${{ matrix.shard }}/4)
run: npx nx run twenty-server:test:ci --shard=${{ matrix.shard }}/4
server-integration-test:
timeout-minutes: 30
+8
View File
@@ -36,6 +36,14 @@
}
}
},
"test:ci": {
"executor": "nx:run-commands",
"dependsOn": ["^build"],
"options": {
"cwd": "packages/twenty-server",
"command": "jest --config ./jest.config.mjs --ci --runInBand"
}
},
"build:packageJson": {
"executor": "@nx/js:tsc",
"options": {
@@ -10,6 +10,7 @@ import { ApplicationEntity } from 'src/engine/core-modules/application/applicati
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
import { CoreEntityCacheService } from 'src/engine/core-entity-cache/services/core-entity-cache.service';
import { ServerFileStorageService } from 'src/engine/core-modules/file-storage/services/server-file-storage.service';
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
describe('ApplicationRegistrationService - upsertFromCatalog', () => {
@@ -18,6 +19,7 @@ describe('ApplicationRegistrationService - upsertFromCatalog', () => {
findOne: jest.Mock;
save: jest.Mock;
create: jest.Mock;
createQueryBuilder: jest.Mock;
};
const catalogParams = {
@@ -45,6 +47,13 @@ describe('ApplicationRegistrationService - upsertFromCatalog', () => {
findOne: jest.fn(),
save: jest.fn(),
create: jest.fn((entity) => entity),
createQueryBuilder: jest.fn(() => ({
update: jest.fn().mockReturnThis(),
set: jest.fn().mockReturnThis(),
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
execute: jest.fn().mockResolvedValue({ affected: 0 }),
})),
};
const module: TestingModule = await Test.createTestingModule({
@@ -82,6 +91,10 @@ describe('ApplicationRegistrationService - upsertFromCatalog', () => {
provide: CoreEntityCacheService,
useValue: { invalidate: jest.fn() },
},
{
provide: MetricsService,
useValue: { incrementCounterBy: jest.fn() },
},
],
}).compile();