Add unit tests for VexLens normalizer, CPE parser, product mapper, and PURL parser
- Implemented comprehensive tests for VexLensNormalizer including format detection and normalization scenarios. - Added tests for CpeParser covering CPE 2.3 and 2.2 formats, invalid inputs, and canonical key generation. - Created tests for ProductMapper to validate parsing and matching logic across different strictness levels. - Developed tests for PurlParser to ensure correct parsing of various PURL formats and validation of identifiers. - Introduced stubs for Monaco editor and worker to facilitate testing in the web application. - Updated project file for the test project to include necessary dependencies.
This commit is contained in:
@@ -1,14 +1,45 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, convertToParamMap } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import type * as Monaco from 'monaco-editor';
|
||||
|
||||
import { PolicyEditorComponent } from './policy-editor.component';
|
||||
import { PolicyApiService } from '../services/policy-api.service';
|
||||
import { MonacoLoaderService } from './monaco-loader.service';
|
||||
|
||||
// Hard mock Monaco for tests to avoid worker/CSS loading
|
||||
class MonacoLoaderStub {
|
||||
model = {
|
||||
getValue: () => this.value,
|
||||
setValue: (v: string) => (this.value = v),
|
||||
} as any;
|
||||
editor = {
|
||||
onDidChangeModelContent: () => ({ dispose: () => undefined }),
|
||||
} as any;
|
||||
lastMarkers: any[] = [];
|
||||
private value = '';
|
||||
|
||||
load = jasmine.createSpy('load').and.resolveTo({
|
||||
editor: {
|
||||
createModel: (v: string) => {
|
||||
this.value = v;
|
||||
return this.model;
|
||||
},
|
||||
create: () => this.editor,
|
||||
setModelMarkers: (_m: any, _o: string, markers: any[]) => {
|
||||
this.lastMarkers = markers;
|
||||
},
|
||||
setTheme: () => undefined,
|
||||
},
|
||||
languages: {
|
||||
register: () => undefined,
|
||||
setMonarchTokensProvider: () => undefined,
|
||||
setLanguageConfiguration: () => undefined,
|
||||
},
|
||||
MarkerSeverity: { Error: 8, Warning: 4, Info: 2 },
|
||||
});
|
||||
}
|
||||
|
||||
describe('PolicyEditorComponent', () => {
|
||||
let fixture: ComponentFixture<PolicyEditorComponent>;
|
||||
let component: PolicyEditorComponent;
|
||||
@@ -23,24 +54,13 @@ describe('PolicyEditorComponent', () => {
|
||||
of({
|
||||
id: 'pack-1',
|
||||
name: 'Demo Policy',
|
||||
description: 'Example policy for tests',
|
||||
syntax: 'stella-dsl@1',
|
||||
content: 'package "demo" { allow = true }',
|
||||
version: '1.0.0',
|
||||
status: 'draft',
|
||||
metadata: { author: 'tester', tags: ['demo'] },
|
||||
createdAt: '2025-12-01T00:00:00Z',
|
||||
modifiedAt: '2025-12-02T00:00:00Z',
|
||||
createdBy: 'tester',
|
||||
modifiedBy: 'tester',
|
||||
tags: ['demo', 'lint'],
|
||||
digest: 'sha256:abc',
|
||||
})
|
||||
);
|
||||
|
||||
policyApi.lint.and.returnValue(
|
||||
of({ valid: true, errors: [], warnings: [], info: [] }) as any
|
||||
);
|
||||
policyApi.lint.and.returnValue(of({ valid: true, errors: [], warnings: [], info: [] }) as any);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CommonModule, PolicyEditorComponent],
|
||||
@@ -65,7 +85,7 @@ describe('PolicyEditorComponent', () => {
|
||||
});
|
||||
|
||||
it('loads pack content into the editor model', () => {
|
||||
expect(monacoLoader.model?.getValue()).toContain('package "demo"');
|
||||
expect(monacoLoader.model.getValue()).toContain('package "demo"');
|
||||
});
|
||||
|
||||
it('applies lint diagnostics as Monaco markers', () => {
|
||||
@@ -93,78 +113,3 @@ describe('PolicyEditorComponent', () => {
|
||||
expect(monacoLoader.lastMarkers[0].message).toContain('Missing rule header');
|
||||
});
|
||||
});
|
||||
|
||||
class MonacoLoaderStub {
|
||||
model: FakeModel = new FakeModel('');
|
||||
editor: FakeEditor = new FakeEditor(this.model);
|
||||
lastMarkers: Monaco.editor.IMarkerData[] = [];
|
||||
|
||||
load = jasmine.createSpy('load').and.callFake(async () => {
|
||||
return mockMonaco(this);
|
||||
});
|
||||
}
|
||||
|
||||
class FakeModel {
|
||||
private value: string;
|
||||
|
||||
constructor(initial: string) {
|
||||
this.value = initial;
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
setValue(v: string): void {
|
||||
this.value = v;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
/* noop */
|
||||
}
|
||||
}
|
||||
|
||||
class FakeEditor {
|
||||
private listeners: Array<() => void> = [];
|
||||
|
||||
constructor(private readonly model: FakeModel) {}
|
||||
|
||||
onDidChangeModelContent(cb: () => void): { dispose: () => void } {
|
||||
this.listeners.push(cb);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.listeners = this.listeners.filter((l) => l !== cb);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getModel(): FakeModel {
|
||||
return this.model;
|
||||
}
|
||||
}
|
||||
|
||||
type MonacoNamespace = typeof import('monaco-editor');
|
||||
|
||||
function mockMonaco(loader: MonacoLoaderStub): MonacoNamespace {
|
||||
const severity = { Error: 8, Warning: 4, Info: 2 };
|
||||
return {
|
||||
editor: {
|
||||
createModel: (value: string) => {
|
||||
loader.model = new FakeModel(value);
|
||||
loader.editor = new FakeEditor(loader.model);
|
||||
return loader.model as unknown as Monaco.editor.ITextModel;
|
||||
},
|
||||
create: () => loader.editor as unknown as Monaco.editor.IStandaloneCodeEditor,
|
||||
setModelMarkers: (_model: Monaco.editor.ITextModel, _owner: string, markers: Monaco.editor.IMarkerData[]) => {
|
||||
loader.lastMarkers = markers;
|
||||
},
|
||||
setTheme: () => undefined,
|
||||
},
|
||||
languages: {
|
||||
register: () => undefined,
|
||||
setMonarchTokensProvider: () => undefined,
|
||||
setLanguageConfiguration: () => undefined,
|
||||
},
|
||||
MarkerSeverity: severity as unknown as Monaco.editor.IMarkerSeverity,
|
||||
} as unknown as MonacoNamespace;
|
||||
}
|
||||
|
||||
19
src/Web/StellaOps.Web/src/app/testing/monaco-stub.ts
Normal file
19
src/Web/StellaOps.Web/src/app/testing/monaco-stub.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const editor = {
|
||||
createModel: (_v?: string) => ({}) as any,
|
||||
setModelMarkers: (_m: any, _o: string, _markers: any[]) => undefined,
|
||||
setTheme: (_t: string) => undefined,
|
||||
};
|
||||
|
||||
export const languages = {
|
||||
register: () => undefined,
|
||||
setMonarchTokensProvider: () => undefined,
|
||||
setLanguageConfiguration: () => undefined,
|
||||
};
|
||||
|
||||
export const MarkerSeverity = {
|
||||
Error: 8,
|
||||
Warning: 4,
|
||||
Info: 2,
|
||||
};
|
||||
|
||||
export default { editor, languages, MarkerSeverity } as any;
|
||||
@@ -0,0 +1,6 @@
|
||||
export default class MonacoDummyWorker {
|
||||
postMessage(): void {}
|
||||
addEventListener(): void {}
|
||||
removeEventListener(): void {}
|
||||
terminate(): void {}
|
||||
}
|
||||
@@ -1,14 +1,22 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
],
|
||||
"paths": {
|
||||
"monaco-editor/esm/vs/editor/editor.api": ["src/app/testing/monaco-stub"],
|
||||
"monaco-editor/esm/vs/editor/editor.worker": ["src/app/testing/monaco-worker-stub"],
|
||||
"monaco-editor/esm/vs/language/json/json.worker": ["src/app/testing/monaco-worker-stub"],
|
||||
"monaco-editor/esm/vs/language/css/css.worker": ["src/app/testing/monaco-worker-stub"],
|
||||
"monaco-editor/esm/vs/language/html/html.worker": ["src/app/testing/monaco-worker-stub"],
|
||||
"monaco-editor/esm/vs/language/typescript/ts.worker": ["src/app/testing/monaco-worker-stub"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user