#------------------------------------------------------------------------------- # Author: Lukasz Janyst <lukasz@jany.st> # Date: 27.11.2017 # # Licensed under the 3-Clause BSD License, see the LICENSE file for details. #------------------------------------------------------------------------------- import tempfile import shutil import os from twisted.application.service import MultiService from twisted.internet.defer import inlineCallbacks from scrapy_do.webservice import PublicHTMLRealm, get_web_app from twisted.trial import unittest from scrapy_do.app import ScrapyDoServiceMaker from unittest.mock import Mock, patch from .utils import web_retrieve_async from copy import deepcopy #------------------------------------------------------------------------------- def build_mock_config(data): def get(section, option, default, dtype): try: val = data[section][option] if isinstance(val, dtype): return val if isinstance(val, Exception): raise val if dtype == str: return str(val) raise ValueError except (KeyError, ValueError): if default is not None: return default raise mock = Mock() mock.get_bool.side_effect = lambda s, o, d=None: get(s, o, d, bool) mock.get_int.side_effect = lambda s, o, d=None: get(s, o, d, int) mock.get_float.side_effect = lambda s, o, d=None: get(s, o, d, float) mock.get_string.side_effect = lambda s, o, d=None: get(s, o, d, str) mock.get_options.side_effect = lambda s: data[s].items() return mock #------------------------------------------------------------------------------- default_config = { 'scrapy-do': { 'project-store': 'projects', 'job-slots': 2, 'completed-cap': 3 }, 'web': { 'interfaces': '127.0.0.1:7654', 'https': False, 'auth': False, 'cert': 'scrapy-do.crt', 'key': 'scrapy-do.key', 'chain': 'ca.crt' }, 'web-modules': { 'status.json': 'scrapy_do.webservice.Status', 'get-log': 'scrapy_do.webservice.GetLog' } } #------------------------------------------------------------------------------- class AppConfigTests(unittest.TestCase): #--------------------------------------------------------------------------- def setUp(self): current_path = os.path.dirname(__file__) cert_file = os.path.join(current_path, 'scrapy-do.crt') key_file = os.path.join(current_path, 'scrapy-do.key') chain_file = os.path.join(current_path, 'ca.crt') self.pstore_path = tempfile.mkdtemp() self.config = deepcopy(default_config) self.config['scrapy-do']['project-store'] = self.pstore_path self.config['web']['cert'] = cert_file self.config['web']['key'] = key_file self.config['web']['chain'] = chain_file self.service_maker = ScrapyDoServiceMaker() #--------------------------------------------------------------------------- def test_web_server_config(self): #----------------------------------------------------------------------- # Correct HTTP config #----------------------------------------------------------------------- config = build_mock_config(self.config) web_config = self.service_maker._validate_web_config(config) self.assertEqual(len(web_config), 7) #----------------------------------------------------------------------- # Correct HTTPS config #----------------------------------------------------------------------- self.config['web']['https'] = True web_config = self.service_maker._validate_web_config(config) self.assertEqual(len(web_config), 7) self.config['web']['https'] = False #----------------------------------------------------------------------- # Incorrect HTTPS config #----------------------------------------------------------------------- self.config['web']['https'] = True self.config['web']['key'] += 'foo' self.assertRaises(FileNotFoundError, f=self.service_maker._validate_web_config, config=config) #--------------------------------------------------------------------------- def test_web_server(self): #----------------------------------------------------------------------- # Correct HTTP config #----------------------------------------------------------------------- config = build_mock_config(self.config) controller = Mock() controller.log_dir = self.pstore_path self.service_maker._configure_web_server(config, controller) #----------------------------------------------------------------------- # Correct HTTPS config #----------------------------------------------------------------------- self.config['web']['https'] = True self.service_maker._configure_web_server(config, controller) self.config['web']['https'] = False #----------------------------------------------------------------------- # Test mock IPv6 server #----------------------------------------------------------------------- self.config['web']['interfaces'] = '127.0.0.1:7654 [::1]:7654' with patch('scrapy_do.app.TCPServer') as mock_server: self.service_maker._configure_web_server(config, controller) mock_server.assert_called() self.config['web']['interfaces'] = '127.0.0.1:7654' #--------------------------------------------------------------------------- def test_service_maker(self): #----------------------------------------------------------------------- # Incorrect HTTP config #----------------------------------------------------------------------- config_class = Mock() config = build_mock_config(self.config) config_class.return_value = config options = self.service_maker.options() with patch('scrapy_do.app.Config', config_class): self.config['web']['interfaces'] = '' service = self.service_maker.makeService(options) self.assertIsInstance(service, MultiService) self.config['web']['interfaces'] = 'localhost:7654' #----------------------------------------------------------------------- # Broken controller #----------------------------------------------------------------------- with patch('scrapy_do.app.Config', config_class): self.config['scrapy-do']['project-store'] = '/dev/null/foo' service = self.service_maker.makeService(options) self.assertIsInstance(service, MultiService) self.config['scrapy-do']['project-store'] = self.pstore_path #--------------------------------------------------------------------------- def tearDown(self): shutil.rmtree(self.pstore_path) #------------------------------------------------------------------------------- class AppTestBase(unittest.TestCase): #--------------------------------------------------------------------------- def set_up(self, auth): self.pstore_path = tempfile.mkdtemp() self.auth_file = tempfile.mkstemp() with open(self.auth_file[0], 'w') as f: print('foo:bar', file=f) self.config = deepcopy(default_config) self.config['scrapy-do']['project-store'] = self.pstore_path self.config['web']['auth-db'] = self.auth_file[1] self.config['web']['auth'] = auth service_maker = ScrapyDoServiceMaker() options = service_maker.options() config_class = Mock() config = build_mock_config(self.config) config_class.return_value = config with patch('scrapy_do.app.Config', config_class): self.service = service_maker.makeService(options) self.service.startService() #--------------------------------------------------------------------------- def tear_down(self): shutil.rmtree(self.pstore_path) os.remove(self.auth_file[1]) return self.service.stopService() #------------------------------------------------------------------------------- class AppNoAuthTest(AppTestBase): #--------------------------------------------------------------------------- def setUp(self): self.set_up(False) #--------------------------------------------------------------------------- @inlineCallbacks def test_app(self): response = yield web_retrieve_async('GET', 'http://127.0.0.1:7654') resp = response[0] self.assertEqual(resp.code, 200) #--------------------------------------------------------------------------- def tearDown(self): return self.tear_down() #------------------------------------------------------------------------------- class AppAuthTest(AppTestBase): #--------------------------------------------------------------------------- def setUp(self): self.set_up(True) #--------------------------------------------------------------------------- @inlineCallbacks def test_app(self): response = yield web_retrieve_async('GET', 'http://127.0.0.1:7654', username='foo', password='bar') resp = response[0] self.assertEqual(resp.code, 200) #--------------------------------------------------------------------------- def test_realm(self): config = Mock() controller = Mock() realm = PublicHTMLRealm(config, controller) self.assertRaises(NotImplementedError, realm.requestAvatar, avatar_id='foo', mind='bar') #--------------------------------------------------------------------------- def test_site(self): config = Mock() controller = Mock() get_web_app(config, controller) #--------------------------------------------------------------------------- def tearDown(self): return self.tear_down()