def test_emit_does_write_cli_rc_record(self): writer = mock.Mock(DatabaseRecordWriter) record_builder = RecordBuilder() handler = DatabaseHistoryHandler(writer, record_builder) handler.emit('CLI_RC', 0, 'CLI') call = writer.write_record.call_args[0][0] self.assertEqual( call, { 'command_id': mock.ANY, 'event_type': 'CLI_RC', 'payload': 0, 'source': 'CLI', 'timestamp': mock.ANY }) self.assertTrue(self.UUID_PATTERN.match(call['command_id'])) self.assertIsInstance(call['timestamp'], numbers.Number)
def test_emit_does_write_api_call_record(self): writer = mock.Mock(DatabaseRecordWriter) record_builder = RecordBuilder() handler = DatabaseHistoryHandler(writer, record_builder) payload = {'foo': 'bar'} handler.emit('API_CALL', payload, 'BOTOCORE') call = writer.write_record.call_args[0][0] self.assertEqual( call, { 'command_id': mock.ANY, 'request_id': mock.ANY, 'event_type': 'API_CALL', 'payload': payload, 'source': 'BOTOCORE', 'timestamp': mock.ANY }) self.assertTrue(self.UUID_PATTERN.match(call['command_id'])) self.assertTrue(self.UUID_PATTERN.match(call['request_id']))
def attach_history_handler(session, parsed_args, **kwargs): if _should_enable_cli_history(session, parsed_args): LOG.debug('Enabling CLI history') history_filename = os.environ.get(HISTORY_FILENAME_ENV_VAR, DEFAULT_HISTORY_FILENAME) if not os.path.isdir(os.path.dirname(history_filename)): os.makedirs(os.path.dirname(history_filename)) connection = DatabaseConnection(history_filename) writer = DatabaseRecordWriter(connection) record_builder = RecordBuilder() db_handler = DatabaseHistoryHandler(writer, record_builder) HISTORY_RECORDER.add_handler(db_handler) HISTORY_RECORDER.enable()
def test_emit_does_write_parsed_response_record(self): writer = mock.Mock(DatabaseRecordWriter) record_builder = RecordBuilder() handler = DatabaseHistoryHandler(writer, record_builder) payload = {'metadata': {'data': 'foobar'}} # In order for an http_response to have a request_id it must have been # preceeded by an api_call record. handler.emit('API_CALL', '', 'BOTOCORE') handler.emit('PARSED_RESPONSE', payload, 'BOTOCORE') call = writer.write_record.call_args[0][0] self.assertEqual( call, { 'command_id': mock.ANY, 'request_id': mock.ANY, 'event_type': 'PARSED_RESPONSE', 'payload': payload, 'source': 'BOTOCORE', 'timestamp': mock.ANY }) self.assertTrue(self.UUID_PATTERN.match(call['command_id'])) self.assertTrue(self.UUID_PATTERN.match(call['request_id']))
def setUp(self): self.db = DatabaseConnection(':memory:') self.writer = DatabaseRecordWriter(connection=self.db) self.record_builder = RecordBuilder() self.handler = DatabaseHistoryHandler( writer=self.writer, record_builder=self.record_builder)
class TestDatabaseHistoryHandler(unittest.TestCase): UUID_PATTERN = re.compile( '^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$', re.I ) def setUp(self): self.db = DatabaseConnection(':memory:') self.writer = DatabaseRecordWriter(connection=self.db) self.record_builder = RecordBuilder() self.handler = DatabaseHistoryHandler( writer=self.writer, record_builder=self.record_builder) def _get_last_record(self): record = self.db.execute('SELECT * FROM records').fetchone() return record def _assert_expected_event_type(self, source, record): self.assertEqual(source, record[3]) def _assert_expected_payload(self, source, record): loaded_payload = json.loads(record[-1]) self.assertEqual(source, loaded_payload) def _assert_expected_source(self, source, record): self.assertEqual(source, record[2]) def _assert_has_request_id(self, record): identifier = record[1] self.assertTrue(self.UUID_PATTERN.match(identifier)) def _assert_record_has_command_id(self, record): identifier = record[0] self.assertTrue(self.UUID_PATTERN.match(identifier)) def test_does_emit_write_record(self): self.handler.emit('event_type', 'payload', 'source') record = self._get_last_record() self._assert_record_has_command_id(record) self._assert_expected_event_type('event_type', record) self._assert_expected_payload('payload', record) self._assert_expected_source('source', record) def test_can_emit_write_record_with_structure(self): payload = {'foo': 'bar'} self.handler.emit('event_type', payload, 'source') record = self._get_last_record() self._assert_record_has_command_id(record) self._assert_expected_event_type('event_type', record) self._assert_expected_payload(payload, record) self._assert_expected_source('source', record) def test_can_emit_cli_version_record(self): # CLI_VERSION records have a list of strings payload payload = 'foobarbaz' self.handler.emit('CLI_VERSION', payload, 'CLI') record = self._get_last_record() self._assert_record_has_command_id(record) self._assert_expected_event_type('CLI_VERSION', record) self._assert_expected_payload(payload, record) self._assert_expected_source('CLI', record) def test_can_emit_cli_arguments_record(self): # CLI_ARGUMENTS records have a list of strings payload payload = ['foo', 'bar', 'baz'] self.handler.emit('CLI_ARGUMENTS', payload, 'CLI') record = self._get_last_record() self._assert_record_has_command_id(record) self._assert_expected_event_type('CLI_ARGUMENTS', record) self._assert_expected_payload(payload, record) self._assert_expected_source('CLI', record) def test_can_emit_api_call_record(self): # API_CALL records have a dictionary based payload payload = { 'service': 's3', 'operation': 'ListBuckets', 'params': {} } self.handler.emit('API_CALL', payload, 'BOTOCORE') record = self._get_last_record() self._assert_record_has_command_id(record) self._assert_has_request_id(record) self._assert_expected_event_type('API_CALL', record) self._assert_expected_payload(payload, record) self._assert_expected_source('BOTOCORE', record) def test_can_emit_api_call_record_with_binary_param(self): # API_CALL records have a dictionary based payload payload = { 'service': 'lambda', 'operation': 'CreateFunction', 'params': { "FunctionName": "Name", "Handler": "mod.fn", "Role": "foobar", "Runtime": "python3", "Code": { "ZipFile": b'zipfile binary content \xfe\xed' } } } self.handler.emit('API_CALL', payload, 'BOTOCORE') record = self._get_last_record() parsed_payload = payload.copy() parsed_payload['params']['Code']['ZipFile'] = \ '<Byte sequence>' self._assert_record_has_command_id(record) self._assert_has_request_id(record) self._assert_expected_event_type('API_CALL', record) self._assert_expected_payload(parsed_payload, record) self._assert_expected_source('BOTOCORE', record) def test_can_emit_http_request_record(self): # HTTP_REQUEST records have have their entire body field as a binary # blob, howver it will all be utf-8 valid since the binary fields # from the api call will have been b64 encoded. payload = { 'url': ('https://lambda.us-west-2.amazonaws.com/2015-03-31/' 'functions'), 'method': 'POST', 'headers': CaseInsensitiveDict({ 'foo': 'bar' }), 'body': b'body with no invalid utf-8 bytes in it', 'streaming': False } self.handler.emit('HTTP_REQUEST', payload, 'BOTOCORE') record = self._get_last_record() parsed_payload = payload.copy() parsed_payload['headers'] = dict(parsed_payload['headers']) parsed_payload['body'] = 'body with no invalid utf-8 bytes in it' self._assert_record_has_command_id(record) self._assert_expected_event_type('HTTP_REQUEST', record) self._assert_expected_payload(parsed_payload, record) self._assert_expected_source('BOTOCORE', record) def test_can_emit_http_response_record(self): # HTTP_RESPONSE also contains a binary response in its body, but it # will not contain any non-unicode characters payload = { 'status_code': 200, 'headers': CaseInsensitiveDict({ 'foo': 'bar' }), 'body': b'body with no invalid utf-8 bytes in it', 'streaming': False } self.handler.emit('HTTP_RESPONSE', payload, 'BOTOCORE') record = self._get_last_record() parsed_payload = payload.copy() parsed_payload['headers'] = dict(parsed_payload['headers']) parsed_payload['body'] = 'body with no invalid utf-8 bytes in it' self._assert_record_has_command_id(record) self._assert_expected_event_type('HTTP_RESPONSE', record) self._assert_expected_payload(parsed_payload, record) self._assert_expected_source('BOTOCORE', record) def test_can_emit_parsed_response_record(self): payload = { "Count": 1, "Items": [ { "strkey": { "S": "string" } } ], "ScannedCount": 1, "ConsumedCapacity": None } self.handler.emit('PARSED_RESPONSE', payload, 'BOTOCORE') record = self._get_last_record() self._assert_record_has_command_id(record) self._assert_expected_event_type('PARSED_RESPONSE', record) self._assert_expected_payload(payload, record) self._assert_expected_source('BOTOCORE', record) def test_can_emit_parsed_response_record_with_binary(self): # PARSED_RESPONSE can also contain raw bytes payload = { "Count": 1, "Items": [ { "bitkey": { "B": b"binary data \xfe\xed" } } ], "ScannedCount": 1, "ConsumedCapacity": None } self.handler.emit('PARSED_RESPONSE', payload, 'BOTOCORE') record = self._get_last_record() parsed_payload = payload.copy() parsed_payload['Items'][0]['bitkey']['B'] = "<Byte sequence>" self._assert_record_has_command_id(record) self._assert_expected_event_type('PARSED_RESPONSE', record) self._assert_expected_payload(payload, record) self._assert_expected_source('BOTOCORE', record) def test_does_not_mutate_dict(self): payload = { "bitkey": b"binary data \xfe\xed" } copy_payload = payload.copy() self.handler.emit('test', payload, 'BOTOCORE') self.assertEqual(payload, copy_payload) def test_does_not_mutate_list(self): payload = ['non binary data', b"binary data \xfe\xed"] copy_payload = list(payload) self.handler.emit('test', payload, 'BOTOCORE') self.assertEqual(payload, copy_payload)