-
Notifications
You must be signed in to change notification settings - Fork 58
Expand file tree
/
Copy pathconftest.py
More file actions
400 lines (286 loc) · 11.1 KB
/
conftest.py
File metadata and controls
400 lines (286 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
import datetime
import json
import os
import warnings
from typing import (
Any,
Tuple,
)
import pytest
from easypost.easypost_client import EasyPostClient
EASYPOST_TEST_API_KEY = os.getenv("EASYPOST_TEST_API_KEY")
EASYPOST_PROD_API_KEY = os.getenv("EASYPOST_PROD_API_KEY")
PARTNER_USER_PROD_API_KEY = os.getenv("PARTNER_USER_PROD_API_KEY", "123")
REFERRAL_CUSTOMER_PROD_API_KEY = os.getenv("REFERRAL_CUSTOMER_PROD_API_KEY", "123")
SCRUBBED_STRING = "<REDACTED>"
SCRUBBED_ARRAY: list = []
SCRUBBED_DICT: dict = {}
def pytest_sessionstart(session):
"""This is for local unit testing with google appengine, otherwise you get a
'No api proxy found for service "urlfetch"' response.
"""
try:
from google.appengine.ext import testbed # type: ignore
session.appengine_testbed = testbed.Testbed() # type: ignore
session.appengine_testbed.activate() # type: ignore
session.appengine_testbed.init_urlfetch_stub() # type: ignore
except ImportError:
# if the import fails then we're not using the appengine sdk, so just
# keep going without initializing the testbed stub
pass
def pytest_sessionfinish(session, exitstatus):
if hasattr(session, "appengine_testbed"):
session.appengine_testbed.deactivate() # type: ignore
@pytest.fixture(autouse=True)
def check_expired_cassettes(expiration_days: int = 365, throw_error: bool = False):
"""Checks for expired cassettes and throws errors if they are too old and must be re-recorded."""
test_name = os.environ.get("PYTEST_CURRENT_TEST").split(":")[-1].split(" ")[0] # type: ignore
cassette_filepath = os.path.join("tests", "cassettes", f"{test_name}.yaml")
if os.path.exists(cassette_filepath):
cassette_timestamp = datetime.datetime.fromtimestamp(os.stat(cassette_filepath).st_mtime)
expiration_timestamp = cassette_timestamp + datetime.timedelta(days=expiration_days)
current_timestamp = datetime.datetime.now()
if current_timestamp > expiration_timestamp:
error_message = f"{cassette_filepath} is older than {expiration_days} days and has expired. Please re-record the cassette." # noqa
if throw_error:
raise Exception(error_message)
else:
warnings.warn(error_message)
@pytest.fixture(scope="session")
def vcr_config():
"""Setup the VCR config for the test suite."""
return {
"match_on": [
"body",
"headers",
"method",
"query",
"uri",
],
"decode_compressed_response": True,
"filter_headers": [
("authorization", SCRUBBED_STRING),
("user-agent", SCRUBBED_STRING),
],
"filter_query_parameters": [
"card[number]",
"card[cvc]",
],
"before_record_response": scrub_response_bodies(
scrubbers=[
["client_ip", SCRUBBED_STRING],
["credentials", SCRUBBED_DICT],
["email", SCRUBBED_STRING],
["fields", SCRUBBED_ARRAY],
["key", SCRUBBED_STRING],
["phone_number", SCRUBBED_STRING],
["phone", SCRUBBED_STRING],
["test_credentials", SCRUBBED_DICT],
]
),
}
def scrub_response_bodies(scrubbers: list[Tuple[str, Any]]) -> Any:
"""Scrub sensitive data from response bodies prior to recording the cassette."""
def before_record_response(response: Any) -> Any:
"""This function fires prior to persisting data to a cassette."""
if response["body"]["string"]:
response_body = json.loads(response["body"]["string"].decode())
for scrubber in scrubbers:
response_body = scrub_data(response_body, scrubber)
response["body"]["string"] = json.dumps(response_body).encode()
return response
def scrub_data(data: Any, scrubber: Tuple[str, Any]) -> Any:
"""Scrub data from a cassette recursively."""
key = scrubber[0]
replacement = scrubber[1]
# Root-level list scrubbing
if isinstance(data, list):
for index, item in enumerate(data):
if key in item:
data[index][key] = replacement
elif isinstance(data, dict):
# Root-level key scrubbing
if key in data:
data[key] = replacement
else:
# Nested scrubbing
for item in data:
element = data[item]
if isinstance(element, list):
for nested_index, nested_item in enumerate(element):
data[item][nested_index] = scrub_data(nested_item, scrubber)
elif isinstance(element, dict):
data[item] = scrub_data(element, scrubber)
return data
return before_record_response
@pytest.fixture
def synchronous_sleep_seconds():
"""Use this fixture for sleeping between API calls where synchronous flows happen."""
return 5
@pytest.fixture
def test_client():
"""If a test needs to use the EasyPost test mode, make it depend on this fixture."""
return EasyPostClient(EASYPOST_TEST_API_KEY)
@pytest.fixture
def prod_client():
"""If a test needs to use the EasyPost prod mode, make it depend on this fixture."""
return EasyPostClient(EASYPOST_PROD_API_KEY)
@pytest.fixture
def partner_user_prod_client():
"""If a test needs to use prod mode with a partner user's API key, make it depend on this fixture."""
return EasyPostClient(PARTNER_USER_PROD_API_KEY)
@pytest.fixture
def referral_customer_prod_client():
"""If a test needs to use prod mode with a referral customer API key, make it depend on this fixture."""
return EasyPostClient(REFERRAL_CUSTOMER_PROD_API_KEY)
def read_fixture_data():
"""Reads fixture data from the fixtures JSON file."""
with open(os.path.join("examples", "official", "fixtures", "client-library-fixtures.json")) as data:
fixtures = json.load(data)
return fixtures
@pytest.fixture
def page_size():
"""We keep the page_size of retrieving `all` records small so cassettes stay small."""
return read_fixture_data()["page_sizes"]["five_results"]
@pytest.fixture
def usps_carrier_account_id():
"""This is the USPS carrier account ID that comes with your
EasyPost account by default and should be used for all tests.
"""
# Fallback to the EasyPost Python Client Library Test User USPS carrier account ID due to strict matching
return os.getenv("USPS_CARRIER_ACCOUNT_ID", "ca_b25657e9896e4d63ac8151ac346ac41e")
@pytest.fixture
def usps():
return read_fixture_data()["carrier_strings"]["usps"]
@pytest.fixture
def usps_service():
return read_fixture_data()["service_names"]["usps"]["first_service"]
@pytest.fixture
def pickup_service():
return read_fixture_data()["service_names"]["usps"]["pickup_service"]
@pytest.fixture
def report_type():
return read_fixture_data()["report_types"]["shipment"]
@pytest.fixture
def report_date():
return "2022-05-04"
@pytest.fixture
def ca_address_1():
return read_fixture_data()["addresses"]["ca_address_1"]
@pytest.fixture
def ca_address_2():
return read_fixture_data()["addresses"]["ca_address_2"]
@pytest.fixture
def incorrect_address():
return read_fixture_data()["addresses"]["incorrect"]
@pytest.fixture
def basic_parcel():
return read_fixture_data()["parcels"]["basic"]
@pytest.fixture
def basic_customs_item():
return read_fixture_data()["customs_items"]["basic"]
@pytest.fixture
def basic_customs_info():
return read_fixture_data()["customs_infos"]["basic"]
@pytest.fixture
def tax_identifier():
return read_fixture_data()["tax_identifiers"]["basic"]
@pytest.fixture
def basic_shipment():
return read_fixture_data()["shipments"]["basic_domestic"]
@pytest.fixture
def full_shipment():
return read_fixture_data()["shipments"]["full"]
@pytest.fixture
def one_call_buy_shipment(
ca_address_1,
ca_address_2,
basic_parcel,
usps_service,
usps_carrier_account_id,
usps,
):
return {
"to_address": ca_address_1,
"from_address": ca_address_2,
"parcel": basic_parcel,
"service": usps_service,
"carrier_accounts": [usps_carrier_account_id],
"carrier": usps,
}
@pytest.fixture
def basic_pickup():
"""This fixture will require you to add a `shipment` key with a Shipment object from a test.
If you need to re-record cassettes, increment the date below and ensure it is one day in the future,
USPS only does "next-day" pickups including Saturday but not Sunday or Holidays.
"""
pickup_date = "2025-03-08"
pickup_data = read_fixture_data()["pickups"]["basic"]
pickup_data["min_datetime"] = pickup_date
pickup_data["max_datetime"] = pickup_date
return pickup_data
@pytest.fixture
def basic_carrier_account():
return read_fixture_data()["carrier_accounts"]["basic"]
@pytest.fixture
def basic_insurance():
"""This fixture will require you to append a `tracking_code` key with the shipment's tracking code."""
return read_fixture_data()["insurances"]["basic"]
@pytest.fixture
def basic_claim():
"""This fixture will require you to append a `tracking_code` key with the shipment's tracking code,
and a `amount` key with the insurance amount."""
return read_fixture_data()["claims"]["basic"]
@pytest.fixture
def basic_order():
return read_fixture_data()["orders"]["basic"]
@pytest.fixture
def event_json():
with open(os.path.join("examples", "official", "fixtures", "event-body.json")) as data:
event_body = json.load(data)
return json.dumps(event_body)
@pytest.fixture
def event_bytes():
with open(os.path.join("examples", "official", "fixtures", "event-body.json")) as data:
event_body = json.load(data)
return json.dumps(event_body, separators=(",", ":")).encode()
@pytest.fixture
def webhook_hmac_signature():
return read_fixture_data()["webhooks"]["hmac_signature"]
@pytest.fixture
def webhook_secret():
return read_fixture_data()["webhooks"]["secret"]
@pytest.fixture
def webhook_url():
return read_fixture_data()["webhooks"]["url"]
@pytest.fixture
def webhook_custom_headers():
return read_fixture_data()["webhooks"]["custom_headers"]
@pytest.fixture
def credit_card_details():
"""The credit card details below are for a valid proxy card usable
for tests only and cannot be used for real transactions.
DO NOT alter these details with real credit card information.
"""
return read_fixture_data()["credit_cards"]["test"]
@pytest.fixture
def rma_form_options():
return read_fixture_data()["form_options"]["rma"]
@pytest.fixture
def planned_ship_date():
return "2025-03-08"
@pytest.fixture
def desired_delivery_date():
return "2025-03-08"
@pytest.fixture
def billing():
return read_fixture_data()["billing"]
@pytest.fixture
def luma_ruleset_name():
return read_fixture_data()["luma"]["ruleset_name"]
@pytest.fixture
def luma_planned_ship_date():
return "2025-06-12"
@pytest.fixture
def referral_user():
return read_fixture_data()["users"]["referral"]