diff --git a/.gitignore b/.gitignore
index 088663472..dec639ce6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,10 +24,14 @@ vendor/
scratch
config.yml
!playground/config.yml
+*.pyc
# Project exclusion
site
venv
.cache
# Binaries
-cmd/greenmask/greenmask
\ No newline at end of file
+cmd/greenmask/greenmask
+
+# Allure artifacts
+allure-results/
\ No newline at end of file
diff --git a/.run/v1-list-dumps.run.xml b/.run/v1-list-dumps.run.xml
new file mode 100644
index 000000000..7d99c0dee
--- /dev/null
+++ b/.run/v1-list-dumps.run.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/v1/playground/mysql/run.sh b/v1/playground/mysql/run.sh
new file mode 100755
index 000000000..2568c1b15
--- /dev/null
+++ b/v1/playground/mysql/run.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+echo "alias mysql='mysql --user $MYSQL_USER --database $ORIGINAL_DB_NAME --host $ORIGINAL_DB_HOST --port $ORIGINAL_DB_PORT'" >> ~/.bashrc
+echo "alias mysql_o='mysql --user $MYSQL_USER --database $ORIGINAL_DB_NAME --host $ORIGINAL_DB_HOST --port $ORIGINAL_DB_PORT'" >> ~/.bashrc
+echo "alias mysql_t='mysql --user $MYSQL_USER --database $TRANSFORMED_DB_NAME --host $TRANSFORMED_DB_HOST --port $TRANSFORMED_DB_PORT'" >> ~/.bashrc
+echo "alias cleanup='/var/lib/playground/cleanup.sh'" >> ~/.bashrc
+bash
diff --git a/v1/tests/integration/README.md b/v1/tests/integration/README.md
new file mode 100644
index 000000000..704e0a84b
--- /dev/null
+++ b/v1/tests/integration/README.md
@@ -0,0 +1,41 @@
+# Integration Tests
+
+This directory contains Python-based integration tests for Greenmask using `pytest` and `allure`.
+
+## Prerequisites
+
+- Python 3.11+
+- Poetry (for dependency management)
+- Docker & Docker Compose (for running Greenmask services)
+
+## Setup
+
+1. Install dependencies:
+ ```bash
+ poetry install
+ ```
+
+2. (Optional) Run Greenmask environment:
+ Ensure your `greenmask` service or target environment is running.
+ Export the service URL if different from default:
+ ```bash
+ export GREENMASK_URL=http://localhost:8080
+ ```
+
+## Running Tests
+
+Run tests with Allure (report data generated in `allure-results`):
+```bash
+poetry run pytest --alluredir=./allure-results
+```
+
+To view the Allure report:
+```bash
+poetry run allure serve ./allure-results
+```
+
+## Structure
+
+- `tests/`: Contains test files.
+- `tests/conftest.py`: Fixtures (async client, configuration).
+- `pyproject.toml`: Dependency and tool configuration.
diff --git a/v1/tests/integration/integration.iml b/v1/tests/integration/integration.iml
new file mode 100644
index 000000000..b2a201522
--- /dev/null
+++ b/v1/tests/integration/integration.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/v1/tests/integration/poetry.lock b/v1/tests/integration/poetry.lock
new file mode 100644
index 000000000..20d228f99
--- /dev/null
+++ b/v1/tests/integration/poetry.lock
@@ -0,0 +1,159 @@
+# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
+
+[[package]]
+name = "allure-pytest"
+version = "2.15.2"
+description = "Allure pytest integration"
+optional = false
+python-versions = "*"
+groups = ["main"]
+files = [
+ {file = "allure_pytest-2.15.2-py3-none-any.whl", hash = "sha256:9b472baa93b2860cad0eb03fced4091e6a75c6d822a1ec5b92fec5a403c84e10"},
+ {file = "allure_pytest-2.15.2.tar.gz", hash = "sha256:6330b09df4dfec04296f1512ddb0be92e1a4741c36ba84e0388b96d327ac5f55"},
+]
+
+[package.dependencies]
+allure-python-commons = "2.15.2"
+pytest = ">=4.5.0"
+
+[[package]]
+name = "allure-python-commons"
+version = "2.15.2"
+description = "Contains the API for end users as well as helper functions and classes to build Allure adapters for Python test frameworks"
+optional = false
+python-versions = ">=3.6"
+groups = ["main"]
+files = [
+ {file = "allure_python_commons-2.15.2-py3-none-any.whl", hash = "sha256:d5dd1b56d978d1a1e3f8dd4efd15037add1c457cfb6d0445695dffde6f196446"},
+ {file = "allure_python_commons-2.15.2.tar.gz", hash = "sha256:19ef4a48bc6d30db484ce95dba74b639fcf87084746aac39d265ab3a1cc70e89"},
+]
+
+[package.dependencies]
+attrs = ">=16.0.0"
+pluggy = ">=0.4.0"
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+description = "Classes Without Boilerplate"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"},
+ {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"},
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"},
+ {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"},
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["coverage", "pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
+ {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"},
+ {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"},
+]
+
+[package.dependencies]
+colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""}
+iniconfig = ">=1"
+packaging = ">=20"
+pluggy = ">=1.5,<2"
+pygments = ">=2.7.2"
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.23.8"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2"},
+ {file = "pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3"},
+]
+
+[package.dependencies]
+pytest = ">=7.0.0,<9"
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[metadata]
+lock-version = "2.1"
+python-versions = "^3.11"
+content-hash = "8814ecbe641256224197d234e79f071f259a8c1eb7e5fb7fc90d9ac8b9e1e541"
diff --git a/v1/tests/integration/pyproject.toml b/v1/tests/integration/pyproject.toml
new file mode 100644
index 000000000..9effb6f00
--- /dev/null
+++ b/v1/tests/integration/pyproject.toml
@@ -0,0 +1,20 @@
+[tool.poetry]
+name = "integration"
+version = "0.1.0"
+description = "Integration tests for Greenmask"
+authors = ["Greenmask Team"]
+readme = "README.md"
+package-mode = false
+
+[tool.poetry.dependencies]
+python = "^3.11"
+pytest = "^8.0.0"
+pytest-asyncio = "^0.23.0"
+allure-pytest = "^2.13.0"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
diff --git a/v1/tests/integration/resources/__init__.py b/v1/tests/integration/resources/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/v1/tests/integration/resources/config.yaml b/v1/tests/integration/resources/config.yaml
new file mode 100644
index 000000000..608208b4e
--- /dev/null
+++ b/v1/tests/integration/resources/config.yaml
@@ -0,0 +1,37 @@
+# Copyright 2025 Greenmask
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+log:
+ level: "debug"
+ format: "text"
+
+storage:
+ type: "directory"
+ directory:
+ path: "/tmp/greenmask_backups"
+
+dump:
+ transformation:
+ - schema: "public"
+ name: "users"
+ transformers:
+ - name: "RandomPerson"
+ params:
+ gender: "Any"
+ columns:
+ - name: "username"
+ template: "{{ .FirstName }}"
+ engine: "random"
+
+# engine: postgresql # Commenting out to see if it fixes root key error
diff --git a/v1/tests/integration/tests/conftest.py b/v1/tests/integration/tests/conftest.py
new file mode 100644
index 000000000..f51e5aa02
--- /dev/null
+++ b/v1/tests/integration/tests/conftest.py
@@ -0,0 +1,75 @@
+import pytest
+import os
+import subprocess
+import shutil
+import logging
+import allure
+
+logger = logging.getLogger(__name__)
+
+@pytest.fixture(scope="session")
+def greenmask_bin():
+ """Locates the greenmask binary.
+ Prioritizes GREENMASK_BIN env var.
+ """
+ bin_path = os.getenv("GREENMASK_BIN")
+ if bin_path:
+ if not os.path.isfile(bin_path):
+ pytest.fail(f"GREENMASK_BIN is set to {bin_path} but it does not exist.")
+ return bin_path
+
+ # Assumes greenmask is in PATH if not specified
+ bin_path = shutil.which("greenmask")
+ if not bin_path:
+ pytest.skip("greenmask binary not found in PATH and GREENMASK_BIN is not set.")
+ return bin_path
+
+@pytest.fixture
+def greenmask_config(request, tmp_path):
+ """Sets up the configuration environment.
+ Copies the default config to a temp dir to allow tests to modify it if needed,
+ or just returns a path to a static config.
+ """
+ # For now, let's just return the path to the resources config
+ # In the future, we might want to copy it to tmp_path
+ base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ config_path = os.path.join(base_dir, "resources", "config.yaml")
+
+ if not os.path.exists(config_path):
+ pytest.logger.warning(f"Config file not found at {config_path}")
+
+ return config_path
+
+@pytest.fixture
+def greenmask_cmd(greenmask_bin, greenmask_config):
+ """Executes greenmask commands.
+ """
+ @allure.step("Run greenmask {args}")
+ def _run_greenmask(args, env=None, check=True):
+ cmd = [greenmask_bin, "--config", greenmask_config] + args
+
+ full_env = os.environ.copy()
+ if env:
+ full_env.update(env)
+
+ logger.info(f"Running command: {' '.join(cmd)}")
+ result = subprocess.run(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True,
+ env=full_env,
+ )
+
+ if check and result.returncode != 0:
+ logger.error(f"Command failed with return code {result.returncode}")
+ logger.error(f"STDOUT: {result.stdout}")
+ logger.error(f"STDERR: {result.stderr}")
+ allure.attach(result.stdout, name="stdout", attachment_type=allure.attachment_type.TEXT)
+ allure.attach(result.stderr, name="stderr", attachment_type=allure.attachment_type.TEXT)
+ pytest.fail(f"greenmask command failed: {result.stderr}")
+
+ allure.attach(result.stdout, name="stdout", attachment_type=allure.attachment_type.TEXT)
+ return result
+
+ return _run_greenmask
diff --git a/v1/tests/integration/tests/test_greenmask.py b/v1/tests/integration/tests/test_greenmask.py
new file mode 100644
index 000000000..c35a19604
--- /dev/null
+++ b/v1/tests/integration/tests/test_greenmask.py
@@ -0,0 +1,17 @@
+@allure.feature("Allure Integration")
+@allure.story("Report Generation")
+def test_allure_dummy():
+ """
+ Dummy test to verify Allure report generation.
+ """
+ with allure.step("Step 1"):
+ pass
+ with allure.step("Step 2"):
+ assert True
+ """
+ Dummy test to verify Allure report generation.
+ """
+ with allure.step("Step 1"):
+ pass
+ with allure.step("Step 2"):
+ assert True
diff --git a/v1/tests/integration/tests/test_skeleton.py b/v1/tests/integration/tests/test_skeleton.py
new file mode 100644
index 000000000..1fd649ad2
--- /dev/null
+++ b/v1/tests/integration/tests/test_skeleton.py
@@ -0,0 +1,22 @@
+import pytest
+import allure
+
+@allure.feature("CLI")
+@allure.story("Help Command")
+@allure.title("Verify greenmask help command")
+def test_greenmask_help(greenmask_cmd):
+ """Verifies that greenmask help command runs successfully.
+ """
+ result = greenmask_cmd(["--help"])
+ assert "Usage:" in result.stdout
+ assert result.returncode == 0
+
+@allure.feature("CLI")
+@allure.story("Transformers")
+@allure.title("Verify greenmask list-transformers command")
+def test_greenmask_list_transformers(greenmask_cmd):
+ """Verifies that greenmask list-transformers command runs successfully.
+ """
+ result = greenmask_cmd(["list-transformers"])
+ assert result.returncode == 0
+ assert "RandomPerson" in result.stdout