Skip to content

Commit 85a48e1

Browse files
committed
[tests] Added VCR-based integration test examples for JSON and YAML configurations
- Introduced integration tests using VCR for recording and replaying HTTP interactions with JSON and YAML formats. - Applied class-specific VCR configurations to handle both JSON and YAML serializers for different test scenarios.
1 parent e4cfa9a commit 85a48e1

11 files changed

+453
-32
lines changed

pyatlan/test_utils/base_vcr.py

+32-32
Original file line numberDiff line numberDiff line change
@@ -204,26 +204,6 @@ def deserialize(cassette_string: str) -> dict:
204204
return cassette_dict
205205

206206

207-
class VCRRemoveAllHeaders:
208-
"""
209-
A class responsible for removing all headers from requests and responses.
210-
This can be useful for scenarios where headers are not needed for matching or comparison
211-
in VCR (Virtual Cassette Recorder) interactions, such as when recording or replaying HTTP requests.
212-
"""
213-
214-
@staticmethod
215-
def remove_all_request_headers(request):
216-
# Save only what's necessary for matching
217-
request.headers = {}
218-
return request
219-
220-
@staticmethod
221-
def remove_all_response_headers(response):
222-
# Save only what's necessary for matching
223-
response["headers"] = {}
224-
return response
225-
226-
227207
class BaseVCR:
228208
"""
229209
A base class for configuring VCR (Virtual Cassette Recorder)
@@ -234,7 +214,37 @@ class BaseVCR:
234214
It also handles cassette directory configuration.
235215
"""
236216

217+
class VCRRemoveAllHeaders:
218+
"""
219+
A class responsible for removing all headers from requests and responses.
220+
This can be useful for scenarios where headers are not needed for matching or comparison
221+
in VCR (Virtual Cassette Recorder) interactions, such as when recording or replaying HTTP requests.
222+
"""
223+
224+
@staticmethod
225+
def remove_all_request_headers(request):
226+
# Save only what's necessary for matching
227+
request.headers = {}
228+
return request
229+
230+
@staticmethod
231+
def remove_all_response_headers(response):
232+
# Save only what's necessary for matching
233+
response["headers"] = {}
234+
return response
235+
237236
_CASSETTES_DIR = None
237+
_BASE_CONFIG = {
238+
# More config options can be found at:
239+
# https://vcrpy.readthedocs.io/en/latest/configuration.html#configuration
240+
"record_mode": "once", # (default: "once", "always", "none", "new_episodes")
241+
"serializer": "pretty-yaml", # (default: "yaml")
242+
"decode_compressed_response": True, # Decode compressed responses
243+
# (optional) Replace the Authorization request header with "**REDACTED**" in cassettes
244+
# "filter_headers": [("authorization", "**REDACTED**")],
245+
"before_record_request": VCRRemoveAllHeaders.remove_all_request_headers,
246+
"before_record_response": VCRRemoveAllHeaders.remove_all_response_headers,
247+
}
238248

239249
@pytest.fixture(scope="module")
240250
def vcr(self, vcr):
@@ -263,17 +273,7 @@ def vcr_config(self):
263273
264274
:returns: a dictionary with VCR configuration options
265275
"""
266-
return {
267-
# More config options can be found at:
268-
# https://vcrpy.readthedocs.io/en/latest/configuration.html#configuration
269-
"record_mode": "once", # (default: "once", "always", "none", "new_episodes")
270-
"serializer": "pretty-yaml", # (default: "yaml")
271-
"decode_compressed_response": True, # Decode compressed responses
272-
# (optional) Replace the Authorization request header with "**REDACTED**" in cassettes
273-
# "filter_headers": [("authorization", "**REDACTED**")],
274-
"before_record_request": VCRRemoveAllHeaders.remove_all_request_headers,
275-
"before_record_response": VCRRemoveAllHeaders.remove_all_response_headers,
276-
}
276+
return self._BASE_CONFIG
277277

278278
@pytest.fixture(scope="module")
279279
def vcr_cassette_dir(self, request):
@@ -290,5 +290,5 @@ def vcr_cassette_dir(self, request):
290290
"""
291291
# Set self._CASSETTES_DIR or use the default directory path based on the test module name
292292
return self._CASSETTES_DIR or os.path.join(
293-
"tests/cassettes", request.module.__name__
293+
"tests/vcr_cassettes", request.module.__name__
294294
)

