initial commit
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/esta_python_template/__init__.py
Normal file
0
tests/esta_python_template/__init__.py
Normal file
486
tests/esta_python_template/test_template.py
Normal file
486
tests/esta_python_template/test_template.py
Normal file
@@ -0,0 +1,486 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from types import TracebackType
|
||||
from typing import Any, Optional, Type
|
||||
|
||||
import tomli
|
||||
import yaml
|
||||
from copier import run_copy
|
||||
|
||||
|
||||
def _run_shell_command_in_dir(command: list[str], dir: str) -> tuple[bytes, bytes, int]:
|
||||
with subprocess.Popen(command, cwd=dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||
outs, errs = proc.communicate()
|
||||
returncode = proc.returncode
|
||||
return outs, errs, returncode
|
||||
|
||||
|
||||
class CopierRenderer:
|
||||
DEFAULT_PARAMETERS = {
|
||||
"project_name": "My Little Project",
|
||||
"author_first_name": "Twilight",
|
||||
"author_last_name": "Sparkle",
|
||||
"bitbucket_organization": "MY_LITTLE_BITBUCKET_ORG",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
temp_source_dir: tempfile.TemporaryDirectory,
|
||||
parameters: dict[str, str] | None = None,
|
||||
):
|
||||
self._temp_source_dir = temp_source_dir
|
||||
if parameters is None:
|
||||
self.parameters = {}
|
||||
else:
|
||||
self.parameters = parameters
|
||||
|
||||
def __enter__(self) -> CopierRenderer:
|
||||
"""Run when the context is entered. Starts the transaction."""
|
||||
self._temp_render_dir = tempfile.TemporaryDirectory()
|
||||
self.render_path = pathlib.Path(self._temp_render_dir.name)
|
||||
run_copy(
|
||||
src_path=self._temp_source_dir.name,
|
||||
dst_path=self.render_path,
|
||||
vcs_ref="HEAD",
|
||||
defaults=True,
|
||||
data=(CopierRenderer.DEFAULT_PARAMETERS | self.parameters),
|
||||
quiet=True,
|
||||
)
|
||||
return self
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_value: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> None:
|
||||
"""Run when the context is exited. Ends the transaction with one of the actions specified in the initializer."""
|
||||
self._temp_render_dir.cleanup()
|
||||
|
||||
def load_yaml(self, file_path: str) -> Any:
|
||||
return yaml.safe_load((self.render_path / file_path).read_text())
|
||||
|
||||
def load_toml(self, file_path: str) -> Any:
|
||||
return tomli.loads((self.render_path / file_path).read_text())
|
||||
|
||||
|
||||
class TestTemplate(unittest.TestCase):
|
||||
def setUp(self):
|
||||
source_dir = pathlib.Path(__file__).parent.parent.parent
|
||||
self.temp_source_dir = tempfile.TemporaryDirectory()
|
||||
shutil.copytree(str(source_dir), self.temp_source_dir.name, dirs_exist_ok=True)
|
||||
return super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
self.temp_source_dir.cleanup()
|
||||
return super().tearDown()
|
||||
|
||||
def test_dot_python_version(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir) as copier_renderer:
|
||||
self.assertEqual(
|
||||
"3.12\n",
|
||||
(copier_renderer.render_path / ".python-version").read_text(),
|
||||
)
|
||||
|
||||
def test_pyproject_toml(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir) as copier_renderer:
|
||||
pyproject = tomli.loads((copier_renderer.render_path / "pyproject.toml").read_text())
|
||||
|
||||
self.assertEqual(pyproject["project"]["name"], "sbb-my-little-project")
|
||||
self.assertEqual(
|
||||
pyproject["project"]["description"],
|
||||
"A project created with esta-python-template",
|
||||
)
|
||||
self.assertEqual(
|
||||
pyproject["project"]["authors"],
|
||||
[
|
||||
{
|
||||
"name": "Twilight Sparkle",
|
||||
"email": "twilight.sparkle@sbb.ch",
|
||||
}
|
||||
],
|
||||
)
|
||||
self.assertEqual(pyproject["project"]["requires-python"], "~= 3.12.0")
|
||||
self.assertEqual(
|
||||
pyproject["project"]["urls"]["repository"],
|
||||
"https://code.sbb.ch/projects/MY_LITTLE_BITBUCKET_ORG/repos/my-little-project",
|
||||
)
|
||||
self.assertEqual(
|
||||
pyproject["project"]["urls"]["documentation"],
|
||||
"https://code.sbb.ch/projects/MY_LITTLE_BITBUCKET_ORG/repos/my-little-project/browse/README.md",
|
||||
)
|
||||
self.assertEqual(
|
||||
pyproject["tool"]["poetry"]["packages"],
|
||||
[{"include": "my_little_project", "from": "src"}],
|
||||
)
|
||||
self.assertEqual(
|
||||
pyproject["project"]["scripts"],
|
||||
{"entrypoint": "my_little_project.main:cli"},
|
||||
)
|
||||
self.assertEqual(pyproject["tool"]["mypy"]["python_version"], "3.12")
|
||||
|
||||
def test_with_docker(self) -> None:
|
||||
with CopierRenderer(
|
||||
self.temp_source_dir,
|
||||
{"docker_repository": "esta.docker", "python_version": "3.9"},
|
||||
) as copier_renderer:
|
||||
dockerfile = copier_renderer.render_path / "Dockerfile"
|
||||
|
||||
# Dockerfile exists
|
||||
self.assertTrue(dockerfile.is_file())
|
||||
|
||||
# Dockerignore exists
|
||||
self.assertTrue((copier_renderer.render_path / ".dockerignore").is_file())
|
||||
|
||||
# FROM-instruction
|
||||
with dockerfile.open() as fh:
|
||||
self.assertEqual(
|
||||
fh.readline(),
|
||||
"FROM registry-redhat.docker.bin.sbb.ch/rhel9/python-39 AS base\n",
|
||||
)
|
||||
|
||||
# Tekton-Pipeline
|
||||
tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
|
||||
# Tekton-Docker section
|
||||
self.assertEqual(
|
||||
tekton_pipeline["docker"],
|
||||
{"artifactoryDockerRepo": "esta.docker", "caching": True},
|
||||
)
|
||||
continuous_build = tekton_pipeline["pipelines"][0]["build"]
|
||||
snapshot_build = tekton_pipeline["pipelines"][1]["build"]
|
||||
release_build = tekton_pipeline["pipelines"][2]["build"]
|
||||
|
||||
# Check build sections
|
||||
self.assertEqual(
|
||||
continuous_build,
|
||||
{
|
||||
"sonarScan": {"enabled": True},
|
||||
"owaspDependencyCheck": {
|
||||
"enabled": True,
|
||||
"additionalParams": "--suppression dependency-check-suppressions.xml --disablePyDist --disablePyPkg --failOnCVSS 9",
|
||||
},
|
||||
"failOnQualityGateFailure": True,
|
||||
"buildDockerImage": True,
|
||||
"deployDockerImage": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
snapshot_build,
|
||||
{
|
||||
"sonarScan": {"enabled": True},
|
||||
"owaspDependencyCheck": {
|
||||
"enabled": True,
|
||||
"additionalParams": "--suppression dependency-check-suppressions.xml --disablePyDist --disablePyPkg --failOnCVSS 9",
|
||||
},
|
||||
"failOnQualityGateFailure": True,
|
||||
"buildDockerImage": True,
|
||||
"deployDockerImage": True,
|
||||
"deployArtifacts": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
release_build,
|
||||
{
|
||||
"sonarScan": {"enabled": True},
|
||||
"owaspDependencyCheck": {
|
||||
"enabled": True,
|
||||
"additionalParams": "--suppression dependency-check-suppressions.xml --disablePyDist --disablePyPkg --failOnCVSS 9",
|
||||
},
|
||||
"failOnQualityGateFailure": True,
|
||||
"buildDockerImage": True,
|
||||
"deployArtifacts": True,
|
||||
"additionalDockerImageTags": ["latest"],
|
||||
},
|
||||
)
|
||||
|
||||
def test_with_docker_default_python_version(self) -> None:
|
||||
with CopierRenderer(
|
||||
self.temp_source_dir,
|
||||
{"docker_repository": "esta.docker"}, # By not specifying a Python version, the default is taken
|
||||
) as copier_renderer:
|
||||
dockerfile = copier_renderer.render_path / "Dockerfile"
|
||||
|
||||
# Dockerfile exists
|
||||
self.assertTrue(dockerfile.is_file())
|
||||
|
||||
# Dockerignore exists
|
||||
self.assertTrue((copier_renderer.render_path / ".dockerignore").is_file())
|
||||
|
||||
# FROM-instruction
|
||||
with dockerfile.open() as fh:
|
||||
self.assertEqual(
|
||||
fh.readline(),
|
||||
"FROM registry-redhat.docker.bin.sbb.ch/rhel9/python-312 AS base\n",
|
||||
)
|
||||
|
||||
def test_without_docker(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir, {"docker_repository": "", "python_version": "3.12"}) as copier_renderer:
|
||||
# Dockerfile does not exist
|
||||
self.assertFalse((copier_renderer.render_path / "Dockerfile").exists())
|
||||
|
||||
# Dockerignore does not exist
|
||||
self.assertFalse((copier_renderer.render_path / ".dockerignore").exists())
|
||||
|
||||
# Tekton-Pipeline
|
||||
tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
|
||||
# Tekton-Docker section
|
||||
self.assertNotIn("docker", tekton_pipeline)
|
||||
|
||||
# Check build sections
|
||||
for i, pipeline in enumerate(["continuous", "snapshot", "release"]):
|
||||
with self.subTest(msg=f"Checking pipeline: '{pipeline}'."):
|
||||
self.assertEqual(
|
||||
tekton_pipeline["pipelines"][i]["build"],
|
||||
{
|
||||
"sonarScan": {"enabled": True},
|
||||
"owaspDependencyCheck": {
|
||||
"enabled": True,
|
||||
"additionalParams": "--suppression dependency-check-suppressions.xml --disablePyDist --disablePyPkg --failOnCVSS 9",
|
||||
},
|
||||
"failOnQualityGateFailure": True,
|
||||
},
|
||||
)
|
||||
|
||||
def test_with_pypi(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir, {"pypi_repository": "esta.pypi"}) as copier_renderer:
|
||||
# Check Tekton-Pipeline
|
||||
tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
self.assertEqual(tekton_pipeline["python"], {"targetRepo": "esta.pypi"})
|
||||
|
||||
def test_without_pypi(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir, {"pypi_repository": ""}) as copier_renderer:
|
||||
# Check Tekton-Pipeline
|
||||
tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
self.assertEqual(tekton_pipeline["python"], {})
|
||||
|
||||
def test_pre_commit_hooks_in_template(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir) as copier_renderer:
|
||||
outs, errs, _ = _run_shell_command_in_dir(["git", "init"], dir=str(copier_renderer.render_path))
|
||||
outs, errs, _ = _run_shell_command_in_dir(["git", "add", "--all"], dir=str(copier_renderer.render_path))
|
||||
|
||||
commands = [
|
||||
["make"],
|
||||
["poetry", "run", "pre-commit", "run", "--all-files"],
|
||||
]
|
||||
|
||||
for command in commands:
|
||||
with self.subTest(msg=f"Running {command=}."):
|
||||
stdout, stderr, returncode = _run_shell_command_in_dir(command=command, dir=str(copier_renderer.render_path))
|
||||
self.assertEqual(
|
||||
returncode,
|
||||
0,
|
||||
msg=f"\nstdout:\n{stdout.decode()}\nstderr:\n{stderr.decode()}.",
|
||||
)
|
||||
|
||||
def test_directory_names(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir, {"project_name": "Funky Grogu"}) as copier_renderer:
|
||||
self.assertTrue((copier_renderer.render_path / "src" / "funky_grogu").is_dir())
|
||||
self.assertTrue((copier_renderer.render_path / "tests" / "funky_grogu").is_dir())
|
||||
|
||||
def test_poetry_toml(self) -> None:
|
||||
with CopierRenderer(self.temp_source_dir, {"pypi_repository": "funky-grogu.pypi"}) as copier_renderer:
|
||||
poetry = tomli.loads((copier_renderer.render_path / "poetry.toml").read_text())
|
||||
|
||||
self.assertEqual(
|
||||
poetry["repositories"]["artifactory"]["url"],
|
||||
"https://bin.sbb.ch/artifactory/api/pypi/funky-grogu.pypi",
|
||||
)
|
||||
|
||||
def test_with_helm(self) -> None:
|
||||
with CopierRenderer(
|
||||
self.temp_source_dir,
|
||||
{
|
||||
"name": "my-project",
|
||||
"description": "My project description",
|
||||
"helm_repository": "esta.helm.local",
|
||||
},
|
||||
) as copier_renderer:
|
||||
for dir_path in [
|
||||
"charts",
|
||||
"charts/my-project",
|
||||
"charts/my-project/templates",
|
||||
]:
|
||||
self.assertTrue((copier_renderer.render_path / dir_path).is_dir())
|
||||
|
||||
for file_path in [
|
||||
"charts/my-project/.helmignore",
|
||||
"charts/my-project/Chart.yaml",
|
||||
"charts/my-project/values.yaml",
|
||||
]:
|
||||
self.assertTrue((copier_renderer.render_path / file_path).is_file())
|
||||
|
||||
chart_yaml = copier_renderer.load_yaml("charts/my-project/Chart.yaml")
|
||||
|
||||
self.assertEqual(
|
||||
chart_yaml,
|
||||
{
|
||||
"apiVersion": "v2",
|
||||
"description": "My project description",
|
||||
"icon": "http://acme.org/replaceme.jpg",
|
||||
"name": "my-project",
|
||||
"type": "application",
|
||||
"version": "0.0.0",
|
||||
},
|
||||
)
|
||||
|
||||
# Tekton-Pipeline
|
||||
tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
|
||||
self.assertEqual(
|
||||
tekton_pipeline["helm"],
|
||||
{"chartRepository": "esta.helm.local", "linting": True},
|
||||
)
|
||||
release_build = tekton_pipeline["pipelines"][2]["build"]
|
||||
|
||||
self.assertEqual(
|
||||
release_build,
|
||||
{
|
||||
"sonarScan": {"enabled": True},
|
||||
"owaspDependencyCheck": {
|
||||
"enabled": True,
|
||||
"additionalParams": "--suppression dependency-check-suppressions.xml --disablePyDist --disablePyPkg --failOnCVSS 9",
|
||||
},
|
||||
"failOnQualityGateFailure": True,
|
||||
"packageAndDeployHelmChart": True,
|
||||
},
|
||||
)
|
||||
|
||||
def test_without_helm(self) -> None:
|
||||
with CopierRenderer(
|
||||
self.temp_source_dir,
|
||||
{
|
||||
"name": "my-project",
|
||||
"description": "My project description",
|
||||
"helm_repository": "",
|
||||
},
|
||||
) as copier_renderer:
|
||||
self.assertFalse((copier_renderer.render_path / "charts").exists())
|
||||
|
||||
# Tekton-Pipeline
|
||||
tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
|
||||
self.assertNotIn("helm", tekton_pipeline)
|
||||
|
||||
release_build = tekton_pipeline["pipelines"][2]["build"]
|
||||
|
||||
self.assertEqual(
|
||||
release_build,
|
||||
{
|
||||
"sonarScan": {"enabled": True},
|
||||
"owaspDependencyCheck": {
|
||||
"enabled": True,
|
||||
"additionalParams": "--suppression dependency-check-suppressions.xml --disablePyDist --disablePyPkg --failOnCVSS 9",
|
||||
},
|
||||
"failOnQualityGateFailure": True,
|
||||
},
|
||||
)
|
||||
|
||||
def test_without_ggshield(self) -> None:
|
||||
with CopierRenderer(
|
||||
self.temp_source_dir,
|
||||
{
|
||||
"name": "my-project",
|
||||
"description": "My project description",
|
||||
"use_ggshield": "False",
|
||||
},
|
||||
) as copier_renderer:
|
||||
# .pre-commit-config.yaml
|
||||
pre_commit_config = copier_renderer.load_yaml(".pre-commit-config.yaml")
|
||||
|
||||
hook_ids = [h["id"] for h in pre_commit_config["repos"][0]["hooks"]]
|
||||
self.assertNotIn("ggshield", hook_ids)
|
||||
|
||||
# estaTektonPipeline.yaml
|
||||
esta_tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
|
||||
for pipeline in esta_tekton_pipeline["pipelines"]:
|
||||
self.assertNotIn("gitguardian", pipeline["build"])
|
||||
|
||||
# .env.example
|
||||
env_example = (copier_renderer.render_path / ".env.example").read_text()
|
||||
self.assertNotIn("GITGUARDIAN_API_KEY", env_example)
|
||||
|
||||
# Makefile
|
||||
makefile = (copier_renderer.render_path / "Makefile").read_text()
|
||||
self.assertNotIn("ggshield_has_key", makefile)
|
||||
|
||||
# pyproject.toml
|
||||
pyproject_toml = (copier_renderer.render_path / "pyproject.toml").read_text()
|
||||
self.assertNotIn("ggshield", pyproject_toml)
|
||||
|
||||
# README.md
|
||||
readme_md = (copier_renderer.render_path / "README.md").read_text()
|
||||
self.assertNotIn("GitGuardian", readme_md)
|
||||
|
||||
def test_with_ggshield(self) -> None:
|
||||
with CopierRenderer(
|
||||
self.temp_source_dir,
|
||||
{
|
||||
"name": "my-project",
|
||||
"description": "My project description",
|
||||
"use_ggshield": "True",
|
||||
},
|
||||
) as copier_renderer:
|
||||
# .pre-commit-config.yaml
|
||||
pre_commit_config = copier_renderer.load_yaml(".pre-commit-config.yaml")
|
||||
|
||||
self.assertIn(
|
||||
{
|
||||
"id": "ggshield",
|
||||
"name": "ggshield",
|
||||
"entry": "bash",
|
||||
"description": "Runs ggshield to detect hardcoded secrets, security vulnerabilities and policy breaks.",
|
||||
"stages": ["pre-commit"],
|
||||
"args": ["-c", '[ -n "$CI" ] || ggshield secret scan pre-commit'],
|
||||
"language": "system",
|
||||
"pass_filenames": True,
|
||||
},
|
||||
pre_commit_config["repos"][0]["hooks"],
|
||||
)
|
||||
|
||||
# estaTektonPipeline.yaml
|
||||
esta_tekton_pipeline = copier_renderer.load_yaml("estaTektonPipeline.yaml")
|
||||
for pipeline in esta_tekton_pipeline["pipelines"]:
|
||||
self.assertEqual(
|
||||
{"enabled": True, "reportmode": "FAILED"},
|
||||
pipeline["build"]["gitguardian"],
|
||||
)
|
||||
|
||||
# .env.example
|
||||
env_example_lines = (copier_renderer.render_path / ".env.example").read_text().splitlines()
|
||||
for line in [
|
||||
"# Template for GGShield",
|
||||
'GITGUARDIAN_API_KEY=""',
|
||||
'GITGUARDIAN_INSTANCE="https://gitguardian.sbb.ch"',
|
||||
]:
|
||||
self.assertIn(line, env_example_lines)
|
||||
|
||||
# Makefile
|
||||
makefile = (copier_renderer.render_path / "Makefile").read_text()
|
||||
self.assertIn(
|
||||
"""
|
||||
ggshield_has_key:
|
||||
ifeq ($(GITGUARDIAN_API_KEY),)
|
||||
$(warning No API-Key for GitGuardian was set!)
|
||||
endif
|
||||
""",
|
||||
makefile,
|
||||
)
|
||||
|
||||
# pyproject.toml
|
||||
pyproject_toml = copier_renderer.load_toml("pyproject.toml")
|
||||
self.assertIn(
|
||||
"ggshield",
|
||||
pyproject_toml["tool"]["poetry"]["group"]["dev"]["dependencies"],
|
||||
)
|
||||
|
||||
# README.md
|
||||
readme_md = (copier_renderer.render_path / "README.md").read_text()
|
||||
self.assertIn("### Setup GGShield (GitGuardian)", readme_md)
|
||||
Reference in New Issue
Block a user