Skip to content

Commit ecc44bf

Browse files
chore(deps): update dependency vitest to v4 [security] (finos#2556)
* chore(deps): update dependency vitest to v4 [security] * chore(deps): align @vitest/coverage-v8 and @vitest/ui with vitest v4 Renovate's vitest 3 -> 4 bump in this PR left @vitest/coverage-v8 and @vitest/ui on ^3, but vitest@4 declares peerOptional matching its own major (e.g. @vitest/ui@"4.1.8" from vitest@4.1.8). npm ci therefore refused to install with an ERESOLVE, failing every CI job before tests could run. Bumps the companion packages to ^4 in the root devDependencies and in the four calm-suite/calm-studio workspaces that also pinned @vitest/coverage-v8@^3. Lockfile regenerated incrementally; all 105 linux / 28 darwin / 35 win32 platform variants preserved. This unblocks npm ci but does not address downstream vitest@4 mock breaking changes (vi.fn(arrow) no longer constructable, v8 coverage branch-count drift) that surface in cli, shared, calm-plugins/vscode, calm-widgets and a couple of calm-studio tests once installs succeed - those need a separate migration pass. * test: migrate test suites to vitest v4 Vitest v4 tightened mocking and module resolution. This commit makes the existing tests pass under v4 without changing behaviour: * Convert `vi.fn(arrow)` / `mockImplementation(arrow)` to function expressions. In v4, arrow-function implementations cannot be called with `new` (TypeError: ... is not a constructor) - tests that mock constructor calls were failing across cli, shared, calm-plugins/vscode, calm-widgets and calm-suite/calm-studio. The function-expression form is semantically identical for non-constructor calls and constructable for `new`. See https://vitest.dev/guide/migration.html. * Add a `default` condition to `@finos/calm-models`'s `exports` map for every subpath. v4's resolver (via newer Vite) no longer falls back from `import` to `default` implicitly, breaking calm-server tests that transitively imported `@finos/calm-models/canonical`. * Lower the branch-coverage threshold from 85 to 78 in calm-models, calm-widgets, shared and cli. v4's v8 coverage reporter counts branches more strictly than v3 (observed drop of ~3-5 points on unchanged source); 78 keeps a meaningful gate while accommodating the new measurement. All workspace test suites now pass with coverage enabled on Node 22. * fix(calm-widgets): re-include @types/node in tsconfig types The workspace's tsconfig.json overrode the base config's `"types": ["vitest", "node"]` with `"types": ["vitest"]`, dropping the Node types. Under vitest 3, the bundled `vitest` types transitively pulled in @types/node so this went unnoticed; under vitest 4 the type surface was cleaned up, leaving `__dirname`, `fs` and `path` unresolved during DTS generation (TS2304). This cascaded into every PR 2556 CI job that builds calm-widgets as a dependency (Shared, CLI, VSCode Extension, CALM Server). * test(calm-hub-ui): apply vitest v4 vi.fn arrow→function conversion Same root cause as 0299dd1 - I scoped the original migration to the workspaces that surfaced in earlier failed CI checks and missed calm-hub-ui, which has its own workflow ("Build CALM Hub UI") whose "Build, Test, and Lint Shared Module" job tests the calm-hub-ui sources rather than @finos/calm-shared. Six React Testing Library specs were affected. 700/700 tests pass locally on Node 22.22.2. * test: backfill tests and revert v4 threshold lowering Vitest 4's v8 coverage reporter counts branches more strictly than v3 (short-circuit operators, nullish coalescing, optional chaining, default parameters all now contribute branches that v3 silently elided). Same source code measures 3–5 points lower under v4. The previous commit lowered the branch threshold from 85 to 78 across calm-models, calm-widgets, shared and cli to paper over this. That was the wrong call — the threshold was a quality gate, and lowering it on account of measurement drift loses information. This commit reverts the threshold to 85 and instead writes real tests for the branches v4 now surfaces, restoring the original gate honestly: * calm-models — 82.27% → 90.71% — adds the missing canonical/ template-models spec (5 type guards, visitRelationship dispatch for all five relationship kinds plus the default, toKindView). * calm-widgets — 82.41% → 85.06% — adds flow-sequence cases for composed-of/options/missing-relationship paths and validateContext guards; adds related-nodes cases for every node-id branch in the filter (interacts actor/nodes, deployed-in container/nodes, composed-of, options-default). * shared — 81.00% → 85.06% — adds full logger.spec for the node and browser branches; covers pretty-output's info/hint severities, fallback header/code paths, snippet trimming and out-of-range branches; covers flow-sequence-helper for all kind branches and empty-nodes fallbacks; covers control-registry's no-controls and duplicate-pass branches; covers handlers.ts (msw test infra) directly; covers c4's unknown-actor / unknown-source / unknown-destination warnings; adds template-preprocessor empty-body and no-mustaches edges. * cli — 79.91% → 85.09% — adds hub-output.spec for parseOutputFormat / printJsonSuccess / printTableSuccess / printError; covers cli-config loadAuthPlugin error paths; covers diff/timeline ends-with-newline and non-Error throw branches; covers index.ts entry point including the catch with Error / non-Error / falsy values; adds getting-started-url-mapping coverage of the JSON-read catch blocks. * fix(test): satisfy strict tsc on new flow-sequence specs The coverage-backfill commit landed new flow-sequence tests that pass vitest (transpile-only) but fail strict tsc on the tsup-then-tsc build step: * `arch.flows[0]` — flows is optional on CalmCoreCanonicalModel; needs a non-null assertion (`arch.flows![0]`) since makeArch always populates it. * The options-kind relationship literal was missing the required `relationships: []` field on CalmDecisionCanonicalModel. These are test-only assertions; production types are unchanged. Caught by every CI job that builds calm-widgets (Shared, CLI, VSCode Ext, CALM Server, calm-widgets itself). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Matthew Bain <66839492+rocketstack-matt@users.noreply.github.com>
1 parent 041246c commit ecc44bf

77 files changed

Lines changed: 2361 additions & 1166 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

calm-hub-ui/src/hub/components/control-detail-section/ControlDetailSection.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ const mockFetchConfigurationVersions = vi.fn();
2525
const mockFetchConfigurationForVersion = vi.fn();
2626

2727
vi.mock('../../../service/control-service.js', () => ({
28-
ControlService: vi.fn().mockImplementation(() => ({
28+
ControlService: vi.fn().mockImplementation(function () { return {
2929
fetchRequirementVersions: (...args: unknown[]) => mockFetchRequirementVersions(...args),
3030
fetchRequirementForVersion: (...args: unknown[]) => mockFetchRequirementForVersion(...args),
3131
fetchConfigurationsForControl: (...args: unknown[]) => mockFetchConfigurationsForControl(...args),
3232
fetchConfigurationVersions: (...args: unknown[]) => mockFetchConfigurationVersions(...args),
3333
fetchConfigurationForVersion: (...args: unknown[]) => mockFetchConfigurationForVersion(...args),
34-
})),
34+
}; }),
3535
}));
3636

3737
// ── Test data ─────────────────────────────────────────────

calm-hub-ui/src/hub/components/diagram-section/DiagramSection.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ vi.mock('react-router-dom', async () => {
2121
const actual = await vi.importActual('react-router-dom');
2222
return {
2323
...actual,
24-
useNavigate: vi.fn(() => vi.fn()),
24+
useNavigate: vi.fn(function () { return vi.fn(); }),
2525
};
2626
});
2727

@@ -61,13 +61,13 @@ vi.mock('./timeline/TimelineBar.js', () => ({
6161
}));
6262

6363
vi.mock('../../../service/calm-service.js', () => ({
64-
CalmService: vi.fn().mockImplementation(() => ({
64+
CalmService: vi.fn().mockImplementation(function () { return {
6565
fetchDecoratorValues: calmServiceMock.fetchDecoratorValues,
6666
fetchVersionsByCustomId: calmServiceMock.fetchVersionsByCustomId,
6767
fetchArchitectureTimeline: calmServiceMock.fetchArchitectureTimeline,
6868
fetchArchitectureSummaries: calmServiceMock.fetchArchitectureSummaries,
6969
fetchPatternSummaries: calmServiceMock.fetchPatternSummaries,
70-
})),
70+
}; }),
7171
}));
7272

7373
const architectureData: Data & { calmType: 'Architectures' } = {

calm-hub-ui/src/hub/components/document-detail-section/DocumentDetailSection.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ vi.mock('react-router-dom', async () => {
1111
const actual = await vi.importActual('react-router-dom');
1212
return {
1313
...actual,
14-
useNavigate: vi.fn(() => mockNavigate),
14+
useNavigate: vi.fn(function () { return mockNavigate; }),
1515
};
1616
});
1717

@@ -24,11 +24,11 @@ const mockFetchFlowVersions = vi.fn();
2424
const mockFetchVersionsByCustomId = vi.fn();
2525

2626
vi.mock('../../../service/calm-service.js', () => ({
27-
CalmService: vi.fn().mockImplementation(() => ({
27+
CalmService: vi.fn().mockImplementation(function () { return {
2828
fetchStandardVersions: mockFetchStandardVersions,
2929
fetchFlowVersions: mockFetchFlowVersions,
3030
fetchVersionsByCustomId: mockFetchVersionsByCustomId,
31-
})),
31+
}; }),
3232
}));
3333

3434
describe('DocumentDetailSection', () => {

calm-hub-ui/src/hub/components/interface-detail-section/InterfaceDetailSection.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ const mockFetchInterfaceVersions = vi.fn();
2222
const mockFetchInterfaceForVersion = vi.fn();
2323

2424
vi.mock('../../../service/interface-service.js', () => ({
25-
InterfaceService: vi.fn().mockImplementation(() => ({
25+
InterfaceService: vi.fn().mockImplementation(function () { return {
2626
fetchInterfaceVersions: (...args: unknown[]) => mockFetchInterfaceVersions(...args),
2727
fetchInterfaceForVersion: (...args: unknown[]) => mockFetchInterfaceForVersion(...args),
28-
})),
28+
}; }),
2929
}));
3030

3131
// ── Test data ─────────────────────────────────────────────

calm-hub-ui/src/hub/components/tree-navigation/TreeNavigation.test.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ let calmServiceInstance: {
3636
} | undefined;
3737

3838
vi.mock('../../../service/calm-service.js', () => ({
39-
CalmService: vi.fn().mockImplementation(() => {
39+
CalmService: vi.fn().mockImplementation(function () {
4040
calmServiceInstance = {
4141
fetchNamespaces: vi.fn().mockResolvedValue(['test-namespace', 'another-namespace']),
4242
fetchPatternSummaries: vi.fn().mockResolvedValue([]),
@@ -65,7 +65,7 @@ let controlServiceInstance: {
6565
} | undefined;
6666

6767
vi.mock('../../../service/control-service.js', () => ({
68-
ControlService: vi.fn().mockImplementation(() => {
68+
ControlService: vi.fn().mockImplementation(function () {
6969
controlServiceInstance = {
7070
fetchDomains: vi.fn().mockResolvedValue(['test-domain']),
7171
fetchControlsForDomain: vi.fn().mockResolvedValue([]),
@@ -79,7 +79,7 @@ let interfaceServiceInstance: {
7979
} | undefined;
8080

8181
vi.mock('../../../service/interface-service.js', () => ({
82-
InterfaceService: vi.fn().mockImplementation(() => {
82+
InterfaceService: vi.fn().mockImplementation(function () {
8383
interfaceServiceInstance = {
8484
fetchInterfacesForNamespace: vi.fn().mockResolvedValue([]),
8585
};
@@ -93,7 +93,7 @@ let adrServiceInstance: {
9393
fetchAdr: Mock;
9494
} | undefined;
9595
vi.mock('../../../service/adr-service/adr-service.js', () => ({
96-
AdrService: vi.fn().mockImplementation(() => {
96+
AdrService: vi.fn().mockImplementation(function () {
9797
adrServiceInstance = {
9898
fetchAdrSummaries: vi.fn().mockResolvedValue([{ id: 201, title: 'Use CALM', status: 'accepted' }, { id: 202, title: 'Use React', status: 'proposed' }]),
9999
fetchAdrRevisions: vi.fn().mockResolvedValue(['v1.0', 'v2.0']),
@@ -230,7 +230,7 @@ describe('TreeNavigation', () => {
230230
});
231231

232232
it('loads data based on deeplink route - interface', async () => {
233-
vi.mocked(InterfaceService).mockImplementation(() => {
233+
vi.mocked(InterfaceService).mockImplementation(function () {
234234
interfaceServiceInstance = {
235235
fetchInterfacesForNamespace: vi.fn().mockResolvedValue([
236236
{ id: 301, name: 'Test Interface', description: 'An interface' },
@@ -262,7 +262,7 @@ describe('TreeNavigation', () => {
262262
});
263263

264264
it('loads data based on deeplink route - control', async () => {
265-
vi.mocked(ControlService).mockImplementation(() => {
265+
vi.mocked(ControlService).mockImplementation(function () {
266266
controlServiceInstance = {
267267
fetchDomains: vi.fn().mockResolvedValue(['test-domain']),
268268
fetchControlsForDomain: vi.fn().mockResolvedValue([
@@ -298,7 +298,7 @@ describe('TreeNavigation', () => {
298298
vi.mocked(useParams).mockReturnValue({});
299299
const navigate = vi.fn();
300300
vi.mocked(useNavigate).mockReturnValue(navigate);
301-
vi.mocked(CalmService).mockImplementationOnce(() => ({
301+
vi.mocked(CalmService).mockImplementationOnce(function () { return {
302302
fetchNamespaces: vi.fn().mockResolvedValue(['test-namespace']),
303303
fetchPatternSummaries: vi.fn().mockResolvedValue([]),
304304
fetchFlowSummaries: vi.fn().mockResolvedValue([]),
@@ -315,7 +315,7 @@ describe('TreeNavigation', () => {
315315
fetchMappings: vi.fn().mockResolvedValue([]),
316316
fetchVersionsByCustomId: vi.fn().mockResolvedValue([]),
317317
fetchResourceByCustomId: vi.fn().mockResolvedValue({}),
318-
}) as unknown as InstanceType<typeof CalmService>);
318+
}; } as unknown as InstanceType<typeof CalmService>);
319319

320320
render(<MemoryRouter initialEntries={["/"]}>
321321
<TreeNavigation {...mockProps} />
@@ -381,7 +381,7 @@ describe('buildNamespaceTree', () => {
381381

382382
it('renders hierarchical namespaces in the tree', async () => {
383383
vi.mocked(useParams).mockReturnValue({});
384-
vi.mocked(CalmService).mockImplementationOnce(() => ({
384+
vi.mocked(CalmService).mockImplementationOnce(function () { return {
385385
fetchNamespaces: vi.fn().mockResolvedValue(['org.finos', 'org.finos.calm', 'com.traderx']),
386386
fetchPatternSummaries: vi.fn().mockResolvedValue([]),
387387
fetchFlowSummaries: vi.fn().mockResolvedValue([]),
@@ -398,7 +398,7 @@ describe('buildNamespaceTree', () => {
398398
fetchMappings: vi.fn().mockResolvedValue([]),
399399
fetchVersionsByCustomId: vi.fn().mockResolvedValue([]),
400400
fetchResourceByCustomId: vi.fn().mockResolvedValue({})
401-
}));
401+
}; });
402402

403403
render(
404404
<MemoryRouter initialEntries={['/']}>

calm-hub-ui/src/visualizer/components/drawer/Drawer.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { DropzoneOptions } from 'react-dropzone';
88
const mockFetchDecoratorValues = vi.fn().mockResolvedValue([]);
99

1010
vi.mock('../../../service/calm-service.js', () => ({
11-
CalmService: vi.fn().mockImplementation(() => ({
11+
CalmService: vi.fn().mockImplementation(function () { return {
1212
fetchDecoratorValues: (...args: unknown[]) => mockFetchDecoratorValues(...args),
13-
})),
13+
}; }),
1414
}));
1515

1616
vi.mock('../reactflow/MetadataPanel.js', () => ({

calm-models/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,24 @@
44
"description": "CALM (Common Architecture Language Model) type definitions and model classes",
55
"exports": {
66
"./model": {
7+
"types": "./dist/model/index.d.ts",
78
"import": "./dist/model/index.js",
8-
"types": "./dist/model/index.d.ts"
9+
"default": "./dist/model/index.js"
910
},
1011
"./types": {
12+
"types": "./dist/types/index.d.ts",
1113
"import": "./dist/types/index.js",
12-
"types": "./dist/types/index.d.ts"
14+
"default": "./dist/types/index.js"
1315
},
1416
"./canonical": {
17+
"types": "./dist/canonical/template-models.d.ts",
1518
"import": "./dist/canonical/template-models.js",
16-
"types": "./dist/canonical/template-models.d.ts"
19+
"default": "./dist/canonical/template-models.js"
1720
},
1821
"./diff": {
22+
"types": "./dist/diff/index.d.ts",
1923
"import": "./dist/diff/index.js",
20-
"types": "./dist/diff/index.d.ts"
24+
"default": "./dist/diff/index.js"
2125
}
2226
},
2327
"files": [
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
isInteracts,
4+
isConnects,
5+
isComposedOf,
6+
isDeployedIn,
7+
isOptions,
8+
visitRelationship,
9+
toKindView,
10+
CalmRelationshipTypeCanonicalModel,
11+
} from './template-models';
12+
13+
const interacts: CalmRelationshipTypeCanonicalModel = {
14+
interacts: { actor: 'user', nodes: ['svc-a'] },
15+
};
16+
const connects: CalmRelationshipTypeCanonicalModel = {
17+
connects: {
18+
source: { node: 'svc-a' },
19+
destination: { node: 'svc-b' },
20+
},
21+
};
22+
const composedOf: CalmRelationshipTypeCanonicalModel = {
23+
'composed-of': { container: 'svc-c', nodes: ['svc-a', 'svc-b'] },
24+
};
25+
const deployedIn: CalmRelationshipTypeCanonicalModel = {
26+
'deployed-in': { container: 'cluster-1', nodes: ['svc-a'] },
27+
};
28+
const options: CalmRelationshipTypeCanonicalModel = {
29+
options: [{ description: 'opt-1', nodes: ['svc-a'] }],
30+
};
31+
32+
describe('relationship type guards', () => {
33+
it('isInteracts narrows interacts and rejects others', () => {
34+
expect(isInteracts(interacts)).toBe(true);
35+
expect(isInteracts(connects)).toBe(false);
36+
});
37+
38+
it('isConnects narrows connects and rejects others', () => {
39+
expect(isConnects(connects)).toBe(true);
40+
expect(isConnects(interacts)).toBe(false);
41+
});
42+
43+
it('isComposedOf narrows composed-of and rejects others', () => {
44+
expect(isComposedOf(composedOf)).toBe(true);
45+
expect(isComposedOf(connects)).toBe(false);
46+
});
47+
48+
it('isDeployedIn narrows deployed-in and rejects others', () => {
49+
expect(isDeployedIn(deployedIn)).toBe(true);
50+
expect(isDeployedIn(connects)).toBe(false);
51+
});
52+
53+
it('isOptions narrows options and rejects others', () => {
54+
expect(isOptions(options)).toBe(true);
55+
expect(isOptions(connects)).toBe(false);
56+
});
57+
});
58+
59+
describe('visitRelationship', () => {
60+
it('dispatches to interacts handler', () => {
61+
const out = visitRelationship<string>(interacts, {
62+
interacts: (r) => `i:${r.interacts.actor}`,
63+
default: () => 'default',
64+
});
65+
expect(out).toBe('i:user');
66+
});
67+
68+
it('dispatches to connects handler', () => {
69+
const out = visitRelationship<string>(connects, {
70+
connects: (r) => `c:${r.connects.source.node}->${r.connects.destination.node}`,
71+
default: () => 'default',
72+
});
73+
expect(out).toBe('c:svc-a->svc-b');
74+
});
75+
76+
it('dispatches to composedOf handler', () => {
77+
const out = visitRelationship<string>(composedOf, {
78+
composedOf: (r) => `co:${r['composed-of'].container}`,
79+
default: () => 'default',
80+
});
81+
expect(out).toBe('co:svc-c');
82+
});
83+
84+
it('dispatches to deployedIn handler', () => {
85+
const out = visitRelationship<string>(deployedIn, {
86+
deployedIn: (r) => `d:${r['deployed-in'].container}`,
87+
default: () => 'default',
88+
});
89+
expect(out).toBe('d:cluster-1');
90+
});
91+
92+
it('dispatches to options handler', () => {
93+
const out = visitRelationship<string>(options, {
94+
options: (r) => `o:${r.options.length}`,
95+
default: () => 'default',
96+
});
97+
expect(out).toBe('o:1');
98+
});
99+
100+
it('falls through to default when no matching handler is provided', () => {
101+
const out = visitRelationship<string>(interacts, { default: () => 'fallback' });
102+
expect(out).toBe('fallback');
103+
});
104+
});
105+
106+
describe('toKindView', () => {
107+
it('maps interacts to kind view', () => {
108+
expect(toKindView(interacts)).toEqual({
109+
kind: 'interacts',
110+
actor: 'user',
111+
nodes: ['svc-a'],
112+
});
113+
});
114+
115+
it('maps connects to kind view', () => {
116+
expect(toKindView(connects)).toEqual({
117+
kind: 'connects',
118+
source: { node: 'svc-a' },
119+
destination: { node: 'svc-b' },
120+
});
121+
});
122+
123+
it('maps composed-of to kind view', () => {
124+
expect(toKindView(composedOf)).toEqual({
125+
kind: 'composed-of',
126+
container: 'svc-c',
127+
nodes: ['svc-a', 'svc-b'],
128+
});
129+
});
130+
131+
it('maps deployed-in to kind view', () => {
132+
expect(toKindView(deployedIn)).toEqual({
133+
kind: 'deployed-in',
134+
container: 'cluster-1',
135+
nodes: ['svc-a'],
136+
});
137+
});
138+
139+
it('maps options to kind view', () => {
140+
expect(toKindView(options)).toEqual({
141+
kind: 'options',
142+
options: [{ description: 'opt-1', nodes: ['svc-a'] }],
143+
});
144+
});
145+
146+
it('throws on an unrecognised relationship type', () => {
147+
const bogus = {} as CalmRelationshipTypeCanonicalModel;
148+
expect(() => toKindView(bogus)).toThrow('Unknown relationship type');
149+
});
150+
});

0 commit comments

Comments
 (0)