Skip to content

Commit fc3920e

Browse files
authored
Merge pull request #922 from mastastny/fapi
2 parents d777b6b + 7fcd94f commit fc3920e

4 files changed

Lines changed: 188 additions & 2 deletions

File tree

doc/examples.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ def policy_settings():
4242
return rawobj.PolicyConfig("upstream_connection", {"read_timeout": 5})
4343

4444

45+
# to add multiple policies you can return them in the list
46+
@pytest.fixture(scope="module")
47+
def policy_settings():
48+
"""Have service with upstream_connection policy added to the chain and
49+
configured to read_timeout after 5 seconds"""
50+
return [rawobj.PolicyConfig("upstream_connection", {"read_timeout": 5}), rawobj.PolicyConfig("fapi", {})]
51+
52+
53+
# if you need to add policy before default 3scale APIcast policy you need to apply different approach
54+
@pytest.fixture(scope="module")
55+
def service(service):
56+
"""
57+
Set upstream connection before #scale APIcast
58+
"""
59+
service.proxy.list().policies.insert(0, rawobj.PolicyConfig("upstream_connection", {"read_timeout": 5}))
60+
return service
61+
62+
4563
################################################################################
4664
# To switch authentication define two following fixtures
4765
@pytest.fixture(scope="module")

testsuite/tests/apicast/policy/conftest.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ def policy_settings():
1111

1212
@pytest.fixture(scope="module")
1313
def service(service, policy_settings):
14-
"Service with prepared policy_settings added"
14+
"""Service with prepared policy_settings added"""
1515
if policy_settings is not None:
16-
service.proxy.list().policies.append(policy_settings)
16+
if not isinstance(policy_settings, list):
17+
policy_settings = [policy_settings]
18+
service.proxy.list().policies.append(*policy_settings)
1719

1820
return service

testsuite/tests/apicast/policy/fapi/__init__.py

