Skip to content

Commit

Permalink
more tests for viewodel and config services
Browse files Browse the repository at this point in the history
  • Loading branch information
adpare committed Jan 25, 2024
1 parent 170f10a commit 77387a9
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 56 deletions.
118 changes: 71 additions & 47 deletions nav-app/src/app/services/config.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ConfigService } from './config.service';
import { DataService } from './data.service';
import { of } from 'rxjs';
import { Subscription, of } from 'rxjs';

describe('ConfigService', () => {
let configVersions: any[] = [{
Expand All @@ -14,64 +14,74 @@ describe('ConfigService', () => {
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v13.1/enterprise-attack/enterprise-attack.json"]
}]
}];
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ConfigService],
});
});

beforeEach(inject([ConfigService], (service: ConfigService) => {
let config = {
"banner":"",
"comment_color":"yellow",
"custom_context_menu_items": [
let versions = [
{
"name": "ATT&CK v13",
"version": "13",
"domains": [
{
"label": "view technique on ATT&CK website",
"url": "https://attack.mitre.org/techniques/{{technique_attackID}}",
"subtechnique_url": "https://attack.mitre.org/techniques/{{parent_technique_attackID}}/{{subtechnique_attackID_suffix}}"
"name": "Enterprise",
"identifier": "enterprise-attack",
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v13.1/enterprise-attack/enterprise-attack.json"]
}
],
"features": [{"name": "leave_site_dialog", "enabled": true, "description": "Disable to remove the dialog prompt when leaving site."}],
"link_color": "blue",
"metadata_color":"purple",
"versions": [{"name": 'ATT&CK v13', "version": '13', "domains": ["Enterprise"]}]
}
let versions = [
{
"name": "ATT&CK v13",
"version": "13",
"domains": [
{
"name": "Enterprise",
"identifier": "enterprise-attack",
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v13.1/enterprise-attack/enterprise-attack.json"]
}
]
},
]
},
{
"name": "ATT&CK v12",
"version": "12",
"domains": [
{
"name": "ATT&CK v12",
"version": "12",
"domains": [
{
"name": "Enterprise",
"identifier": "enterprise-attack",
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v12.1/enterprise-attack/enterprise-attack.json"]
}
]
"name": "Enterprise",
"identifier": "enterprise-attack",
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v12.1/enterprise-attack/enterprise-attack.json"]
}
]
service.dataService.setUpURLs(versions);
let return$ = {versions: configVersions};
spyOn(DataService.prototype, 'getConfig').and.returnValue(of(config));
let cs = new ConfigService(service.dataService); // do not delete
}
]

let config = {
"banner":"",
"comment_color":"yellow",
"custom_context_menu_items": [
{
"label": "view technique on ATT&CK website",
"url": "https://attack.mitre.org/techniques/{{technique_attackID}}",
"subtechnique_url": "https://attack.mitre.org/techniques/{{parent_technique_attackID}}/{{subtechnique_attackID_suffix}}"
}
],
"features": [
{"name": "leave_site_dialog", "enabled": true, "description": "Disable to remove the dialog prompt when leaving site."},
{"name": "comments", "enabled": true, "description": "Disable to remove the ability to add comments to techniques."},
],
"link_color": "blue",
"metadata_color":"purple",
"versions": [{"name": 'ATT&CK v13', "version": '13', "domains": ["Enterprise"]}]
}

}));
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ConfigService],
});
});

it('should be created', inject([ConfigService], (service: ConfigService) => {
expect(service).toBeTruthy();
}));

it('should set up data in constructor', inject([ConfigService], (service: ConfigService) => {
service.dataService.setUpURLs(versions);
spyOn(DataService.prototype, 'getConfig').and.returnValue(of(config));
let fragments = new Map<string, string>();
fragments.set("comments", "false"); //'https://mitre-attack.github.io/attack-navigator/#comments=false'
spyOn(ConfigService.prototype, "getAllFragments").and.returnValue(fragments);
ConfigService.prototype.subscription = new Subscription;
const unsubscribeSpy = spyOn(ConfigService.prototype.subscription, 'unsubscribe');
let cs = new ConfigService(service.dataService);
expect(unsubscribeSpy).toHaveBeenCalled();
}));