tests/unit/test_base_vcr_json.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pytest
2+
import requests
3+
4+
from pyatlan.test_utils.base_vcr import BaseVCR
5+
6+
7+
class TestBaseVCRJSON(BaseVCR):
8+
"""
9+
Integration tests to demonstrate VCR.py capabilities
10+
by recording and replaying HTTP interactions using
11+
HTTPBin (https://httpbin.org) for GET, POST, PUT, and DELETE requests.
12+
"""
13+
14+
BASE_URL = "https://httpbin.org"
15+
16+
@pytest.fixture(scope="session")
17+
def vcr_config(self):
18+
"""
19+
Override the VCR configuration to use JSON serialization across the module.
20+
"""
21+
config = self._BASE_CONFIG.copy()
22+
config.update({"serializer": "pretty-json"})
23+
return config
24+
25+
@pytest.mark.vcr()
26+
def test_httpbin_get(self):
27+
"""
28+
Test a simple GET request to httpbin.
29+
"""
30+
url = f"{self.BASE_URL}/get"
31+
response = requests.get(url, params={"test": "value"})
32+
assert response.status_code == 200
33+
assert response.json()["args"]["test"] == "value"
34+
35+
@pytest.mark.vcr()
36+
def test_httpbin_post(self):
37+
"""
38+
Test a simple POST request to httpbin.
39+
"""
40+
url = f"{self.BASE_URL}/post"
41+
payload = {"name": "atlan", "type": "integration-test"}
42+
response = requests.post(url, json=payload)
43+
assert response.status_code == 200
44+
assert response.json()["json"] == payload
45+
46+
@pytest.mark.vcr()
47+
def test_httpbin_put(self):
48+
"""
49+
Test a simple PUT request to httpbin.
50+
"""
51+
url = f"{self.BASE_URL}/put"
52+
payload = {"update": "value"}
53+
response = requests.put(url, json=payload)
54+
assert response.status_code == 200
55+
assert response.json()["json"] == payload
56+
57+
@pytest.mark.vcr()
58+
def test_httpbin_delete(self):
59+
"""
60+
Test a simple DELETE request to httpbin.
61+
"""
62+
url = f"{self.BASE_URL}/delete"
63+
response = requests.delete(url)
64+
assert response.status_code == 200
65+
assert response.json()["args"] == {}

