blob: ad67f12807f08db9877edb630b951d8bade13727 [file] [log] [blame]
Matteo Scandolo8cf33a32017-11-14 15:52:29 -08001
2/*
3 * Copyright 2017-present Open Networking Foundation
4
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8
9 * http://www.apache.org/licenses/LICENSE-2.0
10
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18import * as _ from 'lodash';
19import * as angular from 'angular';
20import 'angular-mocks';
21import {IXosGraphStore, XosGraphStore} from './graph.store';
22import {Subject} from 'rxjs/Subject';
23import {Graph} from 'graphlib';
24import {XosDebouncer} from '../../core/services/helpers/debounce.helper';
Matteo Scandolo209fc8a2018-03-14 18:14:21 -070025import {IWSEvent} from '../../datasources/websocket/global';
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080026
27interface ITestXosGraphStore extends IXosGraphStore {
28
29 // state
30 serviceGraph: Graph;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080031
32 // private methods
33 getNodeId: any;
34 getModelType: any;
35 addNode: any;
36 addEdge: any;
37 nodesFromGraph: any;
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080038
39 // observables
40 ServiceInstanceSubscription: any;
41 ServiceInstanceLinkSubscription: any;
42}
43
44let service: ITestXosGraphStore;
45
46let scope: ng.IRootScopeService;
47
48const services = [
49 {
50 id: 1,
51 class_names: 'Service,XOSBase',
52 name: 'Service 1'
53 },
54 {
55 id: 2,
56 class_names: 'Service,XOSBase',
57 name: 'Service 2'
58 }
59];
60
61const servicedependencies = [
62 {
63 id: 1,
64 class_names: 'ServiceDependency,XOSBase',
65 provider_service_id: 2,
66 subscriber_service_id: 1
67 }
68];
69
70const serviceInstances = [
71 {
72 id: 1,
73 class_names: 'ServiceInstance,XOSBase',
74 name: 'ServiceInstance 1',
75 owner_id: 1
76 },
77 {
78 id: 2,
79 class_names: 'ServiceInstance,XOSBase',
80 name: 'ServiceInstance 2',
81 owner_id: 2
82 }
83];
84
85const serviceInstanceLinks = [
86 {
87 id: 1,
88 class_names: 'ServiceInstanceLink,XOSBase',
89 provider_service_instance_id: 1,
90 subscriber_service_instance_id: 2,
91 }
92];
93
94const subject_services = new Subject();
95const subject_servicedependency = new Subject();
96const subject_serviceinstances = new Subject();
97const subject_serviceinstancelinks = new Subject();
Matteo Scandolo209fc8a2018-03-14 18:14:21 -070098const subject_websocket = new Subject();
Matteo Scandolo8cf33a32017-11-14 15:52:29 -080099
100let MockModelStore = {
101 query: jasmine.createSpy('XosModelStore.query')
102 .and.callFake((model) => {
103 if (model === 'Service') {
104 return subject_services.asObservable();
105 }
106 else if (model === 'ServiceDependency') {
107 return subject_servicedependency.asObservable();
108 }
109 else if (model === 'ServiceInstance') {
110 return subject_serviceinstances.asObservable();
111 }
112 else if (model === 'ServiceInstanceLink') {
113 return subject_serviceinstancelinks.asObservable();
114 }
115 })
116};
117
Matteo Scandolo209fc8a2018-03-14 18:14:21 -0700118let MockWebSocket = {
119 list: jasmine.createSpy('WebSocket.list')
120 .and.returnValue(subject_websocket)
121};
122
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800123
124describe('The XosGraphStore service', () => {
125
126 beforeEach(() => {
127 angular.module('XosGraphStore', [])
128 .service('XosGraphStore', XosGraphStore)
129 .value('XosModelStore', MockModelStore)
Matteo Scandolo209fc8a2018-03-14 18:14:21 -0700130 .value('WebSocket', MockWebSocket)
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800131 .service('XosDebouncer', XosDebouncer);
132
133 angular.mock.module('XosGraphStore');
134 });
135
136 beforeEach(angular.mock.inject((XosGraphStore: ITestXosGraphStore,
137 $rootScope: ng.IRootScopeService,
138 _$q_: ng.IQService) => {
139
140 service = XosGraphStore;
141 scope = $rootScope;
142
143 }));
144
Matteo Scandolo209fc8a2018-03-14 18:14:21 -0700145 describe('when started', () => {
146
147 let subscription;
148
149 it('should load services and service-dependency and add nodes to the graph', (done) => {
150 let event = 0;
151 subscription = service.get().subscribe(
152 (graph: Graph) => {
153 if (event === 1) {
154 expect(graph.nodes().length).toBe(services.length);
155 expect(graph.nodes()).toEqual(['service~1', 'service~2']);
156 expect(graph.edges().length).toBe(servicedependencies.length);
157 expect(graph.edges()).toEqual([{v: 'service~1', w: 'service~2'}]);
158 done();
159 }
160 else {
161 event = event + 1;
162 }
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800163 }
Matteo Scandolo209fc8a2018-03-14 18:14:21 -0700164 );
165 subject_services.next(services);
166 subject_servicedependency.next(servicedependencies);
167 scope.$apply();
168 });
169
170 afterEach(() => {
171 subscription.unsubscribe();
172 });
173 });
174
175 describe('when an observed model is removed', () => {
176
177 let subscription;
178
179 beforeEach(() => {
180 subject_services.next([]);
181 subject_servicedependency.next([]);
182 subject_services.next(services);
183 subject_servicedependency.next(servicedependencies);
184 service.addServiceInstances();
185 subject_serviceinstances.next([serviceInstances[0]]);
186 });
187
188 it('should be removed from the graph', (done) => {
189 let event = 0;
190 subscription = service.get().subscribe(
191 (graph: Graph) => {
192 if (event === 1) {
193 expect(graph.nodes().length).toBe(0);
194 expect(graph.nodes()).toEqual([]);
195 expect(graph.edges().length).toBe(0);
196 expect(graph.edges()).toEqual([]);
197 done();
198 }
199 else {
200 event = event + 1;
201 }
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800202 }
Matteo Scandolo209fc8a2018-03-14 18:14:21 -0700203 );
204
205 const removeService1: IWSEvent = {
206 model: 'Service',
207 deleted: true,
208 msg: {
209 changed_fields: [],
210 pk: 1,
211 object: {
212 id: 1,
213 class_names: 'Service, XOSBase'
214 }
215 }
216 };
217
218 const removeService2: IWSEvent = {
219 model: 'Service',
220 deleted: true,
221 msg: {
222 changed_fields: [],
223 pk: 2,
224 object: {
225 id: 2,
226 class_names: 'Service, XOSBase'
227 }
228 }
229 };
230
231 const removeServiceDependency1: IWSEvent = {
232 model: 'ServiceDependency',
233 deleted: true,
234 msg: {
235 changed_fields: [],
236 pk: 1,
237 object: {
238 id: 1,
239 class_names: 'ServiceDependency, XOSBase'
240 }
241 }
242 };
243
244 const removeServiceInstnace1: IWSEvent = {
245 model: 'VSGServiceInstance',
246 deleted: true,
247 msg: {
248 changed_fields: [],
249 pk: 1,
250 object: {
251 id: 1,
252 class_names: 'VSGServiceInstance,TenantWithContainer,ServiceInstance'
253 }
254 }
255 };
256
257 subject_websocket.next(removeService1);
258 subject_websocket.next(removeService2);
259 subject_websocket.next(removeServiceDependency1);
260 subject_websocket.next(removeServiceInstnace1);
261 scope.$apply();
262 });
263
264 afterEach(() => {
265 subscription.unsubscribe();
266 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800267 });
268
269 describe(`the getModelType`, () => {
270 it('should return the node type', () => {
271 const res = service.getModelType(services[0]);
272 expect(res).toBe('service');
273 });
274
275 it('should return the node type', () => {
276 const res = service.getModelType(serviceInstances[0]);
277 expect(res).toBe('serviceinstance');
278 });
279 });
280
281 describe('the getNodeId method', () => {
282 it('should return the id for a Service', () => {
283 const res = service.getNodeId(services[0]);
284 expect(res).toBe(`service~1`);
285 });
286
287 it('should return the id for a ServiceInstance', () => {
288 const res = service.getNodeId(serviceInstances[0]);
289 expect(res).toBe(`serviceinstance~1`);
290 });
291 });
292
293 describe('the addNode method', () => {
294
295 beforeEach(() => {
296 spyOn(service.serviceGraph, 'setNode');
297 spyOn(service.serviceGraph, 'setEdge');
298 });
299
300 it(`should a service to the graph`, () => {
301 service.addNode(services[0]);
302 expect(service.serviceGraph.setNode).toHaveBeenCalledWith('service~1', services[0]);
303 });
304
305 it('should add a service instance to the graph', () => {
306 service.addNode(serviceInstances[0]);
307 expect(service.serviceGraph.setNode).toHaveBeenCalledWith('serviceinstance~1', serviceInstances[0]);
308 });
309
310 it('should add an "ownership" edge to the graph', () => {
311 service.addNode(serviceInstances[0]);
312 expect(service.serviceGraph.setEdge).toHaveBeenCalledWith('serviceinstance~1', 'service~1', {service: 1, service_instance: 1, type: 'ownership'});
313 });
314 });
315
316 describe('the addEdge method', () => {
317
318 beforeEach(() => {
319 spyOn(service.serviceGraph, 'setEdge');
320 });
321
322 it('should add a ServiceDependency to the graph', () => {
323 service.addEdge(servicedependencies[0]);
324 expect(service.serviceGraph.setEdge).toHaveBeenCalledWith('service~1', 'service~2', servicedependencies[0]);
325 });
326
327 it('should add a ServiceInstanceLink to the graph', () => {
328 service.addEdge(serviceInstanceLinks[0]);
329 expect(service.serviceGraph.setEdge).toHaveBeenCalledWith('serviceinstance~1', 'serviceinstance~2', serviceInstanceLinks[0]);
330 });
331 });
332
333 describe('the nodesFromGraph and linksFromGraph methods', () => {
334 let graph: Graph;
335
336 beforeEach(() => {
337 graph = new Graph();
338 services.forEach(s => {
339 graph.setNode(`service~${s.id}`, s);
340 });
341
342 servicedependencies.forEach(sd => {
343 graph.setEdge('service~1', 'service~2', sd);
344 });
345 });
346
347 it('should add id and type to the nodes', () => {
348 const nodes = service.nodesFromGraph(graph);
349 expect(nodes[0].id).toBe('service~1');
350 expect(nodes[0].type).toBe('service');
351 expect(nodes[0].data).toBeDefined();
352 });
353
354 it('should add id and type to the links', () => {
355 const links = service.linksFromGraph(graph);
356 expect(links[0].id).toBe('service~1-service~2');
357 expect(links[0].type).toBe('servicedependency');
358 expect(links[0].data).toBeDefined();
359 });
360
361 it('should handle ownership links', () => {
362 graph.setNode(`serviceinstance~1`, serviceInstances[0]);
363 graph.setEdge('service~1', 'serviceinstance~1', {type: 'ownership', service: 1, service_instance: 1});
364 const links = service.linksFromGraph(graph);
365 expect(links[1].source).toBe(0);
366 expect(links[1].target).toBe(2);
367 });
368
369 it('should handle serviceinstancelink links', () => {
370 graph.setNode(`serviceinstance~1`, serviceInstances[0]);
371 graph.setNode(`serviceinstance~2`, serviceInstances[1]);
372 graph.setEdge('serviceinstance~1', 'serviceinstance~2', serviceInstanceLinks[0]);
373 const links = service.linksFromGraph(graph);
374 const targetLink = _.find(links, {id: `serviceinstance~1-serviceinstance~2`});
375 expect(targetLink).toBeDefined();
376 expect(targetLink.source).toBe(3);
377 expect(targetLink.target).toBe(2);
378 });
379 });
380
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800381 describe('the addServiceInstance method', () => {
382 beforeEach(() => {
383 MockModelStore.query.calls.reset();
384 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800385
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800386 it('should add them to the graph', () => {
387 service.addServiceInstances();
388 expect(MockModelStore.query).toHaveBeenCalledWith(`ServiceInstance`, '/core/serviceinstances');
389 expect(MockModelStore.query).toHaveBeenCalledWith(`ServiceInstanceLink`, '/core/serviceinstancelinks');
390 expect(service.ServiceInstanceSubscription).toBeDefined();
391 expect(service.ServiceInstanceLinkSubscription).toBeDefined();
392 // TODO wait for the Observable to return and check the graph
393 });
394 });
395
396 describe('the removeServiceInstance method', () => {
397 beforeEach(() => {
398 service.ServiceInstanceSubscription = {
399 unsubscribe: jasmine.createSpy('ServiceInstanceSubscription')
400 };
401 service.ServiceInstanceLinkSubscription = {
402 unsubscribe: jasmine.createSpy('ServiceInstanceLinkSubscription')
403 };
404
405 service.serviceGraph = new Graph();
406
407 services.forEach(s => {
408 service.addNode(s);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800409 });
410
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800411 serviceInstances.forEach(si => {
412 service.addNode(si);
413 });
414
415 serviceInstanceLinks.forEach(sil => {
416 service.addEdge(sil);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800417 });
418 });
419
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800420 it('should cancel subscriptions', () => {
421 service.removeServiceInstances();
422 expect(service.ServiceInstanceSubscription.unsubscribe).toHaveBeenCalled();
423 expect(service.ServiceInstanceLinkSubscription.unsubscribe).toHaveBeenCalled();
424 });
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800425
Matteo Scandolob8cdf552018-02-12 17:56:26 -0800426 it('should remove ServiceInstance and related nodes/edges from the graph', () => {
427 let filteredGraph = service.removeServiceInstances();
428 expect(filteredGraph.nodes().length).toBe(2);
429 expect(filteredGraph.edges().length).toBe(0);
430 expect(service.serviceGraph.nodes().length).toBe(2);
431 expect(service.serviceGraph.edges().length).toBe(0);
Matteo Scandolo8cf33a32017-11-14 15:52:29 -0800432 });
433 });
434});