it('should set feature object', inject([ConfigService], (service: ConfigService) => {
let feature_object = {"name": "technique_controls", "enabled": true, "description": "Disable to disable all subfeatures", "subfeatures": [
{"name": "disable_techniques", "enabled": true, "description": "Disable to remove the ability to disable techniques."},
Expand All @@ -91,6 +101,7 @@ describe('ConfigService', () => {
}));

it('should get feature group', inject([ConfigService], (service: ConfigService) => {
expect(service.getFeatureGroup("technique_controls")).toBeTruthy();
let feature_object = {"name": "technique_controls", "enabled": true, "description": "Disable to disable all subfeatures", "subfeatures": [
{"name": "disable_techniques", "enabled": true, "description": "Disable to remove the ability to disable techniques."},
{"name": "manual_color", "enabled": true, "description": "Disable to remove the ability to assign manual colors to techniques."},
Expand All @@ -100,6 +111,17 @@ describe('ConfigService', () => {
expect(service.getFeatureGroup("technique_controls")).toBeTruthy();
}));

it('should get feature group count', inject([ConfigService], (service: ConfigService) => {
expect(service.getFeatureGroupCount("technique_controls")).toEqual(-1);
let feature_object = {"name": "technique_controls", "enabled": true, "description": "Disable to disable all subfeatures", "subfeatures": [
{"name": "disable_techniques", "enabled": true, "description": "Disable to remove the ability to disable techniques."},
{"name": "manual_color", "enabled": true, "description": "Disable to remove the ability to assign manual colors to techniques."},
{"name": "background_color", "enabled": true, "description": "Disable to remove the background color effect on manually assigned colors."}
]}
service.setFeature_object(feature_object)
expect(service.getFeatureGroupCount("technique_controls")).toEqual(3);
}));

it('should get all features', inject([ConfigService], (service: ConfigService) => {
let feature_group_one = {"name": "technique_controls", "enabled": true, "description": "Disable to disable all subfeatures", "subfeatures": [
{"name": "disable_techniques", "enabled": true, "description": "Disable to remove the ability to disable techniques."},
Expand Down Expand Up @@ -149,6 +171,7 @@ describe('ConfigService', () => {
}));

it('should get feature list', inject([ConfigService], (service: ConfigService) => {
expect(service.getFeatureList()).toEqual([]);
service.featureStructure = [
{"name": "sticky_toolbar", "enabled": true}
]
Expand All @@ -172,6 +195,7 @@ describe('ConfigService', () => {

it('should get all url fragments', inject([ConfigService], (service: ConfigService) => {
let fragments = new Map<string, string>();
expect(service.getAllFragments()).toEqual(fragments);
fragments.set("comments","false")
expect(service.getAllFragments('https://mitre-attack.github.io/attack-navigator/#comments=false')).toEqual(fragments);
}));
Expand Down
2 changes: 1 addition & 1 deletion nav-app/src/app/services/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class ConfigService {
//override json preferences with preferences from URL fragment
self.getAllFragments().forEach(function (value: string, key: string) {
let valueBool = value == 'true';
if (self.isFeature(key) || self.isFeatureGroup(key)) {
if (self.isFeatureGroup(key) || self.isFeature(key)) {
self.setFeature(key, valueBool);
}
});
Expand Down
7 changes: 6 additions & 1 deletion nav-app/src/app/services/data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
import { DataService } from './data.service';
import { Version, VersionChangelog } from '../classes';
import { Asset, Campaign, DataComponent, Domain, Group, Matrix, Mitigation, Note, Software, Tactic, Technique } from '../classes/stix';
import { of } from 'rxjs';
import { Observable, Subscription, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Collection } from '../utils/taxii2lib';

Expand Down Expand Up @@ -381,9 +381,12 @@ describe('DataService', () => {
let return$ = {versions: configData};
let functionSpy = spyOn(DataService.prototype, 'setUpURLs').and.stub();
spyOn(DataService.prototype, 'getConfig').and.returnValue(of(return$));
DataService.prototype.subscription = new Subscription;
const unsubscribeSpy = spyOn(DataService.prototype.subscription, 'unsubscribe');
const mockService = new DataService(http);
expect(mockService).toBeTruthy();
expect(functionSpy).toHaveBeenCalledOnceWith(return$.versions)
expect(unsubscribeSpy).toHaveBeenCalled();
}));

it('should set up data', inject([DataService], (service: DataService) => {
Expand Down Expand Up @@ -495,6 +498,8 @@ describe('DataService', () => {
service.setUpURLs(configData);
let domain = service.domains[0];
let functionSpy = spyOn(service, 'getDomainData').and.returnValue(of(stixBundles));
DataService.prototype.subscription = new Subscription;
spyOn(DataService.prototype.subscription, 'unsubscribe');
await expectAsync(service.loadDomainData(domain.id, false)).toBeResolved();
expect(functionSpy).toHaveBeenCalledOnceWith(domain, false);
}));
Expand Down
12 changes: 6 additions & 6 deletions nav-app/src/app/services/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ import { Version, VersionChangelog } from '../classes';
providedIn: 'root',
})
export class DataService {
public subscription;
constructor(private http: HttpClient) {
let subscription;
//let subscription;
console.debug('initializing data service');
subscription = this.getConfig().subscribe({
this.subscription = this.getConfig().subscribe({
next: (config) => {
this.setUpURLs(config['versions']);
},
complete: () => {
if (subscription) subscription.unsubscribe();
if (this.subscription) this.subscription.unsubscribe();
}, //prevent memory leaks
});
}
Expand Down Expand Up @@ -330,16 +331,15 @@ export class DataService {
loadDomainData(domainVersionID: string, refresh: boolean = false): Promise<any> {
let dataPromise: Promise<any> = new Promise((resolve, reject) => {
let domain = this.getDomain(domainVersionID);
let subscription;
if (domain) {
if (domain.dataLoaded && !refresh) resolve(null);
subscription = this.getDomainData(domain, refresh).subscribe({
this.subscription = this.getDomainData(domain, refresh).subscribe({
next: (data: Object[]) => {
this.parseBundle(domain, data);
resolve(null);
},
complete: () => {
if (subscription) subscription.unsubscribe();
if (this.subscription) this.subscription.unsubscribe();
}, //prevent memory leaks
});
} else if (!domain) {
Expand Down
29 changes: 29 additions & 0 deletions nav-app/src/app/services/viewmodels.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ describe('ViewmodelsService', () => {

it('should get common value from selected techniques', inject([ViewModelsService], (service: ViewModelsService) => {
let vm1 = service.newViewModel("test1","enterprise-attack-13");
let technique_list: Technique[] = [];
let t1 = new Technique(techniqueSDO,[],null);
let t2 = new Technique(techniqueSDO2,[],null);
technique_list.push(t1);
technique_list.push(t2);
let tactic1 = new Tactic(tacticSDO,technique_list,null);
let tvm_1 = new TechniqueVM("T1595^reconnaissance");
tvm_1.score = '3';
let tvm_2 = new TechniqueVM("T1592^reconnaissance");
Expand All @@ -301,6 +307,8 @@ describe('ViewmodelsService', () => {
vm1.selectAllTechniques();
expect(vm1.getEditingCommonValue("score")).toEqual('3'); // common score value of 3
expect(vm1.getEditingCommonValue("comment")).toEqual(''); // no common value for comment
vm1.unselectAllTechniquesInTactic(tactic1);
expect(vm1.getEditingCommonValue("score")).toEqual('');
}));

it('should select annotated techniques and unannotated techniques', inject([ViewModelsService], (service: ViewModelsService) => {
Expand Down Expand Up @@ -850,4 +858,25 @@ describe('ViewmodelsService', () => {
vm1.checkValues(false,"T1595^reconnaissance");
expect(vm1.linksMatch).toEqual(false); // linkMismatches = ["T1595^reconnaissance"]
}));

it('should load vm data with custom url', inject([ViewModelsService], (service: ViewModelsService) => {
let versions = [
{
"name": "ATT&CK v13",
"version": "13",
"domains": [
{
"name": "Enterprise",
"identifier": "enterprise-attack",
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v13.1/enterprise-attack/enterprise-attack.json"]
}
]
}
]
let vm1 = service.newViewModel("test1","enterprise-attack-13");
vm1.dataService.setUpURLs(versions);
vm1.dataService.domains[0].urls = ["https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"];
vm1.dataService.domains[0].isCustom = true;
expect(vm1.loadVMData()).toBeUndefined();
}));
});
38 changes: 37 additions & 1 deletion nav-app/src/app/tabs/tabs.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,7 @@ describe('TabsComponent', () => {
});
})));

it('should serialize viewmodel', (waitForAsync ( () => {
it('should serialize viewmodel and only save techniqueVMs which have been modified', (waitForAsync ( () => {
let fixture = TestBed.createComponent(TabsComponent);
let app = fixture.debugElement.componentInstance;
let versions = [
Expand Down Expand Up @@ -1101,6 +1101,42 @@ describe('TabsComponent', () => {
expect(app).toBeTruthy();
})));

it('should serialize viewmodel and only save techniqueVMs which have been modified and are visible', (waitForAsync ( () => {
let fixture = TestBed.createComponent(TabsComponent);
let app = fixture.debugElement.componentInstance;
let versions = [
{
"name": "ATT&CK v13",
"version": "13",
"domains": [
{
"name": "Enterprise",
"identifier": "enterprise-attack",
"data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v13.1/enterprise-attack/enterprise-attack.json"]
}
]
}
]
app.dataService.setUpURLs(versions);
let vm1 = app.viewModelsService.newViewModel("layer2","enterprise-attack-13");
let idToTacticSDO = new Map<string, any>();
idToTacticSDO.set("tactic-0", tacticSDO);
let t1 = new Technique(techniqueSDO,[],null);
let t2 = new Technique(techniqueSDO2,[],null);
let tvm_1 = new TechniqueVM("T1595^reconnaissance");
let tvm_2 = new TechniqueVM("T1592^reconnaissance");
tvm_1.isVisible = true;
tvm_1.score = '3';
vm1.setTechniqueVM(tvm_1);
vm1.setTechniqueVM(tvm_2);
app.dataService.domains[0].techniques.push(t1);
app.dataService.domains[0].techniques.push(t2);
vm1.dataService.domains[0].urls = ["https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"];
vm1.dataService.domains[0].isCustom = true;
vm1.serialize(true);
expect(app).toBeTruthy();
})));

it('should throw errors for deserializing viewmodel', (waitForAsync ( () => {
let fixture = TestBed.createComponent(TabsComponent);
let app = fixture.debugElement.componentInstance;
Expand Down

0 comments on commit 77387a9

Please sign in to comment.