Skip to content

Commit

Permalink
Add MockserverBackend & add request expectations
Browse files Browse the repository at this point in the history
  • Loading branch information
averevki committed Jan 24, 2024
1 parent 6677a27 commit 7cbf6f9
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 18 deletions.
96 changes: 89 additions & 7 deletions testsuite/mockserver.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
"""Module for Mockserver integration"""
from typing import Union

import httpx
from apyproxy import ApyProxy

from testsuite.utils import ContentType
from testsuite.httpx import KuadrantClient
from testsuite.gateway import Referencable
from testsuite.lifecycle import LifecycleObject
from testsuite.openshift import Selector
from testsuite.openshift.service import Service, ServicePort
from testsuite.openshift.deployment import Deployment, ContainerResources
from testsuite.openshift.client import OpenShiftClient


class Mockserver:
Expand All @@ -13,21 +19,37 @@ class Mockserver:
"""

def __init__(self, url):
self.client = ApyProxy(url, session=httpx.Client(verify=False, timeout=5))
self.client = ApyProxy(url, session=KuadrantClient(verify=False))

def _expectation(self, expectation_id, response_data):
def _expectation(self, expectation_id, json_data):
"""
Creates an Expectation with given response_data.
Creates an Expectation from given expectation json.
Returns the absolute URL of the expectation
"""
json_data = {"id": expectation_id, "httpRequest": {"path": f"/{expectation_id}"}}
json_data.update(response_data)
json_data["id"] = expectation_id
json_data.setdefault("httpRequest", {})["path"] = f"/{expectation_id}"

self.client.mockserver.expectation.put(json=json_data)
# pylint: disable=protected-access
return f"{self.client._url}/{expectation_id}"

def create_expectation(
def create_request_expectation(
self,
expectation_id,
headers: dict[str, list[str]],
):
"""Creates an Expectation - request with given headers"""
json_data = {
"httpRequest": {
"headers": headers,
},
"httpResponse": {
"body": "",
},
}
return self._expectation(expectation_id, json_data)

def create_response_expectation(
self,
expectation_id,
body,
Expand Down Expand Up @@ -55,3 +77,63 @@ def retrieve_requests(self, expectation_id):
params={"type": "REQUESTS", "format": "JSON"},
json={"path": "/" + expectation_id},
).json()


class MockserverBackend(Mockserver, LifecycleObject, Referencable):
"""Mockserver deployed as backend in Openshift"""

PORT = 1080

def __init__(self, openshift: OpenShiftClient, url: str, name: str, label: str):
super().__init__(url)

self.openshift = openshift
self.name = name
self.label = label

self.deployment = None
self.service = None

@property
def reference(self):
return {
"group": "",
"kind": "Service",
"port": self.PORT,
"name": self.name,
"namespace": self.openshift.project,
}

def commit(self):
match_labels = {"app": self.label, "deployment": self.name}
self.deployment = Deployment.create_instance(
self.openshift,
self.name,
container_name="mockserver",
image="quay.io/mganisin/mockserver:latest",
ports={"api": self.PORT},
selector=Selector(matchLabels=match_labels),
labels={"app": self.label},
resources=ContainerResources(limits_memory="2G"),
lifecycle={"postStart": {"exec": {"command": ["/bin/sh", "init-mockserver"]}}},
)
self.deployment.commit()
self.deployment.wait_for_ready()

self.service = Service.create_instance(
self.openshift,
self.name,
selector=match_labels,
ports=[ServicePort(name="1080-tcp", port=self.PORT, targetPort="api")],
labels={"app": self.label},
)
self.service.commit()

def delete(self):
with self.openshift.context:
if self.service:
self.service.delete()
self.service = None
if self.deployment:
self.deployment.delete()
self.deployment = None
36 changes: 34 additions & 2 deletions testsuite/openshift/deployment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Deployment related objects"""
from dataclasses import dataclass
from typing import Any
from typing import Any, Optional

import openshift as oc

Expand All @@ -10,6 +10,30 @@
# pylint: disable=invalid-name


@dataclass
class ContainerResources:
"""Deployment ContainerResources object"""

limits_cpu: Optional[str] = None
limits_memory: Optional[str] = None
requests_cpu: Optional[str] = None
requests_memory: Optional[str] = None

@dataclass
class _Resources:
"""CPU and memory resources for container"""

cpu: Optional[str] = None
memory: Optional[str] = None

def asdict(self):
"""Custom asdict due to nested structure"""
return {
"limits": asdict(self._Resources(cpu=self.limits_cpu, memory=self.limits_memory)),
"requests": asdict(self._Resources(cpu=self.requests_cpu, memory=self.requests_memory)),
}


