From 5679bdaa52fdb2e5597a1c1d6bd93fb64231dd6e Mon Sep 17 00:00:00 2001 From: mtakaki Date: Mon, 10 Feb 2020 23:51:02 -0800 Subject: [PATCH] 83 (#84) * #83 - Fixing the bug that was preventing the status update * #83 - Refactoring unit tests for configuration to ensure we catch more cases --- cachet_url_monitor/configuration.py | 140 +-------------- cachet_url_monitor/exceptions.py | 10 ++ cachet_url_monitor/expectation.py | 127 ++++++++++++++ cachet_url_monitor/status.py | 1 + dev_requirements.txt | 2 +- setup.py | 2 +- tests/test_configuration.py | 256 +++++++++++++++------------- tests/test_expectation.py | 15 +- 8 files changed, 299 insertions(+), 254 deletions(-) create mode 100644 cachet_url_monitor/expectation.py diff --git a/cachet_url_monitor/configuration.py b/cachet_url_monitor/configuration.py index 7a94ea7..006d0e9 100644 --- a/cachet_url_monitor/configuration.py +++ b/cachet_url_monitor/configuration.py @@ -1,8 +1,6 @@ #!/usr/bin/env python -import abc import copy import logging -import re import time from typing import Dict @@ -11,6 +9,8 @@ from yaml import dump import cachet_url_monitor.status as st from cachet_url_monitor.client import CachetClient, normalize_url +from cachet_url_monitor.exceptions import ConfigurationValidationError +from cachet_url_monitor.expectation import Expectation from cachet_url_monitor.status import ComponentStatus # This is the mandatory fields that must be in the configuration file in this @@ -18,16 +18,6 @@ from cachet_url_monitor.status import ComponentStatus configuration_mandatory_fields = ['url', 'method', 'timeout', 'expectation', 'component_id', 'frequency'] -class ConfigurationValidationError(Exception): - """Exception raised when there's a validation error.""" - - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) - - class Configuration(object): """Represents a configuration file, but it also includes the functionality of assessing the API and pushing the results to cachet. @@ -53,6 +43,7 @@ class Configuration(object): status: ComponentStatus previous_status: ComponentStatus + message: str def __init__(self, config, endpoint_index: int, client: CachetClient, token: str): self.endpoint_index = endpoint_index @@ -167,13 +158,13 @@ class Configuration(object): return # We initially assume the API is healthy. - self.status: ComponentStatus = st.ComponentStatus.OPERATIONAL + self.status = st.ComponentStatus.OPERATIONAL self.message = '' for expectation in self.expectations: status: ComponentStatus = expectation.get_status(self.request) # The greater the status is, the worse the state of the API is. - if status.value >= self.status.value: + if status.value > self.status.value: self.status = status self.message = expectation.get_message(self.request) self.logger.info(self.message) @@ -223,7 +214,6 @@ class Configuration(object): if self.status == api_component_status: return - self.status = api_component_status component_request = self.client.push_status(self.component_id, self.status) if component_request.ok: @@ -278,123 +268,3 @@ class Configuration(object): else: self.logger.warning( f'Incident upload failed with status [{incident_request.status_code}], message: "{self.message}"') - - -class Expectation(object): - """Base class for URL result expectations. Any new expectation should extend - this class and the name added to create() method. - """ - - @staticmethod - def create(configuration): - """Creates a list of expectations based on the configuration types - list. - """ - # If a need expectation is created, this is where we need to add it. - expectations = { - 'HTTP_STATUS': HttpStatus, - 'LATENCY': Latency, - 'REGEX': Regex - } - if configuration['type'] not in expectations: - raise ConfigurationValidationError(f"Invalid type: {configuration['type']}") - - return expectations.get(configuration['type'])(configuration) - - def __init__(self, configuration): - self.incident_status = self.parse_incident_status(configuration) - - @abc.abstractmethod - def get_status(self, response) -> ComponentStatus: - """Returns the status of the API, following cachet's component status - documentation: https://docs.cachethq.io/docs/component-statuses - """ - - @abc.abstractmethod - def get_message(self, response) -> str: - """Gets the error message.""" - - @abc.abstractmethod - def get_default_incident(self): - """Returns the default status when this incident happens.""" - - def parse_incident_status(self, configuration) -> ComponentStatus: - return st.INCIDENT_MAP.get(configuration.get('incident', None), self.get_default_incident()) - - -class HttpStatus(Expectation): - def __init__(self, configuration): - self.status_range = HttpStatus.parse_range(configuration['status_range']) - super(HttpStatus, self).__init__(configuration) - - @staticmethod - def parse_range(range_string): - if isinstance(range_string, int): - # This happens when there's no range and no dash character, it will be parsed as int already. - return range_string, range_string + 1 - - statuses = range_string.split("-") - if len(statuses) == 1: - # When there was no range given, we should treat the first number as a single status check. - return int(statuses[0]), int(statuses[0]) + 1 - else: - # We shouldn't look into more than one value, as this is a range value. - return int(statuses[0]), int(statuses[1]) - - def get_status(self, response) -> ComponentStatus: - if self.status_range[0] <= response.status_code < self.status_range[1]: - return st.ComponentStatus.OPERATIONAL - else: - return self.incident_status - - def get_default_incident(self): - return st.ComponentStatus.PARTIAL_OUTAGE - - def get_message(self, response): - return f'Unexpected HTTP status ({response.status_code})' - - def __str__(self): - return repr(f'HTTP status range: [{self.status_range[0]}, {self.status_range[1]}[') - - -class Latency(Expectation): - def __init__(self, configuration): - self.threshold = configuration['threshold'] - super(Latency, self).__init__(configuration) - - def get_status(self, response) -> ComponentStatus: - if response.elapsed.total_seconds() <= self.threshold: - return st.ComponentStatus.OPERATIONAL - else: - return self.incident_status - - def get_default_incident(self): - return st.ComponentStatus.PERFORMANCE_ISSUES - - def get_message(self, response): - return 'Latency above threshold: %.4f seconds' % (response.elapsed.total_seconds(),) - - def __str__(self): - return repr('Latency threshold: %.4f seconds' % (self.threshold,)) - - -class Regex(Expectation): - def __init__(self, configuration): - self.regex_string = configuration['regex'] - self.regex = re.compile(configuration['regex'], re.UNICODE + re.DOTALL) - super(Regex, self).__init__(configuration) - - def get_status(self, response) -> ComponentStatus: - if self.regex.match(response.text): - return st.ComponentStatus.OPERATIONAL - else: - return self.incident_status - - def get_default_incident(self): - return st.ComponentStatus.PARTIAL_OUTAGE - - def get_message(self, response): - return 'Regex did not match anything in the body' - - def __str__(self): - return repr(f'Regex: {self.regex_string}') diff --git a/cachet_url_monitor/exceptions.py b/cachet_url_monitor/exceptions.py index 9ede85f..b481555 100644 --- a/cachet_url_monitor/exceptions.py +++ b/cachet_url_monitor/exceptions.py @@ -17,3 +17,13 @@ class MetricNonexistentError(Exception): def __str__(self): return repr(f'Metric with id [{self.metric_id}] does not exist.') + + +class ConfigurationValidationError(Exception): + """Exception raised when there's a validation error.""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) \ No newline at end of file diff --git a/cachet_url_monitor/expectation.py b/cachet_url_monitor/expectation.py new file mode 100644 index 0000000..bb4927d --- /dev/null +++ b/cachet_url_monitor/expectation.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +import abc +import re + +import cachet_url_monitor.status as st +from cachet_url_monitor.exceptions import ConfigurationValidationError +from cachet_url_monitor.status import ComponentStatus + + +class Expectation(object): + """Base class for URL result expectations. Any new expectation should extend + this class and the name added to create() method. + """ + + @staticmethod + def create(configuration): + """Creates a list of expectations based on the configuration types + list. + """ + # If a need expectation is created, this is where we need to add it. + expectations = { + 'HTTP_STATUS': HttpStatus, + 'LATENCY': Latency, + 'REGEX': Regex + } + if configuration['type'] not in expectations: + raise ConfigurationValidationError(f"Invalid type: {configuration['type']}") + + return expectations.get(configuration['type'])(configuration) + + def __init__(self, configuration): + self.incident_status = self.parse_incident_status(configuration) + + @abc.abstractmethod + def get_status(self, response) -> ComponentStatus: + """Returns the status of the API, following cachet's component status + documentation: https://docs.cachethq.io/docs/component-statuses + """ + + @abc.abstractmethod + def get_message(self, response) -> str: + """Gets the error message.""" + + @abc.abstractmethod + def get_default_incident(self): + """Returns the default status when this incident happens.""" + + def parse_incident_status(self, configuration) -> ComponentStatus: + return st.INCIDENT_MAP.get(configuration.get('incident', None), self.get_default_incident()) + + +class HttpStatus(Expectation): + def __init__(self, configuration): + self.status_range = HttpStatus.parse_range(configuration['status_range']) + super(HttpStatus, self).__init__(configuration) + + @staticmethod + def parse_range(range_string): + if isinstance(range_string, int): + # This happens when there's no range and no dash character, it will be parsed as int already. + return range_string, range_string + 1 + + statuses = range_string.split("-") + if len(statuses) == 1: + # When there was no range given, we should treat the first number as a single status check. + return int(statuses[0]), int(statuses[0]) + 1 + else: + # We shouldn't look into more than one value, as this is a range value. + return int(statuses[0]), int(statuses[1]) + + def get_status(self, response) -> ComponentStatus: + if self.status_range[0] <= response.status_code < self.status_range[1]: + return st.ComponentStatus.OPERATIONAL + else: + return self.incident_status + + def get_default_incident(self): + return st.ComponentStatus.PARTIAL_OUTAGE + + def get_message(self, response): + return f'Unexpected HTTP status ({response.status_code})' + + def __str__(self): + return repr(f'HTTP status range: [{self.status_range[0]}, {self.status_range[1]}[') + + +class Latency(Expectation): + def __init__(self, configuration): + self.threshold = configuration['threshold'] + super(Latency, self).__init__(configuration) + + def get_status(self, response) -> ComponentStatus: + if response.elapsed.total_seconds() <= self.threshold: + return st.ComponentStatus.OPERATIONAL + else: + return self.incident_status + + def get_default_incident(self): + return st.ComponentStatus.PERFORMANCE_ISSUES + + def get_message(self, response): + return 'Latency above threshold: %.4f seconds' % (response.elapsed.total_seconds(),) + + def __str__(self): + return repr('Latency threshold: %.4f seconds' % (self.threshold,)) + + +class Regex(Expectation): + def __init__(self, configuration): + self.regex_string = configuration['regex'] + self.regex = re.compile(configuration['regex'], re.UNICODE + re.DOTALL) + super(Regex, self).__init__(configuration) + + def get_status(self, response) -> ComponentStatus: + if self.regex.match(response.text): + return st.ComponentStatus.OPERATIONAL + else: + return self.incident_status + + def get_default_incident(self): + return st.ComponentStatus.PARTIAL_OUTAGE + + def get_message(self, response): + return 'Regex did not match anything in the body' + + def __str__(self): + return repr(f'Regex: {self.regex_string}') diff --git a/cachet_url_monitor/status.py b/cachet_url_monitor/status.py index f230e02..a0a4ef2 100644 --- a/cachet_url_monitor/status.py +++ b/cachet_url_monitor/status.py @@ -7,6 +7,7 @@ from enum import Enum class ComponentStatus(Enum): + UNKNOWN = 0 OPERATIONAL = 1 PERFORMANCE_ISSUES = 2 PARTIAL_OUTAGE = 3 diff --git a/dev_requirements.txt b/dev_requirements.txt index 960b412..60685de 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -4,6 +4,6 @@ coveralls==1.10.0 ipython==7.11.1 mock==3.0.5 pudb==2019.2 -pytest==5.3.3 +pytest==5.3.5 pytest-cov==2.8.1 requests-mock==1.7.0 diff --git a/setup.py b/setup.py index d5bcb3b..2da7dc2 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages setup(name='cachet-url-monitor', - version='0.6.6', + version='0.6.7', description='Cachet URL monitor plugin', author='Mitsuo Takaki', author_email='mitsuotakaki@gmail.com', diff --git a/tests/test_configuration.py b/tests/test_configuration.py index c4ca09a..569de48 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1,6 +1,5 @@ #!/usr/bin/env python import sys -import unittest import mock import pytest @@ -8,151 +7,178 @@ import requests import requests_mock from yaml import load, SafeLoader -import cachet_url_monitor.status -from cachet_url_monitor.client import CachetClient import cachet_url_monitor.exceptions +import cachet_url_monitor.status sys.modules['logging'] = mock.Mock() from cachet_url_monitor.configuration import Configuration import os -class ConfigurationTest(unittest.TestCase): - client: CachetClient - configuration: Configuration +@pytest.fixture() +def mock_client(): + client = mock.Mock() + client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL + yield client - def setUp(self): - def getLogger(name): - self.mock_logger = mock.Mock() - return self.mock_logger - sys.modules['logging'].getLogger = getLogger - self.client = mock.Mock() - # We set the initial status to OPERATIONAL. - self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL - self.configuration = Configuration( - load(open(os.path.join(os.path.dirname(__file__), 'configs/config.yml'), 'rt'), SafeLoader), 0, self.client, - 'token2') +@pytest.fixture() +def config_file(): + with open(os.path.join(os.path.dirname(__file__), 'configs/config.yml'), 'rt') as yaml_file: + config_file_data = load(yaml_file, SafeLoader) + yield config_file_data - def test_init(self): - self.assertEqual(len(self.configuration.data), 2, 'Number of root elements in config.yml is incorrect') - self.assertEqual(len(self.configuration.expectations), 3, 'Number of expectations read from file is incorrect') - self.assertDictEqual(self.configuration.headers, {'X-Cachet-Token': 'token2'}, 'Header was not set correctly') - self.assertDictEqual(self.configuration.endpoint_header, {'SOME-HEADER': 'SOME-VALUE'}, 'Header is incorrect') - @requests_mock.mock() - def test_evaluate(self, m): +@pytest.fixture() +def multiple_urls_config_file(): + with open(os.path.join(os.path.dirname(__file__), 'configs/config_multiple_urls.yml'), 'rt') as yaml_file: + config_file_data = load(yaml_file, SafeLoader) + yield config_file_data + + +@pytest.fixture() +def invalid_config_file(): + with open(os.path.join(os.path.dirname(__file__), 'configs/config_invalid_type.yml'), 'rt') as yaml_file: + config_file_data = load(yaml_file, SafeLoader) + yield config_file_data + + +@pytest.fixture() +def mock_logger(): + mock_logger = mock.Mock() + + def getLogger(name): + return mock_logger + + sys.modules['logging'].getLogger = getLogger + yield mock_logger + + +@pytest.fixture() +def configuration(config_file, mock_client, mock_logger): + yield Configuration(config_file, 0, mock_client, 'token2') + + +@pytest.fixture() +def multiple_urls_configuration(multiple_urls_config_file, mock_client, mock_logger): + yield [Configuration(multiple_urls_config_file, index, mock_client, 'token2') for index in + range(len(multiple_urls_config_file['endpoints']))] + + +def test_init(configuration): + assert len(configuration.data) == 2, 'Number of root elements in config.yml is incorrect' + assert len(configuration.expectations) == 3, 'Number of expectations read from file is incorrect' + assert configuration.headers == {'X-Cachet-Token': 'token2'}, 'Header was not set correctly' + assert configuration.endpoint_header == {'SOME-HEADER': 'SOME-VALUE'}, 'Header is incorrect' + + +def test_init_unknown_status(config_file, mock_client): + mock_client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.UNKNOWN + configuration = Configuration(config_file, 0, mock_client, 'token2') + + assert configuration.previous_status == cachet_url_monitor.status.ComponentStatus.UNKNOWN + + +def test_evaluate(configuration): + with requests_mock.mock() as m: m.get('http://localhost:8080/swagger', text='') - self.configuration.evaluate() + configuration.evaluate() - self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.OPERATIONAL, - 'Component status set incorrectly') + assert configuration.status == cachet_url_monitor.status.ComponentStatus.OPERATIONAL, 'Component status set incorrectly' - @requests_mock.mock() - def test_evaluate_without_header(self, m): + +def test_evaluate_without_header(configuration): + with requests_mock.mock() as m: m.get('http://localhost:8080/swagger', text='') - self.configuration.evaluate() + configuration.evaluate() - self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.OPERATIONAL, - 'Component status set incorrectly') + assert configuration.status == cachet_url_monitor.status.ComponentStatus.OPERATIONAL, 'Component status set incorrectly' - @requests_mock.mock() - def test_evaluate_with_failure(self, m): + +def test_evaluate_with_failure(configuration): + with requests_mock.mock() as m: m.get('http://localhost:8080/swagger', text='', status_code=400) - self.configuration.evaluate() + configuration.evaluate() - self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.MAJOR_OUTAGE, - 'Component status set incorrectly or custom incident status is incorrectly parsed') + assert configuration.status == cachet_url_monitor.status.ComponentStatus.MAJOR_OUTAGE, 'Component status set incorrectly or custom incident status is incorrectly parsed' - @requests_mock.mock() - def test_evaluate_with_timeout(self, m): + +def test_evaluate_with_timeout(configuration, mock_logger): + with requests_mock.mock() as m: m.get('http://localhost:8080/swagger', exc=requests.Timeout) - self.configuration.evaluate() + configuration.evaluate() - self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.PERFORMANCE_ISSUES, - 'Component status set incorrectly') - self.mock_logger.warning.assert_called_with('Request timed out') + assert configuration.status == cachet_url_monitor.status.ComponentStatus.PERFORMANCE_ISSUES, 'Component status set incorrectly' + mock_logger.warning.assert_called_with('Request timed out') - @requests_mock.mock() - def test_evaluate_with_connection_error(self, m): + +def test_evaluate_with_connection_error(configuration, mock_logger): + with requests_mock.mock() as m: m.get('http://localhost:8080/swagger', exc=requests.ConnectionError) - self.configuration.evaluate() + configuration.evaluate() - self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, - 'Component status set incorrectly') - self.mock_logger.warning.assert_called_with('The URL is unreachable: GET http://localhost:8080/swagger') + assert configuration.status == cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, 'Component status set incorrectly' + mock_logger.warning.assert_called_with('The URL is unreachable: GET http://localhost:8080/swagger') - @requests_mock.mock() - def test_evaluate_with_http_error(self, m): + +def test_evaluate_with_http_error(configuration, mock_logger): + with requests_mock.mock() as m: m.get('http://localhost:8080/swagger', exc=requests.HTTPError) - self.configuration.evaluate() + configuration.evaluate() - self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, - 'Component status set incorrectly') - self.mock_logger.exception.assert_called_with('Unexpected HTTP response') - - def test_push_status(self): - self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL - push_status_response = mock.Mock() - self.client.push_status.return_value = push_status_response - push_status_response.ok = True - self.configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE - - self.configuration.push_status() - - self.client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) - - def test_push_status_with_failure(self): - self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL - push_status_response = mock.Mock() - self.client.push_status.return_value = push_status_response - push_status_response.ok = False - self.configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE - - self.configuration.push_status() - - self.client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) - - def test_push_status_same_status(self): - self.client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL - self.configuration.status = cachet_url_monitor.status.ComponentStatus.OPERATIONAL - - self.configuration.push_status() - - self.client.push_status.assert_not_called() + assert configuration.status == cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, 'Component status set incorrectly' + mock_logger.exception.assert_called_with('Unexpected HTTP response') -class ConfigurationMultipleUrlTest(unittest.TestCase): - @mock.patch.dict(os.environ, {'CACHET_TOKEN': 'token2'}) - def setUp(self): - config_yaml = load(open(os.path.join(os.path.dirname(__file__), 'configs/config_multiple_urls.yml'), 'rt'), - SafeLoader) - self.client = [] - self.configuration = [] +def test_push_status(configuration, mock_client): + mock_client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE + push_status_response = mock.Mock() + mock_client.push_status.return_value = push_status_response + push_status_response.ok = True + configuration.previous_status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE + configuration.status = cachet_url_monitor.status.ComponentStatus.OPERATIONAL - for index in range(len(config_yaml['endpoints'])): - client = mock.Mock() - self.client.append(client) - self.configuration.append(Configuration(config_yaml, index, client, 'token2')) + configuration.push_status() - def test_init(self): - expected_method = ['GET', 'POST'] - expected_url = ['http://localhost:8080/swagger', 'http://localhost:8080/bar'] - - for index in range(len(self.configuration)): - config = self.configuration[index] - self.assertEqual(len(config.data), 2, 'Number of root elements in config.yml is incorrect') - self.assertEqual(len(config.expectations), 1, 'Number of expectations read from file is incorrect') - self.assertDictEqual(config.headers, {'X-Cachet-Token': 'token2'}, 'Header was not set correctly') - - self.assertEqual(expected_method[index], config.endpoint_method) - self.assertEqual(expected_url[index], config.endpoint_url) + mock_client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) -class ConfigurationNegativeTest(unittest.TestCase): - @mock.patch.dict(os.environ, {'CACHET_TOKEN': 'token2'}) - def test_init(self): - with pytest.raises(cachet_url_monitor.configuration.ConfigurationValidationError): - self.configuration = Configuration( - load(open(os.path.join(os.path.dirname(__file__), 'configs/config_invalid_type.yml'), 'rt'), - SafeLoader), 0, mock.Mock(), 'token2') +def test_push_status_with_new_failure(configuration, mock_client): + mock_client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL + push_status_response = mock.Mock() + mock_client.push_status.return_value = push_status_response + push_status_response.ok = False + configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE + + configuration.push_status() + + mock_client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE) + + +def test_push_status_same_status(configuration, mock_client): + mock_client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL + configuration.status = cachet_url_monitor.status.ComponentStatus.OPERATIONAL + + configuration.push_status() + + mock_client.push_status.assert_not_called() + + +def test_init_multiple_urls(multiple_urls_configuration): + expected_method = ['GET', 'POST'] + expected_url = ['http://localhost:8080/swagger', 'http://localhost:8080/bar'] + + assert len(multiple_urls_configuration) == 2 + for index in range(len(multiple_urls_configuration)): + config = multiple_urls_configuration[index] + assert len(config.data) == 2, 'Number of root elements in config.yml is incorrect' + assert len(config.expectations) == 1, 'Number of expectations read from file is incorrect' + assert config.headers == {'X-Cachet-Token': 'token2'}, 'Header was not set correctly' + + assert expected_method[index] == config.endpoint_method + assert expected_url[index] == config.endpoint_url + + +def test_init_invalid_configuration(invalid_config_file, mock_client): + with pytest.raises(cachet_url_monitor.configuration.ConfigurationValidationError): + Configuration(invalid_config_file, 0, mock_client, 'token2') diff --git a/tests/test_expectation.py b/tests/test_expectation.py index 8d29c8f..77beca8 100644 --- a/tests/test_expectation.py +++ b/tests/test_expectation.py @@ -5,8 +5,7 @@ import unittest import mock import pytest -from cachet_url_monitor.configuration import HttpStatus, Regex -from cachet_url_monitor.configuration import Latency +from cachet_url_monitor.expectation import HttpStatus, Regex, Latency from cachet_url_monitor.status import ComponentStatus @@ -76,12 +75,24 @@ class HttpStatusTest(unittest.TestCase): assert self.expectation.get_status(request) == ComponentStatus.OPERATIONAL + def test_get_status_healthy_boundary(self): + request = mock.Mock() + request.status_code = 299 + + assert self.expectation.get_status(request) == ComponentStatus.OPERATIONAL + def test_get_status_unhealthy(self): request = mock.Mock() request.status_code = 400 assert self.expectation.get_status(request) == ComponentStatus.PARTIAL_OUTAGE + def test_get_status_unhealthy_boundary(self): + request = mock.Mock() + request.status_code = 300 + + assert self.expectation.get_status(request) == ComponentStatus.PARTIAL_OUTAGE + def test_get_message(self): request = mock.Mock() request.status_code = 400