Skip to content

Commit 4baee19

Browse files
feat: add tests
1 parent 743112f commit 4baee19

File tree

2 files changed

+352
-7
lines changed

2 files changed

+352
-7
lines changed
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { readFileSync, unlinkSync } from 'node:fs';
2+
import { exportScorecardResultsToJson } from '../formatters/json-formatter.js';
3+
import type { ScorecardProblem } from '../types.js';
4+
import type { ScorecardJsonOutput } from '../formatters/json-formatter.js';
5+
6+
const createMockSource = (absoluteRef: string) => ({
7+
absoluteRef,
8+
getAst: () => ({}),
9+
getRootAst: () => ({}),
10+
getLineColLocation: () => ({ line: 1, col: 1 }),
11+
});
12+
13+
describe('exportScorecardResultsToJson', () => {
14+
const testOutputPath = './test-scorecard-output.json';
15+
16+
afterEach(() => {
17+
try {
18+
unlinkSync(testOutputPath);
19+
} catch {
20+
// File might not exist
21+
}
22+
});
23+
24+
it('should export empty results when no problems', () => {
25+
exportScorecardResultsToJson([], testOutputPath);
26+
27+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
28+
29+
expect(output).toEqual({});
30+
});
31+
32+
it('should group problems by scorecard level', () => {
33+
const problems: ScorecardProblem[] = [
34+
{
35+
message: 'Error in Gold level',
36+
ruleId: 'test-rule-1',
37+
severity: 'error',
38+
suggest: [],
39+
location: [
40+
{
41+
source: createMockSource('/test/file.yaml') as any,
42+
pointer: '#/paths/~1test/get',
43+
reportOnKey: false,
44+
},
45+
],
46+
scorecardLevel: 'Gold',
47+
},
48+
{
49+
message: 'Warning in Gold level',
50+
ruleId: 'test-rule-2',
51+
severity: 'warn',
52+
suggest: [],
53+
location: [
54+
{
55+
source: createMockSource('/test/file.yaml') as any,
56+
pointer: '#/info',
57+
reportOnKey: false,
58+
},
59+
],
60+
scorecardLevel: 'Gold',
61+
},
62+
{
63+
message: 'Error in Silver level',
64+
ruleId: 'test-rule-3',
65+
severity: 'error',
66+
suggest: [],
67+
location: [],
68+
scorecardLevel: 'Silver',
69+
},
70+
];
71+
72+
exportScorecardResultsToJson(problems, testOutputPath);
73+
74+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
75+
76+
expect(Object.keys(output)).toEqual(['Gold', 'Silver']);
77+
expect(output.Gold.summary).toEqual({ errors: 1, warnings: 1 });
78+
expect(output.Gold.problems).toHaveLength(2);
79+
expect(output.Silver.summary).toEqual({ errors: 1, warnings: 0 });
80+
expect(output.Silver.problems).toHaveLength(1);
81+
});
82+
83+
it('should include rule URLs for non-namespaced rules', () => {
84+
const problems: ScorecardProblem[] = [
85+
{
86+
message: 'Test error',
87+
ruleId: 'operation-summary',
88+
severity: 'error',
89+
suggest: [],
90+
location: [],
91+
scorecardLevel: 'Gold',
92+
},
93+
];
94+
95+
exportScorecardResultsToJson(problems, testOutputPath);
96+
97+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
98+
99+
expect(output.Gold.problems[0].ruleUrl).toBe(
100+
'https://redocly.com/docs/cli/rules/oas/operation-summary.md'
101+
);
102+
});
103+
104+
it('should not include rule URLs for namespaced rules', () => {
105+
const problems: ScorecardProblem[] = [
106+
{
107+
message: 'Test error',
108+
ruleId: 'custom/my-rule',
109+
severity: 'error',
110+
suggest: [],
111+
location: [],
112+
scorecardLevel: 'Gold',
113+
},
114+
];
115+
116+
exportScorecardResultsToJson(problems, testOutputPath);
117+
118+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
119+
120+
expect(output.Gold.problems[0].ruleUrl).toBeUndefined();
121+
});
122+
123+
it('should format location with file path and range', () => {
124+
const problems: ScorecardProblem[] = [
125+
{
126+
message: 'Test error',
127+
ruleId: 'test-rule',
128+
severity: 'error',
129+
suggest: [],
130+
location: [
131+
{
132+
source: createMockSource('/test/file.yaml') as any,
133+
pointer: '#/paths/~1test/get',
134+
reportOnKey: false,
135+
},
136+
],
137+
scorecardLevel: 'Gold',
138+
},
139+
];
140+
141+
exportScorecardResultsToJson(problems, testOutputPath);
142+
143+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
144+
145+
expect(output.Gold.problems[0].location).toHaveLength(1);
146+
expect(output.Gold.problems[0].location[0].file).toBe('/test/file.yaml');
147+
expect(output.Gold.problems[0].location[0].pointer).toBe('#/paths/~1test/get');
148+
expect(output.Gold.problems[0].location[0].range).toContain('Line');
149+
});
150+
151+
it('should handle problems with Unknown level', () => {
152+
const problems: ScorecardProblem[] = [
153+
{
154+
message: 'Error without level',
155+
ruleId: 'test-rule',
156+
severity: 'error',
157+
suggest: [],
158+
location: [],
159+
scorecardLevel: undefined,
160+
},
161+
];
162+
163+
exportScorecardResultsToJson(problems, testOutputPath);
164+
165+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
166+
167+
expect(Object.keys(output)).toEqual(['Unknown']);
168+
expect(output.Unknown.problems).toHaveLength(1);
169+
});
170+
171+
it('should strip ANSI codes from messages', () => {
172+
const problems: ScorecardProblem[] = [
173+
{
174+
message: '\u001b[31mError message with color\u001b[0m',
175+
ruleId: 'test-rule',
176+
severity: 'error',
177+
suggest: [],
178+
location: [],
179+
scorecardLevel: 'Gold',
180+
},
181+
];
182+
183+
exportScorecardResultsToJson(problems, testOutputPath);
184+
185+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
186+
187+
expect(output.Gold.problems[0].message).toBe('Error message with color');
188+
expect(output.Gold.problems[0].message).not.toContain('\u001b');
189+
});
190+
191+
it('should count errors and warnings correctly', () => {
192+
const problems: ScorecardProblem[] = [
193+
{
194+
message: 'Error 1',
195+
ruleId: 'rule-1',
196+
severity: 'error',
197+
suggest: [],
198+
location: [],
199+
scorecardLevel: 'Gold',
200+
},
201+
{
202+
message: 'Error 2',
203+
ruleId: 'rule-2',
204+
severity: 'error',
205+
suggest: [],
206+
location: [],
207+
scorecardLevel: 'Gold',
208+
},
209+
{
210+
message: 'Warning 1',
211+
ruleId: 'rule-3',
212+
severity: 'warn',
213+
suggest: [],
214+
location: [],
215+
scorecardLevel: 'Gold',
216+
},
217+
{
218+
message: 'Warning 2',
219+
ruleId: 'rule-4',
220+
severity: 'warn',
221+
suggest: [],
222+
location: [],
223+
scorecardLevel: 'Gold',
224+
},
225+
{
226+
message: 'Warning 3',
227+
ruleId: 'rule-5',
228+
severity: 'warn',
229+
suggest: [],
230+
location: [],
231+
scorecardLevel: 'Gold',
232+
},
233+
];
234+
235+
exportScorecardResultsToJson(problems, testOutputPath);
236+
237+
const output = JSON.parse(readFileSync(testOutputPath, 'utf-8')) as ScorecardJsonOutput;
238+
239+
expect(output.Gold.summary.errors).toBe(2);
240+
expect(output.Gold.summary.warnings).toBe(3);
241+
});
242+
});
Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,139 @@
11
import { printScorecardResults } from '../formatters/stylish-formatter.js';
22
import * as openapiCore from '@redocly/openapi-core';
3+
import type { ScorecardProblem } from '../types.js';
4+
5+
const createMockSource = (absoluteRef: string) => ({
6+
absoluteRef,
7+
getAst: () => ({}),
8+
getRootAst: () => ({}),
9+
getLineColLocation: () => ({ line: 1, col: 1 }),
10+
});
311

