Commit cdd141c7 authored by Carlos Natalino's avatar Carlos Natalino
Browse files

Including the mocking of objects to perform all tests without the need of other services.

parent 0997a144
Loading
Loading
Loading
Loading
+11 −11
Original line number Diff line number Diff line
@@ -35,18 +35,18 @@ INFLUXDB_HOSTNAME = os.environ.get("INFLUXDB_HOSTNAME")
INFLUXDB_USER = os.environ.get("INFLUXDB_USER")
INFLUXDB_PASSWORD = os.environ.get("INFLUXDB_PASSWORD")
INFLUXDB_DATABASE = os.environ.get("INFLUXDB_DATABASE")
context_client: ContextClient = ContextClient(address=CONTEXT_SERVICE_ADDRESS, port=CONTEXT_GRPC_SERVICE_PORT)
influxdb_client: InfluxDBClient = InfluxDBClient(host=MONITORING_SERVICE_ADDRESS, port=8086, username=INFLUXDB_USER, password=INFLUXDB_PASSWORD, database=INFLUXDB_DATABASE)
monitoring_client: MonitoringClient = MonitoringClient(server=MONITORING_SERVICE_ADDRESS, port=MONITORING_GRPC_SERVICE_PORT)
dbscanserving_client: DbscanServingClient = DbscanServingClient(address=INFERENCE_SERVICE_ADDRESS, port=DBSCANSERVING_GRPC_SERVICE_PORT)
service_client: ServiceClient = ServiceClient(SERVICE_SERVICE_ADDRESS, SERVICE_GRPC_SERVICE_PORT)
attack_mitigator_client: OpticalAttackMitigatorClient = OpticalAttackMitigatorClient(address=ATTACK_MITIGATOR_SERVICE_ADDRESS, port=ATTACK_MITIGATOR_GRPC_SERVICE_PORT)


class OpticalCentralizedAttackDetectorServiceServicerImpl(OpticalCentralizedAttackDetectorServiceServicer):

    def __init__(self):
        LOGGER.debug('Creating Servicer...')
        self.context_client: ContextClient = ContextClient(address=CONTEXT_SERVICE_ADDRESS, port=CONTEXT_GRPC_SERVICE_PORT)
        self.monitoring_client: MonitoringClient = MonitoringClient(server=MONITORING_SERVICE_ADDRESS, port=MONITORING_GRPC_SERVICE_PORT)
        self.service_client: ServiceClient = ServiceClient(SERVICE_SERVICE_ADDRESS, SERVICE_GRPC_SERVICE_PORT)
        self.influxdb_client: InfluxDBClient = InfluxDBClient(host=MONITORING_SERVICE_ADDRESS, port=8086, username=INFLUXDB_USER, password=INFLUXDB_PASSWORD, database=INFLUXDB_DATABASE)
        self.dbscanserving_client: DbscanServingClient = DbscanServingClient(address=INFERENCE_SERVICE_ADDRESS, port=DBSCANSERVING_GRPC_SERVICE_PORT)
        self.attack_mitigator_client: OpticalAttackMitigatorClient = OpticalAttackMitigatorClient(address=ATTACK_MITIGATOR_SERVICE_ADDRESS, port=ATTACK_MITIGATOR_GRPC_SERVICE_PORT)
        LOGGER.debug('Servicer Created')

    @safe_and_metered_rpc_method(METRICS, LOGGER)
