Skip to content

Commit e0ce88c

Browse files
add test case
1 parent 8a493e6 commit e0ce88c

3 files changed

Lines changed: 337 additions & 0 deletions

File tree

packages/code-analyzer-core/test/code-analyzer.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,117 @@ describe("Tests for the run method of CodeAnalyzer", () => {
686686
expect(violations[0].getCodeLocations()[0].getEndColumn()).toBeUndefined();
687687
});
688688

689+
it("When an engine returns a fix with a code location file that does not exist, then an error is thrown", async () => {
690+
const badViolation: engApi.Violation = stubs.getSampleViolationWithFixes();
691+
badViolation.fixes![0].location.file = 'test/doesNotExist';
692+
stubEngine1.resultsToReturn = { violations: [badViolation] };
693+
await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow(
694+
getMessage('EngineReturnedViolationWithCodeLocationFileThatDoesNotExist',
695+
'stubEngine1', 'stub1RuleA', path.resolve('test', 'doesNotExist')));
696+
});
697+
698+
it("When an engine returns a fix with an invalid startLine, then an error is thrown", async () => {
699+
const badViolation: engApi.Violation = stubs.getSampleViolationWithFixes();
700+
badViolation.fixes![0].location.startLine = -1;
701+
stubEngine1.resultsToReturn = { violations: [badViolation] };
702+
await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow(
703+
getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn',
704+
'stubEngine1', 'stub1RuleA', 'startLine', -1));
705+
});
706+
707+
it("When an engine returns a fix with endLine before startLine, then an error is thrown", async () => {
708+
const badViolation: engApi.Violation = stubs.getSampleViolationWithFixes();
709+
badViolation.fixes![0].location.startLine = 10;
710+
badViolation.fixes![0].location.endLine = 5;
711+
stubEngine1.resultsToReturn = { violations: [badViolation] };
712+
await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow(
713+
getMessage('EngineReturnedViolationWithCodeLocationWithEndLineBeforeStartLine',
714+
'stubEngine1', 'stub1RuleA', 5, 10));
715+
});
716+
717+
it("When an engine returns a suggestion with a code location file that does not exist, then an error is thrown", async () => {
718+
const badViolation: engApi.Violation = stubs.getSampleViolationWithSuggestions();
719+
badViolation.suggestions![0].location.file = 'test/doesNotExist';
720+
stubEngine1.resultsToReturn = { violations: [badViolation] };
721+
await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow(
722+
getMessage('EngineReturnedViolationWithCodeLocationFileThatDoesNotExist',
723+
'stubEngine1', 'stub1RuleA', path.resolve('test', 'doesNotExist')));
724+
});
725+
726+
it("When an engine returns a suggestion with an invalid startColumn, then an error is thrown", async () => {
727+
const badViolation: engApi.Violation = stubs.getSampleViolationWithSuggestions();
728+
badViolation.suggestions![0].location.startColumn = 0;
729+
stubEngine1.resultsToReturn = { violations: [badViolation] };
730+
await expect(codeAnalyzer.run(selection, sampleRunOptions)).rejects.toThrow(
731+
getMessage('EngineReturnedViolationWithCodeLocationWithInvalidLineOrColumn',
732+
'stubEngine1', 'stub1RuleA', 'startColumn', 0));
733+
});
734+
735+
it("When an engine returns a violation with fixes, then getFixes returns the correct data", async () => {
736+
stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationWithFixes()] };
737+
const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions);
738+
const violations: Violation[] = overallResults.getViolations();
739+
expect(violations).toHaveLength(1);
740+
741+
const fixes = violations[0].getFixes();
742+
expect(fixes).toHaveLength(1);
743+
expect(fixes[0].getFixedCode()).toEqual('const correctedValue = true;');
744+
expect(fixes[0].getLocation().getFile()).toEqual(path.resolve('test/config.test.ts'));
745+
expect(fixes[0].getLocation().getStartLine()).toEqual(3);
746+
expect(fixes[0].getLocation().getStartColumn()).toEqual(6);
747+
expect(fixes[0].getLocation().getEndLine()).toEqual(3);
748+
expect(fixes[0].getLocation().getEndColumn()).toEqual(20);
749+
});
750+
751+
it("When an engine returns a violation with suggestions, then getSuggestions returns the correct data", async () => {
752+
stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationWithSuggestions()] };
753+
const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions);
754+
const violations: Violation[] = overallResults.getViolations();
755+
expect(violations).toHaveLength(1);
756+
757+
const suggestions = violations[0].getSuggestions();
758+
expect(suggestions).toHaveLength(2);
759+
expect(suggestions[0].getMessage()).toEqual('Consider using a boolean literal instead');
760+
expect(suggestions[0].getLocation().getFile()).toEqual(path.resolve('test/config.test.ts'));
761+
expect(suggestions[1].getMessage()).toEqual('Consider removing this unused variable');
762+
});
763+
764+
it("When an engine returns a violation with both fixes and suggestions, then both are accessible", async () => {
765+
stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationWithFixesAndSuggestions()] };
766+
const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions);
767+
const violations: Violation[] = overallResults.getViolations();
768+
expect(violations).toHaveLength(1);
769+
expect(violations[0].getFixes()).toHaveLength(2);
770+
expect(violations[0].getSuggestions()).toHaveLength(1);
771+
});
772+
773+
it("When an engine returns a violation without fixes or suggestions, then getFixes and getSuggestions return empty arrays", async () => {
774+
stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationForStub1RuleA()] };
775+
const overallResults: RunResults = await codeAnalyzer.run(selection, sampleRunOptions);
776+
const violations: Violation[] = overallResults.getViolations();
777+
expect(violations).toHaveLength(1);
778+
expect(violations[0].getFixes()).toEqual([]);
779+
expect(violations[0].getSuggestions()).toEqual([]);
780+
});
781+
782+
it("When includeFixes and includeSuggestions are specified in RunOptions, they are forwarded to the engine", async () => {
783+
stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationForStub1RuleA()] };
784+
await codeAnalyzer.run(selection, {
785+
...sampleRunOptions,
786+
includeFixes: true,
787+
includeSuggestions: true
788+
});
789+
expect(stubEngine1.runRulesCallHistory[0].runOptions.includeFixes).toBe(true);
790+
expect(stubEngine1.runRulesCallHistory[0].runOptions.includeSuggestions).toBe(true);
791+
});
792+
793+
it("When includeFixes and includeSuggestions are not specified in RunOptions, they are undefined in the engine run options", async () => {
794+
stubEngine1.resultsToReturn = { violations: [stubs.getSampleViolationForStub1RuleA()] };
795+
await codeAnalyzer.run(selection, sampleRunOptions);
796+
expect(stubEngine1.runRulesCallHistory[0].runOptions.includeFixes).toBeUndefined();
797+
expect(stubEngine1.runRulesCallHistory[0].runOptions.includeSuggestions).toBeUndefined();
798+
});
799+
689800
it("When an engine throws an exception when running, then a result is returned with a Critical violation of type UnexpectedError", async () => {
690801
codeAnalyzer = createCodeAnalyzer();
691802
await codeAnalyzer.addEnginePlugin(new stubs.ThrowingEnginePlugin());

packages/code-analyzer-core/test/output-format.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,110 @@ describe("RunResultsFormatter Tests", () => {
174174
});
175175
});
176176

