<filename>tests/unit/test_processors.py
"""Tests for Processor."""
import copy
from unittest.mock import patch

import pytest
from pydantic.error_wrappers import ValidationError

from circuit_maintenance_parser.output import Maintenance
from circuit_maintenance_parser.processor import CombinedProcessor, SimpleProcessor
from circuit_maintenance_parser.data import DataPart, NotificationData
from circuit_maintenance_parser.errors import ProcessorError


from circuit_maintenance_parser.parser import Parser


PARSED_DATA = [{"a": "b"}, {"c": "d"}]
EXTENDED_DATA = {"y": "z"}


class FakeParser(Parser):
    "Fake class to simulate a Parser."
    _parsed_data = PARSED_DATA
    _data_type = "fake_type"

    @classmethod
    def get_data_types(cls):
        return [cls._data_type]

    def parse(self, *args, **kwargs):  # pylint: disable=unused-argument
        return copy.deepcopy(self._parsed_data)

    def parser_hook(self, raw: bytes):
        pass


class FakeParser0(FakeParser):
    "Fake class to simulate another Parser."
    _data_type = "fake_type_0"
    _parsed_data = copy.deepcopy([PARSED_DATA[0]])


class FakeParser1(FakeParser):
    "Fake class to simulate yet another Parser."
    _data_type = "fake_type_1"
    _parsed_data = copy.deepcopy([PARSED_DATA[1]])


# Fake data used for SimpleProcessor
fake_data = NotificationData.init_from_raw("fake_type", b"fake data")
# Fake data used for CombinedProcessor
fake_data_type_0 = NotificationData.init_from_raw("fake_type_0", b"fake data")
fake_data_for_combined = NotificationData.init_from_raw("fake_type_0", b"fake data")
if fake_data_for_combined:
    fake_data_for_combined.data_parts.append(DataPart("fake_type_1", b"fake data"))


def test_simpleprocessor():
    """Tests SimpleProcessor."""
    processor = SimpleProcessor(data_parsers=[FakeParser])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data, EXTENDED_DATA)
        assert mock_maintenance.call_count == len(PARSED_DATA)
        for parsed_data_element in PARSED_DATA:
            parsed_data_element.update(EXTENDED_DATA)
            mock_maintenance.assert_any_call(**parsed_data_element)


def test_simpleprocessor_without_matching_type():
    """Tests SimpleProcessor without matching data types."""
    processor = SimpleProcessor(data_parsers=[FakeParser])
    with pytest.raises(ProcessorError) as e_info:
        processor.process(fake_data_for_combined, EXTENDED_DATA)
    assert "None of the supported parsers for processor SimpleProcessor (FakeParser)" in str(e_info)


def test_combinedprocessor_multiple_data():
    """Tests CombinedProcessor wrong parsed data, with multiple entities."""
    processor = CombinedProcessor(data_parsers=[FakeParser])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data, EXTENDED_DATA)
        assert mock_maintenance.call_count == len(PARSED_DATA)
        for parsed_data_element in PARSED_DATA:
            parsed_data_element.update(EXTENDED_DATA)
            mock_maintenance.assert_any_call(**parsed_data_element)


def test_combinedprocessor():
    """Tests CombinedProcessor."""
    processor = CombinedProcessor(data_parsers=[FakeParser0, FakeParser1])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data_for_combined, EXTENDED_DATA)
        assert mock_maintenance.call_count == 1
        mock_maintenance.assert_any_call(**{**PARSED_DATA[0], **PARSED_DATA[1], **EXTENDED_DATA})


def test_combinedprocessor_missing_data():
    """Tests CombinedProcessor when there is not enough info to create a Maintenance."""
    processor = CombinedProcessor(data_parsers=[FakeParser0, FakeParser1])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        mock_maintenance.side_effect = ValidationError(errors=["whatever"], model=Maintenance)
        with pytest.raises(ProcessorError) as e_info:
            # Using the fake_data that returns mutliple maintenances that are not expected in this processor type
            processor.process(fake_data_for_combined, EXTENDED_DATA)

        assert "Not enough information available to create a Maintenance notification" in str(e_info)


def test_combinedprocessor_bleed():
    """Test CombinedProcessor to make sure that information from one processing doesn't bleed over to another."""
    processor = CombinedProcessor(data_parsers=[FakeParser0, FakeParser1])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data_for_combined, EXTENDED_DATA)
        assert mock_maintenance.call_count == 1
        mock_maintenance.assert_called_with(**{**PARSED_DATA[0], **PARSED_DATA[1], **EXTENDED_DATA})

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data_type_0, EXTENDED_DATA)
        assert mock_maintenance.call_count == 1
        mock_maintenance.assert_called_with(**{**PARSED_DATA[0], **EXTENDED_DATA})

<filename>tests/unit/test_processors.py
"""Tests for Processor."""
import copy
from unittest.mock import patch

import pytest
from pydantic.error_wrappers import ValidationError

from circuit_maintenance_parser.output import Maintenance
from circuit_maintenance_parser.processor import CombinedProcessor, SimpleProcessor
from circuit_maintenance_parser.data import DataPart, NotificationData
from circuit_maintenance_parser.errors import ProcessorError


