5 Commits

Author SHA1 Message Date
Mitsuo Takaki
1c7fff8d92 Adding twine to dev_requirements 2020-02-11 00:14:40 -08:00
mtakaki
5679bdaa52 83 (#84)
* #83 - Fixing the bug that was preventing the status update

* #83 - Refactoring unit tests for configuration to ensure we catch more cases
2020-02-10 23:51:02 -08:00
Mitsuo Takaki
0a55b1f513 Bumping the version to reduce the risk of overwriting an existing release 2020-01-29 02:05:32 -08:00
Christian Strobel
bd610671fa Added os import (#82) 2020-01-29 02:02:06 -08:00
Mitsuo Takaki
46955addf1 Bumping the version to reduce the risk of overwriting an existing release 2020-01-28 01:43:54 -08:00
9 changed files with 301 additions and 254 deletions

View File

@@ -1,8 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
import abc
import copy import copy
import logging import logging
import re
import time import time
from typing import Dict from typing import Dict
@@ -11,6 +9,8 @@ from yaml import dump
import cachet_url_monitor.status as st import cachet_url_monitor.status as st
from cachet_url_monitor.client import CachetClient, normalize_url 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 from cachet_url_monitor.status import ComponentStatus
# This is the mandatory fields that must be in the configuration file in this # 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'] 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): class Configuration(object):
"""Represents a configuration file, but it also includes the functionality """Represents a configuration file, but it also includes the functionality
of assessing the API and pushing the results to cachet. of assessing the API and pushing the results to cachet.
@@ -53,6 +43,7 @@ class Configuration(object):
status: ComponentStatus status: ComponentStatus
previous_status: ComponentStatus previous_status: ComponentStatus
message: str
def __init__(self, config, endpoint_index: int, client: CachetClient, token: str): def __init__(self, config, endpoint_index: int, client: CachetClient, token: str):
self.endpoint_index = endpoint_index self.endpoint_index = endpoint_index
@@ -167,13 +158,13 @@ class Configuration(object):
return return
# We initially assume the API is healthy. # We initially assume the API is healthy.
self.status: ComponentStatus = st.ComponentStatus.OPERATIONAL self.status = st.ComponentStatus.OPERATIONAL
self.message = '' self.message = ''
for expectation in self.expectations: for expectation in self.expectations:
status: ComponentStatus = expectation.get_status(self.request) status: ComponentStatus = expectation.get_status(self.request)
# The greater the status is, the worse the state of the API is. # 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.status = status
self.message = expectation.get_message(self.request) self.message = expectation.get_message(self.request)
self.logger.info(self.message) self.logger.info(self.message)
@@ -223,7 +214,6 @@ class Configuration(object):
if self.status == api_component_status: if self.status == api_component_status:
return return
self.status = api_component_status
component_request = self.client.push_status(self.component_id, self.status) component_request = self.client.push_status(self.component_id, self.status)
if component_request.ok: if component_request.ok:
@@ -278,123 +268,3 @@ class Configuration(object):
else: else:
self.logger.warning( self.logger.warning(
f'Incident upload failed with status [{incident_request.status_code}], message: "{self.message}"') 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}')

View File

@@ -17,3 +17,13 @@ class MetricNonexistentError(Exception):
def __str__(self): def __str__(self):
return repr(f'Metric with id [{self.metric_id}] does not exist.') 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)

View File

@@ -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}')

View File

@@ -3,6 +3,7 @@ import logging
import sys import sys
import threading import threading
import time import time
import os
import schedule import schedule
from yaml import load, SafeLoader from yaml import load, SafeLoader

View File

@@ -7,6 +7,7 @@ from enum import Enum
class ComponentStatus(Enum): class ComponentStatus(Enum):
UNKNOWN = 0
OPERATIONAL = 1 OPERATIONAL = 1
PERFORMANCE_ISSUES = 2 PERFORMANCE_ISSUES = 2
PARTIAL_OUTAGE = 3 PARTIAL_OUTAGE = 3