tests/unit/test_base_vcr_yaml.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import pytest
2+
import requests
3+
4+
from pyatlan.test_utils.base_vcr import BaseVCR
5+
6+
7+
class TestBaseVCRYAML(BaseVCR):
8+
"""
9+
Integration tests to demonstrate VCR.py capabilities
10+
by recording and replaying HTTP interactions using
11+
HTTPBin (https://httpbin.org) for GET, POST, PUT, and DELETE requests.
12+
"""
13+
14+
BASE_URL = "https://httpbin.org"
15+
16+
@pytest.mark.vcr()
17+
def test_httpbin_get(self):
18+
"""
19+
Test a simple GET request to httpbin.
20+
"""
21+
url = f"{self.BASE_URL}/get"
22+
response = requests.get(url, params={"test": "value"})
23+
24+
assert response.status_code == 200
25+
assert response.json()["args"]["test"] == "value"
26+
27+
@pytest.mark.vcr()
28+
def test_httpbin_post(self):
29+
"""
30+
Test a simple POST request to httpbin.
31+
"""
32+
url = f"{self.BASE_URL}/post"
33+
payload = {"name": "atlan", "type": "integration-test"}
34+
response = requests.post(url, json=payload)
35+
36+
assert response.status_code == 200
37+
assert response.json()["json"] == payload
38+
39+
@pytest.mark.vcr()
40+
def test_httpbin_put(self):
41+
"""
42+
Test a simple PUT request to httpbin.
43+
"""
44+
url = f"{self.BASE_URL}/put"
45+
payload = {"update": "value"}
46+
response = requests.put(url, json=payload)
47+
48+
assert response.status_code == 200
49+
assert response.json()["json"] == payload
50+
51+
@pytest.mark.vcr()
52+
def test_httpbin_delete(self):
53+
"""
54+
Test a simple DELETE request to httpbin.
55+
"""
56+
url = f"{self.BASE_URL}/delete"
57+
response = requests.delete(url)
58+
59+
assert response.status_code == 200
60+
# HTTPBin returns an empty JSON object for DELETE
61+
assert response.json()["args"] == {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"version": 1,
3+
"interactions": [
4+
{
5+
"request": {
6+
"method": "DELETE",
7+
"uri": "https://httpbin.org/delete",
8+
"body": null,
9+
"headers": {}
10+
},
11+
"response": {
12+
"status": {
13+
"code": 200,
14+
"message": "OK"
15+
},
16+
"headers": {},
17+
"body": {
18+
"parsed_json": {
19+
"args": {},
20+
"data": "",
21+
"files": {},
22+
"form": {},
23+
"headers": {
24+
"Accept": "*/*",
25+
"Accept-Encoding": "gzip, deflate",
26+
"Content-Length": "0",
27+
"Host": "httpbin.org",
28+
"User-Agent": "python-requests/2.32.3",
29+
"X-Amzn-Trace-Id": "Root=1-680f7263-439ec4f97dc37ffe07c63697"
30+
},
31+
"json": null,
32+
"origin": "x.x.x.x",
33+
"url": "https://httpbin.org/delete"
34+
}
35+
}
36+
}
37+
}
38+
]
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"version": 1,
3+
"interactions": [
4+
{
5+
"request": {
6+
"method": "GET",
7+
"uri": "https://httpbin.org/get?test=value",
8+
"body": null,
9+
"headers": {}
10+
},
11+
"response": {
12+
"status": {
13+
"code": 200,
14+
"message": "OK"
15+
},
16+
"headers": {},
17+
"body": {
18+
"parsed_json": {
19+
"args": {
20+
"test": "value"
21+
},
22+
"headers": {
23+
"Accept": "*/*",
24+
"Accept-Encoding": "gzip, deflate",
25+
"Host": "httpbin.org",
26+
"User-Agent": "python-requests/2.32.3",
27+
"X-Amzn-Trace-Id": "Root=1-680f7259-4e5c927e25a0aa78202e04e1"
28+
},
29+
"origin": "x.x.x.x",
30+
"url": "https://httpbin.org/get?test=value"
31+
}
32+
}
33+
}
34+
}
35+
]
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"version": 1,
3+
"interactions": [
4+
{
5+
"request": {
6+
"method": "POST",
7+
"uri": "https://httpbin.org/post",
8+
"body": "{\"name\": \"atlan\", \"type\": \"integration-test\"}",
9+
"headers": {}
10+
},
11+
"response": {
12+
"status": {
13+
"code": 200,
14+
"message": "OK"
15+
},
16+
"headers": {},
17+
"body": {
18+
"parsed_json": {
19+
"args": {},
20+
"data": "{\"name\": \"atlan\", \"type\": \"integration-test\"}",
21+
"files": {},
22+
"form": {},
23+
"headers": {
24+
"Accept": "*/*",
25+
"Accept-Encoding": "gzip, deflate",
26+
"Content-Length": "45",
27+
"Content-Type": "application/json",
28+
"Host": "httpbin.org",
29+
"User-Agent": "python-requests/2.32.3",
30+
"X-Amzn-Trace-Id": "Root=1-680f725c-53119e7d4121a80069c14836"
31+
},
32+
"json": {
33+
"name": "atlan",
34+
"type": "integration-test"
35+
},
36+
"origin": "x.x.x.x",
37+
"url": "https://httpbin.org/post"
38+
}
39+
}
40+
}
41+
}
42+
]
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"version": 1,
3+
"interactions": [
4+
{
5+
"request": {
6+
"method": "PUT",
7+
"uri": "https://httpbin.org/put",
8+
"body": "{\"update\": \"value\"}",
9+
"headers": {}
10+
},
11+
"response": {
12+
"status": {
13+
"code": 200,
14+
"message": "OK"
15+
},
16+
"headers": {},
17+
"body": {
18+
"parsed_json": {
19+
"args": {},
20+
"data": "{\"update\": \"value\"}",
21+
"files": {},
22+
"form": {},
23+
"headers": {
24+
"Accept": "*/*",
25+
"Accept-Encoding": "gzip, deflate",
26+
"Content-Length": "19",
27+
"Content-Type": "application/json",
28+
"Host": "httpbin.org",
29+
"User-Agent": "python-requests/2.32.3",
30+
"X-Amzn-Trace-Id": "Root=1-680f7261-631f6aae6a8ae85365354c87"
31+
},
32+
"json": {
33+
"update": "value"
34+
},
35+
"origin": "x.x.x.x",
36+
"url": "https://httpbin.org/put"
37+
}
38+
}
39+
}
40+
}
41+
]
42+
}

0 commit comments

Comments
 (0)