Skip to content

Commit 4afd7c8

Browse files
committed
read custom ignore-list of headers for recording vcr cassettes
1 parent dfbb4a3 commit 4afd7c8

File tree

6 files changed

+72
-5
lines changed

6 files changed

+72
-5
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ With this configuration set, you can make the following request to the test agen
163163
curl -X POST 'http://127.0.0.1:9126/vcr/provider1/some/path'
164164
```
165165

166+
#### Ignoring Headers in Recorded Cassettes
167+
168+
To ignore headers in recorded cassettes, you can use the `--vcr-ignore-headers` flag or `VCR_IGNORE_HEADERS` environment variable. The list should take the form of `header1,header2,header3`, and will be omitted from the recorded cassettes.
169+
166170
#### AWS Services
167171
AWS service proxying, specifically recording cassettes for the first time, requires a `AWS_SECRET_ACCESS_KEY` environment variable to be set for the container running the test agent. This is used to recalculate the AWS signature for the request, as the one generated client-side likely used `{test-agent-host}:{test-agent-port}/vcr/{aws-service}` as the host, and the signature will mismatch that on the actual AWS service.
168172

ddapm_test_agent/agent.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,7 @@ def make_app(
15801580
vcr_cassettes_directory: str,
15811581
vcr_ci_mode: bool,
15821582
vcr_provider_map: str,
1583+
vcr_ignore_headers: str,
15831584
) -> web.Application:
15841585
agent = Agent()
15851586
app = web.Application(
@@ -1641,7 +1642,9 @@ def make_app(
16411642
web.route(
16421643
"*",
16431644
"/vcr/{path:.*}",
1644-
lambda request: proxy_request(request, vcr_cassettes_directory, vcr_ci_mode, vcr_provider_map),
1645+
lambda request: proxy_request(
1646+
request, vcr_cassettes_directory, vcr_ci_mode, vcr_provider_map, vcr_ignore_headers
1647+
),
16451648
),
16461649
]
16471650
)
@@ -1955,6 +1958,12 @@ def main(args: Optional[List[str]] = None) -> None:
19551958
default=os.environ.get("VCR_PROVIDER_MAP", ""),
19561959
help="Comma-separated list of provider=base_url tuples to map providers to paths. Used in addition to the default provider paths.",
19571960
)
1961+
parser.add_argument(
1962+
"--vcr-ignore-headers",
1963+
type=str,
1964+
default=os.environ.get("VCR_IGNORE_HEADERS", ""),
1965+
help="Comma-separated list of headers to ignore when recording VCR cassettes.",
1966+
)
19581967
parsed_args = parser.parse_args(args=args)
19591968
logging.basicConfig(level=parsed_args.log_level)
19601969

@@ -2000,6 +2009,7 @@ def main(args: Optional[List[str]] = None) -> None:
20002009
vcr_cassettes_directory=parsed_args.vcr_cassettes_directory,
20012010
vcr_ci_mode=parsed_args.vcr_ci_mode,
20022011
vcr_provider_map=parsed_args.vcr_provider_map,
2012+
vcr_ignore_headers=parsed_args.vcr_ignore_headers,
20032013
)
20042014

20052015
# Validate port configuration

ddapm_test_agent/vcr_proxy.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,15 @@ def parse_authorization_header(auth_header: str) -> Dict[str, str]:
123123
return parsed
124124

125125

126-
def get_vcr(subdirectory: str, vcr_cassettes_directory: str) -> vcr.VCR:
126+
def get_vcr(subdirectory: str, vcr_cassettes_directory: str, vcr_ignore_headers: str) -> vcr.VCR:
127127
cassette_dir = os.path.join(vcr_cassettes_directory, subdirectory)
128+
extra_ignore_headers = vcr_ignore_headers.split(",")
128129

129130
return vcr.VCR(
130131
cassette_library_dir=cassette_dir,
131132
record_mode="once",
132133
match_on=["path", "method"],
133-
filter_headers=CASSETTE_FILTER_HEADERS,
134+
filter_headers=CASSETTE_FILTER_HEADERS + extra_ignore_headers,
134135
)
135136

136137

@@ -156,7 +157,7 @@ def generate_cassette_name(path: str, method: str, body: bytes, vcr_cassette_pre
156157

157158

158159
async def proxy_request(
159-
request: Request, vcr_cassettes_directory: str, vcr_ci_mode: bool, vcr_provider_map: str
160+
request: Request, vcr_cassettes_directory: str, vcr_ci_mode: bool, vcr_provider_map: str, vcr_ignore_headers: str
160161
) -> Response:
161162
set_custom_vcr_providers(vcr_provider_map)
162163

@@ -213,7 +214,7 @@ async def proxy_request(
213214
auth = AWS4Auth(aws_access_key, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_SERVICES[provider])
214215
request_kwargs["auth"] = auth
215216

216-
with get_vcr(provider, vcr_cassettes_directory).use_cassette(cassette_file_name):
217+
with get_vcr(provider, vcr_cassettes_directory, vcr_ignore_headers).use_cassette(cassette_file_name):
217218
provider_response = requests.request(**request_kwargs)
218219

219220
# Extract content type without charset

releasenotes/notes/vcr-custom-providers-map-a43b7e2d62d02015.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
features:
33
- |
44
vcr: Adds support for specifying a list of custom providers in the ``--vcr-provider-map`` flag or ``VCR_PROVIDER_MAP`` environment variable. The list should take the form of ``provider1=http://provider1.com/,provider2=http://provider2.com/``.
5+
- |
6+
vcr: Adds support for specifying a list of headers to ignore when recording VCR cassettes in the ``--vcr-ignore-headers`` flag or ``VCR_IGNORE_HEADERS`` environment variable. The list should take the form of ``header1,header2,header3``.

tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,11 @@ def vcr_provider_map() -> Generator[str, None, None]:
127127
yield ""
128128

129129

130+
@pytest.fixture
131+
def vcr_ignore_headers() -> Generator[str, None, None]:
132+
yield ""
133+
134+
130135
@pytest.fixture
131136
async def agent_app(
132137
aiohttp_server,
@@ -145,6 +150,7 @@ async def agent_app(
145150
vcr_cassettes_directory,
146151
vcr_ci_mode,
147152
vcr_provider_map,
153+
vcr_ignore_headers,
148154
):
149155
app = await aiohttp_server(
150156
make_app(
@@ -163,6 +169,7 @@ async def agent_app(
163169
vcr_cassettes_directory=vcr_cassettes_directory,
164170
vcr_ci_mode=vcr_ci_mode,
165171
vcr_provider_map=vcr_provider_map,
172+
vcr_ignore_headers=vcr_ignore_headers,
166173
)
167174
)
168175
yield app

tests/test_vcr_proxy.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
import threading
66
from typing import Any
77
from typing import AsyncGenerator
8+
from typing import Dict
89
from typing import Generator
910
from typing import List
1011
from typing import Optional
12+
from typing import cast
1113

1214
from aiohttp import FormData
1315
from aiohttp.multipart import MultipartWriter
1416
from aiohttp.test_utils import TestClient
1517
import pytest
18+
import yaml
1619

1720

1821
class DummyHandler(BaseHTTPRequestHandler):
@@ -79,13 +82,24 @@ def vcr_provider_map(dummy_server: HTTPServer) -> Generator[str, None, None]:
7982
yield provider_map
8083

8184

85+
@pytest.fixture
86+
def vcr_ignore_headers() -> Generator[str, None, None]:
87+
yield "foo-bar,user-super-secret-api-key"
88+
89+
8290
@pytest.fixture
8391
async def vcr_test_name(agent: TestClient[Any, Any]) -> AsyncGenerator[None, None]:
8492
await agent.post("/vcr/test/start", json={"test_name": "test_name_prefix"})
8593
yield
8694
await agent.post("/vcr/test/stop")
8795

8896

97+
def get_recorded_request_from_yaml(file_path: str) -> Dict[str, Any]:
98+
with open(file_path, "r") as file:
99+
content = yaml.load(file, Loader=yaml.UnsafeLoader)
100+
return cast(Dict[str, Any], content["interactions"][0])
101+
102+
89103
async def test_vcr_proxy_make_cassette(agent: TestClient[Any, Any], vcr_cassettes_directory: str) -> None:
90104
resp = await agent.post("/vcr/custom/serve", json={"foo": "bar"})
91105

@@ -177,3 +191,32 @@ async def test_vcr_proxy_with_multipart_form_data(agent: TestClient[Any, Any], v
177191

178192
cassette_files = get_cassettes_for_provider("custom", vcr_cassettes_directory)
179193
assert len(cassette_files) == 1
194+
195+
196+
async def test_vcr_proxy_does_not_record_ignored_headers(
197+
agent: TestClient[Any, Any], vcr_cassettes_directory: str
198+
) -> None:
199+
resp = await agent.post(
200+
"/vcr/custom/serve",
201+
json={"foo": "bar"},
202+
headers={
203+
"User-Super-Secret-Api-Key": "secret",
204+
"Foo-Bar": "foo",
205+
"Authorization": "test",
206+
"Please-Record-Header": "test",
207+
},
208+
)
209+
210+
assert resp.status == 200
211+
assert await resp.text() == "OK"
212+
213+
cassette_files = get_cassettes_for_provider("custom", vcr_cassettes_directory)
214+
assert len(cassette_files) == 1
215+
216+
cassette_file = cassette_files[0]
217+
recorded_request = get_recorded_request_from_yaml(os.path.join(vcr_cassettes_directory, "custom", cassette_file))
218+
219+
assert recorded_request["request"]["headers"]["Please-Record-Header"] == ["test"]
220+
assert "User-Super-Secret-Api-Key" not in recorded_request["request"]["headers"]
221+
assert "Foo-Bar" not in recorded_request["request"]["headers"]
222+
assert "Authorization" not in recorded_request["request"]["headers"]

0 commit comments

Comments
 (0)