@@ -58,18 +58,18 @@ class OpticalCentralizedAttackDetectorServiceServicerImpl(OpticalCentralizedAtta
        
        # retrieve list with current contexts
        # import pdb; pdb.set_trace()
        context_ids: ContextIdList = self.context_client.ListContextIds(Empty())
        context_ids: ContextIdList = context_client.ListContextIds(Empty())

        # for each context, retrieve list of current services
        services = []
        for context_id in context_ids.context_ids:

            context_services: ServiceIdList = self.context_client.ListServices(context_id)
            context_services: ServiceIdList = context_client.ListServices(context_id)
            for service in context_services.services:
                services.append(service)

        # get monitoring data for each of the current services
        results = self.influxdb_client.query('select * from samples;')
        results = influxdb_client.query('select * from samples;')

        for service in services:
            for endpoint in service.service_endpoint_ids:
@@ -107,12 +107,12 @@ class OpticalCentralizedAttackDetectorServiceServicerImpl(OpticalCentralizedAtta
                        grpc_sample.features.append(random.uniform(5000., 6000.))
                    request.samples.append(grpc_sample)

                response: DetectionResponse = self.dbscanserving_client.Detect(request)
                response: DetectionResponse = dbscanserving_client.Detect(request)

                if -1 in response.cluster_indices:  # attack detected
                    attack = AttackDescription()
                    attack.cs_id.uuid = service.service_id.service_uuid.uuid
                    response: AttackResponse = self.attack_mitigator_client.NotifyAttack(attack)
                    response: AttackResponse = attack_mitigator_client.NotifyAttack(attack)

        # if attack is detected, run the attack mitigator
        return Empty()
+201 −0
Original line number Diff line number Diff line
from copy import deepcopy
from common.Constants import DEFAULT_CONTEXT_UUID, DEFAULT_TOPOLOGY_UUID
from context.proto.context_pb2 import (
    ConfigActionEnum, DeviceDriverEnum, DeviceOperationalStatusEnum, ServiceStatusEnum, ServiceTypeEnum)

# Some example objects to be used by the tests

# Helper methods
def config_rule(action, resource_key, resource_value):
    return {'action': action, 'resource_key': resource_key, 'resource_value': resource_value}

def endpoint_id(topology_id, device_id, endpoint_uuid):
    return {'topology_id': deepcopy(topology_id), 'device_id': deepcopy(device_id),
            'endpoint_uuid': {'uuid': endpoint_uuid}}

def endpoint(topology_id, device_id, endpoint_uuid, endpoint_type):
    return {'endpoint_id': endpoint_id(topology_id, device_id, endpoint_uuid), 'endpoint_type': endpoint_type}

## use "deepcopy" to prevent propagating forced changes during tests
CONTEXT_ID = {'context_uuid': {'uuid': DEFAULT_CONTEXT_UUID}}
CONTEXT = {
    'context_id': deepcopy(CONTEXT_ID),
    'topology_ids': [],
    'service_ids': [],
}

CONTEXT_ID_2 = {'context_uuid': {'uuid': 'test'}}
CONTEXT_2 = {
    'context_id': deepcopy(CONTEXT_ID_2),
    'topology_ids': [],
    'service_ids': [],
}

TOPOLOGY_ID = {
    'context_id': deepcopy(CONTEXT_ID),
    'topology_uuid': {'uuid': DEFAULT_TOPOLOGY_UUID},
}
TOPOLOGY = {
    'topology_id': deepcopy(TOPOLOGY_ID),
    'device_ids': [],
    'link_ids': [],
}

DEVICE1_UUID = 'DEV1'
DEVICE1_ID = {'device_uuid': {'uuid': DEVICE1_UUID}}
DEVICE1 = {
    'device_id': deepcopy(DEVICE1_ID),
    'device_type': 'packet-router',
    'device_config': {'config_rules': [
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc1/value', 'value1'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc2/value', 'value2'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc3/value', 'value3'),
    ]},
    'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED,
    'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4],
    'device_endpoints': [
        endpoint(TOPOLOGY_ID, DEVICE1_ID, 'EP2', 'port-packet-100G'),
        endpoint(TOPOLOGY_ID, DEVICE1_ID, 'EP3', 'port-packet-100G'),
        endpoint(TOPOLOGY_ID, DEVICE1_ID, 'EP100', 'port-packet-10G'),
    ],
}

DEVICE2_UUID = 'DEV2'
DEVICE2_ID = {'device_uuid': {'uuid': DEVICE2_UUID}}
DEVICE2 = {
    'device_id': deepcopy(DEVICE2_ID),
    'device_type': 'packet-router',
    'device_config': {'config_rules': [
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc1/value', 'value4'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc2/value', 'value5'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc3/value', 'value6'),
    ]},
    'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED,
    'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4],
    'device_endpoints': [
        endpoint(TOPOLOGY_ID, DEVICE2_ID, 'EP1', 'port-packet-100G'),
        endpoint(TOPOLOGY_ID, DEVICE2_ID, 'EP3', 'port-packet-100G'),
        endpoint(TOPOLOGY_ID, DEVICE2_ID, 'EP100', 'port-packet-10G'),
    ],
}

DEVICE3_UUID = 'DEV3'
DEVICE3_ID = {'device_uuid': {'uuid': DEVICE3_UUID}}
DEVICE3 = {
    'device_id': deepcopy(DEVICE3_ID),
    'device_type': 'packet-router',
    'device_config': {'config_rules': [
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc1/value', 'value4'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc2/value', 'value5'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'dev/rsrc3/value', 'value6'),
    ]},
    'device_operational_status': DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED,
    'device_drivers': [DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG, DeviceDriverEnum.DEVICEDRIVER_P4],
    'device_endpoints': [
        endpoint(TOPOLOGY_ID, DEVICE3_ID, 'EP1', 'port-packet-100G'),
        endpoint(TOPOLOGY_ID, DEVICE3_ID, 'EP2', 'port-packet-100G'),
        endpoint(TOPOLOGY_ID, DEVICE3_ID, 'EP100', 'port-packet-10G'),
    ],
}

LINK_DEV1_DEV2_UUID = 'DEV1/EP2 ==> DEV2/EP1'
LINK_DEV1_DEV2_ID = {'link_uuid': {'uuid': LINK_DEV1_DEV2_UUID}}
LINK_DEV1_DEV2 = {
    'link_id': deepcopy(LINK_DEV1_DEV2_ID),
    'link_endpoint_ids' : [
        endpoint_id(TOPOLOGY_ID, DEVICE1_ID, 'EP2'),
        endpoint_id(TOPOLOGY_ID, DEVICE2_ID, 'EP1'),
    ]
}

LINK_DEV2_DEV3_UUID = 'DEV2/EP3 ==> DEV3/EP2'
LINK_DEV2_DEV3_ID = {'link_uuid': {'uuid': LINK_DEV2_DEV3_UUID}}
LINK_DEV2_DEV3 = {
    'link_id': deepcopy(LINK_DEV2_DEV3_ID),
    'link_endpoint_ids' : [
        endpoint_id(TOPOLOGY_ID, DEVICE2_ID, 'EP3'),
        endpoint_id(TOPOLOGY_ID, DEVICE3_ID, 'EP2'),
    ]
}

LINK_DEV1_DEV3_UUID = 'DEV1/EP3 ==> DEV3/EP1'
LINK_DEV1_DEV3_ID = {'link_uuid': {'uuid': LINK_DEV1_DEV3_UUID}}
LINK_DEV1_DEV3 = {
    'link_id': deepcopy(LINK_DEV1_DEV3_ID),
    'link_endpoint_ids' : [
        endpoint_id(TOPOLOGY_ID, DEVICE1_ID, 'EP3'),
        endpoint_id(TOPOLOGY_ID, DEVICE3_ID, 'EP1'),
    ]
}

SERVICE_DEV1_DEV2_UUID = 'SVC:DEV1/EP100-DEV2/EP100'
SERVICE_DEV1_DEV2_ID = {
    'context_id': deepcopy(CONTEXT_ID),
    'service_uuid': {'uuid': SERVICE_DEV1_DEV2_UUID},
}
SERVICE_DEV1_DEV2 = {
    'service_id': deepcopy(SERVICE_DEV1_DEV2_ID),
    'service_type': ServiceTypeEnum.SERVICETYPE_L3NM,
    'service_endpoint_ids' : [
        endpoint_id(TOPOLOGY_ID, DEVICE1_ID, 'EP100'),
        endpoint_id(TOPOLOGY_ID, DEVICE2_ID, 'EP100'),
    ],
    'service_constraints': [
        {'constraint_type': 'latency_ms', 'constraint_value': '15.2'},
        {'constraint_type': 'jitter_us', 'constraint_value': '1.2'},
    ],
    'service_status': {'service_status': ServiceStatusEnum.SERVICESTATUS_ACTIVE},
    'service_config': {'config_rules': [
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc1/value', 'value7'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc2/value', 'value8'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc3/value', 'value9'),
    ]},
}

SERVICE_DEV1_DEV3_UUID = 'SVC:DEV1/EP100-DEV3/EP100'
SERVICE_DEV1_DEV3_ID = {
    'context_id': deepcopy(CONTEXT_ID),
    'service_uuid': {'uuid': SERVICE_DEV1_DEV3_UUID},
}
SERVICE_DEV1_DEV3 = {
    'service_id': deepcopy(SERVICE_DEV1_DEV3_ID),
    'service_type': ServiceTypeEnum.SERVICETYPE_L3NM,
    'service_endpoint_ids' : [
        endpoint_id(TOPOLOGY_ID, DEVICE1_ID, 'EP100'),
        endpoint_id(TOPOLOGY_ID, DEVICE3_ID, 'EP100'),
    ],
    'service_constraints': [
        {'constraint_type': 'latency_ms', 'constraint_value': '5.8'},
        {'constraint_type': 'jitter_us', 'constraint_value': '0.1'},
    ],
    'service_status': {'service_status': ServiceStatusEnum.SERVICESTATUS_ACTIVE},
    'service_config': {'config_rules': [
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc1/value', 'value7'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc2/value', 'value8'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc3/value', 'value9'),
    ]},
}

SERVICE_DEV2_DEV3_UUID = 'SVC:DEV2/EP100-DEV3/EP100'
SERVICE_DEV2_DEV3_ID = {
    'context_id': deepcopy(CONTEXT_ID),
    'service_uuid': {'uuid': SERVICE_DEV2_DEV3_UUID},
}
SERVICE_DEV2_DEV3 = {
    'service_id': deepcopy(SERVICE_DEV2_DEV3_ID),
    'service_type': ServiceTypeEnum.SERVICETYPE_L3NM,
    'service_endpoint_ids' : [
        endpoint_id(TOPOLOGY_ID, DEVICE2_ID, 'EP100'),
        endpoint_id(TOPOLOGY_ID, DEVICE3_ID, 'EP100'),
    ],
    'service_constraints': [
        {'constraint_type': 'latency_ms', 'constraint_value': '23.1'},
        {'constraint_type': 'jitter_us', 'constraint_value': '3.4'},
    ],
    'service_status': {'service_status': ServiceStatusEnum.SERVICESTATUS_ACTIVE},
    'service_config': {'config_rules': [
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc1/value', 'value7'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc2/value', 'value8'),
        config_rule(ConfigActionEnum.CONFIGACTION_SET, 'svc/rsrc3/value', 'value9'),
    ]},
}
+139 −4
Original line number Diff line number Diff line
import logging, pytest
from unittest.mock import patch
from opticalcentralizedattackdetector.Config import GRPC_SERVICE_PORT, GRPC_MAX_WORKERS, GRPC_GRACE_PERIOD
from opticalcentralizedattackdetector.client.OpticalCentralizedAttackDetectorClient import OpticalCentralizedAttackDetectorClient
from opticalcentralizedattackdetector.proto.context_pb2 import Empty, Service
from opticalcentralizedattackdetector.proto.context_pb2 import ContextIdList, ContextId, Empty, Service, ContextId, ServiceList
from opticalcentralizedattackdetector.proto.monitoring_pb2 import Kpi, KpiList
from opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorService import OpticalCentralizedAttackDetectorService
from .example_objects import CONTEXT_ID, CONTEXT_ID_2, SERVICE_DEV1_DEV2

