Move to Flask to fix #17
This commit is contained in:
parent
e0a6c37031
commit
ed5ba680da
23
alertify.py
23
alertify.py
|
@ -8,7 +8,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from src import alertify
|
from src import Alertify
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
@ -16,10 +16,10 @@ if __name__ == '__main__':
|
||||||
"""
|
"""
|
||||||
Function to parse the CLI
|
Function to parse the CLI
|
||||||
"""
|
"""
|
||||||
maxlen = max([len(key) for key in alertify.Config.defaults()])
|
maxlen = max([len(key) for key in Alertify.Config.defaults()])
|
||||||
defaults = [
|
defaults = [
|
||||||
f' * {key.upper().ljust(maxlen)} (default: {val if val != "" else "None"})'
|
f' * {key.upper().ljust(maxlen)} (default: {val if val != "" else "None"})'
|
||||||
for key, val in alertify.Config.defaults().items()
|
for key, val in Alertify.Config.defaults().items()
|
||||||
]
|
]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
@ -56,8 +56,8 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
args = parse_cli()
|
args = parse_cli()
|
||||||
|
|
||||||
# forwarder = alertify.Alertify(args.config)
|
alertify = Alertify.Alertify()
|
||||||
forwarder = alertify.Alertify()
|
alertify.configure(args.config)
|
||||||
|
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
# Calculate logging level
|
# Calculate logging level
|
||||||
|
@ -65,21 +65,22 @@ if __name__ == '__main__':
|
||||||
# Config :: Verbose: 0 = WARNING, 1 = INFO, 2 = DEBUG
|
# Config :: Verbose: 0 = WARNING, 1 = INFO, 2 = DEBUG
|
||||||
# Logging :: Loglevel: 30 = WARNING, 20 = INFO, 10 = DEBUG
|
# Logging :: Loglevel: 30 = WARNING, 20 = INFO, 10 = DEBUG
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.setLevel(max(logging.WARNING - (forwarder.config.verbose * 10), 10))
|
logger.setLevel(max(logging.WARNING - (alertify.config.verbose * 10), 10))
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
|
|
||||||
if args.healthcheck:
|
if args.healthcheck:
|
||||||
# Invert the sense of 'healthy' for Unix CLI usage
|
# Invert the sense of 'healthy' for Unix CLI usage
|
||||||
return not forwarder.healthcheck.report()
|
_, status = alertify.healthcheck()
|
||||||
|
return status == 200
|
||||||
|
|
||||||
logging.info('Version: %s', alertify.__version__)
|
logging.info('Version: %s', Alertify.__version__)
|
||||||
|
|
||||||
if forwarder.config.verbose:
|
if alertify.config.verbose:
|
||||||
logging.debug('Parsed config:')
|
logging.debug('Parsed config:')
|
||||||
for key, val in forwarder.config.items():
|
for key, val in alertify.config.items():
|
||||||
logging.debug('%s: %s', key, val)
|
logging.debug('%s: %s', key, val)
|
||||||
|
|
||||||
forwarder.server.listen_and_run()
|
alertify.run()
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file
|
# To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file
|
||||||
|
flask
|
||||||
|
flask-classful
|
||||||
pyyaml
|
pyyaml
|
||||||
|
|
96
src/Alertify/__init__.py
Normal file
96
src/Alertify/__init__.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
"""
|
||||||
|
Alertify module to act as a bridge between Prometheus Alertmanager and Gotify
|
||||||
|
"""
|
||||||
|
# pylint: disable=invalid-name
|
||||||
|
|
||||||
|
__author__ = 'Scott Wallace'
|
||||||
|
__email__ = 'scott@wallace.sh'
|
||||||
|
__maintainer__ = 'Scott Wallace'
|
||||||
|
|
||||||
|
__version__ = '2.0'
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import werkzeug.exceptions
|
||||||
|
from flask import Flask, request, request_started
|
||||||
|
from flask_classful import FlaskView, route
|
||||||
|
|
||||||
|
from .config import Config
|
||||||
|
from .gotify import Gotify
|
||||||
|
from .health import Healthcheck
|
||||||
|
from .messaging import MessageHandler
|
||||||
|
|
||||||
|
webapp = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Alertify(FlaskView):
|
||||||
|
"""
|
||||||
|
Main alertify class
|
||||||
|
"""
|
||||||
|
|
||||||
|
route_base = '/'
|
||||||
|
trailing_slash = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.configure()
|
||||||
|
|
||||||
|
def configure(self, configfile=None):
|
||||||
|
"""
|
||||||
|
Configure the object from a configfile
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_ = request.args
|
||||||
|
raise werkzeug.exceptions.NotFound
|
||||||
|
except RuntimeError:
|
||||||
|
self.config = Config(configfile)
|
||||||
|
self.gotify = Gotify(
|
||||||
|
self.config.gotify_server,
|
||||||
|
self.config.gotify_port,
|
||||||
|
self.config.gotify_key_app,
|
||||||
|
self.config.gotify_key_client,
|
||||||
|
)
|
||||||
|
self.msg_hndlr = MessageHandler(
|
||||||
|
self.gotify,
|
||||||
|
self.config.disable_resolved,
|
||||||
|
self.config.delete_onresolve,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Listen on port and run webserver
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_ = request.args
|
||||||
|
raise werkzeug.exceptions.NotFound
|
||||||
|
except RuntimeError:
|
||||||
|
webapp.run(host='0.0.0.0', port=self.config.listen_port)
|
||||||
|
|
||||||
|
@route('/alert', methods=['POST'])
|
||||||
|
def alert(self):
|
||||||
|
"""
|
||||||
|
Handle the alerts from Alertmanager
|
||||||
|
"""
|
||||||
|
message = request.get_json()
|
||||||
|
|
||||||
|
logging.debug(
|
||||||
|
'Received from Alertmanager:\n%s',
|
||||||
|
json.dumps(message, indent=2),
|
||||||
|
)
|
||||||
|
|
||||||
|
for alertmsg in message['alerts']:
|
||||||
|
response = self.msg_hndlr.process(alertmsg)
|
||||||
|
try:
|
||||||
|
return response['reason'], response['status']
|
||||||
|
except UnboundLocalError:
|
||||||
|
return '', 204
|
||||||
|
|
||||||
|
def healthcheck(self):
|
||||||
|
"""
|
||||||
|
Perform a healthcheck and return the results
|
||||||
|
"""
|
||||||
|
response = Healthcheck(self.gotify).gotify_alive()
|
||||||
|
return response['reason'], response['status']
|
||||||
|
|
||||||
|
|
||||||
|
Alertify.register(webapp)
|
|
@ -64,7 +64,7 @@ class Gotify:
|
||||||
"""
|
"""
|
||||||
Method to delete a message from the Gotify server
|
Method to delete a message from the Gotify server
|
||||||
"""
|
"""
|
||||||
logging.info('Deleting message ID: %s', msg_id)
|
logging.debug('Deleting message ID: %s', msg_id)
|
||||||
return self._call('DELETE', f'/message/{msg_id}')
|
return self._call('DELETE', f'/message/{msg_id}')
|
||||||
|
|
||||||
def find_byfingerprint(self, message):
|
def find_byfingerprint(self, message):
|
|
@ -11,16 +11,6 @@ class Healthcheck:
|
||||||
def __init__(self, gotify_client):
|
def __init__(self, gotify_client):
|
||||||
self.gotify = gotify_client
|
self.gotify = gotify_client
|
||||||
|
|
||||||
def report(self):
|
|
||||||
"""
|
|
||||||
Simple method to return a boolean state of the general health
|
|
||||||
"""
|
|
||||||
return all(
|
|
||||||
[
|
|
||||||
self.gotify_alive(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def gotify_alive(self):
|
def gotify_alive(self):
|
||||||
"""
|
"""
|
||||||
Simple method to return the Gotify healthcheck response
|
Simple method to return the Gotify healthcheck response
|
|
@ -30,11 +30,11 @@ class MessageHandler:
|
||||||
if self.delete_onresolve:
|
if self.delete_onresolve:
|
||||||
for alert_id in self.gotify.find_byfingerprint(alert):
|
for alert_id in self.gotify.find_byfingerprint(alert):
|
||||||
if not self.gotify.delete(alert_id):
|
if not self.gotify.delete(alert_id):
|
||||||
logging.error('There was a problem removing message ID %d', alert_id)
|
logging.error(
|
||||||
return {
|
'There was a problem removing message ID %d',
|
||||||
'status': 200,
|
alert_id,
|
||||||
'reason': 'Message deletion complete'
|
)
|
||||||
}
|
return {'status': 200, 'reason': 'Message deletion complete'}
|
||||||
|
|
||||||
prefix = 'resolved'
|
prefix = 'resolved'
|
||||||
else:
|
else:
|
|
@ -1,41 +0,0 @@
|
||||||
"""
|
|
||||||
Alertify module to act as a bridge between Prometheus Alertmanager and Gotify
|
|
||||||
"""
|
|
||||||
|
|
||||||
__author__ = 'Scott Wallace'
|
|
||||||
__email__ = 'scott@wallace.sh'
|
|
||||||
__maintainer__ = 'Scott Wallace'
|
|
||||||
|
|
||||||
__version__ = '1.6'
|
|
||||||
|
|
||||||
from .config import Config
|
|
||||||
from .gotify import Gotify
|
|
||||||
from .server import Server
|
|
||||||
from .healthcheck import Healthcheck
|
|
||||||
from .messaging import MessageHandler
|
|
||||||
|
|
||||||
|
|
||||||
class Alertify:
|
|
||||||
"""
|
|
||||||
Class for Alertify
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, configfile=None):
|
|
||||||
self.config = Config(configfile)
|
|
||||||
self.gotify = Gotify(
|
|
||||||
self.config.gotify_server,
|
|
||||||
self.config.gotify_port,
|
|
||||||
self.config.gotify_key_app,
|
|
||||||
self.config.gotify_key_app,
|
|
||||||
)
|
|
||||||
self.message_handler = MessageHandler(
|
|
||||||
self.gotify,
|
|
||||||
self.config.disable_resolved,
|
|
||||||
self.config.delete_onresolve,
|
|
||||||
)
|
|
||||||
self.healthcheck = Healthcheck(self.gotify)
|
|
||||||
self.server = Server(
|
|
||||||
self.config.listen_port,
|
|
||||||
self.message_handler,
|
|
||||||
self.healthcheck,
|
|
||||||
)
|
|
|
@ -1,81 +0,0 @@
|
||||||
"""
|
|
||||||
Module to act as a bridge between Prometheus Alertmanager and Gotify
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
|
||||||
"""
|
|
||||||
Class to handle the webserver for Alertify
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, port, message_handler, healthcheck):
|
|
||||||
self.port = port
|
|
||||||
self.message_handler = message_handler
|
|
||||||
self.healthcheck = healthcheck
|
|
||||||
|
|
||||||
def listen_and_run(self):
|
|
||||||
"""
|
|
||||||
Method to bind to the port and run indefinitely
|
|
||||||
"""
|
|
||||||
logging.info('Starting web server on port %d', self.port)
|
|
||||||
|
|
||||||
# FIXME: Find a better way to handle the injection of these values
|
|
||||||
http_handler = self.HTTPHandler
|
|
||||||
http_handler.message_handler = self.message_handler
|
|
||||||
http_handler.healthcheck = self.healthcheck
|
|
||||||
|
|
||||||
try:
|
|
||||||
with HTTPServer(('', self.port), http_handler) as webserver:
|
|
||||||
webserver.serve_forever()
|
|
||||||
return True
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logging.info('Exiting')
|
|
||||||
|
|
||||||
class HTTPHandler(SimpleHTTPRequestHandler):
|
|
||||||
"""
|
|
||||||
Class to handle the HTTP requests from a client
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _respond(self, status, message):
|
|
||||||
"""
|
|
||||||
Method to output a simple HTTP status and string to the client
|
|
||||||
"""
|
|
||||||
self.send_response(int(status) or 500)
|
|
||||||
self.end_headers()
|
|
||||||
self.wfile.write(bytes(str(message).encode()))
|
|
||||||
|
|
||||||
def do_GET(self): # pylint: disable=invalid-name
|
|
||||||
"""
|
|
||||||
Method to handle GET requests
|
|
||||||
"""
|
|
||||||
if self.path == '/healthcheck':
|
|
||||||
response = self.healthcheck.gotify_alive()
|
|
||||||
self._respond(response['status'], response['reason'])
|
|
||||||
|
|
||||||
def do_POST(self): # pylint: disable=invalid-name
|
|
||||||
"""
|
|
||||||
Method to handle POST requests from AlertManager
|
|
||||||
"""
|
|
||||||
if self.path == '/alert':
|
|
||||||
try:
|
|
||||||
content_length = int(self.headers['Content-Length'])
|
|
||||||
message = json.loads(self.rfile.read(content_length).decode())
|
|
||||||
except json.decoder.JSONDecodeError as error:
|
|
||||||
logging.error('Bad JSON: %s', error)
|
|
||||||
self._respond(400, f'Bad JSON: {error}')
|
|
||||||
|
|
||||||
logging.debug(
|
|
||||||
'Received from Alertmanager:\n%s',
|
|
||||||
json.dumps(message, indent=2),
|
|
||||||
)
|
|
||||||
|
|
||||||
for alert in message['alerts']:
|
|
||||||
response = self.message_handler.process(alert)
|
|
||||||
try:
|
|
||||||
self._respond(response['status'], response['reason'])
|
|
||||||
except UnboundLocalError:
|
|
||||||
self._respond('204', '')
|
|
68
src/tests/Alertify/test___init__.py
Normal file
68
src/tests/Alertify/test___init__.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
"""Test"""
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import flask
|
||||||
|
|
||||||
|
import Alertify # pylint: disable=import-error
|
||||||
|
|
||||||
|
|
||||||
|
class AlertifyTest(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Tests for methods in the Alertify class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.alertify = Alertify.Alertify()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_configure(self):
|
||||||
|
"""Test"""
|
||||||
|
self.alertify.configure(None)
|
||||||
|
self.assertDictEqual(
|
||||||
|
self.alertify.config.defaults(),
|
||||||
|
Alertify.Config.defaults(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch('Alertify.messaging.MessageHandler.process')
|
||||||
|
def test_alert(self, mock_process):
|
||||||
|
"""Test"""
|
||||||
|
mock_process.return_value = {
|
||||||
|
'status': 200,
|
||||||
|
'reason': 'OK',
|
||||||
|
'json': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
with flask.Flask(__name__).test_request_context(
|
||||||
|
'/alert',
|
||||||
|
data='{"alerts": []}',
|
||||||
|
headers={'Content-type': 'application/json'},
|
||||||
|
):
|
||||||
|
self.assertTupleEqual(
|
||||||
|
self.alertify.alert(),
|
||||||
|
('', 204),
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch('Alertify.health.Healthcheck.gotify_alive')
|
||||||
|
def test_healthcheck(self, mock_gotify_alive):
|
||||||
|
"""Test"""
|
||||||
|
mock_gotify_alive.return_value = {
|
||||||
|
'status': 200,
|
||||||
|
'reason': 'OK',
|
||||||
|
'json': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertTupleEqual(
|
||||||
|
self.alertify.healthcheck(),
|
||||||
|
('OK', 200),
|
||||||
|
)
|
|
@ -1,7 +1,7 @@
|
||||||
"""Test"""
|
"""Test"""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from alertify import config # pylint: disable=import-error
|
from Alertify import config # pylint: disable=import-error
|
||||||
|
|
||||||
|
|
||||||
class ConfigTest(unittest.TestCase):
|
class ConfigTest(unittest.TestCase):
|
|
@ -1,10 +1,10 @@
|
||||||
"""
|
"""
|
||||||
Module to handle unit tests for the alertify.gotify module
|
Module to handle unit tests for the Alertify.gotify module
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from alertify import gotify # pylint: disable=import-error
|
from Alertify import gotify # pylint: disable=import-error
|
||||||
|
|
||||||
|
|
||||||
class GotifyTest(unittest.TestCase):
|
class GotifyTest(unittest.TestCase):
|
||||||
|
@ -45,7 +45,7 @@ class GotifyTest(unittest.TestCase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@patch('alertify.gotify.Gotify.messages')
|
@patch('Alertify.gotify.Gotify.messages')
|
||||||
def test_find_byfingerprint(self, mock_messages):
|
def test_find_byfingerprint(self, mock_messages):
|
||||||
"""Test"""
|
"""Test"""
|
||||||
mock_messages.return_value = [
|
mock_messages.return_value = [
|
|
@ -2,7 +2,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from alertify import healthcheck, gotify # pylint: disable=import-error
|
from Alertify import gotify, health # pylint: disable=import-error
|
||||||
|
|
||||||
|
|
||||||
class HealthcheckTest(unittest.TestCase):
|
class HealthcheckTest(unittest.TestCase):
|
||||||
|
@ -12,7 +12,7 @@ class HealthcheckTest(unittest.TestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.healthcheck = healthcheck.Healthcheck(gotify.Gotify('', 0, '', ''))
|
cls.healthcheck = health.Healthcheck(gotify.Gotify('', 0, '', ''))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
|
@ -24,21 +24,10 @@ class HealthcheckTest(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@patch('alertify.healthcheck.Healthcheck.gotify_alive')
|
@patch('Alertify.health.Healthcheck.gotify_alive')
|
||||||
def test_report(self, mock_healthcheck):
|
def test_gotify_alive(self, mock_gotify_alive):
|
||||||
"""Test"""
|
"""Test"""
|
||||||
mock_healthcheck.return_value = {
|
mock_gotify_alive.return_value = {
|
||||||
'status': 200,
|
|
||||||
'reason': 'OK',
|
|
||||||
'json': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.assertTrue(self.healthcheck.report())
|
|
||||||
|
|
||||||
@patch('alertify.healthcheck.Healthcheck.gotify_alive')
|
|
||||||
def test_gotify_alive(self, mock_healthcheck):
|
|
||||||
"""Test"""
|
|
||||||
mock_healthcheck.return_value = {
|
|
||||||
'status': 200,
|
'status': 200,
|
||||||
'reason': 'OK',
|
'reason': 'OK',
|
||||||
'json': None,
|
'json': None,
|
|
@ -2,7 +2,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from alertify import messaging, gotify # pylint: disable=import-error
|
from Alertify import gotify, messaging # pylint: disable=import-error
|
||||||
|
|
||||||
|
|
||||||
class MessageHandlerTest(unittest.TestCase):
|
class MessageHandlerTest(unittest.TestCase):
|
||||||
|
@ -24,7 +24,7 @@ class MessageHandlerTest(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@patch('alertify.gotify.Gotify.send_alert')
|
@patch('Alertify.gotify.Gotify.send_alert')
|
||||||
def test_process(self, mock_send_alert):
|
def test_process(self, mock_send_alert):
|
||||||
"""Test"""
|
"""Test"""
|
||||||
mock_send_alert.return_value = {
|
mock_send_alert.return_value = {
|
|
@ -1,22 +0,0 @@
|
||||||
"""Test"""
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class AlertifyTest(unittest.TestCase):
|
|
||||||
"""
|
|
||||||
Tests for methods in the Alertify class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
|
@ -1,32 +0,0 @@
|
||||||
"""Test"""
|
|
||||||
import unittest
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from alertify import server # pylint: disable=import-error
|
|
||||||
|
|
||||||
|
|
||||||
class ServerTest(unittest.TestCase):
|
|
||||||
"""
|
|
||||||
Tests for methods in the Server class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
cls.server = server.Server(0, None, None)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@patch('http.server.HTTPServer.serve_forever')
|
|
||||||
def test_listen_and_run(self, mock_serve_forever):
|
|
||||||
"""Test"""
|
|
||||||
mock_serve_forever.return_value = True
|
|
||||||
|
|
||||||
self.assertTrue(self.server.listen_and_run())
|
|
Loading…
Reference in a new issue