from circuit_maintenance_parser.parser import Parser


PARSED_DATA = [{"a": "b"}, {"c": "d"}]
EXTENDED_DATA = {"y": "z"}


class FakeParser(Parser):
    "Fake class to simulate a Parser."
    _parsed_data = PARSED_DATA
    _data_type = "fake_type"

    @classmethod
    def get_data_types(cls):
        return [cls._data_type]

    def parse(self, *args, **kwargs):  # pylint: disable=unused-argument
        return copy.deepcopy(self._parsed_data)

    def parser_hook(self, raw: bytes):
        pass


class FakeParser0(FakeParser):
    "Fake class to simulate another Parser."
    _data_type = "fake_type_0"
    _parsed_data = copy.deepcopy([PARSED_DATA[0]])


class FakeParser1(FakeParser):
    "Fake class to simulate yet another Parser."
    _data_type = "fake_type_1"
    _parsed_data = copy.deepcopy([PARSED_DATA[1]])


# Fake data used for SimpleProcessor
fake_data = NotificationData.init_from_raw("fake_type", b"fake data")
# Fake data used for CombinedProcessor
fake_data_type_0 = NotificationData.init_from_raw("fake_type_0", b"fake data")
fake_data_for_combined = NotificationData.init_from_raw("fake_type_0", b"fake data")
if fake_data_for_combined:
    fake_data_for_combined.data_parts.append(DataPart("fake_type_1", b"fake data"))


def test_simpleprocessor():
    """Tests SimpleProcessor."""
    processor = SimpleProcessor(data_parsers=[FakeParser])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data, EXTENDED_DATA)
        assert mock_maintenance.call_count == len(PARSED_DATA)
        for parsed_data_element in PARSED_DATA:
            parsed_data_element.update(EXTENDED_DATA)
            mock_maintenance.assert_any_call(**parsed_data_element)


def test_simpleprocessor_without_matching_type():
    """Tests SimpleProcessor without matching data types."""
    processor = SimpleProcessor(data_parsers=[FakeParser])
    with pytest.raises(ProcessorError) as e_info:
        processor.process(fake_data_for_combined, EXTENDED_DATA)
    assert "None of the supported parsers for processor SimpleProcessor (FakeParser)" in str(e_info)


def test_combinedprocessor_multiple_data():
    """Tests CombinedProcessor wrong parsed data, with multiple entities."""
    processor = CombinedProcessor(data_parsers=[FakeParser])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data, EXTENDED_DATA)
        assert mock_maintenance.call_count == len(PARSED_DATA)
        for parsed_data_element in PARSED_DATA:
            parsed_data_element.update(EXTENDED_DATA)
            mock_maintenance.assert_any_call(**parsed_data_element)


def test_combinedprocessor():
    """Tests CombinedProcessor."""
    processor = CombinedProcessor(data_parsers=[FakeParser0, FakeParser1])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data_for_combined, EXTENDED_DATA)
        assert mock_maintenance.call_count == 1
        mock_maintenance.assert_any_call(**{**PARSED_DATA[0], **PARSED_DATA[1], **EXTENDED_DATA})


def test_combinedprocessor_missing_data():
    """Tests CombinedProcessor when there is not enough info to create a Maintenance."""
    processor = CombinedProcessor(data_parsers=[FakeParser0, FakeParser1])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        mock_maintenance.side_effect = ValidationError(errors=["whatever"], model=Maintenance)
        with pytest.raises(ProcessorError) as e_info:
            # Using the fake_data that returns mutliple maintenances that are not expected in this processor type
            processor.process(fake_data_for_combined, EXTENDED_DATA)

        assert "Not enough information available to create a Maintenance notification" in str(e_info)


def test_combinedprocessor_bleed():
    """Test CombinedProcessor to make sure that information from one processing doesn't bleed over to another."""
    processor = CombinedProcessor(data_parsers=[FakeParser0, FakeParser1])

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data_for_combined, EXTENDED_DATA)
        assert mock_maintenance.call_count == 1
        mock_maintenance.assert_called_with(**{**PARSED_DATA[0], **PARSED_DATA[1], **EXTENDED_DATA})

    with patch("circuit_maintenance_parser.processor.Maintenance") as mock_maintenance:
        processor.process(fake_data_type_0, EXTENDED_DATA)
        assert mock_maintenance.call_count == 1
        mock_maintenance.assert_called_with(**{**PARSED_DATA[0], **EXTENDED_DATA})

Tests for Processor. # pylint: disable=unused-argument # Fake data used for SimpleProcessor # Fake data used for CombinedProcessor Tests SimpleProcessor. Tests SimpleProcessor without matching data types. Tests CombinedProcessor wrong parsed data, with multiple entities. Tests CombinedProcessor. Tests CombinedProcessor when there is not enough info to create a Maintenance. # Using the fake_data that returns mutliple maintenances that are not expected in this processor type Test CombinedProcessor to make sure that information from one processing doesn't bleed over to another.