port = 10000 + GRPC_SERVICE_PORT # avoid privileged ports

@@ -14,9 +16,17 @@ LOGGER.setLevel(logging.DEBUG)
def optical_centralized_attack_detector_service():
    _service = OpticalCentralizedAttackDetectorService(
        port=port, max_workers=GRPC_MAX_WORKERS, grace_period=GRPC_GRACE_PERIOD)
    # mocker_context_client = mock.patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client')
    # mocker_context_client.start()

    # mocker_influx_db = mock.patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client')
    # mocker_influx_db.start()

    _service.start()
    yield _service
    _service.stop()
    # mocker_context_client.stop()
    # mocker_influx_db.stop()

@pytest.fixture(scope='session')
def optical_centralized_attack_detector_client(optical_centralized_attack_detector_service):
@@ -28,10 +38,135 @@ def test_notify_service_update(optical_centralized_attack_detector_client: Optic
    service = Service()
    optical_centralized_attack_detector_client.NotifyServiceUpdate(service)

def test_detect_attack(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
def test_detect_attack_no_contexts(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb:
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)
        context.ListContextIds.assert_called_once()
        influxdb.query.assert_called_once()
        context.ListServices.assert_not_called()

def test_detect_attack_with_context(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb:
        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()

def test_detect_attack_with_contexts(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb:
        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        cid_list.context_ids.append(ContextId(**CONTEXT_ID_2))
        context.ListContextIds.return_value = cid_list

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_any_call(cid_list.context_ids[0])
        context.ListServices.assert_any_call(cid_list.context_ids[1])
        influxdb.query.assert_called_once()

def test_detect_attack_with_service(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.dbscanserving_client') as dbscan:

        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        service_list = ServiceList()
        service_list.services.append(Service(**SERVICE_DEV1_DEV2))
        context.ListServices.return_value = service_list

        influxdb.query.return_value.get_points.return_value = [(1, 2), (3, 4)]

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()
        dbscan.Detect.assert_called()

def test_detect_attack_no_attack(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.dbscanserving_client') as dbscan, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.attack_mitigator_client') as mitigator:

        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        service_list = ServiceList()
        service_list.services.append(Service(**SERVICE_DEV1_DEV2))
        context.ListServices.return_value = service_list

        # dbscan.Detect.return_value = object()
        dbscan.Detect.return_value.cluster_indices = [0, 1, 2, 3, 4, 5]

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()
        dbscan.Detect.assert_called()
        mitigator.NotifyAttack.assert_not_called()

def test_detect_attack_with_attack(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient,):
    with patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.context_client') as context, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.influxdb_client') as influxdb, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.dbscanserving_client') as dbscan, \
         patch('opticalcentralizedattackdetector.service.OpticalCentralizedAttackDetectorServiceServicerImpl.attack_mitigator_client') as mitigator:

        # setting up the mock
        cid_list = ContextIdList()
        cid_list.context_ids.append(ContextId(**CONTEXT_ID))
        context.ListContextIds.return_value = cid_list

        service_list = ServiceList()
        service_list.services.append(Service(**SERVICE_DEV1_DEV2))
        context.ListServices.return_value = service_list

        # dbscan.Detect.return_value = object()
        dbscan.Detect.return_value.cluster_indices = [0, 1, 2, 3, 4, -1]

        # making the test
        request = Empty()
        optical_centralized_attack_detector_client.DetectAttack(request)

        # checking behavior
        context.ListContextIds.assert_called_once()
        context.ListServices.assert_called_with(cid_list.context_ids[0])
        influxdb.query.assert_called_once()
        dbscan.Detect.assert_called()
        mitigator.NotifyAttack.assert_called()

def test_report_summarized_kpi(optical_centralized_attack_detector_client: OpticalCentralizedAttackDetectorClient):
    kpi_list = KpiList()
    optical_centralized_attack_detector_client.ReportSummarizedKpi(kpi_list)