Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
WIP Full feature parity with java test container
checking the code in at this point. extensions are causing
the container to crash.
  • Loading branch information
mikeywaites committed Jul 8, 2023
commit ecd7fbc3ce8f6d580b29911d01138c35b587b242
4 changes: 4 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
ignore = E226,E302,E41
max-line-length = 88
max-complexity = 10
1 change: 1 addition & 0 deletions example/tests/test_testcontainers.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def wm_docker():
Config.base_url = f"{wm.get_url()}/__admin"
os.environ["PRODUCTS_SERVICE_HOST"] = f"http://{wm.get_container_host_ip()}"
os.environ["PRODUCTS_SERVICE_PORT"] = str(wm.port)

[Mappings.create_mapping(mapping=mapping) for mapping in get_mappings()]

yield server
Expand Down
57 changes: 57 additions & 0 deletions tests/test_containers/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from pathlib import Path

import requests

from wiremock.testing.testcontainer import WireMockContainer


def test_start_container():

wm = (
WireMockContainer()
.with_exposed_ports(8080)
.with_mapping(
"hello-world.json",
{
"request": {"method": "GET", "url": "/hello"},
"response": {"status": 200, "body": "hello"},
},
)
.with_mapping(
"hello-world-file.json",
{
"request": {"method": "GET", "url": "/hello2"},
"response": {"status": 200, "bodyFileName": "hello.json"},
},
)
.with_mapping(
"hello-world-transformer.json",
{
"request": {"method": "POST", "url": "/hello3"},
"response": {
"status": 201,
"headers": {"content-type": "application/json"},
"jsonBody": {"message": "Hello, $(name)!"},
"transformers": ["json-body-transformer"],
},
},
)
.with_file("hello.json", {"message": "Hello World !"})
.with_cli_arg("--verbose", "")
.with_cli_arg("--root-dir", "/home/wiremock")
.with_extension(
"com.ninecookies.wiremock.extensions.JsonBodyTransformer",
Path("extensions/wiremock-body-transformer-1.1.3.jar"),
)
.with_env("JAVA_OPTS", "-Djava.net.preferIPv4Stack=true")
)
with wm:
resp1 = requests.get(wm.get_url("/hello"))
resp2 = requests.get(wm.get_url("/hello2"))
resp3 = requests.post(wm.get_url("/hello3"), json={"name": "John Doe"})
assert resp1.status_code == 200
assert resp1.content == b"hello"
assert resp2.status_code == 200
assert resp2.content == b'{"message": "Hello World !"}'
assert resp3.status_code == 201
assert resp3.content == b'{"message": "Hello, John Doe"}'
148 changes: 136 additions & 12 deletions wiremock/testing/testcontainer.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,169 @@
import json
import os
import socket
import urllib
import tarfile
import tempfile
import urllib.request
from contextlib import contextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import Any, TypedDict
from urllib.parse import urljoin

import docker
import requests
from testcontainers.core.container import DockerContainer
from testcontainers.core.exceptions import ContainerStartException
from testcontainers.core.waiting_utils import (wait_container_is_ready,
wait_for_logs)
from testcontainers.core.waiting_utils import wait_container_is_ready


class WireMockContainer(DockerContainer):
"""
Wiremock container.
"""

DEFAULT_IMAGE_NAME: str = "wiremock/wiremock"
DEFAULT_TAG: str = "latest"
MAPPINGS_DIR: str = "/home/wiremock/mappings/"
FILES_DIR: str = "/home/wiremock/__files/"
EXTENSIONS_DIR: str = "/var/wiremock/extensions/"

def __init__(
self, image: str = "wiremock/wiremock", port: int = 8080, **kwargs
) -> None:
self.port = port
super(WireMockContainer, self).__init__(image, **kwargs)
self.with_bind_ports(8080, 8080)
self.with_volume_mapping("/var/run/docker.sock", "/var/run/docker.sock")
self.with_env("JAVA_OPTS", "-Djava.net.preferIPv4Stack=true")
self.wire_mock_args: list[str] = []
self.mapping_stubs: dict[str, str] = {}
self.mapping_files: dict[str, str] = {}
self.extensions: dict[str, bytes] = {}

