@platform-mesh/portal-server-lib 0.11.17 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -36,7 +36,7 @@ npm run test:cov # alias for npm run test
36
36
  npm run test:watch # run tests in watch mode (no coverage)
37
37
  ```
38
38
 
39
- Tests use **Jest** with `ts-jest` (ESM preset). Coverage is collected and enforced at:
39
+ Tests use **Vitest** (`vitest.config.ts`). Coverage is collected with the V8 provider and enforced at:
40
40
 
41
41
  - **Branches: 75%**
42
42
  - **Functions: 89%**
@@ -87,7 +87,7 @@ portal-server-lib/
87
87
  │ ├── account-hierarchy-resolver.ts # account path / entity type manipulation
88
88
  │ ├── domain.ts # getOrganization, getDiscoveryEndpoint
89
89
  │ └── replace-string-deep.ts # deep string replacement utility
90
- ├── jest.config.ts
90
+ ├── vitest.config.ts
91
91
  ├── nest-cli.json
92
92
  ├── tsconfig.json
93
93
  └── package.json
package/CONTRIBUTING.md CHANGED
@@ -27,7 +27,7 @@ The build artifacts will be stored in the `dist/` directory.
27
27
 
28
28
  > **NOTE:** You should always add tests, if you are adding code to our repository.
29
29
 
30
- Run `npm test` to execute the unit tests via [Jest](https://jestjs.io/) in the root directory of the repository.
30
+ Run `npm test` to execute the unit tests via [Vitest](https://vitest.dev/) in the root directory of the repository.
31
31
 
32
32
  ## Issues
33
33
 
package/README.md CHANGED
@@ -19,7 +19,7 @@ $ npm run build
19
19
  ```
20
20
 
21
21
  ## Running unit tests
22
- Run `npm run test` to execute the unit tests via Jest
22
+ Run `npm run test` to execute the unit tests via Vitest
23
23
 
24
24
  ## Requirements
25
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platform-mesh/portal-server-lib",
3
- "version": "0.11.17",
3
+ "version": "0.12.0",
4
4
  "author": "Platform Mesh",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -31,19 +31,19 @@
31
31
  "build:watch": "mkdirp dist && nodemon --ignore dist --ext js,yml,yaml,ts,html,css,scss,json,md --exec \"rimraf dist && npm run build && yalc publish --push --sig\"",
32
32
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"libs/**/*.ts\"",
33
33
  "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
34
- "test": "jest",
35
- "test:watch": "jest --watch",
36
- "test:cov": "jest --coverage",
37
- "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
34
+ "test": "vitest run",
35
+ "test:watch": "vitest --coverage.enabled=false",
36
+ "test:cov": "vitest run",
37
+ "test:debug": "vitest run --inspect-brk --no-file-parallelism --coverage.enabled=false"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@kubernetes/client-node": "^2.0.0",
41
- "@nestjs/axios": "^4.0.1",
42
- "@nestjs/common": "^11.1.17",
43
- "@nestjs/core": "^11.1.9",
44
- "@nestjs/platform-express": "^11.1.18",
45
- "@nestjs/serve-static": "^5.0.5",
46
- "@openmfp/portal-server-lib": "^0.163.7 || ^0.164.0 || ^0.165.1",
41
+ "@nestjs/axios": "^4.0.1 || ^12.0.0",
42
+ "@nestjs/common": "^11.1.17 || ^12.0.0",
43
+ "@nestjs/core": "^11.1.9 || ^12.0.0",
44
+ "@nestjs/platform-express": "^11.1.18 || ^12.0.0",
45
+ "@nestjs/serve-static": "^5.0.5 || ^12.0.0",
46
+ "@openmfp/portal-server-lib": "^0.163.7 || ^0.164.0 || ^0.165.1 || ^0.166.0",
47
47
  "axios": "^1.15.0",
48
48
  "cookie-parser": "^1.4.7",
49
49
  "express": "^5.2.1",
@@ -53,31 +53,28 @@
53
53
  "devDependencies": {
54
54
  "@eslint/js": "10.0.1",
55
55
  "@kubernetes/client-node": "2.0.0",
56
- "@nestjs/cli": "11.0.24",
57
- "@nestjs/testing": "11.1.28",
56
+ "@nestjs/cli": "12.0.11",
57
+ "@nestjs/testing": "12.3.3",
58
58
  "@openmfp/config-prettier": "0.9.3",
59
- "@types/jest": "30.0.0",
60
59
  "@types/node": "26.6.5",
61
60
  "@types/supertest": "7.2.1",
62
61
  "eslint": "10.8.0",
63
62
  "eslint-config-prettier": "10.1.8",
64
63
  "globals": "17.9.0",
65
- "jest": "30.4.2",
66
- "jest-junit": "16.0.0",
67
- "jest-mock-extended": "4.0.1",
68
64
  "mkdirp": "3.0.1",
69
- "nock": "14.0.17",
65
+ "nock": "15.0.5",
70
66
  "nodemon": "3.1.14",
71
67
  "prettier": "3.9.6",
72
68
  "rimraf": "6.1.3",
73
69
  "supertest": "7.2.2",
74
- "ts-jest": "29.4.12",
75
70
  "ts-loader": "9.6.2",
76
- "ts-node": "10.9.2",
77
71
  "tsconfig-paths": "4.2.0",
78
72
  "typescript": "5.9.3",
79
73
  "typescript-eslint": "8.65.0",
80
- "uri-js": "4.4.1"
74
+ "uri-js": "4.4.1",
75
+ "vitest": "5.1.0",
76
+ "@vitest/coverage-v8": "5.1.0",
77
+ "vitest-mock-extended": "5.1.1"
81
78
  },
82
79
  "type": "module",