412
describe('printScorecardResults', () => {
513
beforeEach(() => {
614
vi.spyOn(openapiCore.logger, 'info').mockImplementation(() => {});
15+
vi.spyOn(openapiCore.logger, 'output').mockImplementation(() => {});
716
});
817

918
afterEach(() => {
1019
vi.restoreAllMocks();
1120
});
1221

1322
it('should print success message when no problems', () => {
14-
printScorecardResults([], 'test.yaml');
23+
printScorecardResults([]);
24+
25+
expect(openapiCore.logger.info).toHaveBeenCalledWith(
26+
expect.stringMatching(/Found.*0.*error.*0.*warning.*0.*level/)
27+
);
28+
});
29+
30+
it('should handle problems without location', () => {
31+
const problems: ScorecardProblem[] = [
32+
{
33+
message: 'Error without location',
34+
ruleId: 'test-rule',
35+
severity: 'error',
36+
suggest: [],
37+
location: [],
38+
scorecardLevel: 'Gold',
39+
},
40+
];
41+
42+
printScorecardResults(problems);
1543

44+
expect(openapiCore.logger.output).toHaveBeenCalled();
1645
expect(openapiCore.logger.info).toHaveBeenCalledWith(
17-
expect.stringContaining('No issues found')
46+
expect.stringMatching(/Found.*1.*error.*0.*warning.*1.*level/)
1847
);
48+
expect(openapiCore.logger.info).toHaveBeenCalledWith(expect.stringContaining('📋 Gold'));
1949
});
2050

21-
it('should print results when problems exist', () => {
22-
const problems = [
51+
it('should handle problems with Unknown level', () => {
52+
const problems: ScorecardProblem[] = [
53+
{
54+
message: 'Error without level',
55+
ruleId: 'test-rule',
56+
severity: 'error',
57+
suggest: [],
58+
location: [],
59+
scorecardLevel: undefined,
60+
},
61+
];
62+
63+
printScorecardResults(problems);
64+
65+
expect(openapiCore.logger.info).toHaveBeenCalledWith(expect.stringContaining('Unknown'));
66+
});
67+
68+
it('should show correct severity counts per level', () => {
69+
const problems: ScorecardProblem[] = [
2370
{
2471
message: 'Error 1',
2572
ruleId: 'rule-1',
26-
severity: 'error' as const,
73+
severity: 'error',
74+
suggest: [],
2775
location: [],
2876
scorecardLevel: 'Gold',
2977
},
78+
{
79+
message: 'Error 2',
80+
ruleId: 'rule-2',
81+
severity: 'error',
82+
suggest: [],
83+
location: [],
84+
scorecardLevel: 'Gold',
85+
},
86+
{
87+
message: 'Warning 1',
88+
ruleId: 'rule-3',
89+
severity: 'warn',
90+
suggest: [],
91+
location: [],
92+
scorecardLevel: 'Gold',
93+
},
94+
];
95+
96+
printScorecardResults(problems);
97+
98+
expect(openapiCore.logger.info).toHaveBeenCalledWith(
99+
expect.stringContaining('2 errors, 1 warnings')
100+
);
101+
});
102+
103+
it('should calculate correct padding for alignment', () => {
104+
const problems: ScorecardProblem[] = [
105+
{
106+
message: 'Error 1',
107+
ruleId: 'short',
108+
severity: 'error',
109+
suggest: [],
110+
location: [
111+
{
112+
source: createMockSource('/test/file.yaml') as any,
113+
pointer: '#/paths/~1test/get',
114+
reportOnKey: false,
115+
},
116+
],
117+
scorecardLevel: 'Gold',
118+
},
119+
{
120+
message: 'Error 2',
121+
ruleId: 'very-long-rule-id-name',
122+
severity: 'error',
123+
suggest: [],
124+
location: [
125+
{
126+
source: createMockSource('/test/file.yaml') as any,
127+
pointer: '#/info',
128+
reportOnKey: false,
129+
},
130+
],
131+
scorecardLevel: 'Gold',
132+
},
30133
];
31134

32-
printScorecardResults(problems as any, 'test.yaml');
135+
printScorecardResults(problems);
33136

34-
expect(openapiCore.logger.info).toHaveBeenCalled();
137+
expect(openapiCore.logger.output).toHaveBeenCalledTimes(2);
35138
});
36139
});

0 commit comments

Comments
 (0)