def with_env(self, key: str, value: str) -> "WireMockContainer":
super().with_env(key, value)
return self

def with_exposed_ports(self, *ports) -> "WireMockContainer":
super().with_exposed_ports(*ports)
return self

def with_cli_arg(self, arg_name: str, arg_value: str) -> "WireMockContainer":
self.wire_mock_args.append(arg_name)
self.wire_mock_args.append(arg_value)
return self

def with_mapping(self, name: str, data: dict[str, Any]) -> "WireMockContainer":
self.mapping_stubs[name] = json.dumps(data)
return self

def with_file(self, name: str, data: dict[str, Any]):
self.mapping_files[name] = json.dumps(data)
return self

def with_extension(self, class_name: str, jar_path: Path) -> "WireMockContainer":

with open(jar_path, "rb") as fp:
self.extensions[class_name] = fp.read()

return self

def copy_file_to_container(self, host_path: Path, container_path: Path) -> None:
with open(host_path, "rb") as fp:
self.get_wrapped_container().put_archive(
path=container_path, data=fp.read()
)

def copy_files_to_container(
self, configs: dict[str, Any], container_dir_path: Path, mode: str = "w+"
) -> None:

temp_dir = tempfile.mkdtemp()

# generate temp files all config files
for config_name, config_content in configs.items():
file_name = os.path.basename(config_name)
destination_path = os.path.join(temp_dir, file_name)
with open(destination_path, mode) as fp:
fp.write(config_content)

# tar all files from temp dir
tarfile_path = f"{temp_dir}.tar.gz"
with tarfile.open(tarfile_path, "w:gz") as tar:
for root, _, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, temp_dir)
tar.add(file_path, arcname=arcname)

# copy tar archive onto container and extract at {container_dir_path}
self.copy_file_to_container(
host_path=Path(tarfile_path), container_path=container_dir_path
)

def copy_mappings_to_container(self) -> None:
"""Copies all mappings files generated with
`.with_mapping('hello-world.json', {...})` to the container under
the configured MAPPINGS_DIR
"""

self.copy_files_to_container(
configs=self.mapping_stubs, container_dir_path=Path(f"{self.MAPPINGS_DIR}")
)

def copy_mapping_files_to_container(self) -> None:
"""Copies all mappings files generated with
`.with_file('hello.json', {...})` to the container under
the configured FILES_DIR
"""
self.copy_files_to_container(
configs=self.mapping_files, container_dir_path=Path(f"{self.FILES_DIR}")
)

def copy_extensions_to_container(self) -> None:
"""Copies all extension jar files generated with :meth:`.with_extension`
to the container under the configured EXTENSIONS_DIR

..code-block::
wm.with_extension(
"com.ninecookies.wiremock.extensions.JsonBodyTransformer",
Path("extensions/wiremock-body-transformer-1.1.3.jar"
)
"""
self.exec("mkdir -p /var/wiremock/extensions")
self.copy_files_to_container(
configs=self.extensions,
container_dir_path=Path(f"{self.EXTENSIONS_DIR}"),
mode="wb+",
)

@wait_container_is_ready()
def _connect(self) -> None:
res = urllib.request.urlopen(f"{self.get_url()}/__admin/mappings")
def configure(self) -> None:
res = urllib.request.urlopen(self.get_url("__admin/mappings"))
if res.status != 200:
raise Exception()

def get_url(self) -> str:
host = self.get_container_host_ip()
port = self.get_exposed_port(self.port)
return f"http://{host}:{port}"
self.copy_mappings_to_container()
self.copy_mapping_files_to_container()
self.copy_extensions_to_container()

requests.post(self.get_url("__admin/mappings/reset"))

def get_base_url(self) -> str:
return (
f"http://{self.get_container_host_ip()}:{self.get_exposed_port(self.port)}"
)

def get_url(self, path: str) -> str:
return urljoin(self.get_base_url(), path)

def start(self) -> "WireMockContainer":
# TODO this causes the container to hang
# if self.extensions.keys():
# self.with_cli_arg("--extensions", ",".join(self.extensions.keys()))
cmd = " ".join(self.wire_mock_args)
self.with_command(cmd)
super().start()
self._connect()
self.configure()
return self


Expand Down