def test_uninstall_all(self): before_handlers_root = root.handlers[:] before_handlers_child = child.handlers[:] l1 = LogCapture() l2 = LogCapture('one.child') # We can see that the LogCaptures have changed the # handlers, removing existing ones and installing # their own: assert len(root.handlers) == 1 assert root.handlers != before_handlers_root assert len(child.handlers) == 1 assert child.handlers != before_handlers_child # Now we show the function in action: LogCapture.uninstall_all() # ...and we can see the handlers are back as # they were beefore: assert before_handlers_root == root.handlers assert before_handlers_child == child.handlers
def test_simple(self): root.info('before') l = LogCapture() root.info('during') l.uninstall() root.info('after') assert str(l) == "root INFO\n during"
def test_clear_global_state(self): from logging import _handlers, _handlerList capture = LogCapture() capture.uninstall() self.assertFalse(capture in _handlers) self.assertFalse(capture in _handlerList)
def connectTo(): gearkey = "qnlgzsPUUxYeyQP" gearsecret = "1euJPvxybllEPQZzq2u9wpRJXDbjM7" appid = "testNo3" client.create(gearkey, gearsecret, appid, {'debugmode': "True"}) def on_connected(): print("connect") def on_closed(): print("close") def on_rejected(): print("reject") def on_error(): print("error") def on_message(): print("message") def on_present(): print("present") def on_absent(): print("absent") client.on_connect = on_connected client.on_error = on_error client.on_present = on_present client.on_absent = on_absent client.on_rejected = on_rejected client.on_closed = on_closed client.on_message = on_message logs = LogCapture() client.connect() print(logs) logs.check(('root', 'DEBUG', 'Check stored token.'))
class ExperimentRunningMock(test_integration_mock.GatewayCodeMock): """ Create environment for running experiments """ def setUp(self): super(ExperimentRunningMock, self).setUp() # config experiment and create folder self.g_m._create_user_exp_folders(USER, EXP_ID) self.log_error = LogCapture('gateway_code', level=logging.ERROR) def tearDown(self): super(ExperimentRunningMock, self).tearDown() self.g_m._destroy_user_exp_folders(USER, EXP_ID) self.log_error.uninstall() @staticmethod def send_n_cmds(command, num_times, step=0.5): """ Send a command multiple times and return array of answers """ answers = [] cmd = command.split() for _itr in range(0, num_times): # pylint:disable=unused-variable ans = OpenNodeConnection.send_one_command(cmd) ans = ' '.join(ans) if ans is not None else None answers.append(ans) time.sleep(step) return answers
class UserSignupTests(TestCase): def setUp(self): self.handler = LogCapture() self.formatter = JsonLogFormatter(logger_name='testpilot.newuser') self.username = '******' self.password = '******' self.email = '*****@*****.**' % self.username self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) UserProfile.objects.filter(user=self.user).delete() def tearDown(self): self.handler.uninstall() def test_newuser_log_event(self): """testpilot.newuser log event should be emitted on signup""" self.user.is_active = True user_signed_up.send(sender=self.user.__class__, request=None, user=self.user) self.assertEquals(len(self.handler.records), 1) record = self.handler.records[0] details = json.loads(self.formatter.format(record)) self.assertTrue('Fields' in details) fields = details['Fields'] self.assertEqual(fields['uid'], self.user.id)
class Test(unittest.TestCase): def setUp(self): from testfixtures import LogCapture self.log_capture = LogCapture() self.log_group = "phidgeter.ir_temperature" self.lvl = "DEBUG" def tearDown(self): self.log_capture.uninstall() def test_log_captures(self): # verification of log matching functionality from logging import getLogger getLogger().info("a message") self.log_capture.check(("root", "INFO", "a message")) def test_sensor_is_available(self): ir_temp = IRSensor() assert ir_temp.open_phidget() == True assert ir_temp.get_temperature() >= 0.0 assert ir_temp.close_phidget() == True def test_sensor_is_room_temperature(self): ir_temp = IRSensor() assert ir_temp.open_phidget() == True print ir_temp.get_temperature() assert ir_temp.get_temperature() >= 20.0 assert ir_temp.close_phidget() == True
class TopLevelFormatterTest(unittest.TestCase): def setUp(self): self.handler = LogCapture() self.handler.addFilter(TopLevelFormatter(['test'])) def test_top_level_logger(self): logger = logging.getLogger('test') with self.handler as l: logger.warning('test log msg') l.check(('test', 'WARNING', 'test log msg')) def test_children_logger(self): logger = logging.getLogger('test.test1') with self.handler as l: logger.warning('test log msg') l.check(('test', 'WARNING', 'test log msg')) def test_overlapping_name_logger(self): logger = logging.getLogger('test2') with self.handler as l: logger.warning('test log msg') l.check(('test2', 'WARNING', 'test log msg')) def test_different_name_logger(self): logger = logging.getLogger('different') with self.handler as l: logger.warning('test log msg') l.check(('different', 'WARNING', 'test log msg'))
class TopLevelFormatterTest(unittest.TestCase): def setUp(self): self.handler = LogCapture() self.handler.addFilter(TopLevelFormatter(["test"])) def test_top_level_logger(self): logger = logging.getLogger("test") with self.handler as l: logger.warning("test log msg") l.check(("test", "WARNING", "test log msg")) def test_children_logger(self): logger = logging.getLogger("test.test1") with self.handler as l: logger.warning("test log msg") l.check(("test", "WARNING", "test log msg")) def test_overlapping_name_logger(self): logger = logging.getLogger("test2") with self.handler as l: logger.warning("test log msg") l.check(("test2", "WARNING", "test log msg")) def test_different_name_logger(self): logger = logging.getLogger("different") with self.handler as l: logger.warning("test log msg") l.check(("different", "WARNING", "test log msg"))
def test_simple_manual_install(self): l = LogCapture(install=False) root.info('before') l.install() root.info('during') l.uninstall() root.info('after') assert str(l) == "root INFO\n during"
class TestPickleSerializer(unittest.TestCase): layer = ZAMQP_FUNCTIONAL_TESTING def setUp(self): from testfixtures import LogCapture self.l = LogCapture("c.zamqp.tests") def tearDown(self): self.l.uninstall() def _testDeclareQueue(self): rabbitctl = self.layer['rabbitctl'] self.assertIn("my.picklequeue\t0", rabbitctl('list_queues')[0].split("\n")) def testDeclareQueue(self): runAsyncTest(self._testDeclareQueue) def _testDeclareQueueAgain(self): rabbitctl = self.layer['rabbitctl'] self.assertIn("my.picklequeue\t0", rabbitctl('list_queues')[0].split("\n")) def testDeclareQueueAgain(self): runAsyncTest(self._testDeclareQueueAgain) def _testPublishToQueue(self): rabbitctl = self.layer['rabbitctl'] self.assertIn("my.picklequeue\t1", rabbitctl('list_queues')[0].split("\n")) def _testPublishToQueueAndConsumeIt(self): rabbitctl = self.layer['rabbitctl'] self.assertIn("my.picklequeue\t0", rabbitctl('list_queues')[0].split("\n")) def testPublishToQueueAndConsumeIt(self): runAsyncTest(self._testDeclareQueue) from zope.component import getUtility from collective.zamqp.interfaces import IProducer producer = getUtility(IProducer, name="my.picklequeue") producer.publish({"key": "value"}) runAsyncTest(self._testPublishToQueue) runAsyncTest(self._testPublishToQueueAndConsumeIt) self.l.check( ('c.zamqp.tests', 'INFO', "<BasicProperties(['delivery_mode=2', " "'content_type=application/x-python-serialize'])>"), ('c.zamqp.tests', 'INFO', "{'key': 'value'}"), ('c.zamqp.tests', 'INFO', "<type 'dict'>") )
def test_multiple_loggers(self): l = LogCapture(('one.child','two')) root.info('1') one.info('2') two.info('3') child.info('4') l.uninstall() assert str(l) == ( "two INFO\n 3\n" "one.child INFO\n 4" )
def test_specific_logger(self): l = LogCapture('one') root.info('1') one.info('2') two.info('3') child.info('4') l.uninstall() assert str(l) == ( "one INFO\n 2\n" "one.child INFO\n 4" )
class TestElfTargetIsCompatibleWithNode(unittest.TestCase): """Test elftarget.is_compatible_with_node.""" def setUp(self): self.m3_class = mock.Mock() self.m3_class.ELF_TARGET = ('ELFCLASS32', 'EM_ARM') self.log = LogCapture('gateway_code', level=logging.DEBUG) def tearDown(self): self.log.uninstall() def test_m3_like_elf_check(self): """Test elftarget for an m3 like node.""" ret = elftarget.is_compatible_with_node(firmware('idle_m3.elf'), self.m3_class) self.assertTrue(ret) self.log.check() # invalid target ret = elftarget.is_compatible_with_node(firmware('node.z1'), self.m3_class) self.assertFalse(ret) self.log.check() # invalid, not elf file ret = elftarget.is_compatible_with_node( firmware('wsn430_print_uids.hex'), self.m3_class) self.assertFalse(ret) self.log.check(('gateway_code', 'WARNING', 'Invalid firmware: Not a valid elf file'))
def setUp(self): super(BaseTestCase, self).setUp() self.handler = LogCapture() self.username = '******' self.password = '******' self.email = '*****@*****.**' % self.username self.user = User.objects.create_user( username=self.username, email=self.email, password=self.password) self.users = dict((obj.username, obj) for obj in ( User.objects.create_user( username='******' % idx, email='*****@*****.**' % idx, password='******' % idx ) for idx in range(0, 5))) self.experiments = dict((obj.slug, obj) for (obj, created) in ( Experiment.objects.get_or_create( slug="test-%s" % idx, defaults=dict( order=idx, title="Longer Test Title %s" % idx, short_title="Test %s" % idx, description="This is a test", introduction="<h1>Hello, Test!</h1>", addon_id="*****@*****.**" % idx )) for idx in range(1, 4)))
def setUp(self): # webtest if self.use_cookie: cookiejar = cookielib.CookieJar() else: cookiejar = None self.app = TestApp(self.application, domain=self.domain, cookiejar=cookiejar) # os.environ self.origin_environ = dict() if "HTTP_HOST" not in self.environ.viewkeys(): self.environ["HTTP_HOST"] = "localhost" for key, value in self.environ.viewitems(): self.origin_environ[key], os.environ[key] = os.environ.get(key), value # testbed self.testbed = testbed.Testbed() self.testbed.activate() self.testbed.init_datastore_v3_stub( consistency_policy=datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=0), root_path=self.root_path, ) self.testbed.init_blobstore_stub() self.testbed.init_files_stub() self.testbed.init_memcache_stub() self.testbed.init_taskqueue_stub(root_path=self.root_path) self.testbed.init_urlfetch_stub() self.testbed.init_user_stub() # logging self.log = LogCapture(level=logging.WARNING) self.log.install()
def test_socat_needs_sigkill(self): """Test cases where send_signal must be called multiple times.""" log = LogCapture('gateway_code', level=logging.WARNING) self.addCleanup(log.uninstall) only_sigkill = os.path.join(CURRENT_DIR, 'only_sigkill.py') only_sigkill = 'python %s' % only_sigkill with mock.patch.object(SerialRedirection, 'SOCAT', only_sigkill): self.redirect = SerialRedirection(self.tty, self.baud) self.redirect.start() time.sleep(1) self.redirect.stop() log.check(('gateway_code', 'WARNING', 'SerialRedirection signal: escalading to SIGKILL'))
def setUp(self): self.dir = TempDirectory() self.db_path = self.dir.getpath('test.db') self.conn = sqlite3.connect(self.db_path) self.conn.execute('create table notes ' '(filename varchar, text varchar)') self.conn.commit() self.log = LogCapture()
def test_query_sanitazion(query_sanitazion): app_client = query_sanitazion.app.test_client() l = LogCapture() url = '/v1.0/greeting' response = app_client.post(url, data={'name': 'Jane Doe'}) # This is ugly. The reason for asserting the logging in this way # is that in order to use LogCapture().check, we'd have to assert that # a specific sequence of logging has occurred. This is too restricting # for future development, and we are really only interested in the fact # a single message is logged. messages = [x.strip() for x in str(l).split("\n")] assert "FormData parameter 'name' in function arguments" in messages assert "Query Parameter 'name' in function arguments" not in messages assert "Function argument 'name' not defined in specification" not in messages assert response.status_code == 200 l.uninstall()
def test_atexit(self): m = Mock() with Replacer() as r: # make sure the marker is false, other tests will # probably have set it r.replace('testfixtures.LogCapture.atexit_setup', False) r.replace('atexit.register', m.register) l = LogCapture() expected = [call.register(l.atexit)] compare(expected, m.mock_calls) with catch_warnings(record=True) as w: l.atexit() self.assertTrue(len(w), 1) compare(str(w[0].message), ( # pragma: no branch "LogCapture instances not uninstalled by shutdown, " "loggers captured:\n" "(None,)" )) l.uninstall() compare(set(), LogCapture.instances) # check re-running has no ill effects l.atexit()
class TestJsonLogFormatter(unittest2.TestCase): def setUp(self): self.handler = LogCapture() self.formatter = JsonLogFormatter() def tearDown(self): self.handler.uninstall() def test_basic_operation(self): logging.debug("simple test") self.assertEquals(len(self.handler.records), 1) details = json.loads(self.formatter.format(self.handler.records[0])) self.assertEquals(details["message"], "simple test") self.assertEquals(details["name"], "root") self.assertEquals(details["pid"], os.getpid()) self.assertEquals(details["op"], "root") self.assertEquals(details["v"], 1) self.assertTrue("time" in details) def test_custom_paramters(self): logger = logging.getLogger("mozsvc.test.test_logging") logger.warn("custom test %s", "one", extra={ "more": "stuff", "op": "mytest", }) self.assertEquals(len(self.handler.records), 1) details = json.loads(self.formatter.format(self.handler.records[0])) self.assertEquals(details["message"], "custom test one") self.assertEquals(details["name"], "mozsvc.test.test_logging") self.assertEquals(details["op"], "mytest") self.assertEquals(details["more"], "stuff") def test_logging_error_tracebacks(self): try: raise ValueError("\n") except Exception: logging.exception("there was an error") self.assertEquals(len(self.handler.records), 1) details = json.loads(self.formatter.format(self.handler.records[0])) self.assertEquals(details["message"], "there was an error") self.assertEquals(details["error"], "ValueError('\\n',)") tblines = details["traceback"].strip().split("\n") self.assertEquals(tblines[-1], details["error"]) self.assertEquals(tblines[-2], "<type 'exceptions.ValueError'>")
class TestRequest(unittest.TestCase): def setUp(self): self.log_capture = LogCapture() self.request = api.web.request.SciTranRequest({}) def tearDown(self): LogCapture.uninstall_all() def test_request_id(self): self.assertEqual(len(self.request.id), 19) def test_request_logger_adapter(self): test_log_message = "test log message" self.request.logger.error(test_log_message) expected_log_output = "{0} request_id={1}".format( test_log_message, self.request.id ) self.log_capture.check(('scitran.api', 'ERROR', expected_log_output))
def setup(self): self.log_capture = LogCapture() self.running_procs = [] for p in self.processes: self.running_procs.append(sp.Popen(p, stderr=sp.DEVNULL, stdout=sp.DEVNULL))
def test_two_logcaptures_on_same_logger(self): # If you create more than one LogCapture on a single # logger, the 2nd one installed will stop the first # one working! l1 = LogCapture() root.info('1st message') assert str(l1) == "root INFO\n 1st message" l2 = LogCapture() root.info('2nd message') # So, l1 missed this message: assert str(l1) == "root INFO\n 1st message" # ...because l2 kicked it out and recorded the message: assert str(l2) == "root INFO\n 2nd message" LogCapture.uninstall_all()
def setUp(self): self.config = testing.setUp() settings = {} load_into_settings(self.get_ini(), settings) self.config.add_settings(settings) self.config.include("tokenserver") load_and_register("tokenserver", self.config) self.backend = self.config.registry.getUtility(INodeAssignment) self.backend.add_service(SERVICE, "{node}/{version}/{uid}") self.backend.add_node(SERVICE, "https://phx12", 100) self.logs = LogCapture()
def setup(self): self.log_capture = LogCapture() self.verbose_tester = BaseTester(verbose=True, item='verbose_test') self.default_tester = BaseTester(item='default_test') self.verbose_logname = '{}.{}.verbose_test'.format('servercheck', self.verbose_tester.__class__.__name__) # nopep8 self.default_logname = '{}.{}.default_test'.format('servercheck', self.default_tester.__class__.__name__) # nopep8
def setup(self): self.log_capture = LogCapture() self.running_procs = [] with open('/dev/null', 'w') as DEVNULL: for p in self.processes: self.running_procs.append(sp.Popen(p, stderr=DEVNULL, stdout=DEVNULL))
def setUp(self): self.all_calls = {} self.create_calls = {} # HACK: don't allow other apps to mess with us or vice versa... self.old_cbs = djhookbox.views._callbacks djhookbox.views._callbacks = [] djhookbox.whcallback(self._cb_all) djhookbox.whcallback('create')(self._cb_create) User.objects.create_user('a', '*****@*****.**', 'a').save() self.logcap = LogCapture()
class TestCheck(TestCase): def setUp(self): self.r = Replacer() self.l = LogCapture() def tearDown(self): self.l.uninstall() self.r.restore() def checker_returns(self,output): resolve = Mock() self.r.replace('checker.resolve',resolve) def the_checker(config_folder,param): return output resolve.return_value = the_checker return resolve def test_bad_checker(self): from checker import check check = should_raise(check,ImportError('No module named unknown')) check('/config','unknown',None) def test_normal(self): m = self.checker_returns('some output') check('/config','achecker',None) compare(m.call_args_list,[ (('checker.checkers.achecker.check',), {}) ]) def test_log_newline(self): self.checker_returns('some output\n') check('/config','achecker','aparam') self.l.check( ('root', 'INFO', 'some output'), ) def test_log_no_newline(self): self.checker_returns('some output') check('/config','achecker','aparam') self.l.check( ('root', 'INFO', 'some output'), ) def test_no_log_empty(self): self.checker_returns('') check('/config','achecker','aparam') self.l.check()
class RequestSummaryLoggingTests(TestCase): def setUp(self): super(RequestSummaryLoggingTests, self).setUp() self.handler = LogCapture() def tearDown(self): self.handler.uninstall() def test_unblackisted_are_logged(self): self.handler.records = [] url = '/__version__' resp = self.client.get(url) self.assertEqual(200, resp.status_code) record = self.handler.records[0] self.assertEqual(url, record.path) def test_blacklisted_are_not_logged(self): self.handler.records = [] url = '/__heartbeat__' resp = self.client.get(url) self.assertEqual(200, resp.status_code) self.assertEqual(0, len(self.handler.records))
def setUp(self): self.console_printer = ConsolePrinter(print_colored=False) self.logs = LogCapture() self.logs.__enter__()
class TestPanoptesPluginWithEnrichmentRunner(TestPanoptesPluginRunner): @patch('redis.StrictRedis', panoptes_mock_redis_strict_client) @patch('kazoo.client.KazooClient', panoptes_mock_kazoo_client) def setUp(self): super(TestPanoptesPluginWithEnrichmentRunner, self).setUp() self._panoptes_resource = PanoptesResource( resource_site="test", resource_class="test", resource_subclass="test", resource_type="test", resource_id="test", resource_endpoint="test", resource_creation_timestamp=_TIMESTAMP, resource_plugin="test") self._runner_class = PanoptesPluginWithEnrichmentRunner def test_basic_operations(self): # Test where enrichment is None mock_panoptes_enrichment_cache = Mock(return_value=None) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesEnrichmentCache', mock_panoptes_enrichment_cache): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, self._panoptes_resource, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'ERROR', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] ' 'Could not set up context for plugin')) self._log_capture.uninstall() self._log_capture = LogCapture(attributes=self.extract) # Test with enrichment runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, self._panoptes_resource, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'INFO', 'Attempting to execute plugin "Test Polling Plugin"'), ('panoptes.tests.test_runner', 'DEBUG', '''Starting Plugin Manager for "polling" plugins with the following ''' '''configuration: {'polling': <class''' """ 'yahoo_panoptes.polling.polling_plugin.PanoptesPollingPlugin'>}, """ """['tests/plugins/polling'], panoptes-plugin"""), ('panoptes.tests.test_runner', 'DEBUG', 'Found 3 plugins'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin ' '"Test Polling Plugin", version "0.1" of type "polling"' ', category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin 2", ' 'version "0.1" of type "polling", category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin Second Instance", ' 'version "0.1" of type "polling", category "polling"'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Attempting to get lock for plugin ' '"Test Polling Plugin"'), ('panoptes.tests.test_runner', 'DEBUG', 'Attempting to get lock for plugin "Test Polling Plugin", with lock path and ' 'identifier in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Acquired lock'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test]' ' Ran in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Released lock'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Plugin returned' ' a result set with 1 members'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test]' ' Callback function ran in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|type|' 'test|id|test|endpoint|test] GC took seconds. There are garbage objects.' ), ('panoptes.tests.test_runner', 'ERROR', 'No enrichment data found on KV store for plugin Test Polling Plugin ' 'resource test namespace test using key test'), ('panoptes.tests.test_runner', 'DEBUG', 'Successfully created PanoptesEnrichmentCache enrichment_data {} for plugin ' 'Test Polling Plugin'), order_matters=False) def test_callback_failure(self): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, self._panoptes_resource, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback_with_exception) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'ERROR', '[Test Polling Plugin] ' '[plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Results callback function failed' )) # 'pass' is needed for these methods because the only difference in their logging output from # TestPanoptesPluginRunner is the presence of the PanoptesResource in some log messages. def test_lock_no_lock_object(self): pass def test_lock_is_none(self): pass def test_lock_is_not_locked(self): pass def test_plugin_failure(self): pass def test_plugin_wrong_result_type(self): runner = self._runner_class("Test Polling Plugin 2", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetric, _callback) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'ERROR', '[Test Polling Plugin 2] [None] Could not set up context for plugin' ))
class TestPanoptesPluginRunner(unittest.TestCase): @staticmethod def extract(record): message = record.getMessage() match_obj = re.match(r'(?P<name>.*):\w+(?P<body>.*)', message) if match_obj: message = match_obj.group('name') + match_obj.group('body') match_obj = re.match( r'(?P<start>.*[R|r]an in\s)\d+\.?\d*.*(?P<end>seconds.*)', message) if match_obj: return record.name, record.levelname, match_obj.group( 'start') + match_obj.group('end') match_obj = re.match( r'(?P<start>.*took\s*)\d+\.?\d*.*(?P<seconds>seconds.*)\d+\s(?P<end>garbage objects.*)', message) if match_obj: return record.name, record.levelname, match_obj.group('start') + match_obj.group('seconds') + \ match_obj.group('end') match_obj = re.match( r'(?P<start>Attempting to get lock for plugin .*with lock path) \".*\".*(?P<id> and identifier).*' r'(?P<in> in) \d\.?\d*(?P<seconds> seconds)', message) if match_obj: return record.name, record.levelname, match_obj.group('start') + match_obj.group('id') + \ match_obj.group('in') + match_obj.group('seconds') return record.name, record.levelname, message @patch('redis.StrictRedis', panoptes_mock_redis_strict_client) @patch('kazoo.client.KazooClient', panoptes_mock_kazoo_client) def setUp(self): self.my_dir, self.panoptes_test_conf_file = get_test_conf_file() self._panoptes_context = PanoptesContext( self.panoptes_test_conf_file, key_value_store_class_list=[PanoptesTestKeyValueStore], create_message_producer=False, async_message_producer=False, create_zookeeper_client=True) self._runner_class = PanoptesPluginRunner self._log_capture = LogCapture(attributes=self.extract) def tearDown(self): self._log_capture.uninstall() def test_logging_methods(self): runner = self._runner_class("Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) # Ensure logging methods run: runner.info(PanoptesTestPluginNoLock(), "Test Info log message") runner.warn(PanoptesTestPluginNoLock(), "Test Warning log message") runner.error(PanoptesTestPluginNoLock(), "Test Error log message", Exception) runner.exception(PanoptesTestPluginNoLock(), "Test Exception log message") self._log_capture.check( ('panoptes.tests.test_runner', 'INFO', '[None] [{}] Test Info log message'), ('panoptes.tests.test_runner', 'WARNING', '[None] [{}] Test Warning log message'), ('panoptes.tests.test_runner', 'ERROR', "[None] [{}] Test Error log message: <type 'exceptions.Exception'>" ), ('panoptes.tests.test_runner', 'ERROR', '[None] [{}] Test Exception log message')) def test_basic_operations(self): runner = self._runner_class("Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'INFO', 'Attempting to execute plugin "Test Polling Plugin"'), ('panoptes.tests.test_runner', 'DEBUG', '''Starting Plugin Manager for "polling" plugins with the following ''' '''configuration: {'polling': <class''' """ 'yahoo_panoptes.polling.polling_plugin.PanoptesPollingPlugin'>}, """ """['tests/plugins/polling'], panoptes-plugin"""), ('panoptes.tests.test_runner', 'DEBUG', 'Found 3 plugins'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin ' '"Test Polling Plugin", version "0.1" of type "polling"' ', category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin 2", ' 'version "0.1" of type "polling", category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin Second Instance", ' 'version "0.1" of type "polling", category "polling"'), ('panoptes.tests.test_runner', 'INFO', '''[Test Polling Plugin] [None] ''' '''Attempting to get lock for plugin "Test Polling Plugin"'''), ('panoptes.tests.test_runner', 'DEBUG', 'Attempting to get lock for plugin "Test Polling Plugin", with lock path and ' 'identifier in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [None] Acquired lock'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [None]' ' Ran in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [None] Released lock'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [None] Plugin returned' ' a result set with 1 members'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [None]' ' Callback function ran in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [None] GC took seconds. There are garbage objects.' ), ('panoptes.tests.test_runner', 'DEBUG', 'Deleting module: yapsy_loaded_plugin_Test_Polling_Plugin_0'), ('panoptes.tests.test_runner', 'DEBUG', 'Deleting module: yapsy_loaded_plugin_Test_Polling_Plugin_Second_Instance_0' ), order_matters=False) def test_nonexistent_plugin(self): runner = self._runner_class("Non-existent Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'INFO', 'Attempting to execute plugin "Non-existent Plugin"' ), ( 'panoptes.tests.test_runner', 'DEBUG', 'Starting Plugin Manager for "polling" plugins with the following ' "configuration: {'polling': <class 'yahoo_panoptes.polling.polling_plugin." "PanoptesPollingPlugin'>}, " "['tests/plugins/polling'], panoptes-plugin" ), ('panoptes.tests.test_runner', 'DEBUG', 'Found 3 plugins'), ( 'panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin", version "0.1" of type "polling", ' 'category "polling"' ), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin Second Instance", version "0.1" of type ' '"polling", category "polling"'), ( 'panoptes.tests.test_runner', 'WARNING', 'No plugin named "Non-existent Plugin" found in "' '''['tests/plugins/polling']"''')) def test_bad_plugin_type(self): runner = self._runner_class("Test Polling Plugin", "bad", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'ERROR', '''Error trying to load plugin "Test Polling Plugin": KeyError('bad',)''' )) def test_execute_now_false(self): mock_get_plugin_by_name = MagicMock( return_value=MockPluginExecuteNow()) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginManager.getPluginByName', mock_get_plugin_by_name): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'INFO', 'Attempting to execute plugin "Test Polling Plugin"'), ('panoptes.tests.test_runner', 'DEBUG', '''Starting Plugin Manager for ''' '''"polling" plugins with the ''' '''following configuration: {'polling': ''' """<class 'yahoo_panoptes.polling.polling_plugin.PanoptesPollingPlugin'""" """>}, ['tests/plugins/polling'], panoptes-plugin"""), ('panoptes.tests.test_runner', 'DEBUG', 'Found 3 plugins'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin ' '"Test Polling Plugin", version "0.1" of type "polling"' ', category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin Second Instance", ' 'version "0.1" of type "polling", category "polling"')) def test_callback_failure(self): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback_with_exception) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'ERROR', '[Test Polling Plugin] ' '[None] Results callback function failed')) def test_lock_no_lock_object(self): mock_plugin = MagicMock(return_value=PanoptesTestPluginNoLock) mock_get_context = MagicMock(return_value=self._panoptes_context) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginManager.getPluginByName', mock_plugin): with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginRunner._get_context', mock_get_context): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'ERROR', '[None] [{}] Error in acquiring lock')) def test_lock_is_none(self): mock_get_plugin_by_name = MagicMock(return_value=MockPluginLockNone()) mock_get_context = MagicMock(return_value=self._panoptes_context) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginManager.getPluginByName', mock_get_plugin_by_name): with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginRunner._get_context', mock_get_context): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'INFO', '[None] [{}] Attempting to get lock for plugin' ' "Test Polling Plugin"')) def test_lock_is_not_locked(self): mock_get_plugin_by_name = MagicMock( return_value=MockPluginLockIsNotLocked()) mock_get_context = MagicMock(return_value=self._panoptes_context) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginManager.getPluginByName', mock_get_plugin_by_name): with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginRunner._get_context', mock_get_context): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'INFO', '[None] [{}] Attempting to get lock for plugin' ' "Test Polling Plugin"')) def test_plugin_failure(self): mock_plugin = MagicMock( return_value=PanoptesTestPluginRaisePluginReleaseException) mock_get_context = MagicMock(return_value=self._panoptes_context) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginManager.getPluginByName', mock_plugin): with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesPluginRunner._get_context', mock_get_context): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'ERROR', '[None] [{}] Failed to execute plugin'), ('panoptes.tests.test_runner', 'INFO', '[None] [{}] Ran in seconds'), ('panoptes.tests.test_runner', 'ERROR', '[None] [{}] Failed to release lock for plugin'), ('panoptes.tests.test_runner', 'WARNING', '[None] [{}] Plugin did not return any results')) def test_plugin_wrong_result_type(self): runner = self._runner_class("Test Polling Plugin 2", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, None, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'WARNING', '[Test Polling Plugin 2] [None] Plugin returned an unexpected result type: ' '"PanoptesMetricsGroup"'))
def test_https_connect_tunnel_error(self): crawler = get_crawler(SimpleSpider) with LogCapture() as log: yield crawler.crawl("https://localhost:99999/status?n=200") self._assert_got_tunnel_error(log)
def test_publish(self, mock_access_token): # pylint: disable=unused-argument,too-many-statements publisher_course_run = self._create_course_run_for_publication() currency = Currency.objects.get(code='USD') common_entitlement_kwargs = { 'course': publisher_course_run.course, 'currency': currency, } professional_entitlement = CourseEntitlementFactory( mode=CourseEntitlement.PROFESSIONAL, **common_entitlement_kwargs) verified_entitlement = CourseEntitlementFactory( mode=CourseEntitlement.VERIFIED, **common_entitlement_kwargs) common_seat_kwargs = { 'course_run': publisher_course_run, 'currency': currency, } audit_seat = SeatFactory(type=Seat.AUDIT, upgrade_deadline=None, **common_seat_kwargs) # The credit seat should NOT be published. SeatFactory(type=Seat.CREDIT, **common_seat_kwargs) professional_seat = SeatFactory(type=Seat.PROFESSIONAL, **common_seat_kwargs) verified_seat = SeatFactory(type=Seat.VERIFIED, **common_seat_kwargs) partner = publisher_course_run.course.organizations.first().partner self._set_test_client_domain_and_login(partner) self._mock_studio_api_success(publisher_course_run) self._mock_ecommerce_api(publisher_course_run) with LogCapture(LOGGER_NAME) as log: url = reverse('publisher:api:v1:course_run-publish', kwargs={'pk': publisher_course_run.pk}) response = self.client.post(url, {}) assert response.status_code == 200 log.check(( LOGGER_NAME, 'INFO', 'Published course run with id: [{}] lms_course_id: [{}], user: [{}], date: [{}]' .format(publisher_course_run.id, publisher_course_run.lms_course_id, self.user, date.today()))) assert len(responses.calls) == 3 expected = { 'discovery': CourseRunViewSet.PUBLICATION_SUCCESS_STATUS, 'ecommerce': CourseRunViewSet.PUBLICATION_SUCCESS_STATUS, 'studio': CourseRunViewSet.PUBLICATION_SUCCESS_STATUS, } assert response.data == expected # Verify the correct deadlines were sent to the E-Commerce API ecommerce_body = json.loads(responses.calls[2].request.body) expected = [ serialize_seat_for_ecommerce_api(audit_seat), serialize_seat_for_ecommerce_api(professional_seat), serialize_seat_for_ecommerce_api(verified_seat), serialize_entitlement_for_ecommerce_api(professional_entitlement), serialize_entitlement_for_ecommerce_api(verified_entitlement), ] assert ecommerce_body['products'] == expected assert ecommerce_body['verification_deadline'] == serialize_datetime( publisher_course_run.end_date_temporary) discovery_course_run = CourseRun.objects.get( key=publisher_course_run.lms_course_id) publisher_course = publisher_course_run.course discovery_course = discovery_course_run.course assert ecommerce_body['id'] == publisher_course_run.lms_course_id assert ecommerce_body['uuid'] == str(discovery_course.uuid) # pylint: disable=no-member assert discovery_course_run.title_override == publisher_course_run.title_override assert discovery_course_run.short_description_override is None assert discovery_course_run.full_description_override is None assert discovery_course_run.start == publisher_course_run.start_date_temporary assert discovery_course_run.end == publisher_course_run.end_date_temporary assert discovery_course_run.pacing_type == publisher_course_run.pacing_type_temporary assert discovery_course_run.min_effort == publisher_course_run.min_effort assert discovery_course_run.max_effort == publisher_course_run.max_effort assert discovery_course_run.language == publisher_course_run.language assert discovery_course_run.weeks_to_complete == publisher_course_run.length assert discovery_course_run.has_ofac_restrictions == publisher_course_run.has_ofac_restrictions expected = set(publisher_course_run.transcript_languages.all()) assert set(discovery_course_run.transcript_languages.all()) == expected assert set(discovery_course_run.staff.all()) == set( publisher_course_run.staff.all()) assert discovery_course.canonical_course_run == discovery_course_run assert discovery_course.partner == partner assert discovery_course.title == publisher_course.title assert discovery_course.short_description == publisher_course.short_description assert discovery_course.full_description == publisher_course.full_description assert discovery_course.level_type == publisher_course.level_type assert discovery_course.video == Video.objects.get( src=publisher_course.video_link) assert discovery_course.image.name is not None assert discovery_course.image.url is not None assert discovery_course.image.file is not None assert discovery_course.image.small.url is not None assert discovery_course.image.small.file is not None assert discovery_course.outcome == publisher_course.expected_learnings assert discovery_course.prerequisites_raw == publisher_course.prerequisites assert discovery_course.syllabus_raw == publisher_course.syllabus assert discovery_course.learner_testimonials == publisher_course.learner_testimonial assert discovery_course.faq == publisher_course.faq assert discovery_course.additional_information == publisher_course.additional_information expected = list(publisher_course_run.course.organizations.all()) assert list(discovery_course.authoring_organizations.all()) == expected expected = { publisher_course.primary_subject, publisher_course.secondary_subject } assert set(discovery_course.subjects.all()) == expected common_entitlement_kwargs = { 'course': discovery_course, 'currency': currency, } self.assertEqual(2, DiscoveryCourseEntitlement.objects.all().count()) DiscoveryCourseEntitlement.objects.get( mode=SeatType.objects.get(slug=DiscoverySeat.PROFESSIONAL), price=professional_entitlement.price, **common_entitlement_kwargs) DiscoveryCourseEntitlement.objects.get( mode=SeatType.objects.get(slug=DiscoverySeat.VERIFIED), price=verified_entitlement.price, **common_entitlement_kwargs) common_seat_kwargs = { 'course_run': discovery_course_run, 'currency': currency, } DiscoverySeat.objects.get(type=DiscoverySeat.AUDIT, upgrade_deadline__isnull=True, **common_seat_kwargs) DiscoverySeat.objects.get(type=DiscoverySeat.PROFESSIONAL, upgrade_deadline__isnull=True, price=professional_seat.price, **common_seat_kwargs) DiscoverySeat.objects.get( type=DiscoverySeat.VERIFIED, upgrade_deadline=verified_seat.upgrade_deadline, price=verified_seat.price, **common_seat_kwargs)
def test_no_coupons_found(self): """Test that command logs no offer needs to be changed.""" with LogCapture(LOGGER_NAME) as log: call_command('populate_enterprise_id_product_attribute') log.check( (LOGGER_NAME, 'INFO', 'Found 0 coupon products to update.'))
def test_nothing_done(self): with LogCapture() as capture: nothing_done(self.log_printer) capture.check( ('root', 'WARNING', 'No existent section was targeted or enabled. ' 'Nothing to do.'))
def setUp(self): self.capture = LogCapture(attributes=('name', 'levelname', 'getMessage', 'shoehorn_event')) self.addCleanup(self.capture.uninstall)
class TestBackendParsing(unittest.TestCase): def setUp(self): self.l = LogCapture() def tearDown(self): self.l.uninstall() def test_that_parse_returns_a_backend_dictionary_if_xml_contains_config_element_including_backends_list(self): root = ET.Element('config') ET.SubElement(root, 'backends') op = BackendParser() op.parse(root) result = op.backends assert_that(result, is_not(None)) def test_that_config_includes_a_backends_list_if_config_contains_backends_definition(self): root = ET.Element('config') backends = ET.SubElement(root, 'backends') name = 'db' plugin = 'mysql' ET.SubElement(backends, 'backend', {'name':name, 'plugin':plugin}) op = BackendParser() _dump_xml_as_file(root, 'backend.xml') op.parse(root) result = op.backends assert_that(result, is_not(None)) assert_that(len(result), is_not(0)) assert_that(result, has_key(name)) assert_that(result[name].plugin, is_(plugin)) def test_that_config_backend_is_ignored_if_plugin_is_empty(self): root = ET.Element('config') backends = ET.SubElement(root, 'backends') ET.SubElement(backends, 'backend', {'name':'db', 'plugin':''}) op = BackendParser() op.parse(root) result = op.backends self.l.check(('root', 'WARNING', "Ignoring invalid backend definition, name = 'db', plugin = ''")) assert_that(result, is_not(has_key('db'))) def test_that_config_backend_is_ignored_if_name_is_not_specified(self): root = ET.Element('config') backends = ET.SubElement(root, 'backends') ET.SubElement(backends, 'backend', {'plugin':'mysql'}) op = BackendParser() op.parse(root) result = op.backends self.l.check(('root', 'WARNING', "Ignoring invalid backend definition, name = 'None', plugin = 'mysql'")) assert_that(len(result), is_(0)) def test_that_backends_are_ignored_if_not_included_in_a_config_element(self): root = ET.Element('random_tag') ET.SubElement(root, 'backends') op = BackendParser() result = op.parse(root) assert_that(result, is_(None)) def test_that_dictionary_of_parameters_is_created_if_parameters_specified(self): (root, backend) = _create_valid_backend() key = 'ip' value = '1.2.3.4' params = ET.SubElement(backend, 'params') param = ET.SubElement(params, key) param.text = value op = BackendParser() op.parse(root) result = op.backends assert_that(result[backend.get('name')].params, has_entry(key, value))
def test_msg_is_none(self): with LogCapture(attributes=('msg', 'foo')) as log: getLogger().info(None, extra=dict(foo='bar')) log.check( (None, 'bar') )
def test_missing_attribute(self): with LogCapture(attributes=('msg', 'lolwut')) as log: getLogger().info('oh %s', 'hai') log.check( ('oh %s', None) )
def test_different_attributes(self): with LogCapture(attributes=('funcName', 'processName')) as log: getLogger().info('oh hai') log.check( ('test_different_attributes', 'MainProcess') )
def setUp(self): """Creates data for testing Planes Anual""" super().setUp() self.logger = LogCapture() self.user = User.objects.create_user( email='*****@*****.**', password='******', first_name='David', last_name='Padilla', institution='Colegio Benalcazar', ) self.data = { 'name': 'Plan Anual1', 'ano_lectivo': '2019-2020', 'docentes': 'David Padilla', 'asignatura': self.asignatura.id, 'curso': self.curso_1.id, 'paralelos': 'A y C', 'carga_horaria': 20, 'semanas_trabajo': 20, 'semanas_imprevistos': 20, 'objetivos_generales': [self.general_1.id, self.general_2.id], 'objetivos_curso': [self.objetivo_1.id, self.objetivo_2.id], 'objetivos_generales_curso': [self.general_1.id, self.general_2.id], 'ejes_transversales': 'Lorem ipsum dolor sit amet.', 'bibliografia': 'Lorem ipsum dolor sit amet.', 'aprobado_por': 'Lorem ipsum dolor sit amet.', 'revisado_por': 'Lorem ipsum dolor sit amet.', # Formset Elementos curriculares 1 'desarrollo_unidades-TOTAL_FORMS': '2', 'desarrollo_unidades-INITIAL_FORMS': '0', 'desarrollo_unidades-MIN_NUM_FORMS': '0', 'desarrollo_unidades-MAX_NUM_FORMS': '1000', 'desarrollo_unidades-0-unidad': self.unidad_1.id, 'desarrollo_unidades-0-objetivos': [self.objetivo_1.id, self.objetivo_2.id], 'desarrollo_unidades-0-objetivos_generales': [self.general_1.id, self.general_2.id], 'desarrollo_unidades-0-destrezas': [self.destreza_1.id, self.destreza_2.id], 'desarrollo_unidades-0-orientaciones_metodologicas': 'lorem ipsum', 'desarrollo_unidades-0-semanas': 8, 'desarrollo_unidades-1-unidad': self.unidad_1.id, 'desarrollo_unidades-1-objetivos': [self.objetivo_1.id, self.objetivo_2.id], 'desarrollo_unidades-1-objetivos_generales': [self.general_1.id, self.general_2.id], 'desarrollo_unidades-1-destrezas': [self.destreza_1.id, self.destreza_2.id], 'desarrollo_unidades-1-orientaciones_metodologicas': 'lorem ipsum', 'desarrollo_unidades-1-semanas': 2, } self.plan_anual = mixer.blend(PlanAnual, elaborado_por=self.user) another_user = mixer.blend(User) self.another_plan = mixer.blend(PlanAnual, elaborado_por=another_user) self.common_user = mixer.blend(User)
class TestService(unittest.TestCase): def get_ini(self): return os.path.join(os.path.dirname(__file__), 'test_memorynode.ini') def setUp(self): self.config = testing.setUp() settings = {} load_into_settings(self.get_ini(), settings) self.config.add_settings(settings) self.config.include("tokenserver") load_and_register("tokenserver", self.config) self.backend = self.config.registry.getUtility(INodeAssignment) wsgiapp = self.config.make_wsgi_app() self.app = TestApp(wsgiapp) # Mock out the verifier to return successfully by default. self.mock_browserid_verifier_context = self.mock_browserid_verifier() self.mock_browserid_verifier_context.__enter__() self.mock_oauth_verifier_context = self.mock_oauth_verifier() self.mock_oauth_verifier_context.__enter__() self.logs = LogCapture() def tearDown(self): self.logs.uninstall() self.mock_oauth_verifier_context.__exit__(None, None, None) self.mock_browserid_verifier_context.__exit__(None, None, None) def assertMetricWasLogged(self, key): """Check that a metric was logged during the request.""" for r in self.logs.records: if key in r.__dict__: break else: assert False, "metric %r was not logged" % (key, ) def clearLogs(self): del self.logs.records[:] def unsafelyParseToken(self, token): # For testing purposes, don't check HMAC or anything... token = token.encode("utf8") return json.loads(decode_token_bytes(token)[:-32].decode("utf8")) @contextlib.contextmanager def mock_browserid_verifier(self, response=None, exc=None): def mock_verify_method(assertion): if exc is not None: raise exc if response is not None: return response return { "status": "okay", "email": get_assertion_info(assertion)["principal"]["email"], } verifier = get_browserid_verifier(self.config.registry) orig_verify_method = verifier.__dict__.get("verify", None) verifier.__dict__["verify"] = mock_verify_method try: yield None finally: if orig_verify_method is None: del verifier.__dict__["verify"] else: verifier.__dict__["verify"] = orig_verify_method @contextlib.contextmanager def mock_oauth_verifier(self, response=None, exc=None): def mock_verify_method(token): if exc is not None: raise exc if response is not None: return response return { "email": token.decode("hex"), "idpClaims": {}, } verifier = get_oauth_verifier(self.config.registry) orig_verify_method = verifier.__dict__.get("verify", None) verifier.__dict__["verify"] = mock_verify_method try: yield None finally: if orig_verify_method is None: del verifier.__dict__["verify"] else: verifier.__dict__["verify"] = orig_verify_method def _getassertion(self, **kw): kw.setdefault('email', '*****@*****.**') kw.setdefault('audience', 'http://tokenserver.services.mozilla.com') return make_assertion(**kw).encode('ascii') def _gettoken(self, email='*****@*****.**'): return email.encode('hex') def test_unknown_app(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} resp = self.app.get('/1.0/xXx/token', headers=headers, status=404) self.assertTrue('errors' in resp.json) def test_invalid_client_state(self): headers = {'X-Client-State': 'state!'} resp = self.app.get('/1.0/sync/1.5', headers=headers, status=400) self.assertEquals(resp.json['errors'][0]['location'], 'header') self.assertEquals(resp.json['errors'][0]['name'], 'X-Client-State') headers = {'X-Client-State': 'foobar\n\r\t'} resp = self.app.get('/1.0/sync/1.5', headers=headers, status=400) self.assertEquals(resp.json['errors'][0]['location'], 'header') self.assertEquals(resp.json['errors'][0]['name'], 'X-Client-State') def test_no_auth(self): self.app.get('/1.0/sync/1.5', status=401) def test_valid_app(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertIn('https://example.com/1.1', res.json['api_endpoint']) self.assertIn('duration', res.json) self.assertEquals(res.json['duration'], 3600) self.assertMetricWasLogged('token.assertion.verify_success') self.clearLogs() def test_unknown_pattern(self): # sync 1.5 is defined in the .ini file, but no pattern exists for it. headers = {'Authorization': 'BrowserID %s' % self._getassertion()} self.app.get('/1.0/sync/1.5', headers=headers, status=503) def test_discovery(self): res = self.app.get('/') self.assertEqual( res.json, { 'auth': 'http://localhost', 'services': { 'sync': ['1.1', '1.5'], }, 'browserid': { 'allowed_issuers': None, 'trusted_issuers': None, }, 'oauth': { 'default_issuer': 'api.accounts.firefox.com', 'scope': 'https://identity.mozilla.com/apps/oldsync', 'server_url': 'https://oauth.accounts.firefox.com/v1', } }) def test_version_returns_404_by_default(self): with mock.patch('os.path.exists', return_value=False): self.app.get('/__version__', status=404) def test_version_returns_file_in_current_folder_if_present(self): content = {'version': '0.8.1'} fake_file = mock.mock_open(read_data=json.dumps(content)) with mock.patch('os.path.exists'): with mock.patch('tokenserver.views.open', fake_file): response = self.app.get('/__version__') self.assertEquals(response.json, content) def test_lbheartbeat(self): res = self.app.get('/__lbheartbeat__') self.assertEqual(res.json, {}) def test_unauthorized_error_status(self): # Totally busted auth -> generic error. headers = {'Authorization': 'Unsupported-Auth-Scheme IHACKYOU'} res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'error') # BrowserID verifier errors assertion = self._getassertion() headers = {'Authorization': 'BrowserID %s' % assertion} # Bad signature -> "invalid-credentials" errs = browserid.errors with self.mock_browserid_verifier(exc=errs.InvalidSignatureError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') # Bad audience -> "invalid-credentials" with self.mock_browserid_verifier(exc=errs.AudienceMismatchError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') self.assertMetricWasLogged('token.assertion.verify_failure') self.assertMetricWasLogged('token.assertion.audience_mismatch_error') self.clearLogs() # Expired timestamp -> "invalid-timestamp" with self.mock_browserid_verifier(exc=errs.ExpiredSignatureError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-timestamp') self.assertTrue('X-Timestamp' in res.headers) self.assertMetricWasLogged('token.assertion.verify_failure') self.assertMetricWasLogged('token.assertion.expired_signature_error') self.clearLogs() # Connection error -> 503 with self.mock_browserid_verifier(exc=errs.ConnectionError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=503) self.assertMetricWasLogged('token.assertion.verify_failure') self.assertMetricWasLogged('token.assertion.connection_error') # It should also log a full traceback of the error. for r in self.logs.records: if r.msg == "Unexpected verification error": assert r.exc_info is not None break else: assert False, "failed to log a traceback for ConnectionError" self.clearLogs() # Some other wacky error -> not captured with self.mock_browserid_verifier(exc=ValueError): with self.assertRaises(ValueError): res = self.app.get('/1.0/sync/1.1', headers=headers) # OAuth verifier errors token = self._gettoken() headers = {'Authorization': 'Bearer %s' % token} # Bad token -> "invalid-credentials" err = fxa.errors.TrustError({"code": 400, "errno": 123}) with self.mock_oauth_verifier(exc=err): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') # Connection error -> 503 with self.mock_oauth_verifier(exc=errs.ConnectionError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=503) self.assertMetricWasLogged('token.oauth.verify_failure') self.assertMetricWasLogged('token.oauth.connection_error') # It should also log a full traceback of the error. for r in self.logs.records: if r.msg == "Unexpected verification error": assert r.exc_info is not None break else: assert False, "failed to log a traceback for ConnectionError" self.clearLogs() # Some other wacky error -> not captured with self.mock_oauth_verifier(exc=ValueError): with self.assertRaises(ValueError): res = self.app.get('/1.0/sync/1.1', headers=headers) def test_unverified_token(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} # Assertion should not be rejected if fxa-tokenVerified is unset mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": {} } with self.mock_browserid_verifier(response=mock_response): self.app.get("/1.0/sync/1.1", headers=headers, status=200) # Assertion should not be rejected if fxa-tokenVerified is True mock_response['idpClaims']['fxa-tokenVerified'] = True with self.mock_browserid_verifier(response=mock_response): self.app.get("/1.0/sync/1.1", headers=headers, status=200) # Assertion should be rejected if fxa-tokenVerified is False mock_response['idpClaims']['fxa-tokenVerified'] = False with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') def test_generation_number_change(self): headers = {"Authorization": "BrowserID %s" % self._getassertion()} # Start with no generation number. mock_response = {"status": "okay", "email": "*****@*****.**"} with self.mock_browserid_verifier(response=mock_response): res1 = self.app.get("/1.0/sync/1.1", headers=headers) # Now send an explicit generation number. # The node assignment should not change. mock_response["idpClaims"] = {"fxa-generation": 12} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # Previous generation numbers get an invalid-generation response. del mock_response["idpClaims"] with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"some-nonsense": "lolwut"} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": 10} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Equal generation numbers are accepted. mock_response["idpClaims"] = {"fxa-generation": 12} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # Later generation numbers are accepted. # Again, the node assignment should not change. mock_response["idpClaims"] = {"fxa-generation": 13} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # And that should lock out the previous generation number mock_response["idpClaims"] = {"fxa-generation": 12} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Various nonsense generation numbers should give errors. mock_response["idpClaims"] = {"fxa-generation": "whatswrongwithyour"} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": None} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": "42"} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": ["I", "HACK", "YOU"]} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") def test_client_state_change(self): mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 1234 }, } # Start with no client-state header. headers = {'Authorization': 'BrowserID %s' % self._getassertion()} with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) uid0 = res.json['uid'] # No change == same uid. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid0) # Changing client-state header requires changing generation number. headers['X-Client-State'] = 'aaaa' with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('new value with no generation change')) # Change the client-state header, get a new uid. headers['X-Client-State'] = 'aaaa' mock_response["idpClaims"]["fxa-generation"] += 1 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) uid1 = res.json['uid'] self.assertNotEqual(uid1, uid0) # No change == same uid. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid1) # Send a client-state header, get a new uid. headers['X-Client-State'] = 'bbbb' mock_response["idpClaims"]["fxa-generation"] += 1 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) uid2 = res.json['uid'] self.assertNotEqual(uid2, uid0) self.assertNotEqual(uid2, uid1) # No change == same uid. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid2) # Use a previous client-state, get an auth error. headers['X-Client-State'] = 'aaaa' with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('stale value')) del headers['X-Client-State'] with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') headers['X-Client-State'] = 'aaaa' mock_response["idpClaims"]["fxa-generation"] += 1 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') def test_client_state_cannot_revert_to_empty(self): # Start with a client-state header. headers = { 'Authorization': 'BrowserID %s' % self._getassertion(), 'X-Client-State': 'aaa', } res = self.app.get('/1.0/sync/1.1', headers=headers) uid0 = res.json['uid'] # Sending no client-state will fail. del headers['X-Client-State'] res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('empty string')) headers['X-Client-State'] = '' res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('empty string')) # And the uid will be unchanged. headers['X-Client-State'] = 'aaa' res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid0) def test_credentials_from_oauth_and_browserid(self): # Send initial credentials via oauth. headers_oauth = { "Authorization": "Bearer %s" % self._gettoken(), "X-KeyID": "12-YWFh", } res1 = self.app.get("/1.0/sync/1.1", headers=headers_oauth) # Send the same credentials via BrowserID headers_browserid = { "Authorization": "BrowserID %s" % self._getassertion(), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 12 }, } with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers_browserid) # They should get the same node assignment. self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # Earlier generation number via BrowserID -> invalid-generation mock_response["idpClaims"]['fxa-generation'] = 11 with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers_browserid, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Earlier generation number via OAuth -> invalid-generation headers_oauth['X-KeyID'] = '11-YWFh' res = self.app.get("/1.0/sync/1.1", headers=headers_oauth, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Change client-state via BrowserID. headers_browserid['X-Client-State'] = '626262' mock_response["idpClaims"]['fxa-generation'] = 42 with self.mock_browserid_verifier(response=mock_response): res1 = self.app.get("/1.0/sync/1.1", headers=headers_browserid) # Old OAuth credentials are rejected. headers_oauth['X-KeyID'] = '12-YWFh' res = self.app.get("/1.0/sync/1.1", headers=headers_oauth, status=401) self.assertEqual(res.json["status"], "invalid-client-state") headers_oauth['X-KeyID'] = '12-YmJi' res = self.app.get("/1.0/sync/1.1", headers=headers_oauth, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Updated OAuth credentials are accepted. headers_oauth['X-KeyID'] = '42-YmJi' res2 = self.app.get("/1.0/sync/1.1", headers=headers_oauth) # They should again get the same node assignment. self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) def test_client_specified_duration(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} # It's ok to request a shorter-duration token. res = self.app.get('/1.0/sync/1.1?duration=12', headers=headers) self.assertEquals(res.json['duration'], 12) # But you can't exceed the server's default value. res = self.app.get('/1.0/sync/1.1?duration=4000', headers=headers) self.assertEquals(res.json['duration'], 3600) # And nonsense values are ignored. res = self.app.get('/1.0/sync/1.1?duration=lolwut', headers=headers) self.assertEquals(res.json['duration'], 3600) res = self.app.get('/1.0/sync/1.1?duration=-1', headers=headers) self.assertEquals(res.json['duration'], 3600) def test_allow_new_users(self): # New users are allowed by default. settings = self.config.registry.settings self.assertEquals(settings.get('tokenserver.allow_new_users'), None) assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) # They're allowed if we explicitly allow them. settings['tokenserver.allow_new_users'] = True assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) # They're not allowed if we explicitly disable them. settings['tokenserver.allow_new_users'] = False assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'new-users-disabled') # But existing users are still allowed. assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) def test_metrics_uid_logging(self): assert "fxa.metrics_uid_secret_key" in self.config.registry.settings assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) self.assertMetricWasLogged('uid') self.assertMetricWasLogged('uid.first_seen_at') self.assertMetricWasLogged('metrics_uid') self.assertMetricWasLogged('metrics_device_id') def test_uid_and_kid_from_browserid_assertion(self): assertion = self._getassertion(email="*****@*****.**") headers_browserid = { "Authorization": "BrowserID %s" % (assertion, ), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 12 }, } with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers_browserid) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["uid"], res.json["uid"]) self.assertEqual(token["fxa_uid"], "testuser") self.assertEqual(token["fxa_kid"], "616161") self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) self.assertIn("hashed_device_id", token) def test_uid_and_kid_from_oauth_token(self): oauth_token = self._gettoken(email="*****@*****.**") headers_oauth = { "Authorization": "Bearer %s" % (oauth_token, ), "X-KeyID": "12-YWFh", } res = self.app.get("/1.0/sync/1.1", headers=headers_oauth) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["uid"], res.json["uid"]) self.assertEqual(token["fxa_uid"], "testuser") self.assertEqual(token["fxa_kid"], "616161") self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) self.assertIn("hashed_device_id", token) def test_metrics_uid_is_returned_in_response(self): assert "fxa.metrics_uid_secret_key" in self.config.registry.settings assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} res = self.app.get('/1.0/sync/1.1', headers=headers, status=200) self.assertTrue('hashed_fxa_uid' in res.json)
def test_expire(self): self._setup_enrollments('student_expired_waiting', None, timezone.now() - timedelta(60)) self._setup_enrollments('student_waiting', None, timezone.now() - timedelta(59)) self._setup_enrollments('student_actualized', UserFactory(), timezone.now() - timedelta(90)) expired_program_enrollment = ProgramEnrollment.objects.get( external_user_key='student_expired_waiting') expired_course_enrollments = list( ProgramCourseEnrollment.objects.filter( program_enrollment=expired_program_enrollment)) # assert deleted enrollments are logged (without pii) with LogCapture(log.name) as log_capture: expire_waiting_enrollments(60) program_enrollment_message_tmpl = u'Found expired program_enrollment (id={}) for program_uuid={}' course_enrollment_message_tmpl = ( u'Found expired program_course_enrollment (id={}) for program_uuid={}, course_key={}' ) log_capture.check_present( (log.name, 'INFO', program_enrollment_message_tmpl.format( expired_program_enrollment.id, expired_program_enrollment.program_uuid, )), (log.name, 'INFO', course_enrollment_message_tmpl.format( expired_course_enrollments[0].id, expired_program_enrollment.program_uuid, expired_course_enrollments[0].course_key, )), (log.name, 'INFO', course_enrollment_message_tmpl.format( expired_course_enrollments[1].id, expired_program_enrollment.program_uuid, expired_course_enrollments[1].course_key, )), (log.name, 'INFO', u'Removed 3 expired records:' u' {u\'program_enrollments.ProgramCourseEnrollment\': 2,' u' u\'program_enrollments.ProgramEnrollment\': 1}'), ) program_enrollments = ProgramEnrollment.objects.all() program_course_enrollments = ProgramCourseEnrollment.objects.all() historical_program_enrollments = ProgramEnrollment.historical_records.all( ) # pylint: disable=no-member historical_program_course_enrollments = ProgramCourseEnrollment.historical_records.all( ) # pylint: disable=no-member # assert expired records no longer exist with self.assertRaises(ProgramEnrollment.DoesNotExist): program_enrollments.get( external_user_key='student_expired_waiting') self.assertEqual(len(program_course_enrollments), 4) # assert fresh waiting records are not affected waiting_enrollment = program_enrollments.get( external_user_key='student_waiting') self.assertEqual( len(waiting_enrollment.program_course_enrollments.all()), 2) # assert actualized enrollments are not affected actualized_enrollment = program_enrollments.get( external_user_key='student_actualized') self.assertEqual( len(actualized_enrollment.program_course_enrollments.all()), 2) # assert expired historical records are also removed with self.assertRaises(ObjectDoesNotExist): historical_program_enrollments.get( external_user_key='student_expired_waiting') self.assertEqual( len( historical_program_course_enrollments.filter( program_enrollment_id=expired_program_enrollment.id)), 0) # assert other historical records are not affected self.assertEqual(len(historical_program_enrollments), 2) self.assertEqual(len(historical_program_course_enrollments), 4)
def test_superuser_can_assign_local_user_to_group_andor_set_user_as_superuser_but_not_neither( self): request = self.wagtail_factory.get('/') request.user = self.superuser request.site = self.wagtail_site form_data = { 'username': '******', 'email': '*****@*****.**', 'first_name': 'John', 'last_name': 'Wagtail', 'groups': [], 'is_superuser': True, 'is_active': True, } with LogCapture(): form_data.update({ 'is_superuser': True, 'groups': [], 'is_active': True }) form = LocalUserEditForm(request, self.safe_get_user(self.wagtail_admin), form_data) valid = form.is_valid() self.assertTrue(valid) form_data.update({ 'is_superuser': True, 'groups': [self.wagtail_editors_group.pk], 'is_active': True }) form = LocalUserEditForm(request, self.safe_get_user(self.wagtail_admin), form_data) valid = form.is_valid() self.assertTrue(valid) form_data.update({ 'is_superuser': False, 'groups': [self.wagtail_editors_group.pk], 'is_active': True }) form = LocalUserEditForm(request, self.safe_get_user(self.wagtail_admin), form_data) valid = form.is_valid() self.assertTrue(valid) form_data.update({ 'is_superuser': False, 'groups': [], 'is_active': True }) form = LocalUserEditForm(request, self.safe_get_user(self.wagtail_admin), form_data) valid = form.is_valid() self.assertFalse(valid) self.assertEqual(form.errors['groups'][0], form.error_messages['group_required_superuser'])
def test_https_connect_tunnel(self): crawler = get_crawler(SimpleSpider) with LogCapture() as log: yield crawler.crawl( self.mockserver.url("/status?n=200", is_secure=True)) self._assert_got_response_code(200, log)
def test_multigeom(self) -> None: with LogCapture("tilecloud_chain", level=30) as log_capture: self.assert_tiles_generated( cmd= ".build/venv/bin/generate_tiles -c tilegeneration/test-multigeom.yaml", main_func=generate.main, directory="/tmp/tiles/", tiles_pattern="1.0.0/pp/default/2012/swissgrid_5/%i/%i/%i.png", tiles=[ (0, 5, 4), (0, 5, 5), (0, 5, 6), (0, 5, 7), (0, 6, 4), (0, 6, 5), (0, 6, 6), (0, 6, 7), (0, 7, 4), (0, 7, 5), (0, 7, 6), (0, 7, 7), (1, 11, 8), (1, 11, 9), (1, 11, 10), (1, 11, 11), (1, 11, 12), (1, 11, 13), (1, 11, 14), (1, 12, 8), (1, 12, 9), (1, 12, 10), (1, 12, 11), (1, 12, 12), (1, 12, 13), (1, 12, 14), (1, 13, 8), (1, 13, 9), (1, 13, 10), (1, 13, 11), (1, 13, 12), (1, 13, 13), (1, 13, 14), (1, 14, 8), (1, 14, 9), (1, 14, 10), (1, 14, 11), (1, 14, 12), (1, 14, 13), (1, 14, 14), (1, 15, 8), (1, 15, 9), (1, 15, 10), (1, 15, 11), (1, 15, 12), (1, 15, 13), (1, 15, 14), (2, 29, 35), (2, 39, 21), (3, 78, 42), (3, 58, 70), ], regex=True, expected= r"""The tile generation of layer 'pp \(DATE=2012\)' is finish Nb generated tiles: 51 Nb tiles dropped: 0 Nb tiles stored: 51 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: [34][0-9] Kio Time per tile: [0-9]+ ms Size per tile: [79][0-9][0-9] o """, ) log_capture.check()
def setUp(self): self.lc = LogCapture() self.lc.setLevel(logging.DEBUG) self.lc.addFilter(test_common.MyLogCaptureFilter()) self.addCleanup(self.cleanup)
def test_zoom_identifier(self) -> None: with LogCapture("tilecloud_chain", level=30) as log_capture: xy = list(product(range(585, 592), range(429, 432))) x = [e[0] for e in xy] y = [e[1] for e in xy] xy2 = list(product(range(2929, 2936), range(2148, 2152))) x2 = [e[0] for e in xy2] y2 = [e[1] for e in xy2] xy3 = list(product(range(5859, 5864), range(4296, 4304))) x3 = [e[0] for e in xy3] y3 = [e[1] for e in xy3] for d in ("-d", ""): self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles {} " "-c tilegeneration/test-nosns.yaml -t 1 -l polygon2 -z 0". format(d), main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/%s/default/2012/swissgrid_01/%s/%i/%i.png", tiles=list( zip(repeat("polygon2", len(x)), repeat("1", len(x)), x, y)), regex=True, expected= r"""The tile generation of layer 'polygon2 \(DATE=2012\)' is finish Nb generated metatiles: 1 Nb metatiles dropped: 0 Nb generated tiles: 64 Nb tiles dropped: 43 Nb tiles stored: 21 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: 16 Kio Time per tile: [0-9]+ ms Size per tile: 788 o """, ) self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles {} " "-c tilegeneration/test-nosns.yaml -t 1 -l polygon2 -z 1". format(d), main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/%s/default/2012/swissgrid_01/%s/%i/%i.png", tiles=list( zip(repeat("polygon2", len(x2)), repeat("0_2", len(x2)), x2, y2)), regex=True, expected= r"""The tile generation of layer 'polygon2 \(DATE=2012\)' is finish Nb generated metatiles: 1 Nb metatiles dropped: 0 Nb generated tiles: 64 Nb tiles dropped: 36 Nb tiles stored: 28 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: 22 Kio Time per tile: [0-9]+ ms Size per tile: 806 o """, ) self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles {} " "-c tilegeneration/test-nosns.yaml -t 1 -l polygon2 -z 2". format(d), main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/%s/default/2012/swissgrid_01/%s/%i/%i.png", tiles=list( zip(repeat("polygon2", len(x3)), repeat("0_1", len(x3)), x3, y3)), regex=True, expected= r"""The tile generation of layer 'polygon2 \(DATE=2012\)' is finish Nb generated metatiles: 1 Nb metatiles dropped: 0 Nb generated tiles: 64 Nb tiles dropped: 24 Nb tiles stored: 40 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: 32 Kio Time per tile: [0-9]+ ms Size per tile: 818 o """, ) log_capture.check()
def tearDown(test): TempDirectory.cleanup_all() LogCapture.uninstall_all()
def test_layer_bbox(self) -> None: for d in ("-d", ""): with LogCapture("tilecloud_chain", level=30) as log_capture: self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles {} " "-c tilegeneration/test-nosns.yaml -l polygon -z 0".format( d), main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/polygon/default/2012/swissgrid_5/0/%i/%i.png", tiles=list(product((5, 6, 7), (4, 5, 6, 7))), regex=True, expected= r"""The tile generation of layer 'polygon \(DATE=2012\)' is finish Nb generated tiles: 12 Nb tiles dropped: 0 Nb tiles stored: 12 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: [0-9.]+ Kio Time per tile: [0-9.]+ ms Size per tile: [69][0-9][0-9] o """, ) self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles %s " "-c tilegeneration/test-nosns.yaml -l polygon -z 0" " -b 550000 170000 560000 180000" % d, main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/polygon/default/2012/swissgrid_5/0/%i/%i.png", tiles=[(6, 5), (7, 5)], regex=True, expected= r"""The tile generation of layer 'polygon \(DATE=2012\)' is finish Nb generated tiles: 2 Nb tiles dropped: 0 Nb tiles stored: 2 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: 1.[6-9] Kio Time per tile: [0-9]+ ms Size per tile: [89][0-9][0-9] o """, ) self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles %s " "-c tilegeneration/test-nosns.yaml -l polygon -z 0" " -b 550000.0 170000.0 560000.0 180000.0" % d, main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/polygon/default/2012/swissgrid_5/0/%i/%i.png", tiles=[(6, 5), (7, 5)], regex=True, expected= r"""The tile generation of layer 'polygon \(DATE=2012\)' is finish Nb generated tiles: 2 Nb tiles dropped: 0 Nb tiles stored: 2 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: 1.[6-9] Kio Time per tile: [0-9]+ ms Size per tile: [89][0-9][0-9] o """, ) self.assert_tiles_generated( cmd=".build/venv/bin/generate_tiles {} " "-c tilegeneration/test-nosns.yaml -l all -z 0".format(d), main_func=generate.main, directory="/tmp/tiles/", tiles_pattern= "1.0.0/all/default/2012/swissgrid_5/0/%i/%i.png", tiles=[(6, 5), (7, 5)], regex=True, expected= r"""The tile generation of layer 'all \(DATE=2012\)' is finish Nb generated tiles: 2 Nb tiles dropped: 0 Nb tiles stored: 2 Nb tiles in error: 0 Total time: [0-9]+:[0-9][0-9]:[0-9][0-9] Total size: 1.[6-9] Kio Time per tile: [0-9]+ ms Size per tile: [89][0-9][0-9] o """, ) log_capture.check()
def test_basic_operations(self): # Test where enrichment is None mock_panoptes_enrichment_cache = Mock(return_value=None) with patch( 'yahoo_panoptes.framework.plugins.runner.PanoptesEnrichmentCache', mock_panoptes_enrichment_cache): runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, self._panoptes_resource, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present(( 'panoptes.tests.test_runner', 'ERROR', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] ' 'Could not set up context for plugin')) self._log_capture.uninstall() self._log_capture = LogCapture(attributes=self.extract) # Test with enrichment runner = self._runner_class( "Test Polling Plugin", "polling", PanoptesPollingPlugin, PanoptesPluginInfo, self._panoptes_resource, self._panoptes_context, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, PanoptesTestKeyValueStore, "plugin_logger", PanoptesMetricsGroupSet, _callback) runner.execute_plugin() self._log_capture.check_present( ('panoptes.tests.test_runner', 'INFO', 'Attempting to execute plugin "Test Polling Plugin"'), ('panoptes.tests.test_runner', 'DEBUG', '''Starting Plugin Manager for "polling" plugins with the following ''' '''configuration: {'polling': <class''' """ 'yahoo_panoptes.polling.polling_plugin.PanoptesPollingPlugin'>}, """ """['tests/plugins/polling'], panoptes-plugin"""), ('panoptes.tests.test_runner', 'DEBUG', 'Found 3 plugins'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin ' '"Test Polling Plugin", version "0.1" of type "polling"' ', category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin 2", ' 'version "0.1" of type "polling", category "polling"'), ('panoptes.tests.test_runner', 'DEBUG', 'Loaded plugin "Test Polling Plugin Second Instance", ' 'version "0.1" of type "polling", category "polling"'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Attempting to get lock for plugin ' '"Test Polling Plugin"'), ('panoptes.tests.test_runner', 'DEBUG', 'Attempting to get lock for plugin "Test Polling Plugin", with lock path and ' 'identifier in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Acquired lock'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test]' ' Ran in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Released lock'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test] Plugin returned' ' a result set with 1 members'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|' 'type|test|id|test|endpoint|test]' ' Callback function ran in seconds'), ('panoptes.tests.test_runner', 'INFO', '[Test Polling Plugin] [plugin|test|site|test|class|test|subclass|test|type|' 'test|id|test|endpoint|test] GC took seconds. There are garbage objects.' ), ('panoptes.tests.test_runner', 'ERROR', 'No enrichment data found on KV store for plugin Test Polling Plugin ' 'resource test namespace test using key test'), ('panoptes.tests.test_runner', 'DEBUG', 'Successfully created PanoptesEnrichmentCache enrichment_data {} for plugin ' 'Test Polling Plugin'), order_matters=False)
class TestService(unittest.TestCase): def get_ini(self): return os.path.join(os.path.dirname(__file__), 'test_memorynode.ini') def setUp(self): self.config = testing.setUp() settings = {} load_into_settings(self.get_ini(), settings) self.config.add_settings(settings) self.config.include("tokenserver") load_and_register("tokenserver", self.config) self.backend = self.config.registry.getUtility(INodeAssignment) wsgiapp = self.config.make_wsgi_app() self.app = TestApp(wsgiapp) # Mock out the verifier to return successfully by default. self.mock_browserid_verifier_context = self.mock_browserid_verifier() self.mock_browserid_verifier_context.__enter__() self.mock_oauth_verifier_context = self.mock_oauth_verifier() self.mock_oauth_verifier_context.__enter__() self.logs = LogCapture() def tearDown(self): self.logs.uninstall() self.mock_oauth_verifier_context.__exit__(None, None, None) self.mock_browserid_verifier_context.__exit__(None, None, None) def assertExceptionWasLogged(self, msg): for r in self.logs.records: if r.msg == msg: assert r.exc_info is not None break else: assert False, "exception with message %r was not logged" % (msg, ) def assertMessageWasNotLogged(self, msg): for r in self.logs.records: if r.msg == msg: assert False, "message %r was unexpectedly logged" % (msg, ) def assertMetricWasLogged(self, key): """Check that a metric was logged during the request.""" for r in self.logs.records: if key in r.__dict__: break else: assert False, "metric %r was not logged" % (key, ) def clearLogs(self): del self.logs.records[:] def unsafelyParseToken(self, token): # For testing purposes, don't check HMAC or anything... token = token.encode("utf8") return json.loads(decode_token_bytes(token)[:-32].decode("utf8")) @contextlib.contextmanager def mock_browserid_verifier(self, response=None, exc=None): def mock_verify_method(assertion): if exc is not None: raise exc if response is not None: return response return { "status": "okay", "email": get_assertion_info(assertion)["principal"]["email"], } verifier = get_browserid_verifier(self.config.registry) orig_verify_method = verifier.__dict__.get("verify", None) verifier.__dict__["verify"] = mock_verify_method try: yield None finally: if orig_verify_method is None: del verifier.__dict__["verify"] else: verifier.__dict__["verify"] = orig_verify_method @contextlib.contextmanager def mock_oauth_verifier(self, response=None, exc=None): def mock_verify_method(token): if exc is not None: raise exc if response is not None: return response return { "email": token.decode("hex"), "idpClaims": {}, } verifier = get_oauth_verifier(self.config.registry) orig_verify_method = verifier.__dict__.get("verify", None) verifier.__dict__["verify"] = mock_verify_method try: yield None finally: if orig_verify_method is None: del verifier.__dict__["verify"] else: verifier.__dict__["verify"] = orig_verify_method def _getassertion(self, **kw): kw.setdefault('email', '*****@*****.**') kw.setdefault('audience', 'http://tokenserver.services.mozilla.com') return make_assertion(**kw).encode('ascii') def _gettoken(self, email='*****@*****.**'): return email.encode('hex') def test_unknown_app(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} resp = self.app.get('/1.0/xXx/token', headers=headers, status=404) self.assertTrue('errors' in resp.json) def test_invalid_client_state(self): headers = {'X-Client-State': 'state!'} resp = self.app.get('/1.0/sync/1.5', headers=headers, status=400) self.assertEquals(resp.json['errors'][0]['location'], 'header') self.assertEquals(resp.json['errors'][0]['name'], 'X-Client-State') headers = {'X-Client-State': 'foobar\n\r\t'} resp = self.app.get('/1.0/sync/1.5', headers=headers, status=400) self.assertEquals(resp.json['errors'][0]['location'], 'header') self.assertEquals(resp.json['errors'][0]['name'], 'X-Client-State') def test_no_auth(self): self.app.get('/1.0/sync/1.5', status=401) def test_valid_app(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertIn('https://example.com/1.1', res.json['api_endpoint']) self.assertIn('duration', res.json) self.assertEquals(res.json['duration'], 3600) self.assertMetricWasLogged('token.assertion.verify_success') self.clearLogs() def test_unknown_pattern(self): # sync 1.5 is defined in the .ini file, but no pattern exists for it. headers = {'Authorization': 'BrowserID %s' % self._getassertion()} self.app.get('/1.0/sync/1.5', headers=headers, status=503) def test_discovery(self): res = self.app.get('/') self.assertEqual( res.json, { 'auth': 'http://localhost', 'services': { 'sync': ['1.1', '1.5'], }, 'browserid': { 'allowed_issuers': None, 'trusted_issuers': None, }, 'oauth': { 'default_issuer': 'api.accounts.firefox.com', 'scope': 'https://identity.mozilla.com/apps/oldsync', 'server_url': 'https://oauth.accounts.firefox.com/v1', } }) def test_version_returns_404_by_default(self): # clear cache try: del tokenserver.views.version_view.__json__ except AttributeError: pass with mock.patch('os.path.exists', return_value=False): self.app.get('/__version__', status=404) def test_version_returns_file_in_current_folder_if_present(self): # clear cache try: del tokenserver.views.version_view.__json__ except AttributeError: pass content = {'version': '0.8.1'} fake_file = mock.mock_open(read_data=json.dumps(content)) with mock.patch('os.path.exists'): with mock.patch('tokenserver.views.open', fake_file): response = self.app.get('/__version__') self.assertEquals(response.json, content) def test_lbheartbeat(self): res = self.app.get('/__lbheartbeat__') self.assertEqual(res.json, {}) def test_unauthorized_error_status(self): # Totally busted auth -> generic error. headers = {'Authorization': 'Unsupported-Auth-Scheme IHACKYOU'} res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'error') # BrowserID verifier errors assertion = self._getassertion() headers = {'Authorization': 'BrowserID %s' % assertion} # Bad signature -> "invalid-credentials" errs = browserid.errors with self.mock_browserid_verifier(exc=errs.InvalidSignatureError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') # Bad audience -> "invalid-credentials" with self.mock_browserid_verifier(exc=errs.AudienceMismatchError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') self.assertMetricWasLogged('token.assertion.verify_failure') self.assertMetricWasLogged('token.assertion.audience_mismatch_error') self.clearLogs() # Expired timestamp -> "invalid-timestamp" with self.mock_browserid_verifier(exc=errs.ExpiredSignatureError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-timestamp') self.assertTrue('X-Timestamp' in res.headers) self.assertMetricWasLogged('token.assertion.verify_failure') self.assertMetricWasLogged('token.assertion.expired_signature_error') self.clearLogs() # Connection error -> 503 with self.mock_browserid_verifier(exc=errs.ConnectionError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=503) self.assertMetricWasLogged('token.assertion.verify_failure') self.assertMetricWasLogged('token.assertion.connection_error') self.assertExceptionWasLogged('Unexpected verification error') self.clearLogs() # Some other wacky error -> not captured with self.mock_browserid_verifier(exc=ValueError): with self.assertRaises(ValueError): res = self.app.get('/1.0/sync/1.1', headers=headers) # OAuth verifier errors token = self._gettoken() headers = {'Authorization': 'Bearer %s' % token} # Bad token -> "invalid-credentials" err = fxa.errors.ClientError({"code": 400, "errno": 108}) with self.mock_oauth_verifier(exc=err): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') self.assertMetricWasLogged('token.oauth.errno.108') self.assertMessageWasNotLogged('Unexpected verification error') # Untrusted scopes -> "invalid-credentials" err = fxa.errors.TrustError({"code": 400, "errno": 999}) with self.mock_oauth_verifier(exc=err): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') self.assertMessageWasNotLogged('Unexpected verification error') # Connection error -> 503 with self.mock_oauth_verifier(exc=errs.ConnectionError): res = self.app.get('/1.0/sync/1.1', headers=headers, status=503) self.assertMetricWasLogged('token.oauth.verify_failure') self.assertMetricWasLogged('token.oauth.connection_error') self.assertExceptionWasLogged('Unexpected verification error') self.clearLogs() # Some other wacky error -> not captured with self.mock_oauth_verifier(exc=ValueError): with self.assertRaises(ValueError): res = self.app.get('/1.0/sync/1.1', headers=headers) def test_unverified_token(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} # Assertion should not be rejected if fxa-tokenVerified is unset mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": {} } with self.mock_browserid_verifier(response=mock_response): self.app.get("/1.0/sync/1.1", headers=headers, status=200) # Assertion should not be rejected if fxa-tokenVerified is True mock_response['idpClaims']['fxa-tokenVerified'] = True with self.mock_browserid_verifier(response=mock_response): self.app.get("/1.0/sync/1.1", headers=headers, status=200) # Assertion should be rejected if fxa-tokenVerified is False mock_response['idpClaims']['fxa-tokenVerified'] = False with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-credentials') def test_generation_number_change(self): headers = {"Authorization": "BrowserID %s" % self._getassertion()} # Start with no generation number. mock_response = {"status": "okay", "email": "*****@*****.**"} with self.mock_browserid_verifier(response=mock_response): res1 = self.app.get("/1.0/sync/1.1", headers=headers) # Now send an explicit generation number. # The node assignment should not change. mock_response["idpClaims"] = {"fxa-generation": 12} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # Clients that don't report generation number are still allowed. del mock_response["idpClaims"] with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) mock_response["idpClaims"] = {"some-nonsense": "lolwut"} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) # But previous generation numbers get an invalid-generation response. mock_response["idpClaims"] = {"fxa-generation": 10} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Equal generation numbers are accepted. mock_response["idpClaims"] = {"fxa-generation": 12} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # Later generation numbers are accepted. # Again, the node assignment should not change. mock_response["idpClaims"] = {"fxa-generation": 13} with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers) self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # And that should lock out the previous generation number mock_response["idpClaims"] = {"fxa-generation": 12} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Various nonsense generation numbers should give errors. mock_response["idpClaims"] = {"fxa-generation": "whatswrongwithyour"} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": None} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": "42"} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") mock_response["idpClaims"] = {"fxa-generation": ["I", "HACK", "YOU"]} with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-generation") def test_client_state_change(self): mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 1234, "fxa-keysChangedAt": 1234 }, } # Start with no client-state header. headers = {'Authorization': 'BrowserID %s' % self._getassertion()} with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) uid0 = res.json['uid'] # No change == same uid. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid0) # Changing client-state header requires changing generation. headers['X-Client-State'] = 'aaaa' with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('new value with no generation change')) # Changing client-state header requires changing keys_changed_at. mock_response["idpClaims"]["fxa-generation"] += 1 headers['X-Client-State'] = 'aaaa' with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('with no keys_changed_at change')) # Change the client-state header, get a new uid. mock_response["idpClaims"]["fxa-keysChangedAt"] += 1 headers['X-Client-State'] = 'aaaa' with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) uid1 = res.json['uid'] self.assertNotEqual(uid1, uid0) # No change == same uid. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid1) # Send a client-state header, get a new uid. headers['X-Client-State'] = 'bbbb' mock_response["idpClaims"]["fxa-generation"] += 1 mock_response["idpClaims"]["fxa-keysChangedAt"] += 1 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) uid2 = res.json['uid'] self.assertNotEqual(uid2, uid0) self.assertNotEqual(uid2, uid1) # No change == same uid. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid2) # Use a previous client-state, get an auth error. headers['X-Client-State'] = 'aaaa' with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('stale value')) del headers['X-Client-State'] with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') headers['X-Client-State'] = 'aaaa' mock_response["idpClaims"]["fxa-generation"] += 1 mock_response["idpClaims"]["fxa-keysChangedAt"] += 1 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') def test_fxa_kid_change(self): # Starting off not reporting keys_changed_at. # We don't expect to encounter this in production, but it might # happen to self-hosters who update tokenserver without updating # their FxA stack. headers = { "Authorization": "BrowserID %s" % self._getassertion(), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 1234 }, } with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000001234-YWFh") # Now pretend we updated FxA and it started sending keys_changed_at. mock_response["idpClaims"]["fxa-generation"] = 2345 mock_response["idpClaims"]["fxa-keysChangedAt"] = 2345 headers["X-Client-State"] = "626262" with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000002345-YmJi") # If we roll back the FxA stack so it stops reporting keys_changed_at, # users will get locked out because we can't produce `fxa_kid`. del mock_response["idpClaims"]["fxa-keysChangedAt"] with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-keysChangedAt") # We will likewise reject values below the high-water mark. mock_response["idpClaims"]["fxa-keysChangedAt"] = 2340 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-keysChangedAt") # But accept the correct value, even if generation number changes. mock_response["idpClaims"]["fxa-generation"] = 3456 mock_response["idpClaims"]["fxa-keysChangedAt"] = 2345 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000002345-YmJi") # We should error out if keysChangedAt changes to be larger than # the corresponding generation number. mock_response["idpClaims"]["fxa-keysChangedAt"] = 4567 headers["X-Client-State"] = "636363" with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json["status"], "invalid-keysChangedAt") # But accept further updates if both values change in unison. mock_response["idpClaims"]["fxa-generation"] = 4567 mock_response["idpClaims"]["fxa-keysChangedAt"] = 4567 headers["X-Client-State"] = "636363" with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000004567-Y2Nj") def test_fxa_kid_change_with_oauth(self): # Starting off not reporting keys_changed_at. # This uses BrowserID since OAuth always reports keys_changed_at. headers_browserid = { "Authorization": "BrowserID %s" % self._getassertion(), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 1234 }, } with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers_browserid) token0 = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token0["fxa_kid"], "0000000001234-YWFh") # Now an OAuth client shows up, setting keys_changed_at. # (The value matches generation number above, beause in this scenario # FxA hasn't been updated to track and report keysChangedAt yet). headers_oauth = { "Authorization": "Bearer %s" % self._gettoken("*****@*****.**"), "X-KeyID": "1234-YWFh", } res = self.app.get('/1.0/sync/1.1', headers=headers_oauth) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000001234-YWFh") self.assertEqual(token["uid"], token0["uid"]) self.assertEqual(token["node"], token0["node"]) # At this point, BrowserID clients are locked out until FxA is updated, # because we're now expecting to see keys_changed_at for that user. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers_browserid, status=401) self.assertEqual(res.json["status"], "invalid-keysChangedAt") # We will likewise reject values below the high-water mark. mock_response["idpClaims"]["fxa-keysChangedAt"] = 1230 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers_browserid, status=401) self.assertEqual(res.json["status"], "invalid-keysChangedAt") headers_oauth["X-KeyID"] = "1230-YWFh" res = self.app.get('/1.0/sync/1.1', headers=headers_oauth, status=401) self.assertEqual(res.json["status"], "invalid-keysChangedAt") # We accept new values via OAuth. headers_oauth["X-KeyID"] = "2345-YmJi" res = self.app.get('/1.0/sync/1.1', headers=headers_oauth) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000002345-YmJi") self.assertNotEqual(token["uid"], token0["uid"]) self.assertEqual(token["node"], token0["node"]) # And via BrowserID, as long as generation number increases as well. headers_browserid["X-Client-State"] = "636363" mock_response["idpClaims"]["fxa-generation"] = 3456 mock_response["idpClaims"]["fxa-keysChangedAt"] = 3456 with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers_browserid) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000003456-Y2Nj") def test_kid_change_during_gradual_tokenserver_rollout(self): # Let's start with a user already in the db, with no keys_changed_at. user0 = self.backend.allocate_user("sync-1.1", "*****@*****.**", generation=1234, client_state="616161") # User hits updated tokenserver node, writing keys_changed_at to db. headers = { "Authorization": "BrowserID %s" % self._getassertion(), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 1234, "fxa-keysChangedAt": 1200, }, } with self.mock_browserid_verifier(response=mock_response): self.app.get('/1.0/sync/1.1', headers=headers) # That should not have triggered a node re-assignment. user1 = self.backend.get_user("sync-1.1", mock_response["email"]) self.assertEqual(user1['uid'], user0['uid']) self.assertEqual(user1['node'], user0['node']) # That should have written keys_changed_at into the db. self.assertEqual(user1["generation"], 1234) self.assertEqual(user1["keys_changed_at"], 1200) # User does a password reset on their Firefox Account. mock_response["idpClaims"]["fxa-generation"] = 2345 mock_response["idpClaims"]["fxa-keysChangedAt"] = 2345 headers["X-Client-State"] = "626262" # They sync again, but hit a tokenserver node that isn't updated yet. # Simulate this by writing the updated data directly to the backend, # which should trigger a node re-assignment. self.backend.update_user("sync-1.1", user1, generation=2345, client_state="626262") self.assertNotEqual(user1['uid'], user0['uid']) self.assertEqual(user1['node'], user0['node']) # They sync again, hitting an updated tokenserver node. # This should succeed, despite keys_changed_at appearing to have # changed without any corresponding change in generation number. with self.mock_browserid_verifier(response=mock_response): res = self.app.get('/1.0/sync/1.1', headers=headers) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["fxa_kid"], "0000000002345-YmJi") # That should not have triggered a second node re-assignment. user2 = self.backend.get_user("sync-1.1", mock_response["email"]) self.assertEqual(user2['uid'], user1['uid']) self.assertEqual(user2['node'], user1['node']) def test_client_state_cannot_revert_to_empty(self): # Start with a client-state header. headers = { 'Authorization': 'BrowserID %s' % self._getassertion(), 'X-Client-State': 'aaaa', } res = self.app.get('/1.0/sync/1.1', headers=headers) uid0 = res.json['uid'] # Sending no client-state will fail. del headers['X-Client-State'] res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('empty string')) headers['X-Client-State'] = '' res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'invalid-client-state') desc = res.json['errors'][0]['description'] self.assertTrue(desc.endswith('empty string')) # And the uid will be unchanged. headers['X-Client-State'] = 'aaaa' res = self.app.get('/1.0/sync/1.1', headers=headers) self.assertEqual(res.json['uid'], uid0) def test_credentials_from_oauth_and_browserid(self): # Send initial credentials via oauth. headers_oauth = { "Authorization": "Bearer %s" % self._gettoken(), "X-KeyID": "7-YWFh", } res1 = self.app.get("/1.0/sync/1.1", headers=headers_oauth) # Send the same credentials via BrowserID headers_browserid = { "Authorization": "BrowserID %s" % self._getassertion(), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 12, "fxa-keysChangedAt": 7 }, } with self.mock_browserid_verifier(response=mock_response): res2 = self.app.get("/1.0/sync/1.1", headers=headers_browserid) # They should get the same node assignment. self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) # Earlier generation number via BrowserID -> invalid-generation mock_response["idpClaims"]['fxa-generation'] = 11 with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers_browserid, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Earlier keys_changed_at via BrowserID is not accepted. mock_response["idpClaims"]['fxa-generation'] = 12 mock_response["idpClaims"]['fxa-keysChangedAt'] = 6 with self.mock_browserid_verifier(response=mock_response): res1 = self.app.get("/1.0/sync/1.1", headers=headers_browserid, status=401) self.assertEqual(res1.json['status'], 'invalid-keysChangedAt') # Earlier generation number via OAuth -> invalid-generation mock_response["idpClaims"]['fxa-generation'] = 11 del mock_response["idpClaims"]["fxa-keysChangedAt"] with self.mock_oauth_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers_oauth, status=401) self.assertEqual(res.json["status"], "invalid-generation") # Earlier keys_changed_at via OAuth is not accepted. headers_oauth['X-KeyID'] = '6-YWFh' res1 = self.app.get("/1.0/sync/1.1", headers=headers_oauth, status=401) self.assertEqual(res1.json['status'], 'invalid-keysChangedAt') # Change client-state via BrowserID. headers_browserid['X-Client-State'] = '626262' mock_response["idpClaims"]['fxa-generation'] = 42 mock_response["idpClaims"]['fxa-keysChangedAt'] = 42 with self.mock_browserid_verifier(response=mock_response): res1 = self.app.get("/1.0/sync/1.1", headers=headers_browserid) # Previous OAuth creds are rejected due to keys_changed_at update. headers_oauth['X-KeyID'] = '7-YmJi' res2 = self.app.get("/1.0/sync/1.1", headers=headers_oauth, status=401) self.assertEqual(res2.json['status'], 'invalid-keysChangedAt') # Updated OAuth creds are accepted. headers_oauth['X-KeyID'] = '42-YmJi' res2 = self.app.get("/1.0/sync/1.1", headers=headers_oauth) # They should again get the same node assignment. self.assertEqual(res1.json["uid"], res2.json["uid"]) self.assertEqual(res1.json["api_endpoint"], res2.json["api_endpoint"]) def test_client_specified_duration(self): headers = {'Authorization': 'BrowserID %s' % self._getassertion()} # It's ok to request a shorter-duration token. res = self.app.get('/1.0/sync/1.1?duration=12', headers=headers) self.assertEquals(res.json['duration'], 12) # But you can't exceed the server's default value. res = self.app.get('/1.0/sync/1.1?duration=4000', headers=headers) self.assertEquals(res.json['duration'], 3600) # And nonsense values are ignored. res = self.app.get('/1.0/sync/1.1?duration=lolwut', headers=headers) self.assertEquals(res.json['duration'], 3600) res = self.app.get('/1.0/sync/1.1?duration=-1', headers=headers) self.assertEquals(res.json['duration'], 3600) def test_allow_new_users(self): # New users are allowed by default. settings = self.config.registry.settings self.assertEquals(settings.get('tokenserver.allow_new_users'), None) assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) # They're allowed if we explicitly allow them. settings['tokenserver.allow_new_users'] = True assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) # They're not allowed if we explicitly disable them. settings['tokenserver.allow_new_users'] = False assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} res = self.app.get('/1.0/sync/1.1', headers=headers, status=401) self.assertEqual(res.json['status'], 'new-users-disabled') # But existing users are still allowed. assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) def test_metrics_uid_logging(self): assert "fxa.metrics_uid_secret_key" in self.config.registry.settings assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} self.app.get('/1.0/sync/1.1', headers=headers, status=200) self.assertMetricWasLogged('uid') self.assertMetricWasLogged('uid.first_seen_at') self.assertMetricWasLogged('metrics_uid') self.assertMetricWasLogged('metrics_device_id') def test_uid_and_kid_from_browserid_assertion(self): assertion = self._getassertion(email="*****@*****.**") headers_browserid = { "Authorization": "BrowserID %s" % (assertion, ), "X-Client-State": "616161", } mock_response = { "status": "okay", "email": "*****@*****.**", "idpClaims": { "fxa-generation": 13, 'fxa-keysChangedAt': 12 }, } with self.mock_browserid_verifier(response=mock_response): res = self.app.get("/1.0/sync/1.1", headers=headers_browserid) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["uid"], res.json["uid"]) self.assertEqual(token["fxa_uid"], "testuser") self.assertEqual(token["fxa_kid"], "0000000000012-YWFh") self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) self.assertIn("hashed_device_id", token) def test_uid_and_kid_from_oauth_token(self): oauth_token = self._gettoken(email="*****@*****.**") headers_oauth = { "Authorization": "Bearer %s" % (oauth_token, ), "X-KeyID": "12-YWFh", } res = self.app.get("/1.0/sync/1.1", headers=headers_oauth) token = self.unsafelyParseToken(res.json["id"]) self.assertEqual(token["uid"], res.json["uid"]) self.assertEqual(token["fxa_uid"], "testuser") self.assertEqual(token["fxa_kid"], "0000000000012-YWFh") self.assertNotEqual(token["hashed_fxa_uid"], token["fxa_uid"]) self.assertEqual(token["hashed_fxa_uid"], res.json["hashed_fxa_uid"]) self.assertIn("hashed_device_id", token) def test_metrics_uid_is_returned_in_response(self): assert "fxa.metrics_uid_secret_key" in self.config.registry.settings assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} res = self.app.get('/1.0/sync/1.1', headers=headers, status=200) self.assertTrue('hashed_fxa_uid' in res.json) def test_node_type_is_returned_in_response(self): assertion = self._getassertion(email="*****@*****.**") headers = {'Authorization': 'BrowserID %s' % assertion} res = self.app.get('/1.0/sync/1.1', headers=headers, status=200) self.assertEqual(res.json['node_type'], 'example')
class TestStandardLibraryTarget(TestCase): def setUp(self): self.capture = LogCapture(attributes=('name', 'levelname', 'getMessage', 'shoehorn_event')) self.addCleanup(self.capture.uninstall) def test_minimal(self): logger.info(event='test') self.capture.check( ('root', 'INFO', '', Event(event='test', level='info'))) def test_named_logger(self): logger = get_logger('foo') logger.info(event='test') self.capture.check( ('foo', 'INFO', '', Event(event='test', logger='foo', level='info'))) def test_level(self): logger.warning(event='test') self.capture.check( ('root', 'WARNING', '', Event(event='test', level='warning'))) def test_sub_args(self): logger.info('foo %s', 'bar') self.capture.check(('root', 'INFO', 'foo bar', Event(message='foo %s', args=('bar', ), level='info'))) def test_exc_info(self): bad = Exception('bad') try: raise bad except: logger.exception('foo') self.capture.check(('root', 'ERROR', 'foo', Event(level='error', message='foo', exc_info=True))) compare(bad, actual=self.capture.records[-1].exc_info[1]) def test_stack_info(self): if PY2: return logger.info('foo', stack_info=True) self.capture.check(('root', 'INFO', 'foo', Event(message='foo', stack_info=True, level='info'))) compare('Stack (most recent call last):', actual=self.capture.records[-1].stack_info.split('\n')[0]) def test_default_logger(self): from shoehorn import logger logger.info('er hi') self.capture.check( ('root', 'INFO', 'er hi', Event(message='er hi', level='info')))
class TestMetrics(unittest2.TestCase): def setUp(self): self.logs = LogCapture() def tearDown(self): self.logs.uninstall() def test_service_metrics(self): stub_service = Service(name="stub", path="/stub") @stub_service.get() @metrics_timer("view_time") def stub_view(request): request.metrics["stub"] = "stub-a-dub-dub" return {} with pyramid.testing.testConfig() as config: config.include("cornice") config.include("mozsvc") register_service_views(config, stub_service) app = TestApp(config.make_wsgi_app()) res = app.get("/stub") self.assertEqual(res.body, "{}") self.assertTrue(len(self.logs.records), 1) r = self.logs.records[0] self.assertEqual(r.stub, "stub-a-dub-dub") self.assertTrue(0 < r.request_time < 0.1) self.assertTrue(0 < r.view_time <= r.request_time) def test_timing_decorator(self): @metrics_timer("timer1") def doit1(): time.sleep(0.01) def viewit(request): doit1() request = Request.blank("/") initialize_request_metrics(request) with pyramid.testing.testConfig(request=request): viewit(request) ts = request.metrics["timer1"] self.assertTrue(0.01 < ts < 0.1) def test_timing_contextmanager(self): def viewit(request): with metrics_timer("timer1"): time.sleep(0.01) request = Request.blank("/") initialize_request_metrics(request) with pyramid.testing.testConfig(request=request): viewit(request) ts = request.metrics["timer1"] self.assertTrue(0.01 < ts < 0.1) def test_timing_contextmanager_with_explicit_request_object(self): def viewit(request): with metrics_timer("timer1", request): time.sleep(0.01) request = Request.blank("/") initialize_request_metrics(request) viewit(request) ts = request.metrics["timer1"] self.assertTrue(0.01 < ts < 0.1) def test_timing_contextmanager_doesnt_fail_if_no_metrics_dict(self): def viewit(request): with metrics_timer("timer1"): time.sleep(0.01) request = Request.blank("/") with pyramid.testing.testConfig(request=request): viewit(request) self.assertFalse(hasattr(request, "metrics")) def test_timing_contextmanager_doesnt_fail_if_no_reqest_object(self): with metrics_timer("timer1"): time.sleep(0.01) def test_that_service_metrics_include_correct_response_codes(self): stub_service = Service(name="stub", path="/{what}") @stub_service.get() def stub_view(request): what = request.matchdict["what"] if what == "ok": return Response(status=200) if what == "notfound": return Response(status=404) if what == "forbidden": return Response(status=403) if what == "exc_forbidden": raise HTTPForbidden if what == "impl_forbidden": request.response.status_code = 403 return "" raise HTTPNotFound with pyramid.testing.testConfig() as config: config.include("cornice") config.include("mozsvc") register_service_views(config, stub_service) app = TestApp(config.make_wsgi_app()) app.get("/ok", status=200) r = self.logs.records[-1] self.assertEqual(r.code, 200) app.get("/notfound", status=404) r = self.logs.records[-1] self.assertEqual(r.code, 404) app.get("/forbidden", status=403) r = self.logs.records[-1] self.assertEqual(r.code, 403) app.get("/exc_notfound", status=404) r = self.logs.records[-1] self.assertEqual(r.code, 404) app.get("/exc_forbidden", status=403) r = self.logs.records[-1] self.assertEqual(r.code, 403) app.get("/impl_forbidden", status=403) r = self.logs.records[-1] self.assertEqual(r.code, 403)
class ShowBearsTest(unittest.TestCase): def setUp(self): self.console_printer = ConsolePrinter(print_colored=False) self.logs = LogCapture() self.logs.__enter__() deprecation_messages = [ ('root', 'WARNING', 'show_description parameter is deprecated'), ('root', 'WARNING', 'show_params parameter is deprecated') ] def tearDown(self): self.logs.__exit__(None, None, None) def test_show_bear_minimal(self): with retrieve_stdout() as stdout: show_bear(SomelocalBear, False, False, self.console_printer) self.assertEqual(stdout.getvalue(), 'SomelocalBear\n') self.logs.check(*self.deprecation_messages) def test_show_bear_desc_only(self): with retrieve_stdout() as stdout: show_bear(SomelocalBear, True, False, self.console_printer) self.assertEqual( stdout.getvalue(), 'SomelocalBear\n Some local-bear Description.\n\n') self.logs.check(*self.deprecation_messages) def test_show_bear_details_only(self): with retrieve_stdout() as stdout: show_bear(SomelocalBear, False, True, self.console_printer) self.assertEqual( stdout.getvalue(), 'SomelocalBear\n' ' The bear does not provide information about ' 'which languages it can analyze.\n\n' ' No needed settings.\n\n' ' No optional settings.\n\n' ' This bear does not provide information about ' 'what categories it can detect.\n\n' ' This bear cannot fix issues or does not ' 'provide information about what categories it ' 'can fix.\n\n Path:\n ' + repr(SomelocalBear.source_location) + '\n\n') self.logs.check(*self.deprecation_messages) def test_show_bear_long_without_content(self): with retrieve_stdout() as stdout: show_bear(SomelocalBear, True, True, self.console_printer) self.assertEqual( stdout.getvalue(), 'SomelocalBear\n' ' Some local-bear Description.\n\n' ' The bear does not provide information about ' 'which languages it can analyze.\n\n' ' No needed settings.\n\n' ' No optional settings.\n\n' ' This bear does not provide information about ' 'what categories it can detect.\n\n' ' This bear cannot fix issues or does not ' 'provide information about what categories it ' 'can fix.\n\n Path:\n ' + repr(SomelocalBear.source_location) + '\n\n') self.logs.check(*self.deprecation_messages) def test_show_bear_with_content(self): with retrieve_stdout() as stdout: show_bear(TestBear, True, True, self.console_printer) self.assertEqual( stdout.getvalue(), 'TestBear\n' ' Test bear Description.\n\n' ' Supported languages:\n' ' * F#\n' ' * Shakespearean Programming Language\n\n' ' Needed Settings:\n' ' * setting1: Required Setting.\n\n' ' Optional Settings:\n' ' * setting2: Optional Setting. (' "Optional, defaults to 'None'." ')\n\n' ' Can detect:\n * Formatting\n\n' ' Can fix:\n * Formatting\n\n Path:\n ' + repr(TestBear.source_location) + '\n\n') self.logs.check(*self.deprecation_messages) def test_show_bear_settings_only(self): with retrieve_stdout() as stdout: args = default_arg_parser().parse_args(['--show-settings']) show_bear(TestBear, False, False, self.console_printer, args) self.assertEqual( stdout.getvalue(), 'TestBear\n' ' Needed Settings:\n' ' * setting1: Required Setting.\n\n' ' Optional Settings:\n' ' * setting2: Optional Setting. (' "Optional, defaults to 'None'.)\n\n") self.logs.check(*self.deprecation_messages) def test_show_bears_empty(self): with retrieve_stdout() as stdout: show_bears({}, {}, True, True, self.console_printer) self.assertIn('No bears to show.', stdout.getvalue()) self.logs.check(*self.deprecation_messages) def test_show_bears_with_json(self): args = default_arg_parser().parse_args(['--json']) with retrieve_stdout() as stdout: show_bears({}, {}, True, True, self.console_printer, args) self.assertEqual('{\n "bears": []\n}\n', stdout.getvalue()) self.logs.check(*self.deprecation_messages) @patch('coalib.output.ConsoleInteraction.show_bear') def test_show_bears(self, show_bear): local_bears = OrderedDict([('default', [SomelocalBear]), ('test', [SomelocalBear])]) show_bears(local_bears, {}, True, True, self.console_printer) show_bear.assert_called_once_with(SomelocalBear, True, True, self.console_printer, None) self.logs.check(*self.deprecation_messages) def test_show_bears_sorted(self): local_bears = OrderedDict([('default', [SomelocalBear]), ('test', [aSomelocalBear])]) global_bears = OrderedDict([('default', [SomeglobalBear]), ('test', [BSomeglobalBear])]) with retrieve_stdout() as stdout: show_bears(local_bears, global_bears, False, False, self.console_printer) self.assertEqual( stdout.getvalue(), 'aSomelocalBear\n' 'BSomeglobalBear\n' 'SomeglobalBear\n' 'SomelocalBear\n') self.logs.check(*(self.deprecation_messages * 5)) def test_show_bears_capabilities(self): with retrieve_stdout() as stdout: show_language_bears_capabilities( { 'some_language': ({'Formatting', 'Security'}, {'Formatting'}) }, self.console_printer) self.assertIn( 'coala can do the following for SOME_LANGUAGE\n' ' Can detect only: Formatting, Security\n' ' Can fix : Formatting\n', stdout.getvalue()) show_language_bears_capabilities({'some_language': (set(), set())}, self.console_printer) self.assertIn('coala does not support some_language', stdout.getvalue()) show_language_bears_capabilities({}, self.console_printer) self.assertIn('There is no bear available for this language', stdout.getvalue()) show_language_bears_capabilities( {'some_language': ({'Formatting', 'Security'}, set())}, self.console_printer) self.assertIn( 'coala can do the following for SOME_LANGUAGE\n' ' Can detect only: Formatting, Security\n', stdout.getvalue())
def setUp(self): self.logs = LogCapture()
def test_token_endpoint(self): authreq = AuthorizationRequest(state="state", redirect_uri="http://example.com/authz", client_id="client1") _sdb = self.provider.sdb sid = _sdb.access_token.key(user="******", areq=authreq) access_grant = _sdb.access_token(sid=sid) _sdb[sid] = { "oauth_state": "authz", "sub": "sub", "authzreq": "", "client_id": "client1", "code": access_grant, "code_used": False, "redirect_uri": "http://example.com/authz" } # Construct Access token request areq = AccessTokenRequest(code=access_grant, redirect_uri="http://example.com/authz", client_id="client1", client_secret="hemlighet", grant_type='authorization_code') with LogCapture(level=logging.DEBUG) as logcap: resp = self.provider.token_endpoint(request=areq.to_urlencoded()) atr = AccessTokenResponse().deserialize(resp.message, "json") assert _eq(atr.keys(), ['access_token', 'token_type', 'refresh_token']) expected = ( 'body: code=<REDACTED>&client_secret=<REDACTED>&grant_type' '=authorization_code' ' &client_id=client1&redirect_uri=http%3A%2F%2Fexample.com' '%2Fauthz') assert _eq(parse_qs(logcap.records[1].msg[6:]), parse_qs(expected[6:])) expected = {u'code': '<REDACTED>', u'client_secret': '<REDACTED>', u'redirect_uri': u'http://example.com/authz', u'client_id': 'client1', u'grant_type': 'authorization_code'} # Don't try this at home, kids! # We have to eval() to a dict here because otherwise the arbitrary # ordering of the string causes the test to fail intermittently. assert _eq(eval(logcap.records[2].msg[4:]), expected) assert _eq(logcap.records[3].msg, 'Verified Client ID: client1') expected = {'redirect_uri': u'http://example.com/authz', 'client_secret': '<REDACTED>', 'code': u'<REDACTED>', 'client_id': 'client1', 'grant_type': 'authorization_code'} assert eval(logcap.records[4].msg[20:]) == expected expected = {'code': '<REDACTED>', 'authzreq': '', 'sub': 'sub', 'access_token': '<REDACTED>', 'token_type': 'Bearer', 'redirect_uri': 'http://example.com/authz', 'code_used': True, 'client_id': 'client1', 'oauth_state': 'token', 'refresh_token': '<REDACTED>', 'access_token_scope': '?'} assert _eq(eval(logcap.records[5].msg[7:]), expected) expected = {'access_token': u'<REDACTED>', 'token_type': 'Bearer', 'refresh_token': '<REDACTED>'} assert _eq(eval(logcap.records[6].msg[21:]), expected)