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:
StellaOps Bot
2025-12-06 16:28:12 +02:00
parent 2b892ad1b2
commit efd6850c38
132 changed files with 16675 additions and 5428 deletions

View File

@@ -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;
}

View 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;

View File

@@ -0,0 +1,6 @@
export default class MonacoDummyWorker {
postMessage(): void {}
addEventListener(): void {}
removeEventListener(): void {}
terminate(): void {}
}

View File

@@ -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"
]
}