@dataclass
class VolumeMount:
"""Deployment VolumeMount object"""
Expand Down Expand Up @@ -71,7 +95,9 @@ def create_instance(
volumes: list[Volume] = None,
volume_mounts: list[VolumeMount] = None,
readiness_probe: dict[str, Any] = None,
):
resources: Optional[ContainerResources] = None,
lifecycle: dict[str, Any] = None,
): # pylint: disable=too-many-locals
"""
Creates new instance of Deployment
Supports only single container Deployments everything else should be edited directly
Expand Down Expand Up @@ -116,6 +142,12 @@ def create_instance(
if readiness_probe:
container["readinessProbe"] = readiness_probe

if resources:
container["resources"] = asdict(resources)

if lifecycle:
container["lifecycle"] = lifecycle

return cls(model, context=openshift.context)

def wait_for_ready(self, timeout=90):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def header():
def opa_policy_expectation(request, mockserver, module_label, header):
"""Creates Mockserver Expectation that returns Rego query and returns its endpoint"""
request.addfinalizer(lambda: mockserver.clear_expectation(module_label))
return mockserver.create_expectation(module_label, rego_allow_header(*header))
return mockserver.create_response_expectation(module_label, rego_allow_header(*header))


@pytest.fixture(scope="module")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def updated_header():
@pytest.fixture(scope="module", autouse=True)
def update_external_opa(mockserver, module_label, updated_header):
"""Updates Expectation with updated header"""
mockserver.create_expectation(module_label, rego_allow_header(*updated_header))
mockserver.create_response_expectation(module_label, rego_allow_header(*updated_header))
# Sleeps for 1 second to compensate auto-refresh cycle `authorization.opa.externalRegistry.ttl = 1`
time.sleep(1)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def mockserver_expectation(request, mockserver, module_label):
"""Creates Mockserver Expectation which returns non-empty response on hit"""
request.addfinalizer(lambda: mockserver.clear_expectation(module_label))
return mockserver.create_expectation(module_label, "response")
return mockserver.create_response_expectation(module_label, "response")


@pytest.fixture(scope="module")
Expand Down
6 changes: 3 additions & 3 deletions testsuite/tests/kuadrant/authorino/dinosaur/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def terms_and_conditions(request, mockserver, module_label):
"""Creates Mockserver Expectation that returns whether terms are required and returns its endpoint"""

def _terms_and_conditions(value):
return mockserver.create_expectation(
return mockserver.create_response_expectation(
f"{module_label}-terms",
{"terms_required": value},
ContentType.APPLICATION_JSON,
Expand All @@ -55,7 +55,7 @@ def cluster_info(request, mockserver, module_label):
"""Creates Mockserver Expectation that returns client ID and returns its endpoint"""

def _cluster_info(value):
return mockserver.create_expectation(
return mockserver.create_response_expectation(
f"{module_label}-cluster", {"client_id": value}, ContentType.APPLICATION_JSON
)

Expand All @@ -68,7 +68,7 @@ def resource_info(request, mockserver, module_label):
"""Creates Mockserver Expectation that returns info about resource and returns its endpoint"""

def _resource_info(org_id, owner):
return mockserver.create_expectation(
return mockserver.create_response_expectation(
f"{module_label}-resource",
{"org_id": org_id, "owner": owner},
ContentType.APPLICATION_JSON,
Expand Down
2 changes: 1 addition & 1 deletion testsuite/tests/kuadrant/authorino/metadata/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
def country_mock_expectation(request, mockserver, module_label):
"""Creates Mockserver Expectation which returns simple JSON that contains `allowed_countries`"""
request.addfinalizer(lambda: mockserver.clear_expectation(module_label))
return mockserver.create_expectation(module_label, ALLOWED_COUNTRY, ContentType.APPLICATION_JSON)
return mockserver.create_response_expectation(module_label, ALLOWED_COUNTRY, ContentType.APPLICATION_JSON)


@pytest.fixture(scope="module")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def json_mock_expectation(request, mockserver, module_label):
"""Creates Mockserver Expectation which returns multi-element JSON."""
request.addfinalizer(lambda: mockserver.clear_expectation(module_label))
return mockserver.create_expectation(module_label, MULTI_ELEMENT_JSON, ContentType.APPLICATION_JSON)
return mockserver.create_response_expectation(module_label, MULTI_ELEMENT_JSON, ContentType.APPLICATION_JSON)


@pytest.fixture(scope="module")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
def mockserver_expectation(request, mockserver, module_label):
"""Creates Mockserver Expectation which returns non-empty response on hit"""
request.addfinalizer(lambda: mockserver.clear_expectation(module_label))
return mockserver.create_expectation(module_label, "response")
return mockserver.create_response_expectation(module_label, "response")


@pytest.fixture(scope="module")
Expand Down

0 comments on commit 7cbf6f9

Please sign in to comment.