Whitespace-only changes.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
"""
2+
Test that fapi policy fulfills Baseline profile specification.
3+
"""
4+
5+
import re
6+
import warnings
7+
from uuid import UUID
8+
9+
import pytest
10+
import threescale_api
11+
12+
from testsuite import rawobj
13+
from testsuite.utils import blame
14+
15+
16+
@pytest.fixture(scope="module")
17+
def prod_client(production_gateway, application, request, testconfig):
18+
"""
19+
Duplicate with different scope, the reasoning behind duplicate is that if called prod_client from root conftest
20+
it didn't behave as expected.
21+
Prepares application and service for production use and creates new production client
22+
23+
Parameters:
24+
app (Application): Application for which create the client.
25+
promote (bool): If true, then this method also promotes proxy configuration to production.
26+
version (int): Proxy configuration version of service to promote.
27+
redeploy (bool): If true, then the production gateway will be reloaded
28+
29+
Returns:
30+
api_client (HttpClient): Api client for application
31+
32+
"""
33+
34+
def _prod_client(app=application, promote: bool = True, version: int = -1, redeploy: bool = True):
35+
if promote:
36+
if version == -1:
37+
version = app.service.proxy.list().configs.latest()["version"]
38+
try:
39+
app.service.proxy.list().promote(version=version)
40+
except threescale_api.errors.ApiClientError as err:
41+
warnings.warn(str(err))
42+
redeploy = False
43+
44+
if redeploy:
45+
production_gateway.reload()
46+
47+
client = app.api_client(endpoint="endpoint")
48+
if hasattr(client, "close"):
49+
if not testconfig["skip_cleanup"]:
50+
request.addfinalizer(client.close)
51+
return client
52+
53+
return _prod_client
54+
55+
56+
@pytest.fixture(scope="module")
57+
def policy_settings():
58+
"""Set policy settings"""
59+
fapi_policy = rawobj.PolicyConfig("fapi", configuration={"validate_x_fapi_customer_ip_address": True})
60+
# fmt: off
61+
logging_policy = rawobj.PolicyConfig(
62+
"logging",
63+
{
64+
"enable_access_logs": False,
65+
"custom_logging":
66+
f'{{{{req.headers.{"x-fapi-transaction-id"}}}}}#{{{{resp.headers.{"x-fapi-transaction-id"}}}}}',
67+
},
68+
)
69+
return [fapi_policy, logging_policy]
70+
71+
72+
@pytest.fixture()
73+
def custom_id(request):
74+
"""FAPI id to be used in a request"""
75+
return blame(request, "fapi-id", 10)
76+
77+
78+
@pytest.fixture()
79+
def service_config_version(service):
80+
"""get version of latest change"""
81+
return service.proxy.list().configs.latest()["version"]
82+
83+
84+
@pytest.mark.parametrize("provide_fapi_id", [False, True], ids=["no_fapi_id", "fapi_id"])
85+
@pytest.mark.parametrize(
86+
("client", "gateway"),
87+
[
88+
("api_client", "staging_gateway"),
89+
pytest.param("prod_client", "production_gateway", marks=pytest.mark.disruptive),
90+
],
91+
ids=["staging_apicast", "production_apicast"],
92+
)
93+
# pylint: disable=too-many-arguments
94+
def test_x_fapi_header(request, client, gateway, provide_fapi_id, custom_id, service_config_version):
95+
"""
96+
Test that requests on product with fapi policy returns provided x-fapi-transaction-id header
97+
Test:
98+
- Create product with fapi policy via API
99+
- Add logging policy, which logs "x-fapi-transaction-id" header
100+
- Send http GET request to endpoint / with unique header "x-fapi-transaction-id"
101+
- Assert that response has status code 200 and has header "x-fapi-transaction-id" matches provided id
102+
- Assert that x-fapi-transaction-id was logged
103+
- Send GET request to endpoint / without header "x-fapi-transaction-id"
104+
- Assert that response has status code 200
105+
- Assert that response has header "x-fapi-transaction-id: uuid", where uuid is
106+
valid uuid version 4 specified in RFC 4122
107+
- Assert that newly generated x-fapi-transaction-id was logged
108+
109+
"""
110+
client_kwargs = {}
111+
if client == "prod_client":
112+
client_kwargs = {"version": service_config_version}
113+
114+
client = request.getfixturevalue(client)
115+
gateway = request.getfixturevalue(gateway)
116+
client = client(**client_kwargs)
117+
118+
headers = None
119+
if provide_fapi_id:
120+
headers = {"x-fapi-transaction-id": custom_id}
121+
122+
result = client.get("/", headers=headers)
123+
fapi_id = result.headers.get("x-fapi-transaction-id")
124+
assert result.status_code == 200
125+
if provide_fapi_id:
126+
# test whether apicast returns same x-fapi-transaction-id
127+
assert fapi_id == custom_id
128+
else:
129+
# test whether apicast generates new x-fapi-transaction-id
130+
assert UUID(fapi_id).variant == "specified in RFC 4122"
131+
assert UUID(fapi_id).version == 4
132+
logs = gateway.get_logs()
133+
match = re.search(f"{custom_id if provide_fapi_id else ''}#{fapi_id}", logs, re.MULTILINE)
134+
assert match is not None
135+
136+
137+
@pytest.mark.parametrize("provide_fapi_id", [False, True], ids=["no_fapi_id", "fapi_id"])
138+
@pytest.mark.parametrize(
139+
"ip, ok",
140+
[("198.51.100.119", True), ("2001:db8::1:0", True), ("jggorpesuogojyib", False)],
141+
ids=["valid_IPv4_address", "valid_IPv6_address", "invalid_IP_address"],
142+
)
143+
# pylint: disable=unused-argument
144+
def test_x_fapi_customer_ip(ip, ok, api_client, provide_fapi_id, custom_id):
145+
"""
146+
Test that requests on product with fapi policy returns provided x-fapi-transaction-id header
147+
Test:
148+
- Create product with fapi policy via API
149+
- Send GET request to endpoint / with header "x-fapi-customer-ip-address" and "x-fapi-transaction-id"
150+
- Assert that response has status code 200 for valid IPv4 and IPv6 addresses
151+
- Assert that response status code is not 200 for invalid IP address
152+
- Assert that "x-fapi-transaction-id" stayed the same when was provided and was generated when wasn't provided
153+
154+
"""
155+
headers = {"x-fapi-customer-ip-address": ip}
156+
if provide_fapi_id:
157+
headers.update({"x-fapi-transaction-id": custom_id})
158+
client = api_client()
159+
resp = client.get("/", headers=headers)
160+
assert resp.ok == ok
161+
fapi_id = resp.headers.get("x-fapi-transaction-id")
162+
if provide_fapi_id:
163+
assert fapi_id == custom_id
164+
else:
165+
assert UUID(fapi_id).variant == "specified in RFC 4122"
166+
assert UUID(fapi_id).version == 4

0 commit comments

Comments
 (0)