Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/unit/customizations/history/test_db.py
1569 views
1
# Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
import os
14
import re
15
import json
16
import threading
17
import datetime
18
import numbers
19
20
from awscli.compat import queue
21
from awscli.customizations.history.db import DatabaseConnection
22
from awscli.customizations.history.db import DatabaseHistoryHandler
23
from awscli.customizations.history.db import DatabaseRecordWriter
24
from awscli.customizations.history.db import DatabaseRecordReader
25
from awscli.customizations.history.db import PayloadSerializer
26
from awscli.customizations.history.db import RecordBuilder
27
from awscli.testutils import mock, unittest, FileCreator
28
from tests import CaseInsensitiveDict
29
30
31
class FakeDatabaseConnection(object):
32
def __init__(self):
33
self.execute = mock.MagicMock()
34
self.closed = False
35
36
def close(self):
37
self.closed = True
38
39
40
class TestGetHistoryDBFilename(unittest.TestCase):
41
def setUp(self):
42
self.files = FileCreator()
43
44
def tearDown(self):
45
self.files.remove_all()
46
47
48
class TestDatabaseConnection(unittest.TestCase):
49
@mock.patch('awscli.compat.sqlite3.connect')
50
def test_can_connect_to_argument_file(self, mock_connect):
51
expected_location = os.path.expanduser(os.path.join(
52
'~', 'foo', 'bar', 'baz.db'))
53
DatabaseConnection(expected_location)
54
mock_connect.assert_called_with(
55
expected_location, check_same_thread=False, isolation_level=None)
56
57
@mock.patch('awscli.compat.sqlite3.connect')
58
def test_does_try_to_enable_wal(self, mock_connect):
59
conn = DatabaseConnection(':memory:')
60
conn._connection.execute.assert_any_call('PRAGMA journal_mode=WAL')
61
62
def test_does_ensure_table_created_first(self):
63
db = DatabaseConnection(":memory:")
64
cursor = db.execute('PRAGMA table_info(records)')
65
schema = [col[:3] for col in cursor.fetchall()]
66
expected_schema = [
67
(0, 'id', 'TEXT'),
68
(1, 'request_id', 'TEXT'),
69
(2, 'source', 'TEXT'),
70
(3, 'event_type', 'TEXT'),
71
(4, 'timestamp', 'INTEGER'),
72
(5, 'payload', 'TEXT'),
73
]
74
self.assertEqual(expected_schema, schema)
75
76
@mock.patch('awscli.compat.sqlite3.connect')
77
def test_can_close(self, mock_connect):
78
connection = mock.Mock()
79
mock_connect.return_value = connection
80
conn = DatabaseConnection(':memory:')
81
conn.close()
82
self.assertTrue(connection.close.called)
83
84
85
class TestDatabaseHistoryHandler(unittest.TestCase):
86
UUID_PATTERN = re.compile(
87
'^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$',
88
re.I
89
)
90
91
def test_emit_does_write_cli_rc_record(self):
92
writer = mock.Mock(DatabaseRecordWriter)
93
record_builder = RecordBuilder()
94
handler = DatabaseHistoryHandler(writer, record_builder)
95
handler.emit('CLI_RC', 0, 'CLI')
96
call = writer.write_record.call_args[0][0]
97
self.assertEqual(call, {
98
'command_id': mock.ANY,
99
'event_type': 'CLI_RC',
100
'payload': 0,
101
'source': 'CLI',
102
'timestamp': mock.ANY
103
})
104
self.assertTrue(self.UUID_PATTERN.match(call['command_id']))
105
self.assertIsInstance(call['timestamp'], numbers.Number)
106
107
def test_emit_does_write_cli_version_record(self):
108
writer = mock.Mock(DatabaseRecordWriter)
109
record_builder = RecordBuilder()
110
handler = DatabaseHistoryHandler(writer, record_builder)
111
handler.emit('CLI_VERSION', 'Version Info', 'CLI')
112
call = writer.write_record.call_args[0][0]
113
self.assertEqual(call, {
114
'command_id': mock.ANY,
115
'event_type': 'CLI_VERSION',
116
'payload': 'Version Info',
117
'source': 'CLI',
118
'timestamp': mock.ANY
119
})
120
self.assertTrue(self.UUID_PATTERN.match(call['command_id']))
121
self.assertIsInstance(call['timestamp'], numbers.Number)
122
123
def test_emit_does_write_api_call_record(self):
124
writer = mock.Mock(DatabaseRecordWriter)
125
record_builder = RecordBuilder()
126
handler = DatabaseHistoryHandler(writer, record_builder)
127
payload = {'foo': 'bar'}
128
handler.emit('API_CALL', payload, 'BOTOCORE')
129
call = writer.write_record.call_args[0][0]
130
self.assertEqual(call, {
131
'command_id': mock.ANY,
132
'request_id': mock.ANY,
133
'event_type': 'API_CALL',
134
'payload': payload,
135
'source': 'BOTOCORE',
136
'timestamp': mock.ANY
137
})
138
self.assertTrue(self.UUID_PATTERN.match(call['command_id']))
139
self.assertTrue(self.UUID_PATTERN.match(call['request_id']))
140
141
def test_emit_does_write_http_request_record(self):
142
writer = mock.Mock(DatabaseRecordWriter)
143
record_builder = RecordBuilder()
144
handler = DatabaseHistoryHandler(writer, record_builder)
145
payload = {'body': b'data'}
146
# In order for an http_request to have a request_id it must have been
147
# preceeded by an api_call record.
148
handler.emit('API_CALL', '', 'BOTOCORE')
149
handler.emit('HTTP_REQUEST', payload, 'BOTOCORE')
150
call = writer.write_record.call_args[0][0]
151
self.assertEqual(call, {
152
'command_id': mock.ANY,
153
'request_id': mock.ANY,
154
'event_type': 'HTTP_REQUEST',
155
'payload': payload,
156
'source': 'BOTOCORE',
157
'timestamp': mock.ANY
158
})
159
self.assertTrue(self.UUID_PATTERN.match(call['command_id']))
160
self.assertTrue(self.UUID_PATTERN.match(call['request_id']))
161
162
def test_emit_does_write_http_response_record(self):
163
writer = mock.Mock(DatabaseRecordWriter)
164
record_builder = RecordBuilder()
165
handler = DatabaseHistoryHandler(writer, record_builder)
166
payload = {'body': b'data'}
167
# In order for an http_response to have a request_id it must have been
168
# preceeded by an api_call record.
169
handler.emit('API_CALL', '', 'BOTOCORE')
170
handler.emit('HTTP_RESPONSE', payload, 'BOTOCORE')
171
call = writer.write_record.call_args[0][0]
172
self.assertEqual(call, {
173
'command_id': mock.ANY,
174
'request_id': mock.ANY,
175
'event_type': 'HTTP_RESPONSE',
176
'payload': payload,
177
'source': 'BOTOCORE',
178
'timestamp': mock.ANY
179
})
180
self.assertTrue(self.UUID_PATTERN.match(call['command_id']))
181
self.assertTrue(self.UUID_PATTERN.match(call['request_id']))
182
183
def test_emit_does_write_parsed_response_record(self):
184
writer = mock.Mock(DatabaseRecordWriter)
185
record_builder = RecordBuilder()
186
handler = DatabaseHistoryHandler(writer, record_builder)
187
payload = {'metadata': {'data': 'foobar'}}
188
# In order for an http_response to have a request_id it must have been
189
# preceeded by an api_call record.
190
handler.emit('API_CALL', '', 'BOTOCORE')
191
handler.emit('PARSED_RESPONSE', payload, 'BOTOCORE')
192
call = writer.write_record.call_args[0][0]
193
self.assertEqual(call, {
194
'command_id': mock.ANY,
195
'request_id': mock.ANY,
196
'event_type': 'PARSED_RESPONSE',
197
'payload': payload,
198
'source': 'BOTOCORE',
199
'timestamp': mock.ANY
200
})
201
self.assertTrue(self.UUID_PATTERN.match(call['command_id']))
202
self.assertTrue(self.UUID_PATTERN.match(call['request_id']))
203
204
205
class BaseDatabaseRecordTester(unittest.TestCase):
206
def assert_contains_lines_in_order(self, lines, contents):
207
for line in lines:
208
self.assertIn(line, contents)
209
beginning = contents.find(line)
210
contents = contents[(beginning + len(line)):]
211
212
213
class BaseDatabaseRecordWriterTester(BaseDatabaseRecordTester):
214
UUID_PATTERN = re.compile(
215
'^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$',
216
re.I
217
)
218
219
def setUp(self):
220
self.db = DatabaseConnection(':memory:')
221
self.writer = DatabaseRecordWriter(self.db)
222
223
224
class TestDatabaseRecordWriter(BaseDatabaseRecordWriterTester):
225
def setUp(self):
226
super(TestDatabaseRecordWriter, self).setUp()
227
228
def _read_last_record(self):
229
cursor = self.db.execute('SELECT * FROM records')
230
written_record = cursor.fetchone()
231
return written_record
232
233
def test_can_close(self):
234
connection = mock.Mock()
235
writer = DatabaseRecordWriter(connection)
236
writer.close()
237
self.assertTrue(connection.close.called)
238
239
def test_can_write_record(self):
240
self.writer.write_record({
241
'command_id': 'command',
242
'event_type': 'FOO',
243
'payload': 'bar',
244
'source': 'TEST',
245
'timestamp': 1234
246
})
247
248
# Now that we have verified the order of the fields in the insert
249
# statement we can verify that the record values are in the correct
250
# order in the tuple.
251
# (command_id, request_id, source, event_type, timestamp, payload)
252
written_record = self._read_last_record()
253
self.assertEqual(written_record,
254
('command', None, 'TEST', 'FOO', 1234, '"bar"'))
255
256
def test_commit_count_matches_write_count(self):
257
records_to_write = 10
258
for _ in range(records_to_write):
259
self.writer.write_record({
260
'command_id': 'command',
261
'event_type': 'foo',
262
'payload': '',
263
'source': 'TEST',
264
'timestamp': 1234
265
})
266
cursor = self.db.execute('SELECT COUNT(*) FROM records')
267
record_count = cursor.fetchone()[0]
268
269
self.assertEqual(record_count, records_to_write)
270
271
def test_can_write_cli_version_record(self):
272
self.writer.write_record({
273
'command_id': 'command',
274
'event_type': 'CLI_VERSION',
275
'payload': ('aws-cli/1.11.184 Python/3.6.2 Darwin/15.6.0 '
276
'botocore/1.7.42'),
277
'source': 'TEST',
278
'timestamp': 1234
279
})
280
written_record = self._read_last_record()
281
282
self.assertEqual(
283
written_record,
284
('command', None, 'TEST', 'CLI_VERSION', 1234,
285
'"aws-cli/1.11.184 Python/3.6.2 Darwin/15.6.0 botocore/1.7.42"')
286
)
287
288
def test_can_write_cli_arguments_record(self):
289
self.writer.write_record({
290
'command_id': 'command',
291
'event_type': 'CLI_ARGUMENTS',
292
'payload': ['s3', 'ls'],
293
'source': 'TEST',
294
'timestamp': 1234
295
})
296
297
written_record = self._read_last_record()
298
self.assertEqual(
299
written_record,
300
('command', None, 'TEST', 'CLI_ARGUMENTS', 1234, '["s3", "ls"]')
301
)
302
303
def test_can_write_api_call_record(self):
304
self.writer.write_record({
305
'command_id': 'command',
306
'event_type': 'API_CALL',
307
'payload': {
308
'service': 's3',
309
'operation': 'ListBuckets',
310
'params': {},
311
},
312
'source': 'TEST',
313
'timestamp': 1234
314
})
315
316
written_record = self._read_last_record()
317
self.assertEqual(
318
written_record,
319
('command', None, 'TEST', 'API_CALL', 1234, json.dumps({
320
'service': 's3',
321
'operation': 'ListBuckets',
322
'params': {},
323
}))
324
)
325
326
def test_can_write_http_request_record(self):
327
self.writer.write_record({
328
'command_id': 'command',
329
'event_type': 'HTTP_REQUEST',
330
'payload': {
331
'method': 'GET',
332
'headers': CaseInsensitiveDict({}),
333
'body': '...',
334
},
335
'source': 'TEST',
336
'timestamp': 1234
337
})
338
339
written_record = self._read_last_record()
340
self.assertEqual(
341
written_record,
342
('command', None, 'TEST', 'HTTP_REQUEST', 1234, json.dumps({
343
'method': 'GET',
344
'headers': {},
345
'body': '...',
346
}))
347
)
348
349
def test_can_write_http_response_record(self):
350
self.writer.write_record({
351
'command_id': 'command',
352
'event_type': 'HTTP_RESPONSE',
353
'payload': {
354
'streaming': False,
355
'headers': {},
356
'body': '...',
357
'status_code': 200,
358
'request_id': '1234abcd'
359
},
360
'source': 'TEST',
361
'timestamp': 1234
362
})
363
364
written_record = self._read_last_record()
365
self.assertEqual(
366
written_record,
367
('command', None, 'TEST', 'HTTP_RESPONSE', 1234, json.dumps({
368
'streaming': False,
369
'headers': {},
370
'body': '...',
371
'status_code': 200,
372
'request_id': '1234abcd'
373
}))
374
)
375
376
def test_can_write_parsed_response_record(self):
377
self.writer.write_record({
378
'command_id': 'command',
379
'event_type': 'PARSED_RESPONSE',
380
'payload': {},
381
'source': 'TEST',
382
'timestamp': 1234
383
})
384
385
written_record = self._read_last_record()
386
self.assertEqual(
387
written_record,
388
('command', None, 'TEST', 'PARSED_RESPONSE', 1234, '{}')
389
)
390
391
def test_can_write_cli_rc_record(self):
392
self.writer.write_record({
393
'command_id': 'command',
394
'event_type': 'CLI_RC',
395
'payload': 0,
396
'source': 'TEST',
397
'timestamp': 1234
398
})
399
400
written_record = self._read_last_record()
401
self.assertEqual(
402
written_record,
403
('command', None, 'TEST', 'CLI_RC', 1234, '0')
404
)
405
406
407
class ThreadedRecordBuilder(object):
408
def __init__(self, tracker):
409
self._read_q = queue.Queue()
410
self._write_q = queue.Queue()
411
self._thread = threading.Thread(
412
target=self._threaded_request_tracker,
413
args=(tracker,))
414
415
def _threaded_request_tracker(self, builder):
416
while True:
417
event_type = self._read_q.get()
418
if event_type is False:
419
return
420
payload = {'body': b''}
421
request_id = builder.build_record(event_type, payload, '')
422
self._write_q.put_nowait(request_id)
423
424
def read_n_results(self, n):
425
records = [self._write_q.get() for _ in range(n)]
426
return records
427
428
def request_id_for_event(self, event_type):
429
self._read_q.put_nowait(event_type)
430
431
def start(self):
432
self._thread.start()
433
434
def close(self):
435
self._read_q.put_nowait(False)
436
self._thread.join()
437
438
439
class TestMultithreadRequestId(unittest.TestCase):
440
def setUp(self):
441
self.builder = RecordBuilder()
442
self.threads = []
443
444
def tearDown(self):
445
for t in self.threads:
446
t.close()
447
448
def start_n_threads(self, n):
449
for _ in range(n):
450
t = ThreadedRecordBuilder(self.builder)
451
t.start()
452
self.threads.append(t)
453
454
def test_each_thread_has_separate_request_id(self):
455
self.start_n_threads(2)
456
self.threads[0].request_id_for_event('API_CALL')
457
self.threads[0].request_id_for_event('HTTP_REQUEST')
458
self.threads[1].request_id_for_event('API_CALL')
459
self.threads[1].request_id_for_event('HTTP_REQUEST')
460
461
a_records = self.threads[0].read_n_results(2)
462
b_records = self.threads[1].read_n_results(2)
463
464
# Each thread should have its own set of request_ids so the request
465
# ids in each set of records should match, but should not match the
466
# request_ids from the other thread.
467
a_request_ids = [record['request_id'] for record in a_records]
468
self.assertEqual(len(a_request_ids), 2)
469
self.assertEqual(a_request_ids[0], a_request_ids[1])
470
thread_a_request_id = a_request_ids[0]
471
472
b_request_ids = [record['request_id'] for record in b_records]
473
self.assertEqual(len(b_request_ids), 2)
474
self.assertEqual(b_request_ids[0], b_request_ids[1])
475
thread_b_request_id = b_request_ids[0]
476
477
# Since the request_id is reset by the API_CALL record being written
478
# and thread b has now written an API_CALL record the request id has
479
# been reset once. To ensure this doesnt bleed over to other threads
480
# we will write another record in thread a and ensure that it's
481
# request_id matches the previous ones from thread a rather than then
482
# thread b request_id
483
self.threads[0].request_id_for_event('HTTP_RESPONSE')
484
self.threads[1].request_id_for_event('HTTP_RESPONSE')
485
a_record = self.threads[0].read_n_results(1)[0]
486
b_record = self.threads[1].read_n_results(1)[0]
487
self.assertEqual(a_record['request_id'], thread_a_request_id)
488
self.assertEqual(b_record['request_id'], thread_b_request_id)
489
490
491
class TestDatabaseRecordReader(BaseDatabaseRecordTester):
492
def setUp(self):
493
self.fake_connection = FakeDatabaseConnection()
494
self.reader = DatabaseRecordReader(self.fake_connection)
495
496
def test_can_close(self):
497
self.reader.close()
498
self.assertTrue(self.fake_connection.closed)
499
500
def test_row_factory_set(self):
501
self.assertEqual(self.fake_connection.row_factory,
502
self.reader._row_factory)
503
504
def test_iter_latest_records_performs_correct_query(self):
505
expected_query = (
506
' SELECT * FROM records\n'
507
' WHERE id =\n'
508
' (SELECT id FROM records WHERE timestamp =\n'
509
' (SELECT max(timestamp) FROM records)) ORDER BY timestamp;'
510
)
511
[_ for _ in self.reader.iter_latest_records()]
512
self.assertEqual(
513
self.fake_connection.execute.call_args[0][0].strip(),
514
expected_query.strip())
515
516
def test_iter_latest_records_does_iter_records(self):
517
records_to_get = [1, 2, 3]
518
self.fake_connection.execute.return_value.__iter__.return_value = iter(
519
records_to_get)
520
records = [r for r in self.reader.iter_latest_records()]
521
self.assertEqual(records, records_to_get)
522
523
def test_iter_records_performs_correct_query(self):
524
expected_query = ('SELECT * from records where id = ? '
525
'ORDER BY timestamp')
526
[_ for _ in self.reader.iter_records('fake_id')]
527
self.assertEqual(
528
self.fake_connection.execute.call_args[0][0].strip(),
529
expected_query.strip())
530
531
def test_iter_records_does_iter_records(self):
532
records_to_get = [1, 2, 3]
533
self.fake_connection.execute.return_value.__iter__.return_value = iter(
534
records_to_get)
535
records = [r for r in self.reader.iter_records('fake_id')]
536
self.assertEqual(records, records_to_get)
537
538
539
class TestPayloadSerialzier(unittest.TestCase):
540
def test_can_serialize_basic_types(self):
541
original = {
542
'string': 'foo',
543
'int': 4,
544
'list': [1, 2, 'bar'],
545
'dict': {
546
'sun': 'moon'
547
},
548
'float': 1.2
549
}
550
string_value = json.dumps(original, cls=PayloadSerializer)
551
reloaded = json.loads(string_value)
552
self.assertEqual(original, reloaded)
553
554
def test_can_serialize_datetime(self):
555
now = datetime.datetime.now()
556
iso_now = now.isoformat()
557
string_value = json.dumps(now, cls=PayloadSerializer)
558
reloaded = json.loads(string_value)
559
self.assertEqual(iso_now, reloaded)
560
561
def test_can_serialize_case_insensitive_dict(self):
562
original = CaseInsensitiveDict({
563
'fOo': 'bar'
564
})
565
string_value = json.dumps(original, cls=PayloadSerializer)
566
reloaded = json.loads(string_value)
567
self.assertEqual(original, reloaded)
568
569
def test_can_serialize_unknown_type(self):
570
original = queue.Queue()
571
encoded = repr(original)
572
string_value = json.dumps(original, cls=PayloadSerializer)
573
reloaded = json.loads(string_value)
574
self.assertEqual(encoded, reloaded)
575
576
def test_can_serialize_non_utf_8_bytes_type(self):
577
original = b'\xfe\xed' # Non utf-8 byte squence
578
encoded = '<Byte sequence>'
579
string_value = json.dumps(original, cls=PayloadSerializer)
580
reloaded = json.loads(string_value)
581
self.assertEqual(encoded, reloaded)
582
583
def test_does_preserve_utf_8_bytes_type(self):
584
original = b'foobar' # utf-8 byte squence
585
encoded = 'foobar'
586
string_value = json.dumps(original, cls=PayloadSerializer)
587
reloaded = json.loads(string_value)
588
self.assertEqual(encoded, reloaded)
589
590
def test_can_serialize_non_utf_8_bytes_type_in_dict(self):
591
original = {'foo': 'bar', 'bytes': b'\xfe\xed'}
592
encoded = {'foo': 'bar', 'bytes': '<Byte sequence>'}
593
string_value = json.dumps(original, cls=PayloadSerializer)
594
reloaded = json.loads(string_value)
595
self.assertEqual(encoded, reloaded)
596
597
def test_can_serialize_non_utf_8_bytes_type_in_list(self):
598
original = ['foo', b'\xfe\xed']
599
encoded = ['foo', '<Byte sequence>']
600
string_value = json.dumps(original, cls=PayloadSerializer)
601
reloaded = json.loads(string_value)
602
self.assertEqual(encoded, reloaded)
603
604
def test_can_serialize_non_utf_8_bytes_type_in_tuple(self):
605
original = ('foo', b'\xfe\xed')
606
encoded = ['foo', '<Byte sequence>']
607
string_value = json.dumps(original, cls=PayloadSerializer)
608
reloaded = json.loads(string_value)
609
self.assertEqual(encoded, reloaded)
610
611
def test_can_serialize_non_utf_8_bytes_nested(self):
612
original = {
613
'foo': 'bar',
614
'bytes': b'\xfe\xed',
615
'list': ['foo', b'\xfe\xed'],
616
'more_nesting': {
617
'bytes': b'\xfe\xed',
618
'tuple': ('bar', 'baz', b'\xfe\ed')
619
}
620
}
621
encoded = {
622
'foo': 'bar',
623
'bytes': '<Byte sequence>',
624
'list': ['foo', '<Byte sequence>'],
625
'more_nesting': {
626
'bytes': '<Byte sequence>',
627
'tuple': ['bar', 'baz', '<Byte sequence>']
628
}
629
}
630
string_value = json.dumps(original, cls=PayloadSerializer)
631
reloaded = json.loads(string_value)
632
self.assertEqual(encoded, reloaded)
633
634
635
class TestRecordBuilder(unittest.TestCase):
636
UUID_PATTERN = re.compile(
637
'^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$',
638
re.I
639
)
640
641
def setUp(self):
642
self.builder = RecordBuilder()
643
644
def _get_request_id_for_event_type(self, event_type):
645
record = self.builder.build_record(event_type, {'body': b''}, '')
646
return record.get('request_id')
647
648
def test_does_inject_timestamp(self):
649
record = self.builder.build_record('TEST', '', '')
650
self.assertTrue('timestamp' in record)
651
self.assertTrue(isinstance(record['timestamp'], numbers.Number))
652
653
def test_does_inject_command_id(self):
654
record = self.builder.build_record('TEST', '', '')
655
self.assertTrue('timestamp' in record)
656
self.assertTrue(isinstance(record['timestamp'], numbers.Number))
657
self.assertTrue('command_id' in record)
658
self.assertTrue(self.UUID_PATTERN.match(record['command_id']))
659
660
def test_does_create_record_with_correct_fields(self):
661
record = self.builder.build_record('type', 'payload', 'source')
662
self.assertEqual(record['event_type'], 'type')
663
self.assertEqual(record['payload'], 'payload')
664
self.assertEqual(record['source'], 'source')
665
self.assertTrue('command_id' in record)
666
self.assertTrue('timestamp' in record)
667
668
def test_can_process_http_request_with_none_body(self):
669
try:
670
self.builder.build_record('HTTP_REQUEST', {'body': None}, '')
671
except ValueError:
672
self.fail("Should not raise value error")
673
674
def test_can_process_http_response_with_nono_body(self):
675
try:
676
self.builder.build_record('HTTP_RESPONSE', {'body': None}, '')
677
except ValueError:
678
self.fail("Should not raise value error")
679
680
def test_can_get_request_id_from_api_call(self):
681
identifier = self._get_request_id_for_event_type('API_CALL')
682
self.assertTrue(self.UUID_PATTERN.match(identifier))
683
684
def test_does_get_id_for_http_request_with_api_call(self):
685
call_identifier = self._get_request_id_for_event_type('API_CALL')
686
request_identifier = self._get_request_id_for_event_type(
687
'HTTP_REQUEST')
688
689
self.assertEqual(call_identifier, request_identifier)
690
self.assertTrue(self.UUID_PATTERN.match(call_identifier))
691
692
def test_does_get_id_for_http_response_with_api_call(self):
693
call_identifier = self._get_request_id_for_event_type('API_CALL')
694
response_identifier = self._get_request_id_for_event_type(
695
'HTTP_RESPONSE')
696
697
self.assertEqual(call_identifier, response_identifier)
698
self.assertTrue(self.UUID_PATTERN.match(call_identifier))
699
700
def test_does_get_id_for_parsed_response_with_api_call(self):
701
call_identifier = self._get_request_id_for_event_type('API_CALL')
702
response_identifier = self._get_request_id_for_event_type(
703
'PARSED_RESPONSE')
704
705
self.assertEqual(call_identifier, response_identifier)
706
self.assertTrue(self.UUID_PATTERN.match(call_identifier))
707
708
def test_does_not_get_id_for_http_request_without_api_call(self):
709
identifier = self._get_request_id_for_event_type('HTTP_REQUEST')
710
self.assertIsNone(identifier)
711
712
def test_does_not_get_id_for_http_response_without_api_call(self):
713
identifier = self._get_request_id_for_event_type('HTTP_RESPONSE')
714
self.assertIsNone(identifier)
715
716
def test_does_not_get_id_for_parsed_response_without_api_call(self):
717
identifier = self._get_request_id_for_event_type('PARSED_RESPONSE')
718
self.assertIsNone(identifier)
719
720
721
class TestIdentifierLifecycles(unittest.TestCase):
722
def setUp(self):
723
self.builder = RecordBuilder()
724
725
def _get_multiple_request_ids(self, events):
726
fake_payload = {'body': b''}
727
request_ids = [
728
self.builder.build_record(
729
event,
730
fake_payload.copy(),
731
''
732
)['request_id']
733
for event in events
734
]
735
return request_ids
736
737
def test_multiple_http_lifecycle_writes_have_same_request_id(self):
738
request_ids = self._get_multiple_request_ids(
739
['API_CALL', 'HTTP_REQUEST', 'HTTP_RESPONSE', 'PARSED_RESPONSE']
740
)
741
# All request_ids should match since this is one request lifecycle
742
unique_request_ids = set(request_ids)
743
self.assertEqual(len(unique_request_ids), 1)
744
745
def test_request_id_reset_on_api_call(self):
746
request_ids = self._get_multiple_request_ids(
747
['API_CALL', 'HTTP_REQUEST', 'HTTP_RESPONSE', 'PARSED_RESPONSE',
748
'API_CALL', 'HTTP_REQUEST', 'HTTP_RESPONSE', 'PARSED_RESPONSE',
749
'API_CALL', 'HTTP_REQUEST', 'HTTP_RESPONSE', 'PARSED_RESPONSE']
750
)
751
752
# There should be three distinct requet_ids since there are three
753
# distinct calls that end with a parsed response.
754
unique_request_ids = set(request_ids)
755
self.assertEqual(len(unique_request_ids), 3)
756
757
# Check that the request ids match the correct events.
758
first_request_ids = request_ids[:4]
759
unique_first_request_ids = set(first_request_ids)
760
self.assertEqual(len(unique_first_request_ids), 1)
761
762
second_request_ids = request_ids[4:8]
763
unique_second_request_ids = set(second_request_ids)
764
self.assertEqual(len(unique_second_request_ids), 1)
765
766
third_request_ids = request_ids[8:]
767
unique_third_request_ids = set(third_request_ids)
768
self.assertEqual(len(unique_third_request_ids), 1)
769
770