83
80
  "overrides": {
@@ -5,37 +5,38 @@ import * as domainUtils from './utils/domain.js';
5
5
  import { HttpException, HttpStatus } from '@nestjs/common';
6
6
  import { DiscoveryService } from '@openmfp/portal-server-lib';
7
7
  import type { Request } from 'express';
8
- import { mock } from 'jest-mock-extended';
8
+ import { mock } from 'vitest-mock-extended';
9
+ import type { Mocked } from 'vitest';
9
10
 
10
- jest.mock('@kubernetes/client-node', () => {
11
- const mockReadNamespacedSecret = jest.fn();
11
+ vi.mock('@kubernetes/client-node', () => {
12
+ const mockReadNamespacedSecret = vi.fn();
12
13
  class KubeConfig {
13
- loadFromDefault = jest.fn();
14
- loadFromFile = jest.fn();
15
- getCurrentCluster = jest.fn().mockReturnValue({
14
+ loadFromDefault = vi.fn();
15
+ loadFromFile = vi.fn();
16
+ getCurrentCluster = vi.fn().mockReturnValue({
16
17
  server: 'https://k8s.example.com',
17
18
  name: 'test-cluster',
18
19
  });
19
- makeApiClient = jest.fn().mockReturnValue({
20
+ makeApiClient = vi.fn().mockReturnValue({
20
21
  readNamespacedSecret: mockReadNamespacedSecret,
21
22
  });
22
- addUser = jest.fn();
23
- addContext = jest.fn();
24
- setCurrentContext = jest.fn();
23
+ addUser = vi.fn();
24
+ addContext = vi.fn();
25
+ setCurrentContext = vi.fn();
25
26
  }
26
27
  class CoreV1Api {}
27
28
  class CustomObjectsApi {}
28
29
  return { KubeConfig, CoreV1Api, CustomObjectsApi, mockReadNamespacedSecret };
29
30
  });
30
31
 
31
- jest.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
32
+ vi.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
32
33
  PromiseMiddlewareWrapper: class {},
33
34
  }));
34
35
 
35
36
  describe('PMAuthConfigProvider', () => {
36
37
  let provider: PMAuthConfigProvider;
37
- let discoveryService: jest.Mocked<DiscoveryService>;
38
- let kcpKubernetesService: jest.Mocked<KcpKubernetesService>;
38
+ let discoveryService: Mocked<DiscoveryService>;
39
+ let kcpKubernetesService: Mocked<KcpKubernetesService>;
39
40
  let mockRequest: Request;
40
41
 
41
42
  const mockOidcDiscovery = {
@@ -58,18 +59,18 @@ describe('PMAuthConfigProvider', () => {
58
59
  TOKEN_URL_DEFAULT: 'https://default-auth.com/token',
59
60
  };
60
61
 
61
- jest
62
+ vi
62
63
  .spyOn(domainUtils, 'getDiscoveryEndpoint')
63
64
  .mockReturnValue(
64
65
  'https://oidc.example.com/.well-known/openid-configuration',
65
66
  );
66
- jest.spyOn(domainUtils, 'getOrganization').mockReturnValue('org1');
67
+ vi.spyOn(domainUtils, 'getOrganization').mockReturnValue('org1');
67
68
 
68
69
  discoveryService.getOIDC.mockResolvedValue(mockOidcDiscovery);
69
70
  });
70
71
 
71
72
  afterEach(() => {
72
- jest.clearAllMocks();
73
+ vi.clearAllMocks();
73
74
  });
74
75
 
75
76
  describe('getAuthConfig', () => {
@@ -118,7 +119,7 @@ describe('PMAuthConfigProvider', () => {
118
119
  });
119
120
 
120
121
  it('should handle welcome organization', async () => {
121
- jest.spyOn(domainUtils, 'getOrganization').mockReturnValue('welcome');
122
+ vi.spyOn(domainUtils, 'getOrganization').mockReturnValue('welcome');
122
123
 
123
124
  const mockIdpConfig: IdentityProviderConfiguration = {
124
125
  status: {
@@ -300,7 +301,7 @@ describe('PMAuthConfigProvider', () => {
300
301
 
301
302
  try {
302
303
  await provider.getAuthConfig(mockRequest);
303
- fail('Should have thrown HttpException');
304
+ expect.fail('Should have thrown HttpException');
304
305
  } catch (error) {
305
306
  expect(error).toBeInstanceOf(HttpException);
306
307
  const response = (error as HttpException).getResponse() as any;
@@ -481,7 +482,7 @@ describe('PMAuthConfigProvider', () => {
481
482
 
482
483
  try {
483
484
  await provider.getAuthConfig(mockRequest);
484
- fail('Should have thrown');
485
+ expect.fail('Should have thrown');
485
486
  } catch (error) {
486
487
  const response = (error as HttpException).getResponse() as any;
487
488
  expect(response.error).toContain('has client secret: false');
@@ -8,6 +8,7 @@ import {
8
8
  } from '@openmfp/portal-server-lib';
9
9
  import { Request, Response } from 'express';
10
10
  import { of, throwError } from 'rxjs';
11
+ import type { Mock } from 'vitest';
11
12
 
12
13
  describe('PMLogoutService', () => {
13
14
  let service: PMLogoutService;
@@ -21,15 +22,15 @@ describe('PMLogoutService', () => {
21
22
  PMLogoutService,
22
23
  {
23
24
  provide: HttpService,
24
- useValue: { post: jest.fn() },
25
+ useValue: { post: vi.fn() },
25
26
  },
26
27
  {
27
28
  provide: AUTH_CONFIG_INJECTION_TOKEN,
28
- useValue: { getAuthConfig: jest.fn() },
29
+ useValue: { getAuthConfig: vi.fn() },
29
30
  },
30
31
  {
31
32
  provide: CookiesService,
32
- useValue: { getAuthCookie: jest.fn() },
33
+ useValue: { getAuthCookie: vi.fn() },
33
34
  },
34
35
  {
35
36
  provide: 'AUTH_CONFIG_INJECTION_TOKEN',
@@ -48,15 +49,15 @@ describe('PMLogoutService', () => {
48
49
  const mockRequest = {} as Request;
49
50
  const mockResponse = {} as Response;
50
51
 
51
- (authConfigService.getAuthConfig as jest.Mock).mockResolvedValue({
52
+ (authConfigService.getAuthConfig as Mock).mockResolvedValue({
52
53
  clientId: 'client-id',
53
54
  clientSecret: 'secret',
54
55
  endSessionUrl: 'https://keycloak/logout',
55
56
  });
56
- (cookiesService.getAuthCookie as jest.Mock).mockReturnValue(
57
+ (cookiesService.getAuthCookie as Mock).mockReturnValue(
57
58
  'refresh-token',
58
59
  );
59
- (httpService.post as jest.Mock).mockReturnValue(of({ data: {} }));
60
+ (httpService.post as Mock).mockReturnValue(of({ data: {} }));
60
61
 
61
62
  await service.handleLogout(mockRequest, mockResponse);
62
63
 
@@ -74,19 +75,19 @@ describe('PMLogoutService', () => {
74
75
  const mockRequest = {} as Request;
75
76
  const mockResponse = {} as Response;
76
77
 
77
- (authConfigService.getAuthConfig as jest.Mock).mockResolvedValue({
78
+ (authConfigService.getAuthConfig as Mock).mockResolvedValue({
78
79
  clientId: 'client-id',
79
80
  clientSecret: 'secret',
80
81
  endSessionUrl: 'https://keycloak/logout',
81
82
  });
82
- (cookiesService.getAuthCookie as jest.Mock).mockReturnValue(
83
+ (cookiesService.getAuthCookie as Mock).mockReturnValue(
83
84
  'refresh-token',
84
85
  );
85
- (httpService.post as jest.Mock).mockReturnValue(of({ data: {} }));
86
+ (httpService.post as Mock).mockReturnValue(of({ data: {} }));
86
87
 
87
88
  await service.handleLogout(mockRequest, mockResponse);
88
89
 
89
- const body = (httpService.post as jest.Mock).mock
90
+ const body = (httpService.post as Mock).mock
90
91
  .calls[0][1] as URLSearchParams;
91
92
  expect(body.get('refresh_token')).toBe('refresh-token');
92
93
  expect(body.get('client_secret')).toBeNull();
@@ -102,15 +103,15 @@ describe('PMLogoutService', () => {
102
103
  } as unknown as Request;
103
104
  const mockResponse = {} as Response;
104
105
 
105
- (authConfigService.getAuthConfig as jest.Mock).mockResolvedValue({
106
+ (authConfigService.getAuthConfig as Mock).mockResolvedValue({
106
107
  clientId: 'client-id',
107
108
  clientSecret: 'secret',
108
109
  endSessionUrl: 'https://keycloak/logout',
109
110
  });
110
- (cookiesService.getAuthCookie as jest.Mock).mockReturnValue(
111
+ (cookiesService.getAuthCookie as Mock).mockReturnValue(
111
112
  'refresh-token',
112
113
  );
113
- (httpService.post as jest.Mock).mockReturnValue(
114
+ (httpService.post as Mock).mockReturnValue(
114
115
  throwError(() => new Error('Network error')),
115
116
  );
116
117
 
@@ -3,38 +3,39 @@ import { KcpKubernetesService } from './services/kcp-k8s.service.js';
3
3
  import { getOrganization } from './utils/domain.js';
4
4
  import { Test, TestingModule } from '@nestjs/testing';
5
5
  import { Request } from 'express';
6
- import { mock } from 'jest-mock-extended';
6
+ import { mock } from 'vitest-mock-extended';
7
7
  import process from 'node:process';
8
+ import type { Mocked } from 'vitest';
8
9
 
9
- jest.mock('@kubernetes/client-node', () => {
10
+ vi.mock('@kubernetes/client-node', () => {
10
11
  class KubeConfig {
11
- loadFromDefault = jest.fn();
12
- loadFromFile = jest.fn();
13
- getCurrentCluster = jest.fn().mockReturnValue({
12
+ loadFromDefault = vi.fn();
13
+ loadFromFile = vi.fn();
14
+ getCurrentCluster = vi.fn().mockReturnValue({
14
15
  server: 'https://k8s.example.com/base',
15
16
  name: 'test-cluster',
16
17
  });
17
- makeApiClient = jest.fn();
18
- addUser = jest.fn();
19
- addContext = jest.fn();
20
- setCurrentContext = jest.fn();
18
+ makeApiClient = vi.fn();
19
+ addUser = vi.fn();
20
+ addContext = vi.fn();
21
+ setCurrentContext = vi.fn();
21
22
  }
22
23
  class CustomObjectsApi {}
23
24
  return { KubeConfig, CustomObjectsApi };
24
25
  });
25
26
 
26
- jest.mock('./utils/domain.js', () => ({
27
- getOrganization: jest.fn(),
27
+ vi.mock('./utils/domain.js', () => ({
28
+ getOrganization: vi.fn(),
28
29
  }));
29
30
 
30
- jest.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
31
+ vi.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
31
32
  PromiseMiddlewareWrapper: class {},
32
33
  }));
33
34
 
34
35
  describe('PMPortalContextService', () => {
35
36
  let service: PMPortalContextService;
36
- let kcpKubernetesServiceMock: jest.Mocked<KcpKubernetesService>;
37
- const mockedGetDomainAndOrganization = jest.mocked(getOrganization);
37
+ let kcpKubernetesServiceMock: Mocked<KcpKubernetesService>;
38
+ const mockedGetDomainAndOrganization = vi.mocked(getOrganization);
38
39
  let mockRequest: any;
39
40
 
40
41
  beforeEach(async () => {
@@ -54,11 +55,11 @@ describe('PMPortalContextService', () => {
54
55
  hostname: 'test.example.com',
55
56
  };
56
57
 
57
- jest.spyOn(console, 'log').mockImplementation();
58
+ vi.spyOn(console, 'log').mockImplementation(() => {});
58
59
  });
59
60
 
60
61
  afterEach(() => {
61
- jest.restoreAllMocks();
62
+ vi.restoreAllMocks();
62
63
  });
63
64
 
64
65
  it('should be defined', () => {
@@ -2,39 +2,40 @@ import { PMRequestContextProvider } from './pm-request-context-provider.js';
2
2
  import { getOrganization } from './utils/domain.js';
3
3
  import { PortalContextProviderImpl } from '@openmfp/portal-server-lib';
4
4
  import type { Request } from 'express';
5
- import { mock } from 'jest-mock-extended';
5
+ import { mock } from 'vitest-mock-extended';
6
+ import type { Mock } from 'vitest';
6
7
 
7
- jest.mock('@kubernetes/client-node', () => {
8
+ vi.mock('@kubernetes/client-node', () => {
8
9
  class KubeConfig {
9
- loadFromDefault = jest.fn();
10
- loadFromFile = jest.fn();
11
- getCurrentCluster = jest.fn().mockReturnValue({
10
+ loadFromDefault = vi.fn();
11
+ loadFromFile = vi.fn();
12
+ getCurrentCluster = vi.fn().mockReturnValue({
12
13
  server: 'https://k8s.example.com/base',
13
14
  name: 'test-cluster',
14
15
  });
15
- makeApiClient = jest.fn();
16
- addUser = jest.fn();
17
- addContext = jest.fn();
18
- setCurrentContext = jest.fn();
16
+ makeApiClient = vi.fn();
17
+ addUser = vi.fn();
18
+ addContext = vi.fn();
19
+ setCurrentContext = vi.fn();
19
20
  }
20
21
  class CustomObjectsApi {}
21
22
  return { KubeConfig, CustomObjectsApi };
22
23
  });
23
24
 
24
- jest.mock('./utils/domain.js', () => ({
25
- getOrganization: jest.fn(),
25
+ vi.mock('./utils/domain.js', () => ({
26
+ getOrganization: vi.fn(),
26
27
  }));
27
28
 
28
29
  describe('PMRequestContextProvider', () => {
29
30
  let provider: PMRequestContextProvider;
30
31
  const portalContextService = mock<PortalContextProviderImpl>();
31
- const mockedGetOrganization = jest.mocked(getOrganization);
32
+ const mockedGetOrganization = vi.mocked(getOrganization);
32
33
 
33
34
  beforeEach(() => {
34
- jest.resetAllMocks();
35
+ vi.resetAllMocks();
35
36
  mockedGetOrganization.mockReturnValue('org1');
36
37
  (
37
- portalContextService.getContextValues as unknown as jest.Mock
38
+ portalContextService.getContextValues as unknown as Mock
38
39
  ).mockResolvedValue({
39
40
  crdGatewayApiUrl: 'http://gateway/graphql',
40
41
  other: 'x',
@@ -3,12 +3,15 @@ import { PermissionsProxyService } from '../services/permissions/permissions-pro
3
3
  import { ContentConfigurationServiceProvidersService } from './content-configuration-service-providers.service.js';
4
4
  import { welcomeNodeConfig } from './models/welcome-node-config.js';
5
5
  import { GraphQLClient } from 'graphql-request';
6
+ import type { Mock, Mocked } from 'vitest';
6
7
 
7
- jest.mock('graphql-request', () => {
8
+ vi.mock('graphql-request', () => {
8
9
  return {
9
- GraphQLClient: jest.fn().mockImplementation(() => ({
10
- request: jest.fn(),
11
- })),
10
+ GraphQLClient: vi.fn().mockImplementation(function () {
11
+ return {
12
+ request: vi.fn(),
13
+ };
14
+ }),
12
15
  gql(query: TemplateStringsArray) {
13
16
  return query[0];
14
17
  },
@@ -17,19 +20,21 @@ jest.mock('graphql-request', () => {
17
20
 
18
21
  describe('ContentConfigurationServiceProvidersService', () => {
19
22
  let service: ContentConfigurationServiceProvidersService;
20
- let mockClient: jest.Mocked<GraphQLClient>;
23
+ let mockClient: Mocked<GraphQLClient>;
21
24
  let context: RequestContext;
22
- let permissionsProxyService: jest.Mocked<PermissionsProxyService>;
25
+ let permissionsProxyService: Mocked<PermissionsProxyService>;
23
26
 
24
27
  beforeEach(() => {
25
28
  permissionsProxyService = {
26
- resolvePermissions: jest.fn().mockResolvedValue([]),
27
- } as unknown as jest.Mocked<PermissionsProxyService>;
29
+ resolvePermissions: vi.fn().mockResolvedValue([]),
30
+ } as unknown as Mocked<PermissionsProxyService>;
28
31
  service = new ContentConfigurationServiceProvidersService(
29
32
  permissionsProxyService,
30
33
  );
31
34
  mockClient = new GraphQLClient('') as any;
32
- (GraphQLClient as jest.Mock).mockReturnValue(mockClient);
35
+ (GraphQLClient as Mock).mockImplementation(function () {
36
+ return mockClient;
37
+ });
33
38
  context = {
34
39
  isSubDomain: true,
35
40
  organization: 'org1',
@@ -46,7 +51,7 @@ describe('ContentConfigurationServiceProvidersService', () => {
46
51
  });
47
52
 
48
53
  it.each([{ entities: [] }, { entities: ['core_platform-mesh_io_account'] }])(
49
- 'normalizes root and entity configuration through the GraphQL provider for %p',
54
+ 'normalizes root and entity configuration through the GraphQL provider for %o',
50
55
  async ({ entities }) => {
51
56
  const config = {
52
57
  name: 'accounts',
@@ -5,37 +5,38 @@ import { KubernetesServiceProvidersService } from './kubernetes-service-provider
5
5
  import { welcomeNodeConfig } from './models/welcome-node-config.js';
6
6
  import { Test, TestingModule } from '@nestjs/testing';
7
7
  import { ContentConfiguration } from '@openmfp/portal-server-lib';
8
- import { mock } from 'jest-mock-extended';
8
+ import { mock } from 'vitest-mock-extended';
9
+ import type { Mocked } from 'vitest';
9
10
 
10
- const listClusterCustomObject = jest.fn();
11
+ const listClusterCustomObject = vi.fn();
11
12
 
12
- jest.mock('@kubernetes/client-node', () => {
13
+ vi.mock('@kubernetes/client-node', () => {
13
14
  class KubeConfig {
14
- loadFromDefault = jest.fn();
15
- loadFromFile = jest.fn();
16
- getCurrentCluster = jest.fn().mockReturnValue({
15
+ loadFromDefault = vi.fn();
16
+ loadFromFile = vi.fn();
17
+ getCurrentCluster = vi.fn().mockReturnValue({
17
18
  server: 'https://k8s.example.com/base',
18
19
  name: 'test-cluster',
19
20
  });
20
- makeApiClient = jest.fn().mockImplementation(() => ({
21
+ makeApiClient = vi.fn().mockImplementation(() => ({
21
22
  listClusterCustomObject,
22
23
  }));
23
- addUser = jest.fn();
24
- addContext = jest.fn();
25
- setCurrentContext = jest.fn();
24
+ addUser = vi.fn();
25
+ addContext = vi.fn();
26
+ setCurrentContext = vi.fn();
26
27
  }
27
28
  class CustomObjectsApi {}
28
29
  return { KubeConfig, CustomObjectsApi };
29
30
  });
30
31
 
31
- jest.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
32
+ vi.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
32
33
  PromiseMiddlewareWrapper: class {},
33
34
  }));
34
35
 
35
36
  describe('KubernetesServiceProvidersService', () => {
36
37
  let service: KubernetesServiceProvidersService;
37
- let kcpKubernetesServiceMock: jest.Mocked<KcpKubernetesService>;
38
- let permissionsProxyServiceMock: jest.Mocked<PermissionsProxyService>;
38
+ let kcpKubernetesServiceMock: Mocked<KcpKubernetesService>;
39
+ let permissionsProxyServiceMock: Mocked<PermissionsProxyService>;
39
40
 
40
41
  const mockToken = 'test-token-123';
41
42
  const mockEntities = ['test-entity'];
@@ -47,8 +48,8 @@ describe('KubernetesServiceProvidersService', () => {
47
48
  beforeEach(async () => {
48
49
  kcpKubernetesServiceMock = mock<KcpKubernetesService>();
49
50
  permissionsProxyServiceMock = {
50
- resolvePermissions: jest.fn().mockResolvedValue([]),
51
- } as unknown as jest.Mocked<PermissionsProxyService>;
51
+ resolvePermissions: vi.fn().mockResolvedValue([]),
52
+ } as unknown as Mocked<PermissionsProxyService>;
52
53
 
53
54
  const module: TestingModule = await Test.createTestingModule({
54
55
  providers: [
@@ -71,7 +72,7 @@ describe('KubernetesServiceProvidersService', () => {
71
72
 
72
73
  describe('getServiceProviders', () => {
73
74
  it.each([{ entities: [] }, { entities: ['core_platform-mesh_io_account'] }])(
74
- 'normalizes built-in and provider bundle nodes for entity selection %p',
75
+ 'normalizes built-in and provider bundle nodes for entity selection %o',
75
76
  async ({ entities }) => {
76
77
  const portalOrigin = 'https://portal.example.test';
77
78
  const config = {
@@ -391,9 +392,9 @@ describe('KubernetesServiceProvidersService', () => {
391
392
  .mockRejectedValueOnce(error)
392
393
  .mockResolvedValueOnce(mockResponse as any);
393
394
 
394
- jest.spyOn(console, 'error').mockImplementation();
395
- jest.spyOn(console, 'log').mockImplementation();
396
- jest.spyOn(global, 'setTimeout');
395
+ vi.spyOn(console, 'error').mockImplementation(() => {});
396
+ vi.spyOn(console, 'log').mockImplementation(() => {});
397
+ vi.spyOn(global, 'setTimeout');
397
398
 
398
399
  await service.getServiceProviders(mockToken, mockEntities, mockContext);
399
400
 
@@ -402,7 +403,7 @@ describe('KubernetesServiceProvidersService', () => {
402
403
  ).toHaveBeenCalledTimes(2);
403
404
  expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), 1000);
404
405
 
405
- jest.restoreAllMocks();
406
+ vi.restoreAllMocks();
406
407
  });
407
408
 
408
409
  it('should retry once on statusCode 429 error', async () => {
@@ -413,8 +414,8 @@ describe('KubernetesServiceProvidersService', () => {
413
414
  .mockRejectedValueOnce(error)
414
415
  .mockResolvedValueOnce(mockResponse as any);
415
416
 
416
- jest.spyOn(console, 'error').mockImplementation();
417
- jest.spyOn(console, 'log').mockImplementation();
417
+ vi.spyOn(console, 'error').mockImplementation(() => {});
418
+ vi.spyOn(console, 'log').mockImplementation(() => {});
418
419
 
419
420
  await service.getServiceProviders(mockToken, mockEntities, mockContext);
420
421
 
@@ -422,7 +423,7 @@ describe('KubernetesServiceProvidersService', () => {
422
423
  kcpKubernetesServiceMock.listClusterCustomObjectInKcpVirtualWorkspace,
423
424
  ).toHaveBeenCalledTimes(2);
424
425
 
425
- jest.restoreAllMocks();
426
+ vi.restoreAllMocks();
426
427
  });
427
428
 
428
429
  it('should log error on kubernetes service failure', async () => {
@@ -431,7 +432,9 @@ describe('KubernetesServiceProvidersService', () => {
431
432
  error,
432
433
  );
433
434
 
434
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
435
+ const consoleSpy = vi
436
+ .spyOn(console, 'error')
437
+ .mockImplementation(() => {});
435
438
 
436
439
  await service.getServiceProviders(mockToken, mockEntities, mockContext);
437
440
 
@@ -446,7 +449,7 @@ describe('KubernetesServiceProvidersService', () => {
446
449
  error,
447
450
  );
448
451
 
449
- jest.spyOn(console, 'error').mockImplementation();
452
+ vi.spyOn(console, 'error').mockImplementation(() => {});
450
453
 
451
454
  await service.getServiceProviders(mockToken, mockEntities, mockContext);
452
455
 
@@ -454,7 +457,7 @@ describe('KubernetesServiceProvidersService', () => {
454
457
  kcpKubernetesServiceMock.listClusterCustomObjectInKcpVirtualWorkspace,
455
458
  ).toHaveBeenCalledTimes(1);
456
459
 
457
- jest.restoreAllMocks();
460
+ vi.restoreAllMocks();
458
461
  });
459
462
 
460
463
  it('should return result after successful retry', async () => {
@@ -477,8 +480,8 @@ describe('KubernetesServiceProvidersService', () => {
477
480
  .mockRejectedValueOnce(error)
478
481
  .mockResolvedValueOnce(mockResponse as any);
479
482
 
480
- jest.spyOn(console, 'error').mockImplementation();
481
- jest.spyOn(console, 'log').mockImplementation();
483
+ vi.spyOn(console, 'error').mockImplementation(() => {});
484
+ vi.spyOn(console, 'log').mockImplementation(() => {});
482
485
 
483
486
  const result = await service.getServiceProviders(
484
487
  mockToken,
@@ -493,7 +496,7 @@ describe('KubernetesServiceProvidersService', () => {
493
496
  'https://test.com',
494
497
  );
495
498
 
496
- jest.restoreAllMocks();
499
+ vi.restoreAllMocks();
497
500
  });
498
501
  });
499
502
 
@@ -2,37 +2,43 @@ import { K8sRequestContext, K8sResourceDescriptor } from '../models/k8s.js';
2
2
  import { KcpKubernetesService } from './kcp-k8s.service.js';
3
3
  import type { Request } from 'express';
4
4
 
5
- const mockListClusterCustomObject = jest.fn();
6
- const mockReadNamespacedSecret = jest.fn();
7
- const mockMakeApiClient = jest.fn();
8
- const mockGetCurrentCluster = jest.fn();
9
- const mockLoadFromFile = jest.fn();
10
- const mockAddUser = jest.fn();
11
- const mockAddContext = jest.fn();
12
- const mockSetCurrentContext = jest.fn();
13
-
14
- jest.mock('@kubernetes/client-node', () => ({
15
- CustomObjectsApi: jest.fn().mockImplementation(() => ({
16
- listClusterCustomObject: mockListClusterCustomObject,
17
- })),
18
- CoreV1Api: jest.fn().mockImplementation(() => ({
19
- readNamespacedSecret: mockReadNamespacedSecret,
20
- })),
21
- KubeConfig: jest.fn().mockImplementation(() => ({
22
- loadFromFile: mockLoadFromFile,
23
- addUser: mockAddUser,
24
- addContext: mockAddContext,
25
- setCurrentContext: mockSetCurrentContext,
26
- getCurrentCluster: mockGetCurrentCluster,
27
- makeApiClient: mockMakeApiClient,
28
- })),
5
+ const mockListClusterCustomObject = vi.fn();
6
+ const mockReadNamespacedSecret = vi.fn();
7
+ const mockMakeApiClient = vi.fn();
8
+ const mockGetCurrentCluster = vi.fn();
9
+ const mockLoadFromFile = vi.fn();
10
+ const mockAddUser = vi.fn();
11
+ const mockAddContext = vi.fn();
12
+ const mockSetCurrentContext = vi.fn();
13
+
14
+ vi.mock('@kubernetes/client-node', () => ({
15
+ CustomObjectsApi: vi.fn().mockImplementation(function () {
16
+ return {
17
+ listClusterCustomObject: mockListClusterCustomObject,
18
+ };
19
+ }),
20
+ CoreV1Api: vi.fn().mockImplementation(function () {
21
+ return {
22
+ readNamespacedSecret: mockReadNamespacedSecret,
23
+ };
24
+ }),
25
+ KubeConfig: vi.fn().mockImplementation(function () {
26
+ return {
27
+ loadFromFile: mockLoadFromFile,
28
+ addUser: mockAddUser,
29
+ addContext: mockAddContext,
30
+ setCurrentContext: mockSetCurrentContext,
31
+ getCurrentCluster: mockGetCurrentCluster,
32
+ makeApiClient: mockMakeApiClient,
33
+ };
34
+ }),
29
35
  }));
30
36
 
31
- jest.mock('../utils/domain.js', () => ({
32
- getOrganization: jest.fn(() => 'org-1'),
37
+ vi.mock('../utils/domain.js', () => ({
38
+ getOrganization: vi.fn(() => 'org-1'),
33
39
  }));
34
40
 
35
- jest.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
41
+ vi.mock('@kubernetes/client-node/dist/gen/middleware.js', () => ({
36
42
  PromiseMiddlewareWrapper: class {
37
43
  constructor(public options: any) {}
38
44
  },
@@ -42,7 +48,7 @@ describe('KcpKubernetesService', () => {
42
48
  const OLD_ENV = process.env;
43
49
 
44
50
  beforeEach(() => {
45
- jest.clearAllMocks();
51
+ vi.clearAllMocks();
46
52
  process.env = {
47
53
  ...OLD_ENV,
48
54
  KUBECONFIG_KCP: '/tmp/kcp.kubeconfig',
@@ -367,7 +373,7 @@ describe('KcpKubernetesService', () => {
367
373
  mockListClusterCustomObject.mockImplementation(async (_gvr, options) => {
368
374
  const middleware = options.middleware[0];
369
375
  const mockContext = {
370
- setUrl: jest.fn(),
376
+ setUrl: vi.fn(),
371
377
  };
372
378
  await middleware.options.pre(mockContext);
373
379
  postResult = await middleware.options.post(mockContext);
@@ -397,7 +403,7 @@ describe('KcpKubernetesService', () => {
397
403
  async (gvrParam, options) => {
398
404
  const middleware = options.middleware[0];
399
405
  const mockContext = {
400
- setUrl: jest.fn(),
406
+ setUrl: vi.fn(),
401
407
  };
402
408
  capturedContext = mockContext;
403
409
  await middleware.options.pre(mockContext);
@@ -429,7 +435,7 @@ describe('KcpKubernetesService', () => {
429
435
  async (gvrParam, options) => {
430
436
  const middleware = options.middleware[0];
431
437
  const mockContext = {
432
- setUrl: jest.fn(),
438
+ setUrl: vi.fn(),
433
439
  };
434
440
  capturedContext = mockContext;
435
441
  await middleware.options.pre(mockContext);
@@ -490,8 +496,8 @@ describe('KcpKubernetesService', () => {
490
496
  async (gvrParam, options) => {
491
497
  const middleware = options.middleware[0];
492
498
  const mockContext = {
493
- setUrl: jest.fn(),
494
- setHeaderParam: jest.fn(),
499
+ setUrl: vi.fn(),
500
+ setHeaderParam: vi.fn(),
495
501
  };
496
502
  capturedContext = mockContext;
497
503
  await middleware.options.pre(mockContext);
@@ -529,8 +535,8 @@ describe('KcpKubernetesService', () => {
529
535
  async (gvrParam, options) => {
530
536
  const middleware = options.middleware[0];
531
537
  const mockContext = {
532
- setUrl: jest.fn(),
533
- setHeaderParam: jest.fn(),
538
+ setUrl: vi.fn(),
539
+ setHeaderParam: vi.fn(),
534
540
  };
535
541
  capturedContext = mockContext;
536
542
  await middleware.options.pre(mockContext);
@@ -604,7 +610,7 @@ describe('KcpKubernetesService', () => {
604
610
  mockReadNamespacedSecret.mockImplementation(async (params, options) => {
605
611
  const middleware = options.middleware[0];
606
612
  const mockContext = {
607
- setUrl: jest.fn(),
613
+ setUrl: vi.fn(),
608
614
  };
609
615
  capturedContext = mockContext;
610
616
  await middleware.options.pre(mockContext);
@@ -630,7 +636,7 @@ describe('KcpKubernetesService', () => {
630
636
  mockReadNamespacedSecret.mockImplementation(async (_params, options) => {
631
637
  const middleware = options.middleware[0];
632
638
  const mockContext = {
633
- setUrl: jest.fn(),
639
+ setUrl: vi.fn(),
634
640
  };
635
641
  await middleware.options.pre(mockContext);
636
642
  postResult = await middleware.options.post(mockContext);
@@ -6,6 +6,7 @@ import {
6
6
  SubjectAccessReview,
7
7
  } from '../models/permissions.model.js';
8
8
  import { AuthzWebhookService } from './authz-webhook.service.js';
9
+ import type { Mock } from 'vitest';
9
10
 
10
11
  // Build a valid JWT token with the given payload
11
12
  function makeJwt(payload: Record<string, unknown>): string {
@@ -21,7 +22,7 @@ const WEBHOOK_URL = 'https://authz.example.com';
21
22
  const BATCH_URL = `${WEBHOOK_URL}/batch-authz`;
22
23
 
23
24
  // The posted body is the SubjectAccessReview[] array itself.
24
- function postedItems(post: jest.Mock): SubjectAccessReview[] {
25
+ function postedItems(post: Mock): SubjectAccessReview[] {
25
26
  return post.mock.calls[0][1] as SubjectAccessReview[];
26
27
  }
27
28
 
@@ -73,14 +74,14 @@ function makeInstanceRequest(
73
74
 
74
75
  describe('AuthzWebhookService', () => {
75
76
  let service: AuthzWebhookService;
76
- let httpService: { post: jest.Mock };
77
+ let httpService: { post: Mock };
77
78
  const OLD_ENV = process.env;
78
79
 
79
80
  beforeEach(async () => {
80
81
  process.env = { ...OLD_ENV };
81
82
  process.env['OPENMFP_PORTAL_CONTEXT_AUTHZ_WEBHOOK_URL'] = WEBHOOK_URL;
82
83
 
83
- httpService = { post: jest.fn() };
84
+ httpService = { post: vi.fn() };
84
85
 
85
86
  const module: TestingModule = await Test.createTestingModule({
86
87
  providers: [
@@ -1,5 +1,5 @@
1
1
  import { ContentConfiguration } from '@openmfp/portal-server-lib';
2
- import { mock } from 'jest-mock-extended';
2
+ import { mock } from 'vitest-mock-extended';
3
3
  import { AuthzWebhookService } from './adapters/authz-webhook.service.js';
4
4
  import {
5
5
  AuthorizationRequest,
@@ -7,6 +7,7 @@ import {
7
7
  PermissionsDefinition,
8
8
  } from './models/permissions.model.js';
9
9
  import { PermissionsProxyService } from './permissions-proxy.service.js';
10
+ import type { Mocked } from 'vitest';
10
11
 
11
12
  function makeCC(
12
13
  nodes: ContentConfiguration['luigiConfigFragment']['data']['nodes'] = [],
@@ -66,7 +67,7 @@ function makeAuthzRequest(
66
67
  }
67
68
 
68
69
  describe('PermissionsProxyService', () => {
69
- let authzWebhook: jest.Mocked<AuthzWebhookService>;
70
+ let authzWebhook: Mocked<AuthzWebhookService>;
70
71
  let svc: PermissionsProxyService;
71
72
 
72
73
  beforeEach(() => {
@@ -1,10 +1,11 @@
1
1
  import { UnauthorizedException } from '@nestjs/common';
2
2
  import { HeaderParserService } from '@openmfp/portal-server-lib';
3
3
  import { Test, TestingModule } from '@nestjs/testing';
4
- import { mock } from 'jest-mock-extended';
4
+ import { mock } from 'vitest-mock-extended';
5
5
  import { PermissionsController } from './permissions.controller.js';
6
6
  import { PermissionsProxyService } from './permissions-proxy.service.js';
7
7
  import { AuthorizationRequest, Permission } from './models/permissions.model.js';
8
+ import type { Mocked } from 'vitest';
8
9
 
9
10
  function makeBody(
10
11
  overrides: Partial<AuthorizationRequest> = {},
@@ -30,8 +31,8 @@ const request = {} as Request;
30
31
 
31
32
  describe('PermissionsController', () => {
32
33
  let controller: PermissionsController;
33
- let proxyService: jest.Mocked<PermissionsProxyService>;
34
- let headerParser: jest.Mocked<HeaderParserService>;
34
+ let proxyService: Mocked<PermissionsProxyService>;
35
+ let headerParser: Mocked<HeaderParserService>;
35
36
 
36
37
  beforeEach(async () => {
37
38
  proxyService = mock<PermissionsProxyService>();
@@ -5,7 +5,7 @@ describe('getOrganization', () => {
5
5
  const OLD_ENV = process.env;
6
6
 
7
7
  beforeEach(() => {
8
- jest.resetModules();
8
+ vi.resetModules();
9
9
  process.env = { ...OLD_ENV };
10
10
  process.env.OIDC_CLIENT_ID_DEFAULT = 'default-client';
11
11
  process.env.BASE_DOMAINS_DEFAULT = 'example.com';
@@ -53,7 +53,7 @@ describe('getDiscoveryEndpoint', () => {
53
53
  ({ hostname }) as unknown as Request;
54
54
 
55
55
  beforeEach(() => {
56
- jest.resetModules();
56
+ vi.resetModules();
57
57
  process.env = { ...OLD_ENV };
58
58
  process.env.OIDC_CLIENT_ID_DEFAULT = 'default-client';
59
59
  process.env.BASE_DOMAINS_DEFAULT = 'example.com';
@@ -151,7 +151,7 @@ describe('normalizePortalWebComponentModules', () => {
151
151
  });
152
152
 
153
153
  it.each(['module', 'classic', '', null])(
154
- 'preserves explicitly configured type %p',
154
+ 'preserves explicitly configured type %o',
155
155
  (type) => {
156
156
  const config = configuration([
157
157
  node({ webcomponent: { selfRegistered: true, type } }),
@@ -165,7 +165,7 @@ describe('normalizePortalWebComponentModules', () => {
165
165
  );
166
166
 
167
167
  it.each([undefined, false, true, { selfRegistered: false }])(
168
- 'preserves non-self-registered setting %p',
168
+ 'preserves non-self-registered setting %o',
169
169
  (webcomponent) => {
170
170
  const config = configuration([node({ webcomponent })]);
171
171
  normalizePortalWebComponentModules(config);
@@ -259,7 +259,7 @@ describe('normalizePortalWebComponentModules', () => {
259
259
  {},
260
260
  { luigiConfigFragment: {} },
261
261
  { luigiConfigFragment: { data: { nodes: null } } },
262
- ])('ignores absent node structures %p', (value) => {
262
+ ])('ignores absent node structures %o', (value) => {
263
263
  expect(() =>
264
264
  normalizePortalWebComponentModules(value as ContentConfiguration),
265
265
  ).not.toThrow();
@@ -4,7 +4,6 @@
4
4
  "node_modules",
5
5
  "dist",
6
6
  "**/*spec.ts",
7
- "jest.config.ts",
8
- "base.jest.config.ts"
7
+ "vitest.config.ts"
9
8
  ]
10
9
  }
package/tsconfig.json CHANGED
@@ -14,6 +14,6 @@
14
14
  "experimentalDecorators": true,
15
15
  "sourceMap": true,
16
16
  "skipLibCheck": true,
17
- "types": ["jest"]
17
+ "types": ["vitest/globals"]
18
18
  },
19
19
  }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ include: ['src/**/*.spec.ts'],
7
+ coverage: {
8
+ enabled: true,
9
+ provider: 'v8',
10
+ reporter: ['text', 'cobertura', 'lcov'],
11
+ reportsDirectory: 'test-run-reports/coverage/unit',
12
+ thresholds: {
13
+ branches: 75,
14
+ functions: 89,
15
+ lines: 90,
16
+ statements: -12,
17
+ },
18
+ },
19
+ },
20
+ });
@@ -1,15 +0,0 @@
1
- export default {
2
- testEnvironment: 'node',
3
- coverageReporters: ['text', 'cobertura', 'lcov'],
4
- transform: {
5
- '^.+\\.(t|j)s$': [
6
- 'ts-jest',
7
- {
8
- tsconfig: 'tsconfig.test.json',
9
- },
10
- ],
11
- },
12
- moduleNameMapper: {
13
- '^(\\.{1,2}/.*)\\.js$': '$1',
14
- },
15
- };
package/jest.config.ts DELETED
@@ -1,42 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-require-imports */
2
- import baseConfig from './base.jest.config.js';
3
-
4
- export default {
5
- ...baseConfig,
6
- rootDir: 'src',
7
- testRegex: '.spec.ts$',
8
- collectCoverage: true,
9
- reporters: ['default'],
10
- coverageThreshold: {
11
- global: {
12
- branches: 75,
13
- functions: 89,
14
- lines: 90,
15
- statements: -12,
16
- },
17
- },
18
- coveragePathIgnorePatterns: ['/node_modules/', '/integration-tests/'],
19
- coverageDirectory: '../test-run-reports/coverage/unit',
20
- transformIgnorePatterns: [
21
- '/node_modules/(?!(@openmfp/portal-server-lib|graphql-request)/)',
22
- ],
23
- transform: {
24
- '^.+\\.(t|j)s$': [
25
- 'ts-jest',
26
- {
27
- tsconfig: 'tsconfig.test.json',
28
- useESM: true,
29
- },
30
- ],
31
- },
32
- testEnvironment: 'node',
33
- passWithNoTests: true,
34
- roots: ['<rootDir>'],
35
- moduleNameMapper: {
36
- '^@openmfp/portal-lib(|/.*)$': '<rootDir>/libs/portal-lib/src/$1',
37
- '^(\\.{1,2}/.*)\\.js$': '$1',
38
- },
39
- preset: 'ts-jest/presets/default-esm',
40
- extensionsToTreatAsEsm: ['.ts'],
41
- moduleFileExtensions: ['js', 'json', 'ts'],
42
- };
@@ -1,3 +0,0 @@
1
- {
2
- "extends": "./tsconfig.json",
3
- }