View File

@@ -4,6 +4,7 @@ coveralls==1.10.0
ipython==7.11.1 ipython==7.11.1
mock==3.0.5 mock==3.0.5
pudb==2019.2 pudb==2019.2
pytest==5.3.3 pytest==5.3.5
pytest-cov==2.8.1 pytest-cov==2.8.1
requests-mock==1.7.0 requests-mock==1.7.0
twine==3.1.1

View File

@@ -3,7 +3,7 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
setup(name='cachet-url-monitor', setup(name='cachet-url-monitor',
version='0.6.4', version='0.6.7',
description='Cachet URL monitor plugin', description='Cachet URL monitor plugin',
author='Mitsuo Takaki', author='Mitsuo Takaki',
author_email='mitsuotakaki@gmail.com', author_email='mitsuotakaki@gmail.com',

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
import sys import sys
import unittest
import mock import mock
import pytest import pytest
@@ -8,151 +7,178 @@ import requests
import requests_mock import requests_mock
from yaml import load, SafeLoader 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.exceptions
import cachet_url_monitor.status
sys.modules['logging'] = mock.Mock() sys.modules['logging'] = mock.Mock()
from cachet_url_monitor.configuration import Configuration from cachet_url_monitor.configuration import Configuration
import os import os
class ConfigurationTest(unittest.TestCase): @pytest.fixture()
client: CachetClient def mock_client():
configuration: Configuration client = mock.Mock()
client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL
yield client
@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
@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 setUp(self):
def getLogger(name): def getLogger(name):
self.mock_logger = mock.Mock() return mock_logger
return self.mock_logger
sys.modules['logging'].getLogger = getLogger sys.modules['logging'].getLogger = getLogger
self.client = mock.Mock() yield mock_logger
# 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')
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() @pytest.fixture()
def test_evaluate(self, m): 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='<body>') m.get('http://localhost:8080/swagger', text='<body>')
self.configuration.evaluate() configuration.evaluate()
self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.OPERATIONAL, assert configuration.status == cachet_url_monitor.status.ComponentStatus.OPERATIONAL, 'Component status set incorrectly'
'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='<body>') m.get('http://localhost:8080/swagger', text='<body>')
self.configuration.evaluate() configuration.evaluate()
self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.OPERATIONAL, assert configuration.status == cachet_url_monitor.status.ComponentStatus.OPERATIONAL, 'Component status set incorrectly'
'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='<body>', status_code=400) m.get('http://localhost:8080/swagger', text='<body>', status_code=400)
self.configuration.evaluate() configuration.evaluate()
self.assertEqual(self.configuration.status, cachet_url_monitor.status.ComponentStatus.MAJOR_OUTAGE, assert configuration.status == cachet_url_monitor.status.ComponentStatus.MAJOR_OUTAGE, 'Component status set incorrectly or custom incident status is incorrectly parsed'
'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) 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, assert configuration.status == cachet_url_monitor.status.ComponentStatus.PERFORMANCE_ISSUES, 'Component status set incorrectly'
'Component status set incorrectly') mock_logger.warning.assert_called_with('Request timed out')
self.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) 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, assert configuration.status == cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, 'Component status set incorrectly'
'Component status set incorrectly') mock_logger.warning.assert_called_with('The URL is unreachable: GET http://localhost:8080/swagger')
self.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) 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, assert configuration.status == cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE, 'Component status set incorrectly'
'Component status set incorrectly') mock_logger.exception.assert_called_with('Unexpected HTTP response')
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 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() push_status_response = mock.Mock()
self.client.push_status.return_value = push_status_response mock_client.push_status.return_value = push_status_response
push_status_response.ok = True push_status_response.ok = True
self.configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE configuration.previous_status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE
configuration.status = cachet_url_monitor.status.ComponentStatus.OPERATIONAL
self.configuration.push_status() configuration.push_status()
self.client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) mock_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 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() push_status_response = mock.Mock()
self.client.push_status.return_value = push_status_response mock_client.push_status.return_value = push_status_response
push_status_response.ok = False push_status_response.ok = False
self.configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE configuration.status = cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE
self.configuration.push_status() configuration.push_status()
self.client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.OPERATIONAL) mock_client.push_status.assert_called_once_with(1, cachet_url_monitor.status.ComponentStatus.PARTIAL_OUTAGE)
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()
class ConfigurationMultipleUrlTest(unittest.TestCase): def test_push_status_same_status(configuration, mock_client):
@mock.patch.dict(os.environ, {'CACHET_TOKEN': 'token2'}) mock_client.get_component_status.return_value = cachet_url_monitor.status.ComponentStatus.OPERATIONAL
def setUp(self): configuration.status = cachet_url_monitor.status.ComponentStatus.OPERATIONAL
config_yaml = load(open(os.path.join(os.path.dirname(__file__), 'configs/config_multiple_urls.yml'), 'rt'),
SafeLoader)
self.client = []
self.configuration = []
for index in range(len(config_yaml['endpoints'])): configuration.push_status()
client = mock.Mock()
self.client.append(client)
self.configuration.append(Configuration(config_yaml, index, client, 'token2'))
def test_init(self): mock_client.push_status.assert_not_called()
def test_init_multiple_urls(multiple_urls_configuration):
expected_method = ['GET', 'POST'] expected_method = ['GET', 'POST']
expected_url = ['http://localhost:8080/swagger', 'http://localhost:8080/bar'] expected_url = ['http://localhost:8080/swagger', 'http://localhost:8080/bar']
for index in range(len(self.configuration)): assert len(multiple_urls_configuration) == 2
config = self.configuration[index] for index in range(len(multiple_urls_configuration)):
self.assertEqual(len(config.data), 2, 'Number of root elements in config.yml is incorrect') config = multiple_urls_configuration[index]
self.assertEqual(len(config.expectations), 1, 'Number of expectations read from file is incorrect') assert len(config.data) == 2, 'Number of root elements in config.yml is incorrect'
self.assertDictEqual(config.headers, {'X-Cachet-Token': 'token2'}, 'Header was not set correctly') 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'
self.assertEqual(expected_method[index], config.endpoint_method) assert expected_method[index] == config.endpoint_method
self.assertEqual(expected_url[index], config.endpoint_url) assert expected_url[index] == config.endpoint_url
class ConfigurationNegativeTest(unittest.TestCase): def test_init_invalid_configuration(invalid_config_file, mock_client):
@mock.patch.dict(os.environ, {'CACHET_TOKEN': 'token2'})
def test_init(self):
with pytest.raises(cachet_url_monitor.configuration.ConfigurationValidationError): with pytest.raises(cachet_url_monitor.configuration.ConfigurationValidationError):
self.configuration = Configuration( Configuration(invalid_config_file, 0, mock_client, 'token2')
load(open(os.path.join(os.path.dirname(__file__), 'configs/config_invalid_type.yml'), 'rt'),
SafeLoader), 0, mock.Mock(), 'token2')

View File

@@ -5,8 +5,7 @@ import unittest
import mock import mock
import pytest import pytest
from cachet_url_monitor.configuration import HttpStatus, Regex from cachet_url_monitor.expectation import HttpStatus, Regex, Latency
from cachet_url_monitor.configuration import Latency
from cachet_url_monitor.status import ComponentStatus from cachet_url_monitor.status import ComponentStatus
@@ -76,12 +75,24 @@ class HttpStatusTest(unittest.TestCase):
assert self.expectation.get_status(request) == ComponentStatus.OPERATIONAL 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): def test_get_status_unhealthy(self):
request = mock.Mock() request = mock.Mock()
request.status_code = 400 request.status_code = 400
assert self.expectation.get_status(request) == ComponentStatus.PARTIAL_OUTAGE 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): def test_get_message(self):
request = mock.Mock() request = mock.Mock()
request.status_code = 400 request.status_code = 400