177+
describe("Output format tests for fixes and suggestions", () => {
178+
let resultsWithFixes: RunResults;
179+
180+
beforeAll(async () => {
181+
const codeAnalyzer: CodeAnalyzer = new CodeAnalyzer(CodeAnalyzerConfig.withDefaults());
182+
codeAnalyzer._setClock(new FixedClock(new Date(2024, 6, 3, 9, 14, 34, 567)));
183+
const stubPlugin: stubs.StubEnginePlugin = new stubs.StubEnginePlugin();
184+
await codeAnalyzer.addEnginePlugin(stubPlugin);
185+
(stubPlugin.getCreatedEngine('stubEngine1') as stubs.StubEngine1).resultsToReturn = {
186+
violations: [
187+
stubs.getSampleViolationWithFixes(),
188+
stubs.getSampleViolationWithSuggestions(),
189+
stubs.getSampleViolationWithFixesAndSuggestions()
190+
]
191+
};
192+
const selection = await codeAnalyzer.selectRules(['all']);
193+
resultsWithFixes = await codeAnalyzer.run(selection, {workspace: await codeAnalyzer.createWorkspace(['test'])});
194+
});
195+
196+
describe("JSON output format with fixes and suggestions", () => {
197+
it("Violations with fixes include fixes array in JSON output", () => {
198+
const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON));
199+
const violationsWithFixes = json.violations.filter((v: {fixes?: unknown[]}) => v.fixes && v.fixes.length > 0);
200+
expect(violationsWithFixes.length).toBeGreaterThanOrEqual(1);
201+
202+
const fix = violationsWithFixes[0].fixes[0];
203+
expect(fix).toHaveProperty('location');
204+
expect(fix).toHaveProperty('fixedCode');
205+
expect(fix.location).toHaveProperty('file');
206+
expect(fix.location).toHaveProperty('startLine');
207+
expect(fix.location).toHaveProperty('startColumn');
208+
});
209+
210+
it("Violations with suggestions include suggestions array in JSON output", () => {
211+
const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON));
212+
const violationsWithSuggestions = json.violations.filter((v: {suggestions?: unknown[]}) => v.suggestions && v.suggestions.length > 0);
213+
expect(violationsWithSuggestions.length).toBeGreaterThanOrEqual(1);
214+
215+
const suggestion = violationsWithSuggestions[0].suggestions[0];
216+
expect(suggestion).toHaveProperty('location');
217+
expect(suggestion).toHaveProperty('message');
218+
});
219+
220+
it("Violations without fixes do not have a fixes key in JSON output", () => {
221+
const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON));
222+
const violationsWithoutFixes = json.violations.filter((v: {fixes?: unknown[]}) => !v.fixes);
223+
expect(violationsWithoutFixes.length).toBeGreaterThanOrEqual(1);
224+
expect(violationsWithoutFixes[0]).not.toHaveProperty('fixes');
225+
});
226+
227+
it("Fix location file paths are relative to runDir in JSON output", () => {
228+
const json = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.JSON));
229+
const violationWithFix = json.violations.find((v: {fixes?: unknown[]}) => v.fixes && v.fixes.length > 0);
230+
const fixFile = violationWithFix.fixes[0].location.file;
231+
expect(fixFile).not.toContain(json.runDir);
232+
expect(path.isAbsolute(fixFile)).toBe(false);
233+
});
234+
});
235+
236+
describe("XML output format with fixes and suggestions", () => {
237+
it("Violations with fixes include fix nodes in XML output", () => {
238+
const xml = resultsWithFixes.toFormattedOutput(OutputFormat.XML);
239+
expect(xml).toContain('<fixes>');
240+
expect(xml).toContain('<fix>');
241+
expect(xml).toContain('<fixedCode>');
242+
});
243+
244+
it("Violations with suggestions include suggestion nodes in XML output", () => {
245+
const xml = resultsWithFixes.toFormattedOutput(OutputFormat.XML);
246+
expect(xml).toContain('<suggestions>');
247+
expect(xml).toContain('<suggestion>');
248+
expect(xml).toContain('<message>');
249+
});
250+
});
251+
252+
describe("SARIF output format with fixes", () => {
253+
it("Violations with fixes include fix data in SARIF output", () => {
254+
const sarif = JSON.parse(resultsWithFixes.toFormattedOutput(OutputFormat.SARIF));
255+
const allResults = sarif.runs.flatMap((run: {results: unknown[]}) => run.results);
256+
const resultsWithFixData = allResults.filter((r: {fixes?: unknown[]}) => r.fixes && r.fixes.length > 0);
257+
expect(resultsWithFixData.length).toBeGreaterThanOrEqual(1);
258+
259+
const sarifFix = resultsWithFixData[0].fixes[0];
260+
expect(sarifFix).toHaveProperty('artifactChanges');
261+
expect(sarifFix.artifactChanges[0]).toHaveProperty('replacements');
262+
expect(sarifFix.artifactChanges[0].replacements[0]).toHaveProperty('deletedRegion');
263+
expect(sarifFix.artifactChanges[0].replacements[0]).toHaveProperty('insertedContent');
264+
});
265+
266+
it("Suggestions are not included in SARIF output", () => {
267+
const sarifStr = resultsWithFixes.toFormattedOutput(OutputFormat.SARIF);
268+
expect(sarifStr).not.toContain('"suggestions"');
269+
});
270+
});
271+
272+
describe("CSV output format with fixes and suggestions", () => {
273+
it("Fixes and suggestions are not included in CSV output", () => {
274+
const csv = resultsWithFixes.toFormattedOutput(OutputFormat.CSV);
275+
expect(csv).not.toContain('fixedCode');
276+
expect(csv).not.toContain('const correctedValue');
277+
});
278+
});
279+
});
280+
177281
describe("RuleSelectionFormatter Tests", () => {
178282

179283
describe("Tests for the JSON output format", () => {

packages/code-analyzer-core/test/stubs.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,128 @@ export function getSampleViolationForStub3RuleA(): engApi.Violation {
433433
}
434434
}
435435

436+
export function getSampleViolationWithFixes(): engApi.Violation {
437+
return {
438+
ruleName: 'stub1RuleA',
439+
message: 'SomeViolationWithFixes',
440+
codeLocations: [
441+
{
442+
file: 'test/config.test.ts',
443+
startLine: 3,
444+
startColumn: 6,
445+
endLine: 3,
446+
endColumn: 20
447+
}
448+
],
449+
primaryLocationIndex: 0,
450+
fixes: [
451+
{
452+
location: {
453+
file: 'test/config.test.ts',
454+
startLine: 3,
455+
startColumn: 6,
456+
endLine: 3,
457+
endColumn: 20
458+
},
459+
fixedCode: 'const correctedValue = true;'
460+
}
461+
]
462+
};
463+
}
464+
465+
export function getSampleViolationWithSuggestions(): engApi.Violation {
466+
return {
467+
ruleName: 'stub1RuleA',
468+
message: 'SomeViolationWithSuggestions',
469+
codeLocations: [
470+
{
471+
file: 'test/config.test.ts',
472+
startLine: 5,
473+
startColumn: 1,
474+
endLine: 5,
475+
endColumn: 10
476+
}
477+
],
478+
primaryLocationIndex: 0,
479+
suggestions: [
480+
{
481+
location: {
482+
file: 'test/config.test.ts',
483+
startLine: 5,
484+
startColumn: 1,
485+
endLine: 5,
486+
endColumn: 10
487+
},
488+
message: 'Consider using a boolean literal instead'
489+
},
490+
{
491+
location: {
492+
file: 'test/config.test.ts',
493+
startLine: 5,
494+
startColumn: 1,
495+
endLine: 5,
496+
endColumn: 10
497+
},
498+
message: 'Consider removing this unused variable'
499+
}
500+
]
501+
};
502+
}
503+
504+
export function getSampleViolationWithFixesAndSuggestions(): engApi.Violation {
505+
return {
506+
ruleName: 'stub1RuleC',
507+
message: 'SomeViolationWithBoth',
508+
codeLocations: [
509+
{
510+
file: 'test/code-analyzer.test.ts',
511+
startLine: 21,
512+
startColumn: 7,
513+
endLine: 25,
514+
endColumn: 4
515+
}
516+
],
517+
primaryLocationIndex: 0,
518+
fixes: [
519+
{
520+
location: {
521+
file: 'test/code-analyzer.test.ts',
522+
startLine: 21,
523+
startColumn: 7,
524+
endLine: 21,
525+
endColumn: 15
526+
},
527+
fixedCode: 'const x = 1;'
528+
},
529+
{
530+
location: {
531+
file: 'test/code-analyzer.test.ts',
532+
startLine: 23,
533+
startColumn: 1,
534+
endLine: 23,
535+
endColumn: 10
536+
},
537+
fixedCode: 'let y = 2;'
538+
}
539+
],
540+
suggestions: [
541+
{
542+
location: {
543+
file: 'test/code-analyzer.test.ts',
544+
startLine: 21,
545+
startColumn: 7,
546+
endLine: 25,
547+
endColumn: 4
548+
},
549+
message: 'Refactor this block to use modern syntax'
550+
}
551+
],
552+
resourceUrls: [
553+
"https://example.com/aViolationSpecificUrl1",
554+
]
555+
};
556+
}
557+
436558
/**
437559
* EmptyTagEnginePlugin - A plugin to help with testing rules with empty tags
438560
*/

0 commit comments

Comments
 (0)