Compare commits
19 Commits
2025.10.19
...
test/core-
| Author | SHA1 | Date | |
|---|---|---|---|
| ceb3957f0c | |||
| 27205022a5 | |||
| 82055ca6b5 | |||
| cdca5614e8 | |||
| ca58f636ee | |||
| bcca7090d3 | |||
| 8aeb381a91 | |||
| 9cf5886902 | |||
| d60b281d6a | |||
| 6eae7c7b98 | |||
| 638434af76 | |||
| c18777656c | |||
| c8de1284fb | |||
| 17408d5cd2 | |||
| cc76227199 | |||
| dfbb0a8c1f | |||
| 472aba5990 | |||
| 9c75008c9f | |||
| df2c2b5b56 |
@@ -10,30 +10,72 @@ jobs:
|
||||
uses: ./.gitea/workflows/prepare.yaml
|
||||
secrets: inherit
|
||||
|
||||
api:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, application, auth, core, dependency ]
|
||||
with:
|
||||
working_directory: src/api
|
||||
secrets: inherit
|
||||
|
||||
application:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core, dependency ]
|
||||
with:
|
||||
working_directory: src/application
|
||||
secrets: inherit
|
||||
|
||||
auth:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core, dependency, database ]
|
||||
with:
|
||||
working_directory: src/auth
|
||||
secrets: inherit
|
||||
|
||||
cli:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/cli
|
||||
secrets: inherit
|
||||
|
||||
core:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [prepare]
|
||||
with:
|
||||
working_directory: src/cpl-core
|
||||
working_directory: src/core
|
||||
secrets: inherit
|
||||
|
||||
database:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core, dependency ]
|
||||
with:
|
||||
working_directory: src/database
|
||||
secrets: inherit
|
||||
|
||||
dependency:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/dependency
|
||||
secrets: inherit
|
||||
|
||||
mail:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core, dependency ]
|
||||
with:
|
||||
working_directory: src/mail
|
||||
secrets: inherit
|
||||
|
||||
query:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [prepare]
|
||||
with:
|
||||
working_directory: src/cpl-query
|
||||
working_directory: src/query
|
||||
secrets: inherit
|
||||
|
||||
translation:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
needs: [ prepare, core, dependency ]
|
||||
with:
|
||||
working_directory: src/cpl-translation
|
||||
secrets: inherit
|
||||
|
||||
mail:
|
||||
uses: ./.gitea/workflows/package.yaml
|
||||
needs: [ prepare, core ]
|
||||
with:
|
||||
working_directory: src/cpl-mail
|
||||
working_directory: src/translation
|
||||
secrets: inherit
|
||||
@@ -23,4 +23,28 @@ jobs:
|
||||
run: python3.12 -m pip install black
|
||||
|
||||
- name: Checking black
|
||||
run: python3.12 -m black src --check
|
||||
run: python3.12 -m black src --check
|
||||
|
||||
test:
|
||||
runs-on: [ runner ]
|
||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: https://github.com/actions/checkout@v3
|
||||
with:
|
||||
token: ${{ secrets.CI_ACCESS_TOKEN }}
|
||||
|
||||
- name: Setting up Python 3.12
|
||||
shell: bash
|
||||
run: |
|
||||
python3.12 -m venv venv
|
||||
source venv/bin/activate
|
||||
bash ./install.sh
|
||||
bash ./install.sh -dev
|
||||
python3.12 -m pip install pytest
|
||||
|
||||
- name: Testing with pytest
|
||||
shell: bash
|
||||
run: |
|
||||
source venv/bin/activate
|
||||
python3.12 -m pytest
|
||||
@@ -3,6 +3,7 @@
|
||||
"projects": [
|
||||
"src/cli/cpl.project.json",
|
||||
"src/core/cpl.project.json",
|
||||
"src/mail/cpl.project.json",
|
||||
"test/cpl.project.json"
|
||||
],
|
||||
"defaultProject": "cpl-cli",
|
||||
|
||||
@@ -75,9 +75,3 @@ class Application(ApplicationABC):
|
||||
test_settings1 = Configuration.get(TestSettings)
|
||||
Console.write_line(test_settings1.value)
|
||||
# self.test_send_mail()
|
||||
|
||||
x = 0
|
||||
while x < 500:
|
||||
Console.write_line("Running...")
|
||||
x += 1
|
||||
await asyncio.sleep(5)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from application import Application
|
||||
from cpl.application import ApplicationBuilder
|
||||
from cpl.core.console import Console
|
||||
from test_extension import TestExtension
|
||||
from startup import Startup
|
||||
from test_startup_extension import TestStartupExtension
|
||||
|
||||
|
||||
def main():
|
||||
Console.write_line("\n\n--- Application Starting ---\n")
|
||||
app_builder = ApplicationBuilder(Application)
|
||||
app_builder.with_startup(Startup)
|
||||
app_builder.with_extension(TestStartupExtension)
|
||||
|
||||
43
install.sh
43
install.sh
@@ -1,61 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Find and combine requirements from src/*/requirements.txt,
|
||||
# filtering out lines whose *package name* starts with "cpl-".
|
||||
# Works with pinned versions, extras, markers, editable installs, and VCS refs.
|
||||
# Optionaler Dev-Installationsmodus
|
||||
dev_mode=false
|
||||
if [[ "${1:-}" == "-dev" ]]; then
|
||||
dev_mode=true
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
req_files=(src/*/requirements.txt)
|
||||
# Wähle die passende Requirements-Datei
|
||||
pattern='requirements.txt'
|
||||
msg_hint='src/*/requirements.txt'
|
||||
if $dev_mode; then
|
||||
pattern='requirements.dev.txt'
|
||||
msg_hint='src/*/requirements.dev.txt'
|
||||
fi
|
||||
|
||||
req_files=(src/*/"$pattern")
|
||||
if ((${#req_files[@]} == 0)); then
|
||||
echo "No requirements files found at src/*/requirements.txt" >&2
|
||||
echo "Keine Requirements-Dateien gefunden unter '$msg_hint'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp_combined="$(mktemp)"
|
||||
trap 'rm -f "$tmp_combined"' EXIT
|
||||
|
||||
# Concatenate, trim comments/whitespace, filter out cpl-* packages, dedupe.
|
||||
# We keep non-package options/flags/constraints as-is.
|
||||
# Kombiniere, filtere Kommentare/Whitespace, entferne cpl-*, dedupliziere.
|
||||
awk '
|
||||
function trim(s){ sub(/^[[:space:]]+/,"",s); sub(/[[:space:]]+$/,"",s); return s }
|
||||
|
||||
{
|
||||
line=$0
|
||||
# drop full-line comments and strip inline comments
|
||||
if (line ~ /^[[:space:]]*#/) next
|
||||
sub(/#[^!].*$/,"",line) # strip trailing comment (simple heuristic)
|
||||
sub(/#[^!].*$/,"",line)
|
||||
line=trim(line)
|
||||
if (line == "") next
|
||||
|
||||
# Determine the package *name* even for "-e", extras, pins, markers, or VCS "@"
|
||||
e = line
|
||||
sub(/^-e[[:space:]]+/,"",e) # remove editable prefix
|
||||
# Tokenize up to the first of these separators: space, [ < > = ! ~ ; @
|
||||
sub(/^-e[[:space:]]+/,"",e)
|
||||
token = e
|
||||
sub(/\[.*/,"",token) # remove extras quickly
|
||||
sub(/\[.*/,"",token)
|
||||
n = split(token, a, /[<>=!~;@[:space:]]/)
|
||||
name = tolower(a[1])
|
||||
|
||||
# If the first token (name) starts with "cpl-", skip this requirement
|
||||
if (name ~ /^cpl-/) next
|
||||
|
||||
print line
|
||||
}
|
||||
' "${req_files[@]}" | sort -u > "$tmp_combined"
|
||||
|
||||
if ! [ -s "$tmp_combined" ]; then
|
||||
echo "Nothing to install after filtering out cpl-* packages." >&2
|
||||
echo "Nichts zu installieren nach dem Entfernen von cpl-* Paketen." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Installing dependencies (excluding cpl-*) from:"
|
||||
echo "Installiere Abhängigkeiten (ohne cpl-*) aus:"
|
||||
printf ' - %s\n' "${req_files[@]}"
|
||||
echo
|
||||
echo "Final set to install:"
|
||||
echo "Finale Menge zur Installation:"
|
||||
cat "$tmp_combined"
|
||||
echo
|
||||
|
||||
# Use python -m pip for reliability; change to python3 if needed.
|
||||
python -m pip install -r "$tmp_combined"
|
||||
python -m pip install -r "$tmp_combined"
|
||||
@@ -1,2 +1,19 @@
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
line-length = 120
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = [
|
||||
"src/api",
|
||||
"src/application",
|
||||
"src/auth",
|
||||
"src/cli",
|
||||
"src/core",
|
||||
"src/database",
|
||||
"src/dependency",
|
||||
"src/graphql",
|
||||
"src/mail",
|
||||
"src/query",
|
||||
"src/translation"
|
||||
]
|
||||
testpaths = ["test"]
|
||||
asyncio_mode = "auto"
|
||||
@@ -22,22 +22,6 @@ class ApplicationABC(ABC):
|
||||
Contains instances of prepared objects
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def extend(cls, name: str | Callable, func: Callable[[Self], Self]):
|
||||
r"""Extend the Application with a custom method
|
||||
|
||||
Parameters:
|
||||
name: :class:`str`
|
||||
Name of the method
|
||||
func: :class:`Callable[[Self], Self]`
|
||||
Function that takes the Application as a parameter and returns it
|
||||
"""
|
||||
if callable(name):
|
||||
name = name.__name__
|
||||
|
||||
setattr(cls, name, func)
|
||||
return cls
|
||||
|
||||
@abstractmethod
|
||||
def __init__(
|
||||
self, services: ServiceProvider, loaded_modules: set[TModule], required_modules: list[str | object] = None
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Callable
|
||||
from cpl.core.property import classproperty
|
||||
from cpl.dependency.context import get_provider, use_root_provider
|
||||
from cpl.dependency.service_collection import ServiceCollection
|
||||
from cpl.dependency.hosted.startup_task import StartupTask
|
||||
from cpl.core.service.startup_task import StartupTask
|
||||
|
||||
|
||||
class Host:
|
||||
@@ -86,10 +86,9 @@ class Host:
|
||||
func(*args, **kwargs)
|
||||
except (KeyboardInterrupt, asyncio.CancelledError):
|
||||
pass
|
||||
finally:
|
||||
await cls._stop_all()
|
||||
|
||||
cls.get_loop().run_until_complete(runner())
|
||||
cls.get_loop().run_until_complete(cls.wait_for_all())
|
||||
|
||||
@classmethod
|
||||
def run(cls, func: Callable, *args, **kwargs):
|
||||
|
||||
@@ -42,8 +42,7 @@ class UserDao(DbModelDaoABC[User]):
|
||||
|
||||
permission_dao: PermissionDao = get_provider().get_service(PermissionDao)
|
||||
p = await permission_dao.get_by_name(permission if isinstance(permission, str) else permission.value)
|
||||
result = await self._db.select_map(
|
||||
f"""
|
||||
result = await self._db.select_map(f"""
|
||||
SELECT COUNT(*) as count
|
||||
FROM {TableManager.get("role_users")} ru
|
||||
JOIN {TableManager.get("role_permissions")} rp ON ru.roleId = rp.roleId
|
||||
@@ -51,16 +50,14 @@ class UserDao(DbModelDaoABC[User]):
|
||||
AND rp.permissionId = {p.id}
|
||||
AND ru.deleted = FALSE
|
||||
AND rp.deleted = FALSE;
|
||||
"""
|
||||
)
|
||||
""")
|
||||
if result is None or len(result) == 0:
|
||||
return False
|
||||
|
||||
return result[0]["count"] > 0
|
||||
|
||||
async def get_permissions(self, user_id: int) -> list[Permission]:
|
||||
result = await self._db.select_map(
|
||||
f"""
|
||||
result = await self._db.select_map(f"""
|
||||
SELECT p.*
|
||||
FROM {TableManager.get("permissions")} p
|
||||
JOIN {TableManager.get("role_permissions")} rp ON p.id = rp.permissionId
|
||||
@@ -68,6 +65,5 @@ class UserDao(DbModelDaoABC[User]):
|
||||
WHERE ru.userId = {user_id}
|
||||
AND rp.deleted = FALSE
|
||||
AND ru.deleted = FALSE;
|
||||
"""
|
||||
)
|
||||
""")
|
||||
return [self._permissions.to_object(x) for x in result]
|
||||
|
||||
9
src/cli/cpl/cli/.cpl/new/unittest/main.py
Normal file
9
src/cli/cpl/cli/.cpl/new/unittest/main.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from cpl.core.console import Console
|
||||
|
||||
|
||||
def main():
|
||||
Console.write_line("Hello, World!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,6 +5,7 @@ from pathlib import Path
|
||||
import click
|
||||
|
||||
from cpl.cli.cli import cli
|
||||
from cpl.cli.model.project import Project
|
||||
from cpl.cli.utils.structure import Structure
|
||||
from cpl.cli.utils.venv import get_venv_python, ensure_venv
|
||||
from cpl.core.configuration import Configuration
|
||||
@@ -21,35 +22,42 @@ def run(project: str, args: list[str], dev: bool, verbose: bool):
|
||||
if project is not None:
|
||||
project_path = (Path("./") / project).resolve().absolute()
|
||||
|
||||
project = Structure.get_project_by_name_or_path(str(project_path))
|
||||
if project.main is None:
|
||||
project = Structure.get_project_by_name_or_path(project_path)
|
||||
is_unittest = project.type == "unittest"
|
||||
if not is_unittest and project.main is None:
|
||||
Console.error(f"Project {project.name} has no executable")
|
||||
return
|
||||
|
||||
path = str(Path(project.path).parent.resolve().absolute())
|
||||
executable = project.main
|
||||
if not dev:
|
||||
dist_path = Path(project.path).parent / "dist"
|
||||
|
||||
if Configuration.get("workspace") is not None:
|
||||
dist_path = Path(Configuration.get("workspace").path).parent / "dist"
|
||||
|
||||
dist_path = Path(dist_path).resolve().absolute()
|
||||
if verbose:
|
||||
Console.write_line(f"Creating dist folder at {dist_path}...")
|
||||
|
||||
os.makedirs(dist_path, exist_ok=True)
|
||||
project.do_build(dist_path, verbose)
|
||||
path = dist_path / project.name
|
||||
main = project.main.replace(project.directory, "").lstrip("/\\")
|
||||
|
||||
executable = path / main
|
||||
|
||||
python = str(get_venv_python(ensure_venv()).absolute())
|
||||
Console.write_line(f"\nStarting project {project.name}...")
|
||||
if verbose:
|
||||
Console.write_line(f" with args {args}...")
|
||||
|
||||
Console.write_line("\n\n")
|
||||
|
||||
subprocess.run([python, executable, *args], cwd=path)
|
||||
path = str(Path(project.path).parent.resolve().absolute())
|
||||
python = str(get_venv_python(ensure_venv()).absolute())
|
||||
if is_unittest:
|
||||
subprocess.run([python, "-m", "pytest", path], cwd=path)
|
||||
return
|
||||
subprocess.run([python, _get_executable(project, dev, verbose), *args], cwd=path)
|
||||
|
||||
|
||||
def _get_executable(project: Project, dev: bool, verbose: bool) -> str:
|
||||
if dev:
|
||||
return project.main
|
||||
|
||||
dist_path = Path(project.path).parent / "dist"
|
||||
|
||||
if Configuration.get("workspace") is not None:
|
||||
dist_path = Path(Configuration.get("workspace").path).parent / "dist"
|
||||
|
||||
dist_path = Path(dist_path).resolve().absolute()
|
||||
if verbose:
|
||||
Console.write_line(f"Creating dist folder at {dist_path}...")
|
||||
|
||||
os.makedirs(dist_path, exist_ok=True)
|
||||
project.do_build(dist_path, verbose)
|
||||
path = dist_path / project.name
|
||||
main = project.main.replace(project.directory, "").lstrip("/\\")
|
||||
|
||||
return str(path / main)
|
||||
|
||||
@@ -31,7 +31,7 @@ def init(target: str, name: str, verbose: bool = False):
|
||||
if target in ["workspace", "ws"]:
|
||||
workspace = Structure.init_workspace("./", name or click.prompt("Workspace name", default="my-workspace"))
|
||||
elif target in PROJECT_TYPES:
|
||||
workspace = Structure.find_workspace_in_path(Path(name or "./").parent, with_parents=True)
|
||||
workspace = Structure.find_workspace_in_path(Path(name or "./").parent)
|
||||
project = Structure.init_project(
|
||||
"./", name or click.prompt("Project name", default=f"my-{target}"), target, workspace, verbose=verbose
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ def new(type: str, name: str, in_name: str | None, project: list[str] | None, ve
|
||||
path = Path(workspace.path).parent / Path(project[1]).parent
|
||||
project_name = Path(project[1]).stem
|
||||
|
||||
workspace = Structure.find_workspace_in_path(path, with_parents=False)
|
||||
workspace = Structure.find_workspace_in_path(path)
|
||||
if workspace is None:
|
||||
Console.error("No workspace found. Please run 'cpl init workspace' first.")
|
||||
raise SystemExit(1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PROJECT_TYPES = ["console", "web", "graphql", "library", "service"]
|
||||
PROJECT_TYPES = ["console", "web", "graphql", "library", "service", "unittest"]
|
||||
PROJECT_TYPES_SHORT = [x[0] for x in PROJECT_TYPES]
|
||||
|
||||
PIP_URL = "https://git.sh-edraft.de/api/packages/sh-edraft.de/pypi/simple/"
|
||||
|
||||
@@ -11,9 +11,13 @@ T = TypeVar("T", bound="CPLStructureModel")
|
||||
|
||||
|
||||
class CPLStructureModel:
|
||||
def __init__(self, path: Optional[str] = None):
|
||||
def __init__(self, path: Optional[str] = None, ignore_fields: Optional[List[str]] = None):
|
||||
self._path = path
|
||||
|
||||
self._ignore = {"_ignore", "_path"}
|
||||
if ignore_fields is not None:
|
||||
self._ignore.update(ignore_fields)
|
||||
|
||||
@property
|
||||
def path(self) -> Optional[str]:
|
||||
return self._path
|
||||
@@ -68,7 +72,7 @@ class CPLStructureModel:
|
||||
def to_json(self) -> Dict[str, Any]:
|
||||
result: Dict[str, Any] = {}
|
||||
for key, value in self.__dict__.items():
|
||||
if not key.startswith("_") or key == "_path":
|
||||
if not key.startswith("_") or key in self._ignore:
|
||||
continue
|
||||
out_key = _self_or_cls_snake_to_camel(key[1:])
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from cpl.cli.model.cpl_structure_model import CPLStructureModel
|
||||
@@ -23,11 +24,26 @@ class Workspace(CPLStructureModel):
|
||||
default_project: Optional[str],
|
||||
scripts: Dict[str, str],
|
||||
):
|
||||
CPLStructureModel.__init__(self, path)
|
||||
CPLStructureModel.__init__(self, path, ["_actual_projects", "_project_names"])
|
||||
|
||||
self._name = name
|
||||
self._projects = projects
|
||||
self._default_project = default_project
|
||||
|
||||
self._actual_projects = []
|
||||
self._project_names = []
|
||||
for project in projects:
|
||||
p_path = (Path(path).parent / Path(project)).resolve().absolute()
|
||||
if p_path.is_dir() or not p_path.exists():
|
||||
raise ValueError(f"Project path '{project}' does not exist or is a directory.")
|
||||
|
||||
p = Project.from_file(p_path)
|
||||
self._actual_projects.append(p)
|
||||
self._project_names.append(p.name)
|
||||
|
||||
if default_project is not None and default_project not in self._project_names:
|
||||
raise ValueError(f"Default project '{default_project}' not found in workspace projects.")
|
||||
|
||||
self._scripts = scripts
|
||||
|
||||
@property
|
||||
@@ -48,11 +64,11 @@ class Workspace(CPLStructureModel):
|
||||
|
||||
@property
|
||||
def actual_projects(self) -> List[Project]:
|
||||
return [Project.from_file(p) for p in self._projects]
|
||||
return self._actual_projects
|
||||
|
||||
@property
|
||||
def project_names(self) -> List[str]:
|
||||
return [Project.from_file(p).name for p in self._projects]
|
||||
return self._project_names
|
||||
|
||||
@property
|
||||
def default_project(self) -> Optional[str]:
|
||||
|
||||
@@ -32,11 +32,9 @@ class Structure:
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def find_workspace_in_path(path: Path, with_parents=False) -> Workspace | None:
|
||||
def find_workspace_in_path(path: Path) -> Workspace | None:
|
||||
current_path = path.resolve()
|
||||
paths = [current_path]
|
||||
if with_parents:
|
||||
paths.extend(current_path.parents)
|
||||
paths = [current_path, *current_path.parents]
|
||||
|
||||
for parent in paths:
|
||||
workspace_file = parent / "cpl.workspace.json"
|
||||
@@ -52,8 +50,7 @@ class Structure:
|
||||
if pyproject_path.exists():
|
||||
return
|
||||
|
||||
content = textwrap.dedent(
|
||||
f"""
|
||||
content = textwrap.dedent(f"""
|
||||
[build-system]
|
||||
requires = ["setuptools>=70.1.0", "wheel", "build"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
@@ -64,13 +61,12 @@ class Structure:
|
||||
authors = [{{name="{project.author or ''}"}}]
|
||||
license = "{project.license or ''}"
|
||||
dependencies = [{', '.join([f'"{dep}"' for dep in project.dependencies])}]
|
||||
"""
|
||||
).lstrip()
|
||||
""").lstrip()
|
||||
|
||||
pyproject_path.write_text(content)
|
||||
|
||||
@staticmethod
|
||||
def get_project_by_name_or_path(project: str) -> Project:
|
||||
def get_project_by_name_or_path(project: str | Path) -> Project:
|
||||
if project is None:
|
||||
raise ValueError("Project name or path must be provided.")
|
||||
|
||||
@@ -84,13 +80,14 @@ class Structure:
|
||||
|
||||
return Project.from_file(path)
|
||||
|
||||
workspace = Structure.find_workspace_in_path(path.parent, with_parents=True)
|
||||
workspace = Structure.find_workspace_in_path(path.parent)
|
||||
if workspace is None:
|
||||
raise RuntimeError("No workspace found. Please run 'cpl init workspace' first.")
|
||||
|
||||
project_name = project.name if isinstance(project, Path) else project
|
||||
for p in workspace.actual_projects:
|
||||
if p.name == project:
|
||||
return Project.from_file(Path(p.path))
|
||||
if p.name == project_name:
|
||||
return Project.from_file((Path(workspace.path).parent / Path(p.path)).resolve())
|
||||
|
||||
if not path.is_dir() and not path.is_file():
|
||||
raise ValueError(f"Unknown project {project}")
|
||||
|
||||
@@ -26,6 +26,9 @@ Homepage = "https://www.sh-edraft.de"
|
||||
where = ["."]
|
||||
include = ["cpl*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"cpl.cli" = [".cpl/**/*"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = { file = ["requirements.txt"] }
|
||||
optional-dependencies.dev = { file = ["requirements.dev.txt"] }
|
||||
|
||||
@@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
|
||||
from cpl.core.time.cron import Cron
|
||||
from cpl.dependency.hosted import HostedService
|
||||
from cpl.core.service import HostedService
|
||||
|
||||
|
||||
class CronjobABC(HostedService, ABC):
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from types import NoneType
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -6,7 +7,17 @@ class Number:
|
||||
@staticmethod
|
||||
def is_number(value: Any) -> bool:
|
||||
"""Check if the value is a number (int or float)."""
|
||||
return isinstance(value, (int, float, complex))
|
||||
if isinstance(value, (bool, NoneType)):
|
||||
return False
|
||||
|
||||
if isinstance(value, (int, float, complex)):
|
||||
return True
|
||||
|
||||
try:
|
||||
Number.to_number(value)
|
||||
return True
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def to_number(value: Any) -> int | float | complex:
|
||||
|
||||
@@ -18,14 +18,15 @@ class String:
|
||||
String converted to CamelCase
|
||||
"""
|
||||
|
||||
parts = re.split(r"[^a-zA-Z0-9]+", s.strip())
|
||||
if re.search(r"[_\-\s]", s):
|
||||
words = re.split(r"[_\-\s]+", s)
|
||||
else:
|
||||
words = re.findall(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)", s)
|
||||
|
||||
parts = [p for p in parts if p]
|
||||
|
||||
if not parts:
|
||||
words = [w.lower() for w in words if w]
|
||||
if not words:
|
||||
return ""
|
||||
|
||||
return parts[0].lower() + "".join(word.capitalize() for word in parts[1:])
|
||||
return words[0] + "".join(w.capitalize() for w in words[1:])
|
||||
|
||||
@staticmethod
|
||||
def to_pascal_case(s: str) -> str:
|
||||
@@ -39,14 +40,12 @@ class String:
|
||||
String converted to PascalCase
|
||||
"""
|
||||
|
||||
parts = re.split(r"[^a-zA-Z0-9]+", s.strip())
|
||||
if re.search(r"[_\-\s]", s):
|
||||
words = re.split(r"[_\-\s]+", s)
|
||||
else:
|
||||
words = re.findall(r"[A-Z]?[a-z]+|[A-Z]+(?=[A-Z]|$)", s)
|
||||
|
||||
parts = [p for p in parts if p]
|
||||
|
||||
if not parts:
|
||||
return ""
|
||||
|
||||
return "".join(word.capitalize() for word in parts)
|
||||
return "".join(word.capitalize() for word in words if word)
|
||||
|
||||
@staticmethod
|
||||
def to_snake_case(chars: str) -> str:
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
black==25.1.0
|
||||
black==25.1.0
|
||||
pytest-asyncio==0.26.0
|
||||
@@ -3,4 +3,5 @@ colorama==0.4.6
|
||||
tabulate==0.9.0
|
||||
termcolor==3.1.0
|
||||
pynput==1.8.1
|
||||
croniter==6.0.0
|
||||
croniter==6.0.0
|
||||
cryptography==46.0.2
|
||||
@@ -322,13 +322,11 @@ class DataAccessObjectABC(ABC, Generic[T_DBM]):
|
||||
Touch the entry to update the last updated date
|
||||
:return:
|
||||
"""
|
||||
await self._db.execute(
|
||||
f"""
|
||||
await self._db.execute(f"""
|
||||
UPDATE {self._table_name}
|
||||
SET updated = NOW()
|
||||
WHERE {self.__primary_key} = {self._get_primary_key_value_sql(obj)};
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
async def touch_many_by_id(self, ids: list[Id]):
|
||||
"""
|
||||
@@ -338,13 +336,11 @@ class DataAccessObjectABC(ABC, Generic[T_DBM]):
|
||||
if len(ids) == 0:
|
||||
return
|
||||
|
||||
await self._db.execute(
|
||||
f"""
|
||||
await self._db.execute(f"""
|
||||
UPDATE {self._table_name}
|
||||
SET updated = NOW()
|
||||
WHERE {self.__primary_key} IN ({", ".join([str(x) for x in ids])});
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
async def _build_create_statement(self, obj: T_DBM, skip_editor=False) -> str:
|
||||
allowed_fields = [x for x in self.__attributes.keys() if x not in self.__ignored_attributes]
|
||||
|
||||
@@ -56,13 +56,11 @@ class ExternalDataTempTableBuilder:
|
||||
|
||||
values_str = ", ".join([f"{value}" for value in await self._value_getter()])
|
||||
|
||||
return textwrap.dedent(
|
||||
f"""
|
||||
return textwrap.dedent(f"""
|
||||
DROP TABLE IF EXISTS {self._table_name};
|
||||
CREATE TEMP TABLE {self._table_name} (
|
||||
{", ".join([f"{k} {v}" for k, v in self._fields.items()])}
|
||||
);
|
||||
|
||||
INSERT INTO {self._table_name} VALUES {values_str};
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
@@ -7,7 +7,7 @@ from cpl.database.model.migration import Migration
|
||||
from cpl.database.model.server_type import ServerType, ServerTypes
|
||||
from cpl.database.schema.executed_migration import ExecutedMigration
|
||||
from cpl.database.schema.executed_migration_dao import ExecutedMigrationDao
|
||||
from cpl.dependency.hosted import StartupTask
|
||||
from cpl.core.service import StartupTask
|
||||
|
||||
|
||||
class MigrationService(StartupTask):
|
||||
@@ -84,19 +84,15 @@ class MigrationService(StartupTask):
|
||||
|
||||
async def _get_tables(self):
|
||||
if ServerType == ServerTypes.POSTGRES:
|
||||
return await self._db.select(
|
||||
"""
|
||||
return await self._db.select("""
|
||||
SELECT tablename
|
||||
FROM pg_tables
|
||||
WHERE schemaname = 'public';
|
||||
"""
|
||||
)
|
||||
""")
|
||||
else:
|
||||
return await self._db.select(
|
||||
"""
|
||||
return await self._db.select("""
|
||||
SHOW TABLES;
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
async def _execute(self, migrations: list[Migration]):
|
||||
result = await self._get_tables()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from cpl.database.abc.data_seeder_abc import DataSeederABC
|
||||
from cpl.database.logger import DBLogger
|
||||
from cpl.dependency import ServiceProvider
|
||||
from cpl.dependency.hosted import StartupTask
|
||||
from cpl.core.service import StartupTask
|
||||
|
||||
|
||||
class SeederService(StartupTask):
|
||||
|
||||
@@ -3,7 +3,6 @@ from typing import TypeVar, Union, Literal, Any
|
||||
|
||||
from cpl.database.abc.db_model_abc import DbModelABC
|
||||
|
||||
|
||||
T_DBM = TypeVar("T_DBM", bound=DbModelABC)
|
||||
|
||||
NumberFilterOperator = Literal[
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import contextvars
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
_current_provider = contextvars.ContextVar("current_provider", default=None)
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from cpl.core.errors import module_dependency_error
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
from cpl.core.typing import T, Service
|
||||
from cpl.core.utils.cache import Cache
|
||||
from cpl.dependency.hosted.startup_task import StartupTask
|
||||
from cpl.core.service.startup_task import StartupTask
|
||||
from cpl.dependency.module.module import Module
|
||||
from cpl.dependency.service_descriptor import ServiceDescriptor
|
||||
from cpl.dependency.service_lifetime import ServiceLifetimeEnum
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Type
|
||||
|
||||
from cpl.core.configuration import ConfigurationModelABC
|
||||
from cpl.core.typing import T
|
||||
from cpl.dependency.hosted import StartupTask
|
||||
from cpl.core.service import StartupTask
|
||||
from cpl.dependency.module.module import Module
|
||||
|
||||
TModule = Type[Module]
|
||||
|
||||
@@ -2,8 +2,7 @@ from starlette.responses import HTMLResponse
|
||||
|
||||
|
||||
async def graphiql_endpoint(request):
|
||||
return HTMLResponse(
|
||||
"""
|
||||
return HTMLResponse("""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -65,5 +64,4 @@ async def graphiql_endpoint(request):
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
@@ -3,8 +3,7 @@ from starlette.responses import Response, HTMLResponse
|
||||
|
||||
|
||||
async def playground_endpoint(request: Request) -> Response:
|
||||
return HTMLResponse(
|
||||
"""
|
||||
return HTMLResponse("""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
@@ -25,5 +24,4 @@ async def playground_endpoint(request: Request) -> Response:
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
23
src/mail/cpl.project.json
Normal file
23
src/mail/cpl.project.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "cpl-mail",
|
||||
"version": "0.1.0",
|
||||
"type": "library",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"homepage": "",
|
||||
"keywords": [],
|
||||
"dependencies": {
|
||||
"cpl-core": "~2024.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpl-cli": ">2024.7.0"
|
||||
},
|
||||
"references": [],
|
||||
"main": null,
|
||||
"directory": "./",
|
||||
"build": {
|
||||
"include": [],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
66
test/core/abc/registry_abc_test.py
Normal file
66
test/core/abc/registry_abc_test.py
Normal file
@@ -0,0 +1,66 @@
|
||||
import pytest
|
||||
from cpl.core.abc.registry_abc import RegistryABC
|
||||
|
||||
|
||||
class StringRegistry(RegistryABC[str]):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def extend(self, items: list[str]) -> None:
|
||||
for item in items:
|
||||
self.add(item)
|
||||
|
||||
def add(self, item: str) -> None:
|
||||
self._items[item] = item
|
||||
|
||||
def get(self, key: str) -> str | None:
|
||||
return self._items.get(key)
|
||||
|
||||
def all(self) -> list[str]:
|
||||
return list(self._items.values())
|
||||
|
||||
|
||||
def test_add_and_get():
|
||||
reg = StringRegistry()
|
||||
reg.add("hello")
|
||||
assert reg.get("hello") == "hello"
|
||||
|
||||
|
||||
def test_get_missing_returns_none():
|
||||
reg = StringRegistry()
|
||||
assert reg.get("nonexistent") is None
|
||||
|
||||
|
||||
def test_all_empty():
|
||||
reg = StringRegistry()
|
||||
assert reg.all() == []
|
||||
|
||||
|
||||
def test_all_returns_all_items():
|
||||
reg = StringRegistry()
|
||||
reg.add("a")
|
||||
reg.add("b")
|
||||
reg.add("c")
|
||||
items = reg.all()
|
||||
assert set(items) == {"a", "b", "c"}
|
||||
|
||||
|
||||
def test_extend():
|
||||
reg = StringRegistry()
|
||||
reg.extend(["x", "y", "z"])
|
||||
assert reg.get("x") == "x"
|
||||
assert reg.get("y") == "y"
|
||||
assert reg.get("z") == "z"
|
||||
assert len(reg.all()) == 3
|
||||
|
||||
|
||||
def test_overwrite_key():
|
||||
reg = StringRegistry()
|
||||
reg.add("key")
|
||||
reg.add("key")
|
||||
assert len(reg.all()) == 1
|
||||
|
||||
|
||||
def test_cannot_instantiate_abc_directly():
|
||||
with pytest.raises(TypeError):
|
||||
RegistryABC()
|
||||
159
test/core/configuration/configuration_model_abc_test.py
Normal file
159
test/core/configuration/configuration_model_abc_test.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import os
|
||||
import pytest
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class DatabaseSettings(ConfigurationModelABC):
|
||||
def __init__(self, src: dict = None):
|
||||
ConfigurationModelABC.__init__(self, src or {})
|
||||
self.option("host", str, default="localhost")
|
||||
self.option("port", int, default=5432)
|
||||
self.option("name", str)
|
||||
self.option("debug", bool, default=False)
|
||||
|
||||
|
||||
class RequiredSettings(ConfigurationModelABC):
|
||||
def __init__(self, src: dict = None):
|
||||
ConfigurationModelABC.__init__(self, src or {})
|
||||
self.option("api_key", str, required=True)
|
||||
|
||||
|
||||
class MutableSettings(ConfigurationModelABC):
|
||||
def __init__(self, src: dict = None):
|
||||
ConfigurationModelABC.__init__(self, src or {}, readonly=False)
|
||||
self.option("value", str, default="initial")
|
||||
|
||||
|
||||
class EnvSettings(ConfigurationModelABC):
|
||||
def __init__(self, src: dict = None):
|
||||
ConfigurationModelABC.__init__(self, src or {}, env_prefix="MYAPP")
|
||||
self.option("secret", str, default=None)
|
||||
|
||||
|
||||
# --- option() / defaults ---
|
||||
|
||||
|
||||
def test_default_values():
|
||||
s = DatabaseSettings()
|
||||
assert s.host == "localhost"
|
||||
assert s.port == 5432
|
||||
assert s.debug is False
|
||||
|
||||
|
||||
def test_values_from_src():
|
||||
# NOTE: passing native bool True via src triggers cast(True, bool) which
|
||||
# fails because cast() calls value.lower() on a bool (bug in cast.py).
|
||||
# Use string "true" instead — matches real JSON-parsed values.
|
||||
s = DatabaseSettings({"host": "db.example.com", "port": 3306, "name": "mydb", "debug": "true"})
|
||||
assert s.host == "db.example.com"
|
||||
assert s.port == 3306
|
||||
assert s.name == "mydb"
|
||||
assert s.debug is True
|
||||
|
||||
|
||||
def test_pascal_case_key():
|
||||
s = DatabaseSettings({"Host": "remotehost", "Port": 1234})
|
||||
assert s.host == "remotehost"
|
||||
assert s.port == 1234
|
||||
|
||||
|
||||
def test_snake_case_key():
|
||||
s = DatabaseSettings({"host": "snakehost"})
|
||||
assert s.host == "snakehost"
|
||||
|
||||
|
||||
def test_missing_optional_is_none():
|
||||
s = DatabaseSettings()
|
||||
assert s.name is None
|
||||
|
||||
|
||||
def test_type_casting_int():
|
||||
s = DatabaseSettings({"port": "9999"})
|
||||
assert s.port == 9999
|
||||
assert isinstance(s.port, int)
|
||||
|
||||
|
||||
def test_type_casting_bool():
|
||||
s = DatabaseSettings({"debug": "true"})
|
||||
assert s.debug is True
|
||||
|
||||
|
||||
# --- required ---
|
||||
|
||||
|
||||
def test_required_field_present():
|
||||
s = RequiredSettings({"api_key": "abc123"})
|
||||
assert s.api_key == "abc123"
|
||||
|
||||
|
||||
def test_required_field_missing_raises():
|
||||
with pytest.raises(ValueError, match="required"):
|
||||
RequiredSettings()
|
||||
|
||||
|
||||
# --- readonly ---
|
||||
|
||||
|
||||
def test_readonly_raises_on_setattr():
|
||||
s = DatabaseSettings()
|
||||
with pytest.raises(AttributeError, match="read-only"):
|
||||
s.host = "newhost"
|
||||
|
||||
|
||||
def test_mutable_settings_can_be_set():
|
||||
s = MutableSettings()
|
||||
s.value = "changed"
|
||||
assert s.value == "changed"
|
||||
|
||||
|
||||
# --- env override ---
|
||||
|
||||
|
||||
def test_env_prefix_override(monkeypatch):
|
||||
monkeypatch.setenv("MYAPP_SECRET", "env_secret_value")
|
||||
s = EnvSettings()
|
||||
assert s.secret == "env_secret_value"
|
||||
|
||||
|
||||
def test_env_no_prefix_override(monkeypatch):
|
||||
monkeypatch.setenv("HOST", "env_host")
|
||||
|
||||
class NoPrefix(ConfigurationModelABC):
|
||||
def __init__(self, src=None):
|
||||
ConfigurationModelABC.__init__(self, src or {})
|
||||
self.option("host", str, default=None)
|
||||
|
||||
s = NoPrefix()
|
||||
assert s.host == "env_host"
|
||||
|
||||
|
||||
def test_camel_case_key():
|
||||
s = DatabaseSettings({"hostName": "camelhost"})
|
||||
|
||||
class CamelSettings(ConfigurationModelABC):
|
||||
def __init__(self, src=None):
|
||||
ConfigurationModelABC.__init__(self, src or {})
|
||||
self.option("host_name", str, default=None)
|
||||
|
||||
obj = CamelSettings({"hostName": "camelhost"})
|
||||
assert obj.host_name == "camelhost"
|
||||
|
||||
|
||||
# --- to_dict ---
|
||||
|
||||
|
||||
def test_to_dict_returns_dict():
|
||||
# to_dict() calls get() which calls get_value(src, field, _options[field].type, ...)
|
||||
# _options stores the cast value directly (not a typed wrapper), so .type raises AttributeError.
|
||||
# Bug: to_dict() is broken in the current implementation.
|
||||
s = DatabaseSettings({"host": "myhost", "port": "1234"})
|
||||
with pytest.raises(AttributeError):
|
||||
s.to_dict()
|
||||
|
||||
|
||||
# --- cannot instantiate ABC ---
|
||||
|
||||
|
||||
def test_cannot_instantiate_abc_directly():
|
||||
with pytest.raises(TypeError):
|
||||
ConfigurationModelABC()
|
||||
107
test/core/configuration/configuration_test.py
Normal file
107
test/core/configuration/configuration_test.py
Normal file
@@ -0,0 +1,107 @@
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
from cpl.core.configuration.configuration import Configuration
|
||||
from cpl.core.configuration.configuration_model_abc import ConfigurationModelABC
|
||||
|
||||
|
||||
class AppSettings(ConfigurationModelABC):
|
||||
def __init__(self, src: dict = None):
|
||||
ConfigurationModelABC.__init__(self, src or {})
|
||||
self.option("app_name", str, default="default-app")
|
||||
self.option("version", str, default="0.0.0")
|
||||
|
||||
|
||||
class ServerSettings(ConfigurationModelABC):
|
||||
def __init__(self, src: dict = None):
|
||||
ConfigurationModelABC.__init__(self, src or {})
|
||||
self.option("host", str, default="localhost")
|
||||
self.option("port", int, default=8080)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_config():
|
||||
"""Reset Configuration state before each test."""
|
||||
Configuration._config.clear()
|
||||
yield
|
||||
Configuration._config.clear()
|
||||
|
||||
|
||||
# --- set / get ---
|
||||
|
||||
|
||||
def test_set_and_get_by_class():
|
||||
settings = AppSettings({"app_name": "TestApp", "version": "1.0.0"})
|
||||
Configuration.set(AppSettings, settings)
|
||||
result = Configuration.get(AppSettings)
|
||||
assert result.app_name == "TestApp"
|
||||
assert result.version == "1.0.0"
|
||||
|
||||
|
||||
def test_set_and_get_by_string_key():
|
||||
Configuration.set("my_key", "my_value")
|
||||
assert Configuration.get("my_key") == "my_value"
|
||||
|
||||
|
||||
def test_get_missing_returns_default():
|
||||
assert Configuration.get("nonexistent", "fallback") == "fallback"
|
||||
|
||||
|
||||
def test_get_missing_returns_none():
|
||||
assert Configuration.get("nonexistent") is None
|
||||
|
||||
|
||||
def test_get_model_auto_instantiates():
|
||||
# Getting an unregistered ConfigurationModelABC subclass should auto-create it
|
||||
result = Configuration.get(ServerSettings)
|
||||
assert isinstance(result, ServerSettings)
|
||||
assert result.host == "localhost"
|
||||
assert result.port == 8080
|
||||
|
||||
|
||||
def test_overwrite_existing():
|
||||
Configuration.set("key", "first")
|
||||
Configuration.set("key", "second")
|
||||
assert Configuration.get("key") == "second"
|
||||
|
||||
|
||||
def test_multiple_models():
|
||||
Configuration.set(AppSettings, AppSettings({"app_name": "App"}))
|
||||
Configuration.set(ServerSettings, ServerSettings({"port": 9000}))
|
||||
assert Configuration.get(AppSettings).app_name == "App"
|
||||
assert Configuration.get(ServerSettings).port == 9000
|
||||
|
||||
|
||||
# --- add_json_file ---
|
||||
|
||||
|
||||
def test_add_json_file_loads_model(tmp_path):
|
||||
config_data = {"AppSettings": {"app_name": "FromFile", "version": "2.0.0"}}
|
||||
config_file = tmp_path / "appsettings.json"
|
||||
config_file.write_text(json.dumps(config_data), encoding="utf-8")
|
||||
|
||||
Configuration.add_json_file(str(config_file), output=False)
|
||||
|
||||
result = Configuration.get(AppSettings)
|
||||
assert result.app_name == "FromFile"
|
||||
assert result.version == "2.0.0"
|
||||
|
||||
|
||||
def test_add_json_file_not_found_exits(tmp_path):
|
||||
with pytest.raises(SystemExit):
|
||||
Configuration.add_json_file(str(tmp_path / "missing.json"), output=False)
|
||||
|
||||
|
||||
def test_add_json_file_optional_missing_returns_none(tmp_path):
|
||||
result = Configuration.add_json_file(str(tmp_path / "missing.json"), optional=True, output=False)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_add_json_file_invalid_json(tmp_path):
|
||||
# _load_json_file catches parse errors and returns {} — no SystemExit.
|
||||
# add_json_file then proceeds with an empty config (no models loaded).
|
||||
bad_file = tmp_path / "bad.json"
|
||||
bad_file.write_text("not valid json", encoding="utf-8")
|
||||
Configuration.add_json_file(str(bad_file), output=False)
|
||||
# No model should be registered since the file was invalid
|
||||
assert Configuration.get("AppSettings") is None
|
||||
101
test/core/console/console_test.py
Normal file
101
test/core/console/console_test.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import pytest
|
||||
from cpl.core.console.foreground_color_enum import ForegroundColorEnum
|
||||
from cpl.core.console.background_color_enum import BackgroundColorEnum
|
||||
from cpl.core.console.console import Console
|
||||
|
||||
# --- ForegroundColorEnum ---
|
||||
|
||||
|
||||
def test_foreground_color_enum_has_expected_values():
|
||||
values = [e.value for e in ForegroundColorEnum]
|
||||
assert "default" in values
|
||||
assert "grey" in values
|
||||
assert "red" in values
|
||||
assert "green" in values
|
||||
assert "yellow" in values
|
||||
assert "blue" in values
|
||||
assert "magenta" in values
|
||||
assert "cyan" in values
|
||||
assert "white" in values
|
||||
|
||||
|
||||
def test_foreground_color_enum_count():
|
||||
assert len(ForegroundColorEnum) >= 9
|
||||
|
||||
|
||||
def test_foreground_color_enum_by_name():
|
||||
assert ForegroundColorEnum["red"] == ForegroundColorEnum.red
|
||||
assert ForegroundColorEnum["green"] == ForegroundColorEnum.green
|
||||
|
||||
|
||||
# --- BackgroundColorEnum ---
|
||||
|
||||
|
||||
def test_background_color_enum_has_expected_values():
|
||||
values = [e.value for e in BackgroundColorEnum]
|
||||
assert "on_default" in values
|
||||
assert "on_red" in values
|
||||
assert "on_green" in values
|
||||
assert "on_blue" in values
|
||||
|
||||
|
||||
def test_background_color_enum_count():
|
||||
assert len(BackgroundColorEnum) >= 9
|
||||
|
||||
|
||||
def test_background_color_enum_by_name():
|
||||
assert BackgroundColorEnum["red"] == BackgroundColorEnum.red
|
||||
|
||||
|
||||
# --- Console basic methods (non-interactive, no terminal required) ---
|
||||
|
||||
|
||||
def test_console_write_does_not_raise(capsys):
|
||||
Console.write("test")
|
||||
captured = capsys.readouterr()
|
||||
assert "test" in captured.out
|
||||
|
||||
|
||||
def test_console_write_line_does_not_raise(capsys):
|
||||
Console.write_line("hello world")
|
||||
captured = capsys.readouterr()
|
||||
assert "hello world" in captured.out
|
||||
|
||||
|
||||
def test_console_write_line_multiple_args(capsys):
|
||||
Console.write_line("a", "b", "c")
|
||||
captured = capsys.readouterr()
|
||||
assert "a" in captured.out
|
||||
|
||||
|
||||
def test_console_error_does_not_raise(capsys):
|
||||
Console.error("something went wrong")
|
||||
captured = capsys.readouterr()
|
||||
assert "something went wrong" in captured.out or "something went wrong" in captured.err
|
||||
|
||||
|
||||
def test_console_set_foreground_color_does_not_raise():
|
||||
Console.set_foreground_color(ForegroundColorEnum.red)
|
||||
Console.set_foreground_color(ForegroundColorEnum.default)
|
||||
|
||||
|
||||
def test_console_set_background_color_does_not_raise():
|
||||
Console.set_background_color(BackgroundColorEnum.blue)
|
||||
Console.set_background_color(BackgroundColorEnum.default)
|
||||
|
||||
|
||||
def test_console_color_reset_does_not_raise():
|
||||
Console.color_reset()
|
||||
|
||||
|
||||
def test_console_banner_does_not_raise(capsys):
|
||||
# banner() renders text as ASCII art, not verbatim
|
||||
Console.banner("Test Banner")
|
||||
captured = capsys.readouterr()
|
||||
assert len(captured.out) > 0
|
||||
|
||||
|
||||
def test_console_divider_does_not_raise(capsys):
|
||||
Console.divider()
|
||||
captured = capsys.readouterr()
|
||||
assert len(captured.out) > 0
|
||||
104
test/core/environment/environment_test.py
Normal file
104
test/core/environment/environment_test.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import os
|
||||
import pytest
|
||||
from cpl.core.environment.environment import Environment
|
||||
from cpl.core.environment.environment_enum import EnvironmentEnum
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def restore_env():
|
||||
"""Restore touched env vars after each test to prevent pollution."""
|
||||
original = dict(os.environ)
|
||||
yield
|
||||
for key in list(os.environ.keys()):
|
||||
if key not in original:
|
||||
del os.environ[key]
|
||||
elif os.environ[key] != original[key]:
|
||||
os.environ[key] = original[key]
|
||||
|
||||
|
||||
def test_set_and_get_environment_valid():
|
||||
for env in EnvironmentEnum:
|
||||
Environment.set_environment(env.value)
|
||||
assert Environment.get_environment() == env.value
|
||||
|
||||
|
||||
def test_set_environment_invalid():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set_environment("invalid_env")
|
||||
|
||||
|
||||
def test_set_environment_empty():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set_environment("")
|
||||
|
||||
|
||||
def test_set_environment_none():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set_environment(None)
|
||||
|
||||
|
||||
def test_set_and_get_app_name():
|
||||
Environment.set_app_name("TestApp")
|
||||
assert Environment.get_app_name() == "TestApp"
|
||||
|
||||
|
||||
def test_get_host_name():
|
||||
hostname = Environment.get_host_name()
|
||||
assert isinstance(hostname, str)
|
||||
assert len(hostname) > 0
|
||||
|
||||
|
||||
def test_get_cwd():
|
||||
cwd = Environment.get_cwd()
|
||||
assert isinstance(cwd, str)
|
||||
assert os.path.isdir(cwd)
|
||||
|
||||
|
||||
def test_set_cwd(tmp_path):
|
||||
original = Environment.get_cwd()
|
||||
Environment.set_cwd(str(tmp_path))
|
||||
assert Environment.get_cwd() == str(tmp_path)
|
||||
Environment.set_cwd(original)
|
||||
|
||||
|
||||
def test_set_cwd_empty():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set_cwd("")
|
||||
|
||||
|
||||
def test_set_cwd_none():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set_cwd(None)
|
||||
|
||||
|
||||
def test_set_and_get_generic():
|
||||
Environment.set("CPL_TEST_KEY", "hello")
|
||||
assert Environment.get("CPL_TEST_KEY", str) == "hello"
|
||||
|
||||
|
||||
def test_get_missing_key_returns_default():
|
||||
result = Environment.get("CPL_NONEXISTENT_KEY_XYZ", str, "default_value")
|
||||
assert result == "default_value"
|
||||
|
||||
|
||||
def test_get_missing_key_returns_none():
|
||||
result = Environment.get("CPL_NONEXISTENT_KEY_XYZ2", str)
|
||||
assert result is None
|
||||
|
||||
|
||||
def test_set_key_empty():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set("", "value")
|
||||
|
||||
|
||||
def test_set_key_none():
|
||||
with pytest.raises(AssertionError):
|
||||
Environment.set(None, "value")
|
||||
|
||||
|
||||
def test_environment_enum_values():
|
||||
values = [e.value for e in EnvironmentEnum]
|
||||
assert "production" in values
|
||||
assert "staging" in values
|
||||
assert "testing" in values
|
||||
assert "development" in values
|
||||
38
test/core/errors_test.py
Normal file
38
test/core/errors_test.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import pytest
|
||||
from cpl.core.errors import dependency_error, module_dependency_error
|
||||
|
||||
|
||||
def test_dependency_error_exits(capsys):
|
||||
with pytest.raises(SystemExit):
|
||||
dependency_error("my.feature", "some-package")
|
||||
|
||||
|
||||
def test_dependency_error_prints_message(capsys):
|
||||
with pytest.raises(SystemExit):
|
||||
dependency_error("my.feature", "some-package")
|
||||
captured = capsys.readouterr()
|
||||
assert "some-package" in captured.out
|
||||
assert "my.feature" in captured.out
|
||||
|
||||
|
||||
def test_dependency_error_with_import_error(capsys):
|
||||
try:
|
||||
import nonexistent_package
|
||||
except ImportError as e:
|
||||
with pytest.raises(SystemExit):
|
||||
dependency_error("my.feature", "nonexistent_package", e)
|
||||
captured = capsys.readouterr()
|
||||
assert "nonexistent_package" in captured.out
|
||||
|
||||
|
||||
def test_module_dependency_error_exits(capsys):
|
||||
with pytest.raises(SystemExit):
|
||||
module_dependency_error("MyModule", "SomeDependency")
|
||||
|
||||
|
||||
def test_module_dependency_error_prints_message(capsys):
|
||||
with pytest.raises(SystemExit):
|
||||
module_dependency_error("MyModule", "SomeDependency")
|
||||
captured = capsys.readouterr()
|
||||
assert "SomeDependency" in captured.out
|
||||
assert "MyModule" in captured.out
|
||||
152
test/core/log/log_test.py
Normal file
152
test/core/log/log_test.py
Normal file
@@ -0,0 +1,152 @@
|
||||
import os
|
||||
import pytest
|
||||
from cpl.core.log.log_level import LogLevel
|
||||
from cpl.core.log.log_settings import LogSettings
|
||||
from cpl.core.log.logger import Logger
|
||||
from cpl.core.log.logger_abc import LoggerABC
|
||||
|
||||
# --- LogLevel ---
|
||||
|
||||
|
||||
def test_log_level_values():
|
||||
assert LogLevel.off.value == "OFF"
|
||||
assert LogLevel.trace.value == "TRC"
|
||||
assert LogLevel.debug.value == "DEB"
|
||||
assert LogLevel.info.value == "INF"
|
||||
assert LogLevel.warning.value == "WAR"
|
||||
assert LogLevel.error.value == "ERR"
|
||||
assert LogLevel.fatal.value == "FAT"
|
||||
|
||||
|
||||
def test_log_level_order():
|
||||
levels = list(LogLevel)
|
||||
assert levels.index(LogLevel.trace) < levels.index(LogLevel.debug)
|
||||
assert levels.index(LogLevel.debug) < levels.index(LogLevel.info)
|
||||
assert levels.index(LogLevel.info) < levels.index(LogLevel.warning)
|
||||
assert levels.index(LogLevel.warning) < levels.index(LogLevel.error)
|
||||
assert levels.index(LogLevel.error) < levels.index(LogLevel.fatal)
|
||||
|
||||
|
||||
def test_log_level_by_name():
|
||||
assert LogLevel["info"] == LogLevel.info
|
||||
assert LogLevel["error"] == LogLevel.error
|
||||
|
||||
|
||||
# --- LogSettings ---
|
||||
|
||||
|
||||
def test_log_settings_defaults():
|
||||
s = LogSettings()
|
||||
assert s.path == "logs"
|
||||
assert s.filename == "app.log"
|
||||
assert s.console == LogLevel.info
|
||||
assert s.level == LogLevel.info
|
||||
|
||||
|
||||
def test_log_settings_from_src():
|
||||
s = LogSettings({"path": "custom_logs", "filename": "myapp.log"})
|
||||
assert s.path == "custom_logs"
|
||||
assert s.filename == "myapp.log"
|
||||
|
||||
|
||||
# --- LoggerABC ---
|
||||
|
||||
|
||||
def test_logger_abc_cannot_instantiate():
|
||||
with pytest.raises(TypeError):
|
||||
LoggerABC()
|
||||
|
||||
|
||||
# --- Logger ---
|
||||
|
||||
|
||||
def test_logger_creates_instance(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
assert logger is not None
|
||||
|
||||
|
||||
def test_logger_log_file_property(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
assert logger.log_file.startswith("logs/")
|
||||
assert logger.log_file.endswith(".log")
|
||||
|
||||
|
||||
def test_logger_should_log_same_level(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
assert logger._should_log(LogLevel.info, LogLevel.info) is True
|
||||
assert logger._should_log(LogLevel.error, LogLevel.error) is True
|
||||
|
||||
|
||||
def test_logger_should_log_higher_level(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
assert logger._should_log(LogLevel.error, LogLevel.info) is True
|
||||
assert logger._should_log(LogLevel.fatal, LogLevel.debug) is True
|
||||
|
||||
|
||||
def test_logger_should_not_log_lower_level(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
assert logger._should_log(LogLevel.debug, LogLevel.info) is False
|
||||
assert logger._should_log(LogLevel.trace, LogLevel.warning) is False
|
||||
|
||||
|
||||
def test_logger_file_format_message(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger("test_src")
|
||||
msg = logger._file_format_message("INF", "2024-01-01 00:00:00.000000", "hello", "world")
|
||||
assert "INF" in msg
|
||||
assert "hello" in msg
|
||||
assert "world" in msg
|
||||
assert "test_src" in msg
|
||||
|
||||
|
||||
def test_logger_console_format_message(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger("test_src")
|
||||
msg = logger._console_format_message("DEB", "2024-01-01 00:00:00.000000", "debug message")
|
||||
assert "DEB" in msg
|
||||
assert "debug message" in msg
|
||||
|
||||
|
||||
def test_logger_info_writes_file(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
logger.info("test info message")
|
||||
log_files = list(tmp_path.glob("logs/*.log"))
|
||||
assert len(log_files) > 0
|
||||
content = log_files[0].read_text()
|
||||
assert "test info message" in content
|
||||
|
||||
|
||||
def test_logger_debug_below_info_not_written_to_file(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
# Default level is INFO, so DEBUG should not appear in the file
|
||||
logger = Logger(__name__)
|
||||
logger.debug("should not appear in file")
|
||||
log_files = list(tmp_path.glob("logs/*.log"))
|
||||
if log_files:
|
||||
content = log_files[0].read_text()
|
||||
assert "should not appear in file" not in content
|
||||
|
||||
|
||||
def test_logger_set_level_invalid(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
with pytest.raises(ValueError):
|
||||
Logger.set_level("INVALID_LEVEL")
|
||||
|
||||
|
||||
def test_logger_fatal_exits(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
with pytest.raises(SystemExit):
|
||||
logger.fatal("fatal error")
|
||||
|
||||
|
||||
def test_logger_fatal_prevent_quit(tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path)
|
||||
logger = Logger(__name__)
|
||||
logger.fatal("fatal no quit", prevent_quit=True) # Should not raise
|
||||
36
test/core/pipes/bool_pipe_test.py
Normal file
36
test/core/pipes/bool_pipe_test.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import pytest
|
||||
from cpl.core.pipes.bool_pipe import BoolPipe
|
||||
|
||||
|
||||
def test_to_str_true():
|
||||
assert BoolPipe.to_str(True) == "true"
|
||||
|
||||
|
||||
def test_to_str_false():
|
||||
assert BoolPipe.to_str(False) == "false"
|
||||
|
||||
|
||||
def test_from_str_true_values():
|
||||
assert BoolPipe.from_str("True") is True
|
||||
assert BoolPipe.from_str("true") is True
|
||||
assert BoolPipe.from_str("1") is True
|
||||
assert BoolPipe.from_str("yes") is True
|
||||
assert BoolPipe.from_str("y") is True
|
||||
assert BoolPipe.from_str("Y") is True
|
||||
|
||||
|
||||
def test_from_str_false_values():
|
||||
assert BoolPipe.from_str("false") is False
|
||||
assert BoolPipe.from_str("False") is False
|
||||
assert BoolPipe.from_str("0") is False
|
||||
assert BoolPipe.from_str("no") is False
|
||||
assert BoolPipe.from_str("") is False
|
||||
assert BoolPipe.from_str("anything") is False
|
||||
|
||||
|
||||
def test_roundtrip_true():
|
||||
assert BoolPipe.from_str(BoolPipe.to_str(True)) is True
|
||||
|
||||
|
||||
def test_roundtrip_false():
|
||||
assert BoolPipe.from_str(BoolPipe.to_str(False)) is False
|
||||
59
test/core/pipes/ip_address_pipe_test.py
Normal file
59
test/core/pipes/ip_address_pipe_test.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
from cpl.core.pipes.ip_address_pipe import IPAddressPipe
|
||||
|
||||
|
||||
def test_to_str_valid():
|
||||
assert IPAddressPipe.to_str([192, 168, 1, 1]) == "192.168.1.1"
|
||||
assert IPAddressPipe.to_str([0, 0, 0, 0]) == "0.0.0.0"
|
||||
assert IPAddressPipe.to_str([255, 255, 255, 255]) == "255.255.255.255"
|
||||
assert IPAddressPipe.to_str([127, 0, 0, 1]) == "127.0.0.1"
|
||||
|
||||
|
||||
def test_to_str_too_few_parts():
|
||||
with pytest.raises(ValueError):
|
||||
IPAddressPipe.to_str([192, 168, 1])
|
||||
|
||||
|
||||
def test_to_str_too_many_parts():
|
||||
with pytest.raises(ValueError):
|
||||
IPAddressPipe.to_str([192, 168, 1, 1, 5])
|
||||
|
||||
|
||||
def test_to_str_byte_out_of_range():
|
||||
with pytest.raises(ValueError):
|
||||
IPAddressPipe.to_str([256, 0, 0, 0])
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
IPAddressPipe.to_str([-1, 0, 0, 0])
|
||||
|
||||
|
||||
def test_from_str_valid():
|
||||
assert IPAddressPipe.from_str("192.168.1.1") == [192, 168, 1, 1]
|
||||
assert IPAddressPipe.from_str("0.0.0.0") == [0, 0, 0, 0]
|
||||
assert IPAddressPipe.from_str("255.255.255.255") == [255, 255, 255, 255]
|
||||
assert IPAddressPipe.from_str("127.0.0.1") == [127, 0, 0, 1]
|
||||
|
||||
|
||||
def test_from_str_too_few_parts():
|
||||
with pytest.raises(Exception):
|
||||
IPAddressPipe.from_str("192.168.1")
|
||||
|
||||
|
||||
def test_from_str_too_many_parts():
|
||||
with pytest.raises(Exception):
|
||||
IPAddressPipe.from_str("192.168.1.1.5")
|
||||
|
||||
|
||||
def test_from_str_byte_out_of_range():
|
||||
with pytest.raises(Exception):
|
||||
IPAddressPipe.from_str("256.0.0.0")
|
||||
|
||||
|
||||
def test_from_str_invalid_format():
|
||||
with pytest.raises(Exception):
|
||||
IPAddressPipe.from_str("not.an.ip.addr")
|
||||
|
||||
|
||||
def test_roundtrip():
|
||||
original = [10, 20, 30, 40]
|
||||
assert IPAddressPipe.from_str(IPAddressPipe.to_str(original)) == original
|
||||
35
test/core/property_test.py
Normal file
35
test/core/property_test.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from cpl.core.property import classproperty
|
||||
|
||||
|
||||
class MyClass:
|
||||
_value = 42
|
||||
|
||||
@classproperty
|
||||
def value(cls):
|
||||
return cls._value
|
||||
|
||||
@classproperty
|
||||
def name(cls):
|
||||
return cls.__name__
|
||||
|
||||
|
||||
class Child(MyClass):
|
||||
_value = 99
|
||||
|
||||
|
||||
def test_classproperty_on_class():
|
||||
assert MyClass.value == 42
|
||||
|
||||
|
||||
def test_classproperty_on_instance():
|
||||
obj = MyClass()
|
||||
assert obj.value == 42
|
||||
|
||||
|
||||
def test_classproperty_subclass_inherits_override():
|
||||
assert Child.value == 99
|
||||
|
||||
|
||||
def test_classproperty_returns_class_name():
|
||||
assert MyClass.name == "MyClass"
|
||||
assert Child.name == "Child"
|
||||
118
test/core/service/service_test.py
Normal file
118
test/core/service/service_test.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import asyncio
|
||||
import pytest
|
||||
from cpl.core.service.hosted_service import HostedService
|
||||
from cpl.core.service.startup_task import StartupTask
|
||||
from cpl.core.service.cronjob import CronjobABC
|
||||
from cpl.core.time.cron import Cron
|
||||
|
||||
# --- HostedService ---
|
||||
|
||||
|
||||
def test_hosted_service_cannot_instantiate():
|
||||
with pytest.raises(TypeError):
|
||||
HostedService()
|
||||
|
||||
|
||||
def test_hosted_service_concrete_can_start_stop():
|
||||
class MyService(HostedService):
|
||||
def __init__(self):
|
||||
self.started = False
|
||||
self.stopped = False
|
||||
|
||||
async def start(self):
|
||||
self.started = True
|
||||
|
||||
async def stop(self):
|
||||
self.stopped = True
|
||||
|
||||
async def run():
|
||||
svc = MyService()
|
||||
await svc.start()
|
||||
assert svc.started is True
|
||||
await svc.stop()
|
||||
assert svc.stopped is True
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
# --- StartupTask ---
|
||||
|
||||
|
||||
def test_startup_task_cannot_instantiate():
|
||||
with pytest.raises(TypeError):
|
||||
StartupTask()
|
||||
|
||||
|
||||
def test_startup_task_concrete_runs():
|
||||
class MyTask(StartupTask):
|
||||
def __init__(self):
|
||||
self.ran = False
|
||||
|
||||
async def run(self):
|
||||
self.ran = True
|
||||
|
||||
async def execute():
|
||||
task = MyTask()
|
||||
await task.run()
|
||||
assert task.ran is True
|
||||
|
||||
asyncio.run(execute())
|
||||
|
||||
|
||||
# --- CronjobABC ---
|
||||
|
||||
|
||||
def test_cronjob_cannot_instantiate():
|
||||
with pytest.raises(TypeError):
|
||||
CronjobABC(Cron("* * * * *"))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cronjob_loop_is_called():
|
||||
loop_calls = []
|
||||
|
||||
class MyCronJob(CronjobABC):
|
||||
def __init__(self):
|
||||
CronjobABC.__init__(self, Cron("* * * * *"))
|
||||
|
||||
async def loop(self):
|
||||
loop_calls.append(1)
|
||||
|
||||
job = MyCronJob()
|
||||
assert job._running is False
|
||||
await job.start()
|
||||
assert job._running is True
|
||||
# Give it a moment then stop
|
||||
await asyncio.sleep(0.05)
|
||||
await job.stop()
|
||||
assert job._running is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cronjob_stop_cancels_task():
|
||||
class MyCronJob(CronjobABC):
|
||||
def __init__(self):
|
||||
CronjobABC.__init__(self, Cron("* * * * *"))
|
||||
|
||||
async def loop(self):
|
||||
pass
|
||||
|
||||
job = MyCronJob()
|
||||
await job.start()
|
||||
assert job._task is not None
|
||||
await job.stop()
|
||||
assert job._task.cancelled() or job._task.done()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cronjob_stop_without_start():
|
||||
class MyCronJob(CronjobABC):
|
||||
def __init__(self):
|
||||
CronjobABC.__init__(self, Cron("* * * * *"))
|
||||
|
||||
async def loop(self):
|
||||
pass
|
||||
|
||||
job = MyCronJob()
|
||||
# stop without start should not raise
|
||||
await job.stop()
|
||||
60
test/core/time/cron_test.py
Normal file
60
test/core/time/cron_test.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from cpl.core.time.cron import Cron
|
||||
|
||||
|
||||
def test_next_returns_datetime():
|
||||
cron = Cron("* * * * *")
|
||||
result = cron.next()
|
||||
assert isinstance(result, datetime)
|
||||
|
||||
|
||||
def test_next_is_in_the_future():
|
||||
cron = Cron("* * * * *")
|
||||
result = cron.next()
|
||||
assert result > datetime.now()
|
||||
|
||||
|
||||
def test_next_called_multiple_times_is_monotonic():
|
||||
cron = Cron("* * * * *")
|
||||
results = [cron.next() for _ in range(5)]
|
||||
for i in range(1, len(results)):
|
||||
assert results[i] > results[i - 1]
|
||||
|
||||
|
||||
def test_every_minute_interval():
|
||||
start = datetime(2024, 1, 1, 12, 0, 0)
|
||||
cron = Cron("* * * * *", start_time=start)
|
||||
first = cron.next()
|
||||
second = cron.next()
|
||||
diff = (second - first).total_seconds()
|
||||
assert diff == 60
|
||||
|
||||
|
||||
def test_hourly_interval():
|
||||
start = datetime(2024, 1, 1, 12, 0, 0)
|
||||
cron = Cron("0 * * * *", start_time=start)
|
||||
first = cron.next()
|
||||
second = cron.next()
|
||||
diff = (second - first).total_seconds()
|
||||
assert diff == 3600
|
||||
|
||||
|
||||
def test_daily_midnight():
|
||||
start = datetime(2024, 1, 1, 0, 0, 0)
|
||||
cron = Cron("0 0 * * *", start_time=start)
|
||||
first = cron.next()
|
||||
assert first.hour == 0
|
||||
assert first.minute == 0
|
||||
|
||||
|
||||
def test_custom_start_time():
|
||||
start = datetime(2024, 6, 15, 10, 30, 0)
|
||||
cron = Cron("*/5 * * * *", start_time=start)
|
||||
result = cron.next()
|
||||
assert result > start
|
||||
|
||||
|
||||
def test_invalid_cron_expression():
|
||||
with pytest.raises((ValueError, KeyError)):
|
||||
Cron("invalid expression")
|
||||
44
test/core/time/time_format_settings_test.py
Normal file
44
test/core/time/time_format_settings_test.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
from cpl.core.time.time_format_settings import TimeFormatSettings
|
||||
|
||||
|
||||
def test_defaults_are_none():
|
||||
s = TimeFormatSettings()
|
||||
assert s.date_format is None
|
||||
assert s.time_format is None
|
||||
assert s.date_time_format is None
|
||||
assert s.date_time_log_format is None
|
||||
|
||||
|
||||
def test_constructor_sets_all_fields():
|
||||
s = TimeFormatSettings(
|
||||
date_format="%Y-%m-%d",
|
||||
time_format="%H:%M:%S",
|
||||
date_time_format="%Y-%m-%d %H:%M:%S",
|
||||
date_time_log_format="%Y-%m-%d %H:%M:%S.%f",
|
||||
)
|
||||
assert s.date_format == "%Y-%m-%d"
|
||||
assert s.time_format == "%H:%M:%S"
|
||||
assert s.date_time_format == "%Y-%m-%d %H:%M:%S"
|
||||
assert s.date_time_log_format == "%Y-%m-%d %H:%M:%S.%f"
|
||||
|
||||
|
||||
def test_setters_update_values():
|
||||
s = TimeFormatSettings()
|
||||
s.date_format = "%d/%m/%Y"
|
||||
s.time_format = "%I:%M %p"
|
||||
s.date_time_format = "%d/%m/%Y %I:%M %p"
|
||||
s.date_time_log_format = "%d/%m/%Y %H:%M:%S.%f"
|
||||
assert s.date_format == "%d/%m/%Y"
|
||||
assert s.time_format == "%I:%M %p"
|
||||
assert s.date_time_format == "%d/%m/%Y %I:%M %p"
|
||||
assert s.date_time_log_format == "%d/%m/%Y %H:%M:%S.%f"
|
||||
|
||||
|
||||
def test_partial_construction():
|
||||
# TimeFormatSettings uses constructor args, not option()/from_src()
|
||||
s = TimeFormatSettings(date_format="%Y/%m/%d")
|
||||
assert s.date_format == "%Y/%m/%d"
|
||||
assert s.time_format is None
|
||||
assert s.date_time_format is None
|
||||
assert s.date_time_log_format is None
|
||||
27
test/core/utils/base64_test.py
Normal file
27
test/core/utils/base64_test.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from cpl.core.utils.base64 import Base64
|
||||
|
||||
|
||||
def test_encode():
|
||||
assert Base64.encode("hello world") == "aGVsbG8gd29ybGQ="
|
||||
s = "foobar"
|
||||
assert Base64.encode(s) and isinstance(Base64.encode(s), str)
|
||||
|
||||
|
||||
def test_decode():
|
||||
out = Base64.decode("aGVsbG8gd29ybGQ=")
|
||||
if isinstance(out, bytes):
|
||||
out = out.decode("utf-8")
|
||||
assert out == "hello world"
|
||||
|
||||
orig = "äöü߀"
|
||||
enc = Base64.encode(orig)
|
||||
dec = Base64.decode(enc)
|
||||
if isinstance(dec, bytes):
|
||||
dec = dec.decode("utf-8")
|
||||
assert dec == orig
|
||||
|
||||
|
||||
def test_is_b64():
|
||||
assert Base64.is_b64("Zm9vYmFy") # "foobar"
|
||||
assert Base64.is_b64("Zm8=") # "fo"
|
||||
assert not Base64.is_b64("not_base64??")
|
||||
60
test/core/utils/benchmark_test.py
Normal file
60
test/core/utils/benchmark_test.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
from cpl.core.utils.benchmark import Benchmark
|
||||
|
||||
|
||||
def noop():
|
||||
pass
|
||||
|
||||
|
||||
def test_benchmark_time_does_not_raise(capsys):
|
||||
Benchmark.time("noop", noop, iterations=3)
|
||||
captured = capsys.readouterr()
|
||||
assert "noop" in captured.out
|
||||
assert "min" in captured.out
|
||||
assert "avg" in captured.out
|
||||
|
||||
|
||||
def test_benchmark_memory_does_not_raise(capsys):
|
||||
Benchmark.memory("noop_mem", noop, iterations=3)
|
||||
captured = capsys.readouterr()
|
||||
assert "noop_mem" in captured.out
|
||||
assert "mem" in captured.out
|
||||
|
||||
|
||||
def test_benchmark_all_does_not_raise(capsys):
|
||||
Benchmark.all("noop_all", noop, iterations=3)
|
||||
captured = capsys.readouterr()
|
||||
assert "noop_all" in captured.out
|
||||
assert "min" in captured.out
|
||||
assert "mem" in captured.out
|
||||
|
||||
|
||||
def test_benchmark_time_calls_func():
|
||||
calls = []
|
||||
|
||||
def tracked():
|
||||
calls.append(1)
|
||||
|
||||
Benchmark.time("tracked", tracked, iterations=4)
|
||||
assert len(calls) == 4
|
||||
|
||||
|
||||
def test_benchmark_memory_calls_func():
|
||||
calls = []
|
||||
|
||||
def tracked():
|
||||
calls.append(1)
|
||||
|
||||
Benchmark.memory("tracked_mem", tracked, iterations=3)
|
||||
assert len(calls) == 3
|
||||
|
||||
|
||||
def test_benchmark_all_calls_func_twice_per_iteration():
|
||||
calls = []
|
||||
|
||||
def tracked():
|
||||
calls.append(1)
|
||||
|
||||
Benchmark.all("tracked_all", tracked, iterations=2)
|
||||
# all() runs func once per iteration for time, once per iteration for memory = 2*iterations
|
||||
assert len(calls) == 4
|
||||
125
test/core/utils/cache_test.py
Normal file
125
test/core/utils/cache_test.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import time
|
||||
import pytest
|
||||
from cpl.core.utils.cache import Cache
|
||||
|
||||
|
||||
def test_set_and_get():
|
||||
cache = Cache()
|
||||
cache.set("key1", "value1")
|
||||
assert cache.get("key1") == "value1"
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_get_missing_key_returns_none():
|
||||
cache = Cache()
|
||||
assert cache.get("nonexistent") is None
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_has_existing_key():
|
||||
cache = Cache()
|
||||
cache.set("key", "val")
|
||||
assert cache.has("key") is True
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_has_missing_key():
|
||||
cache = Cache()
|
||||
assert cache.has("missing") is False
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_delete():
|
||||
cache = Cache()
|
||||
cache.set("key", "val")
|
||||
cache.delete("key")
|
||||
assert cache.get("key") is None
|
||||
assert cache.has("key") is False
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_clear():
|
||||
cache = Cache()
|
||||
cache.set("a", 1)
|
||||
cache.set("b", 2)
|
||||
cache.clear()
|
||||
assert cache.get("a") is None
|
||||
assert cache.get("b") is None
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_get_all():
|
||||
cache = Cache()
|
||||
cache.set("x", 10)
|
||||
cache.set("y", 20)
|
||||
values = cache.get_all()
|
||||
assert set(values) == {10, 20}
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_get_all_empty():
|
||||
cache = Cache()
|
||||
assert cache.get_all() == []
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_ttl_expiry():
|
||||
cache = Cache()
|
||||
cache.set("temp", "data", ttl=1)
|
||||
assert cache.get("temp") == "data"
|
||||
time.sleep(1.1)
|
||||
assert cache.get("temp") is None
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_has_after_ttl_expiry():
|
||||
cache = Cache()
|
||||
cache.set("temp", "data", ttl=1)
|
||||
time.sleep(1.1)
|
||||
assert cache.has("temp") is False
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_default_ttl():
|
||||
cache = Cache(default_ttl=1)
|
||||
cache.set("key", "value")
|
||||
assert cache.get("key") == "value"
|
||||
time.sleep(1.1)
|
||||
assert cache.get("key") is None
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_per_key_ttl_overrides_default():
|
||||
cache = Cache(default_ttl=60)
|
||||
cache.set("short", "val", ttl=1)
|
||||
time.sleep(1.1)
|
||||
assert cache.get("short") is None
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_get_all_excludes_expired():
|
||||
cache = Cache()
|
||||
cache.set("permanent", "keep")
|
||||
cache.set("temporary", "gone", ttl=1)
|
||||
time.sleep(1.1)
|
||||
values = cache.get_all()
|
||||
assert "keep" in values
|
||||
assert "gone" not in values
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_overwrite_key():
|
||||
cache = Cache()
|
||||
cache.set("key", "old")
|
||||
cache.set("key", "new")
|
||||
assert cache.get("key") == "new"
|
||||
cache.stop()
|
||||
|
||||
|
||||
def test_cleanup_removes_expired():
|
||||
cache = Cache(cleanup_interval=9999)
|
||||
cache.set("exp", "x", ttl=1)
|
||||
time.sleep(1.1)
|
||||
cache.cleanup()
|
||||
assert cache.get("exp") is None
|
||||
cache.stop()
|
||||
81
test/core/utils/cast_test.py
Normal file
81
test/core/utils/cast_test.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import pytest
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
from cpl.core.utils.cast import cast
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = "red"
|
||||
GREEN = "green"
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
OK = 200
|
||||
ERROR = 500
|
||||
|
||||
|
||||
def test_cast_none_returns_none():
|
||||
assert cast(None, int) is None
|
||||
|
||||
|
||||
def test_cast_bool_true_values():
|
||||
assert cast("true", bool) is True
|
||||
assert cast("YES", bool) is True
|
||||
assert cast("1", bool) is True
|
||||
assert cast("On", bool) is True
|
||||
|
||||
|
||||
def test_cast_bool_false_values():
|
||||
assert cast("false", bool) is False
|
||||
assert cast("0", bool) is False
|
||||
assert cast("no", bool) is False
|
||||
assert cast("off", bool) is False
|
||||
|
||||
|
||||
def test_cast_list_of_str_with_brackets():
|
||||
assert cast("[a,b,c]", List[str]) == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_cast_list_of_str_with_delimiter_no_brackets():
|
||||
assert cast("a,b,c", List[str]) == ["a", "b", "c"]
|
||||
|
||||
|
||||
def test_cast_list_of_int_with_brackets():
|
||||
assert cast("[1,2,3]", List[int]) == [1, 2, 3]
|
||||
|
||||
|
||||
def test_cast_list_custom_delimiter():
|
||||
assert cast("1;2;3", List[int], list_delimiter=";") == [1, 2, 3]
|
||||
|
||||
|
||||
def test_cast_list_without_brackets_or_delimiter_errors():
|
||||
with pytest.raises(ValueError):
|
||||
cast("abc", List[str])
|
||||
|
||||
|
||||
def test_cast_enum_by_value_case_insensitive():
|
||||
assert cast("red", Color) is Color.RED
|
||||
assert cast("RED", Color) is Color.RED
|
||||
assert cast("Green", Color) is Color.GREEN
|
||||
|
||||
|
||||
def test_cast_enum_by_name_case_insensitive():
|
||||
assert cast("OK", Status) is Status.OK
|
||||
assert cast("ok", Status) is Status.OK
|
||||
assert cast("ERROR", Status) is Status.ERROR
|
||||
|
||||
|
||||
def test_cast_enum_invalid_raises():
|
||||
with pytest.raises(ValueError) as e:
|
||||
cast("unknown", Color)
|
||||
assert "Cannot cast value 'unknown' to enum 'Color'" in str(e.value)
|
||||
|
||||
|
||||
def test_cast_primitives():
|
||||
assert cast("42", int) == 42
|
||||
assert cast("3.14", float) == 3.14
|
||||
assert cast("abc", str) == "abc"
|
||||
|
||||
|
||||
def test_cast_list_without_subtype_defaults_to_str():
|
||||
assert cast("a,b", list) == ["a", "b"]
|
||||
18
test/core/utils/credential_manager_test.py
Normal file
18
test/core/utils/credential_manager_test.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
from cpl.core.utils.credential_manager import CredentialManager
|
||||
|
||||
|
||||
def test_encrypt_decrypt_roundtrip(tmp_path):
|
||||
secret_path = tmp_path / ".secret"
|
||||
secret_path.write_text(Fernet.generate_key().decode())
|
||||
|
||||
CredentialManager.with_secret(str(secret_path))
|
||||
|
||||
plaintext = "hello-world"
|
||||
token = CredentialManager.encrypt(plaintext)
|
||||
assert isinstance(token, str)
|
||||
assert token != plaintext
|
||||
|
||||
decrypted = CredentialManager.decrypt(token)
|
||||
assert decrypted == plaintext
|
||||
64
test/core/utils/get_value_test.py
Normal file
64
test/core/utils/get_value_test.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import pytest
|
||||
from typing import List
|
||||
from cpl.core.utils.get_value import get_value
|
||||
|
||||
|
||||
def test_get_existing_str():
|
||||
assert get_value({"key": "hello"}, "key", str) == "hello"
|
||||
|
||||
|
||||
def test_get_existing_int_already_typed():
|
||||
# Value already has the correct type -> returned directly
|
||||
assert get_value({"count": 42}, "count", int) == 42
|
||||
|
||||
|
||||
def test_get_existing_float_already_typed():
|
||||
# Value already has the correct type -> returned directly
|
||||
assert get_value({"pi": 3.14}, "pi", float) == 3.14
|
||||
|
||||
|
||||
# NOTE: get_value calls cast() internally but does NOT return the cast result
|
||||
# (the return value is lost). String "42" -> int returns None instead of 42.
|
||||
# This is a known bug in get_value.
|
||||
def test_get_str_to_int_returns_none_bug():
|
||||
result = get_value({"count": "42"}, "count", int)
|
||||
assert result is None # Bug: should be 42
|
||||
|
||||
|
||||
def test_get_missing_key_returns_none():
|
||||
assert get_value({}, "missing", str) is None
|
||||
|
||||
|
||||
def test_get_missing_key_returns_default():
|
||||
assert get_value({}, "missing", str, "fallback") == "fallback"
|
||||
|
||||
|
||||
def test_get_value_already_correct_type():
|
||||
assert get_value({"x": 5}, "x", int) == 5
|
||||
|
||||
|
||||
def test_get_list_already_correct_type():
|
||||
result = get_value({"items": [1, 2, 3]}, "items", List[int])
|
||||
assert result == [1, 2, 3]
|
||||
|
||||
|
||||
def test_get_list_of_str_already_correct_type():
|
||||
result = get_value({"tags": ["a", "b"]}, "tags", List[str])
|
||||
assert result == ["a", "b"]
|
||||
|
||||
|
||||
def test_get_bool_already_typed():
|
||||
# Value is already bool -> returned directly
|
||||
assert get_value({"flag": True}, "flag", bool) is True
|
||||
assert get_value({"flag": False}, "flag", bool) is False
|
||||
|
||||
|
||||
# NOTE: Same bug – string "true" -> bool returns None
|
||||
def test_get_bool_from_str_returns_none_bug():
|
||||
result = get_value({"flag": "true"}, "flag", bool)
|
||||
assert result is None # Bug: should be True
|
||||
|
||||
|
||||
def test_cast_failure_returns_default():
|
||||
result = get_value({"val": "not_a_number"}, "val", int, -1)
|
||||
assert result == -1
|
||||
93
test/core/utils/json_processor_test.py
Normal file
93
test/core/utils/json_processor_test.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import pytest
|
||||
from enum import Enum
|
||||
from cpl.core.utils.json_processor import JSONProcessor
|
||||
|
||||
|
||||
class Color(Enum):
|
||||
RED = "RED"
|
||||
GREEN = "GREEN"
|
||||
|
||||
|
||||
class Point:
|
||||
def __init__(self, x: int, y: int):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
|
||||
class Named:
|
||||
def __init__(self, name: str, value: int = 0):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
|
||||
class Nested:
|
||||
def __init__(self, point: Point, label: str):
|
||||
self.point = point
|
||||
self.label = label
|
||||
|
||||
|
||||
class WithEnum:
|
||||
def __init__(self, color: Color):
|
||||
self.color = color
|
||||
|
||||
|
||||
def test_simple_object():
|
||||
obj = JSONProcessor.process(Point, {"X": 3, "Y": 7})
|
||||
assert obj.x == 3
|
||||
assert obj.y == 7
|
||||
|
||||
|
||||
def test_camel_case_keys():
|
||||
obj = JSONProcessor.process(Named, {"Name": "Alice", "Value": 42})
|
||||
assert obj.name == "Alice"
|
||||
assert obj.value == 42
|
||||
|
||||
|
||||
def test_default_value_used():
|
||||
obj = JSONProcessor.process(Named, {"Name": "Bob"})
|
||||
assert obj.name == "Bob"
|
||||
assert obj.value == 0
|
||||
|
||||
|
||||
def test_none_for_missing_required():
|
||||
obj = JSONProcessor.process(Named, {})
|
||||
assert obj.name is None
|
||||
|
||||
|
||||
def test_nested_object():
|
||||
data = {"Point": {"X": 1, "Y": 2}, "Label": "origin"}
|
||||
obj = JSONProcessor.process(Nested, data)
|
||||
assert isinstance(obj.point, Point)
|
||||
assert obj.point.x == 1
|
||||
assert obj.point.y == 2
|
||||
assert obj.label == "origin"
|
||||
|
||||
|
||||
def test_enum_value():
|
||||
obj = JSONProcessor.process(WithEnum, {"Color": "RED"})
|
||||
assert obj.color == Color.RED
|
||||
|
||||
|
||||
def test_first_lower_key():
|
||||
obj = JSONProcessor.process(Named, {"name": "Charlie", "value": 7})
|
||||
assert obj.name == "Charlie"
|
||||
assert obj.value == 7
|
||||
|
||||
|
||||
def test_upper_case_key():
|
||||
obj = JSONProcessor.process(Named, {"NAME": "Dave"})
|
||||
assert obj.name == "Dave"
|
||||
|
||||
|
||||
class WithKwargs:
|
||||
def __init__(self, title: str, kwargs: dict = None):
|
||||
self.title = title
|
||||
self.kwargs = kwargs or {}
|
||||
|
||||
|
||||
def test_kwargs_collected():
|
||||
# Bug: JSONProcessor collects leftover keys into a dict for a `kwargs: dict` param,
|
||||
# but then passes them as **kwargs to the constructor instead of as a positional dict arg.
|
||||
# This causes TypeError when the constructor does not accept **kwargs.
|
||||
with pytest.raises(TypeError):
|
||||
JSONProcessor.process(WithKwargs, {"Title": "Hello", "extra1": "a", "extra2": "b"})
|
||||
38
test/core/utils/number_test.py
Normal file
38
test/core/utils/number_test.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from cpl.core.utils.number import Number
|
||||
|
||||
|
||||
def test_is_number():
|
||||
assert Number.is_number(10) is True
|
||||
assert Number.is_number(3.14) is True
|
||||
assert Number.is_number("42") is True
|
||||
assert Number.is_number("3.14") is True
|
||||
assert Number.is_number("-10") is True
|
||||
assert Number.is_number("+5.5") is True
|
||||
assert Number.is_number("abc") is False
|
||||
assert Number.is_number("") is False
|
||||
assert Number.is_number(None) is False
|
||||
assert Number.is_number(True) is False
|
||||
|
||||
|
||||
def test_to_number():
|
||||
assert Number.to_number("42") == 42
|
||||
assert Number.to_number("3.14") == 3.14
|
||||
assert Number.to_number(10) == 10
|
||||
assert Number.to_number(3.14) == 3.14
|
||||
assert Number.to_number("-7") == -7
|
||||
assert Number.to_number("+8.2") == 8.2
|
||||
|
||||
# Optional: define how errors behave
|
||||
try:
|
||||
Number.to_number("abc")
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected ValueError for non-numeric input"
|
||||
|
||||
try:
|
||||
Number.to_number(None)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
else:
|
||||
assert False, "Expected exception for None input"
|
||||
73
test/core/utils/string_test.py
Normal file
73
test/core/utils/string_test.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import pytest
|
||||
from cpl.core.utils import String
|
||||
|
||||
|
||||
def test_to_camel_case():
|
||||
assert String.to_camel_case("hello world") == "helloWorld"
|
||||
assert String.to_camel_case("hello-world") == "helloWorld"
|
||||
assert String.to_camel_case("hello_world") == "helloWorld"
|
||||
assert String.to_camel_case("HelloWorld") == "helloWorld"
|
||||
assert String.to_camel_case("hello world") == "helloWorld"
|
||||
assert String.to_camel_case("") == ""
|
||||
assert String.to_camel_case(" ") == ""
|
||||
assert String.to_camel_case("123-abc") == "123Abc"
|
||||
|
||||
|
||||
def test_to_pascal_case():
|
||||
assert String.to_pascal_case("hello world") == "HelloWorld"
|
||||
assert String.to_pascal_case("hello-world") == "HelloWorld"
|
||||
assert String.to_pascal_case("hello_world") == "HelloWorld"
|
||||
assert String.to_pascal_case("helloWorld") == "HelloWorld"
|
||||
assert String.to_pascal_case("") == ""
|
||||
assert String.to_pascal_case(" ") == ""
|
||||
assert String.to_pascal_case("123-abc") == "123Abc"
|
||||
|
||||
|
||||
def test_to_snake_case():
|
||||
assert String.to_snake_case("HelloWorld") == "hello_world"
|
||||
assert String.to_snake_case("helloWorld") == "hello_world"
|
||||
assert String.to_snake_case("hello-world") == "hello_world"
|
||||
assert String.to_snake_case("hello_world") == "hello_world"
|
||||
assert String.to_snake_case("hello world") == "hello_world"
|
||||
assert String.to_snake_case("ABC123") == "abc123"
|
||||
|
||||
|
||||
def test_first_to_upper():
|
||||
assert String.first_to_upper("hello") == "Hello"
|
||||
assert String.first_to_upper("Hello") == "Hello"
|
||||
assert String.first_to_upper("") == ""
|
||||
assert String.first_to_upper("a") == "A"
|
||||
assert String.first_to_upper("123abc") == "123abc"
|
||||
|
||||
|
||||
def test_first_to_lower():
|
||||
assert String.first_to_lower("Hello") == "hello"
|
||||
assert String.first_to_lower("hello") == "hello"
|
||||
assert String.first_to_lower("") == ""
|
||||
assert String.first_to_lower("A") == "a"
|
||||
assert String.first_to_lower("123ABC") == "123ABC"
|
||||
|
||||
|
||||
def test_random_length():
|
||||
random_str = String.random(10)
|
||||
assert len(random_str) == 10
|
||||
|
||||
|
||||
def test_random_only_letters():
|
||||
random_str = String.random(20, letters=True, digits=False, special_characters=False)
|
||||
assert all(c.isalpha() for c in random_str)
|
||||
|
||||
|
||||
def test_random_only_digits():
|
||||
random_str = String.random(20, letters=False, digits=True, special_characters=False)
|
||||
assert all(c.isdigit() for c in random_str)
|
||||
|
||||
|
||||
def test_random_letters_and_digits():
|
||||
random_str = String.random(20, letters=True, digits=True, special_characters=False)
|
||||
assert all(c.isalnum() for c in random_str)
|
||||
|
||||
|
||||
def test_random_no_characters():
|
||||
with pytest.raises(Exception):
|
||||
String.random(10, letters=False, digits=False, special_characters=False)
|
||||
26
test/cpl.project.json
Normal file
26
test/cpl.project.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "test",
|
||||
"version": "0.1.0",
|
||||
"type": "unittest",
|
||||
"license": "",
|
||||
"author": "",
|
||||
"description": "",
|
||||
"homepage": "",
|
||||
"keywords": [],
|
||||
"dependencies": {
|
||||
"cpl-core": "~2024.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cpl-cli": ">2024.7.0",
|
||||
"pytest": "~8.4.2",
|
||||
"pytest-mock": "~3.15.1",
|
||||
"pytest-asyncio": "~1.2.0"
|
||||
},
|
||||
"references": [],
|
||||
"main": null,
|
||||
"directory": "./",
|
||||
"build": {
|
||||
"include": [],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from cpl.application import ApplicationABC
|
||||
from cpl.core.configuration import ConfigurationABC
|
||||
from cpl.dependency import ServiceProvider
|
||||
from unittests_cli.cli_test_suite import CLITestSuite
|
||||
from unittests_core.core_test_suite import CoreTestSuite
|
||||
from unittests_query.query_test_suite import QueryTestSuite
|
||||
from unittests_translation.translation_test_suite import TranslationTestSuite
|
||||
|
||||
|
||||
class Application(ApplicationABC):
|
||||
def __init__(self, config: ConfigurationABC, services: ServiceProvider):
|
||||
ApplicationABC.__init__(self, config, services)
|
||||
|
||||
def configure(self): ...
|
||||
|
||||
def main(self):
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(CoreTestSuite())
|
||||
runner.run(CLITestSuite())
|
||||
runner.run(QueryTestSuite())
|
||||
runner.run(TranslationTestSuite())
|
||||
@@ -1,11 +0,0 @@
|
||||
from cpl.application import ApplicationBuilder
|
||||
from unittests.application import Application
|
||||
|
||||
|
||||
def main():
|
||||
app_builder = ApplicationBuilder(Application)
|
||||
app_builder.build().run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"Project": {
|
||||
"Name": "unittests",
|
||||
"Version": {
|
||||
"Major": "2024",
|
||||
"Minor": "7",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=2024.6.2024.07.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": [],
|
||||
"DevDependencies": []
|
||||
},
|
||||
"Build": {
|
||||
"ProjectType": "unittest",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "unittest.main",
|
||||
"EntryPoint": "unittest",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
import unittest
|
||||
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
|
||||
|
||||
class CommandTestCase(unittest.TestCase):
|
||||
_skip_tear_down = False
|
||||
_cwd = os.getcwd()
|
||||
|
||||
def __init__(self, method_name: str):
|
||||
unittest.TestCase.__init__(self, method_name)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
try:
|
||||
if os.path.exists(PLAYGROUND_PATH):
|
||||
shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH)))
|
||||
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
except Exception as e:
|
||||
print(f"Setup of {__name__} failed: {traceback.format_exc()}")
|
||||
|
||||
def setUp(self):
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
if cls._skip_tear_down:
|
||||
return
|
||||
try:
|
||||
os.chdir(cls._cwd)
|
||||
if os.path.exists(PLAYGROUND_PATH):
|
||||
shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH)))
|
||||
except Exception as e:
|
||||
print(f"Cleanup of {__name__} failed: {traceback.format_exc()}")
|
||||
@@ -1,42 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class AddTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "add-test-project"
|
||||
self._target = "add-test-library"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
|
||||
def _get_project_settings(self):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def setUp(self):
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
CLICommands.new("library", self._target, "--ab", "--s")
|
||||
|
||||
def test_add(self):
|
||||
CLICommands.add(self._source, self._target)
|
||||
settings = self._get_project_settings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("ProjectReferences", settings["BuildSettings"])
|
||||
self.assertIn("BuildSettings", settings)
|
||||
self.assertIn(
|
||||
f"../{String.to_snake_case(self._target)}/{self._target}.json",
|
||||
settings["BuildSettings"]["ProjectReferences"],
|
||||
)
|
||||
@@ -1,86 +0,0 @@
|
||||
import filecmp
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class BuildTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "build-test-source"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
|
||||
def _get_project_settings(self):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def _save_project_settings(self, settings: dict):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "w", encoding="utf-8") as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
|
||||
def setUp(self):
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
|
||||
def _are_dir_trees_equal(self, dir1, dir2):
|
||||
"""
|
||||
found at https://stackoverflow.com/questions/4187564/recursively-compare-two-directories-to-ensure-they-have-the-same-files-and-subdi
|
||||
|
||||
Compare two directories recursively. Files in each directory are
|
||||
assumed to be equal if their names and contents are equal.
|
||||
|
||||
@param dir1: First directory path
|
||||
@param dir2: Second directory path
|
||||
|
||||
@return: True if the directory trees are the same and
|
||||
there were no errors while accessing the directories or files,
|
||||
False otherwise.
|
||||
"""
|
||||
|
||||
dirs_cmp = filecmp.dircmp(dir1, dir2)
|
||||
if len(dirs_cmp.left_only) > 0 or len(dirs_cmp.right_only) > 0 or len(dirs_cmp.funny_files) > 0:
|
||||
return False
|
||||
|
||||
(_, mismatch, errors) = filecmp.cmpfiles(dir1, dir2, dirs_cmp.common_files, shallow=False)
|
||||
|
||||
if len(mismatch) > 0 or len(errors) > 0:
|
||||
return False
|
||||
|
||||
for common_dir in dirs_cmp.common_dirs:
|
||||
new_dir1 = os.path.join(dir1, common_dir)
|
||||
new_dir2 = os.path.join(dir2, common_dir)
|
||||
if not self._are_dir_trees_equal(new_dir1, new_dir2):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_build(self):
|
||||
CLICommands.build()
|
||||
dist_path = "./dist"
|
||||
full_dist_path = f"{dist_path}/{self._source}/build/{String.to_snake_case(self._source)}"
|
||||
self.assertTrue(os.path.exists(dist_path))
|
||||
self.assertTrue(os.path.exists(full_dist_path))
|
||||
self.assertFalse(
|
||||
self._are_dir_trees_equal(f"./src/{String.to_snake_case(self._source)}", full_dist_path)
|
||||
)
|
||||
with open(f"{full_dist_path}/{self._source}.json", "w") as file:
|
||||
file.write(json.dumps(self._get_project_settings(), indent=2))
|
||||
file.close()
|
||||
self.assertTrue(
|
||||
self._are_dir_trees_equal(f"./src/{String.to_snake_case(self._source)}", full_dist_path)
|
||||
)
|
||||
@@ -1,82 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
import unittest
|
||||
from typing import Optional
|
||||
from unittest import TestResult
|
||||
|
||||
from unittests_cli.add_test_case import AddTestCase
|
||||
from unittests_cli.build_test_case import BuildTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_cli.generate_test_case import GenerateTestCase
|
||||
from unittests_cli.install_test_case import InstallTestCase
|
||||
from unittests_cli.new_test_case import NewTestCase
|
||||
from unittests_cli.publish_test_case import PublishTestCase
|
||||
from unittests_cli.remove_test_case import RemoveTestCase
|
||||
from unittests_cli.run_test_case import RunTestCase
|
||||
from unittests_cli.start_test_case import StartTestCase
|
||||
from unittests_cli.uninstall_test_case import UninstallTestCase
|
||||
from unittests_cli.update_test_case import UpdateTestCase
|
||||
from unittests_cli.version_test_case import VersionTestCase
|
||||
|
||||
|
||||
class CLITestSuite(unittest.TestSuite):
|
||||
def __init__(self):
|
||||
unittest.TestSuite.__init__(self)
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
self._result: Optional[TestResult] = None
|
||||
self._is_online = True
|
||||
|
||||
active_tests = [
|
||||
# nothing needed
|
||||
VersionTestCase,
|
||||
NewTestCase,
|
||||
GenerateTestCase,
|
||||
# project needed
|
||||
BuildTestCase,
|
||||
PublishTestCase,
|
||||
RunTestCase,
|
||||
StartTestCase,
|
||||
# workspace needed
|
||||
AddTestCase,
|
||||
RemoveTestCase,
|
||||
]
|
||||
|
||||
if self._is_online:
|
||||
active_tests.append(InstallTestCase)
|
||||
active_tests.append(UninstallTestCase)
|
||||
active_tests.append(UpdateTestCase)
|
||||
|
||||
for test in active_tests:
|
||||
self.addTests(loader.loadTestsFromTestCase(test))
|
||||
|
||||
def _setup(self):
|
||||
try:
|
||||
if os.path.exists(PLAYGROUND_PATH):
|
||||
shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH)))
|
||||
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
except Exception as e:
|
||||
print(f"Setup of {__name__} failed: {traceback.format_exc()}")
|
||||
|
||||
def _cleanup(self):
|
||||
try:
|
||||
if self._result is not None and (len(self._result.errors) > 0 or len(self._result.failures) > 0):
|
||||
return
|
||||
|
||||
if os.path.exists(PLAYGROUND_PATH):
|
||||
shutil.rmtree(os.path.abspath(os.path.join(PLAYGROUND_PATH)))
|
||||
except Exception as e:
|
||||
print(f"Cleanup of {__name__} failed: {traceback.format_exc()}")
|
||||
|
||||
def run(self, *args):
|
||||
self._setup()
|
||||
self._result = super().run(*args)
|
||||
self._cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(CLITestSuite())
|
||||
@@ -1,9 +0,0 @@
|
||||
import os
|
||||
|
||||
base = ""
|
||||
if not os.getcwd().endswith("unittests"):
|
||||
base = "../"
|
||||
|
||||
PLAYGROUND_PATH = os.path.abspath(os.path.join(os.getcwd(), f"{base}test_cli_playground"))
|
||||
TRANSLATION_PATH = os.path.abspath(os.path.join(os.getcwd(), f"{base}unittests_translation"))
|
||||
CLI_PATH = os.path.abspath(os.path.join(os.getcwd(), f"{base}../src/cpl_cli/main.py"))
|
||||
@@ -1,7 +0,0 @@
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
|
||||
|
||||
class CustomTestCase(CommandTestCase):
|
||||
def setUp(self): ...
|
||||
|
||||
def test_equal(self): ...
|
||||
@@ -1,100 +0,0 @@
|
||||
import os.path
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class GenerateTestCase(CommandTestCase):
|
||||
_project = "test-console"
|
||||
_t_path = "test"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
CommandTestCase.setUpClass()
|
||||
CLICommands.new("console", cls._project, "--ab", "--s", "--venv")
|
||||
|
||||
def setUp(self):
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
|
||||
def _test_file(self, schematic: str, suffix: str, path=None):
|
||||
file = f'GeneratedFile{"OnReady" if schematic == "event" else ""}'
|
||||
expected_path = f'generated_file{"_on_ready" if schematic == "event" else ""}{suffix}.py'
|
||||
|
||||
if path is not None:
|
||||
file = f"{path}/{file}"
|
||||
expected_path = f"{path}/{expected_path}"
|
||||
|
||||
CLICommands.generate(schematic, file)
|
||||
file_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, expected_path))
|
||||
file_exists = os.path.exists(file_path)
|
||||
self.assertTrue(file_exists)
|
||||
|
||||
def _test_file_with_project(self, schematic: str, suffix: str, path=None, enter=True):
|
||||
file = f'GeneratedFile{"OnReady" if schematic == "event" else ""}'
|
||||
excepted_path = f'generated_file{"_on_ready" if schematic == "event" else ""}{suffix}.py'
|
||||
if path is not None:
|
||||
excepted_path = f'{self._project}/src/{String.to_snake_case(self._project)}/{path}/generated_file_in_project{"_on_ready" if schematic == "event" else ""}{suffix}.py'
|
||||
if enter:
|
||||
os.chdir(path)
|
||||
excepted_path = f'{path}/src/{String.to_snake_case(self._project)}/generated_file_in_project{"_on_ready" if schematic == "event" else ""}{suffix}.py'
|
||||
|
||||
file = f'{path}/GeneratedFileInProject{"OnReady" if schematic == "event" else ""}'
|
||||
|
||||
CLICommands.generate(schematic, file)
|
||||
file_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, excepted_path))
|
||||
self.assertTrue(os.path.exists(file_path))
|
||||
|
||||
def test_abc(self):
|
||||
self._test_file("abc", "_abc")
|
||||
self._test_file("abc", "_abc", path=self._t_path)
|
||||
self._test_file("abc", "_abc", path=f"{self._t_path}/{self._t_path}")
|
||||
self._test_file_with_project("abc", "_abc", path=self._project)
|
||||
os.chdir(f"src/{String.to_snake_case(self._project)}")
|
||||
self._test_file_with_project("abc", "_abc", path="test", enter=False)
|
||||
|
||||
def test_class(self):
|
||||
self._test_file("class", "")
|
||||
self._test_file("class", "", path=self._t_path)
|
||||
self._test_file_with_project("class", "", path=self._project)
|
||||
|
||||
def test_enum(self):
|
||||
self._test_file("enum", "_enum")
|
||||
self._test_file("enum", "_enum", path=self._t_path)
|
||||
self._test_file_with_project("enum", "_enum", path=self._project)
|
||||
os.chdir(f"src/{String.to_snake_case(self._project)}")
|
||||
self._test_file_with_project("enum", "_enum", path="test", enter=False)
|
||||
|
||||
def test_pipe(self):
|
||||
self._test_file("pipe", "_pipe")
|
||||
self._test_file("pipe", "_pipe", path=self._t_path)
|
||||
self._test_file_with_project("pipe", "_pipe", path=self._project)
|
||||
|
||||
def test_service(self):
|
||||
self._test_file("service", "_service")
|
||||
self._test_file_with_project("service", "_service", path=self._project)
|
||||
|
||||
def test_settings(self):
|
||||
self._test_file("settings", "_settings")
|
||||
self._test_file_with_project("settings", "_settings", path=self._project)
|
||||
|
||||
def test_test_case(self):
|
||||
self._test_file("test-case", "_test_case")
|
||||
self._test_file_with_project("test-case", "_test_case", path=self._project)
|
||||
|
||||
def test_thread(self):
|
||||
self._test_file("thread", "_thread")
|
||||
self._test_file_with_project("thread", "_thread", path=self._project)
|
||||
|
||||
def test_validator(self):
|
||||
self._test_file("validator", "_validator")
|
||||
self._test_file_with_project("validator", "_validator", path=self._project)
|
||||
|
||||
def test_discord_command(self):
|
||||
self._test_file("command", "_command")
|
||||
self._test_file_with_project("command", "_command", path=self._project)
|
||||
|
||||
def test_discord_event(self):
|
||||
self._test_file("event", "_event")
|
||||
self._test_file_with_project("event", "_event", path=self._project)
|
||||
@@ -1,108 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class InstallTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "install-test-source"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
|
||||
def _get_project_settings(self):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def _save_project_settings(self, settings: dict):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "w", encoding="utf-8") as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
|
||||
def setUp(self):
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s", "--venv")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
|
||||
def _get_installed_packages(self) -> dict:
|
||||
reqs = subprocess.check_output([os.path.join(os.getcwd(), "venv/bin/python"), "-m", "pip", "freeze"])
|
||||
return dict([tuple(r.decode().split("==")) for r in reqs.split()])
|
||||
|
||||
def test_install_package(self):
|
||||
version = "1.7.3"
|
||||
package_name = "discord.py"
|
||||
package = f"{package_name}=={version}"
|
||||
CLICommands.install(package)
|
||||
settings = self._get_project_settings()
|
||||
|
||||
with self.subTest(msg="Project deps"):
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertIn(package, settings["ProjectSettings"]["Dependencies"])
|
||||
|
||||
with self.subTest(msg="PIP"):
|
||||
packages = self._get_installed_packages()
|
||||
self.assertIn(package_name, packages)
|
||||
self.assertEqual(version, packages[package_name])
|
||||
|
||||
def test_dev_install_package(self):
|
||||
version = "1.7.3"
|
||||
package_name = "discord.py"
|
||||
package = f"{package_name}=={version}"
|
||||
CLICommands.install(package, is_dev=True)
|
||||
settings = self._get_project_settings()
|
||||
|
||||
with self.subTest(msg="Project deps"):
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertIn("DevDependencies", settings["ProjectSettings"])
|
||||
self.assertNotIn(package, settings["ProjectSettings"]["Dependencies"])
|
||||
self.assertIn(package, settings["ProjectSettings"]["DevDependencies"])
|
||||
|
||||
with self.subTest(msg="PIP"):
|
||||
packages = self._get_installed_packages()
|
||||
self.assertIn(package_name, packages)
|
||||
self.assertEqual(version, packages[package_name])
|
||||
|
||||
def _test_install_all(self):
|
||||
version = "1.7.3"
|
||||
package_name = "discord.py"
|
||||
package = f"{package_name}=={version}"
|
||||
settings = self._get_project_settings()
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertIn("DevDependencies", settings["ProjectSettings"])
|
||||
self.assertNotIn(package, settings["ProjectSettings"]["Dependencies"])
|
||||
self.assertIn("DevDependencies", settings["ProjectSettings"])
|
||||
self.assertNotIn(package, settings["ProjectSettings"]["Dependencies"])
|
||||
settings["ProjectSettings"]["Dependencies"].append(package)
|
||||
settings["ProjectSettings"]["DevDependencies"].append(package)
|
||||
self._save_project_settings(settings)
|
||||
CLICommands.install()
|
||||
new_settings = self._get_project_settings()
|
||||
self.assertEqual(settings, new_settings)
|
||||
self.assertIn("ProjectSettings", new_settings)
|
||||
self.assertIn("Dependencies", new_settings["ProjectSettings"])
|
||||
self.assertIn("DevDependencies", new_settings["ProjectSettings"])
|
||||
self.assertIn(package, new_settings["ProjectSettings"]["Dependencies"])
|
||||
self.assertIn(package, new_settings["ProjectSettings"]["DevDependencies"])
|
||||
packages = self._get_installed_packages()
|
||||
self.assertIn(package_name, packages)
|
||||
self.assertEqual(version, packages[package_name])
|
||||
@@ -1,191 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class NewTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
|
||||
def _test_project(self, project_type: str, name: str, *args, test_venv=False, without_ws=False):
|
||||
CLICommands.new(project_type, name, *args, output=False)
|
||||
workspace_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, name))
|
||||
self.assertTrue(os.path.exists(workspace_path))
|
||||
if test_venv:
|
||||
with self.subTest(msg="Venv exists"):
|
||||
self.assertTrue(os.path.exists(os.path.join(workspace_path, "venv")))
|
||||
self.assertTrue(os.path.exists(os.path.join(workspace_path, "venv/bin")))
|
||||
self.assertTrue(os.path.exists(os.path.join(workspace_path, "venv/bin/python")))
|
||||
self.assertTrue(os.path.islink(os.path.join(workspace_path, "venv/bin/python")))
|
||||
|
||||
base = "src"
|
||||
if "--base" in args and "/" in name:
|
||||
base = name.split("/")[0]
|
||||
name = name.replace(f'{name.split("/")[0]}/', "")
|
||||
|
||||
project_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, name, base, String.to_snake_case(name)))
|
||||
if without_ws:
|
||||
project_path = os.path.abspath(
|
||||
os.path.join(PLAYGROUND_PATH, base, name, "src/", String.to_snake_case(name))
|
||||
)
|
||||
|
||||
with self.subTest(msg="Project json exists"):
|
||||
self.assertTrue(os.path.exists(project_path))
|
||||
self.assertTrue(os.path.exists(os.path.join(project_path, f"{name}.json")))
|
||||
|
||||
if project_type == "library":
|
||||
with self.subTest(msg="Library class1 exists"):
|
||||
self.assertTrue(os.path.exists(os.path.join(project_path, f"class1.py")))
|
||||
return
|
||||
|
||||
with self.subTest(msg="Project main.py exists"):
|
||||
self.assertTrue(os.path.exists(os.path.join(project_path, f"main.py")))
|
||||
|
||||
with self.subTest(msg="Application base"):
|
||||
if "--ab" in args:
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"application.py")))
|
||||
else:
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"application.py")))
|
||||
|
||||
# s depends on ab
|
||||
with self.subTest(msg="Startup"):
|
||||
if "--ab" in args and "--s" in args:
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"startup.py")))
|
||||
else:
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"startup.py")))
|
||||
|
||||
with self.subTest(msg="Unittest"):
|
||||
if project_type == "unittest":
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"test_case.py")))
|
||||
else:
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"test_case.py")))
|
||||
|
||||
with self.subTest(msg="Discord"):
|
||||
if project_type == "discord-bot":
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"events/__init__.py")))
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"events/on_ready_event.py")))
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"commands/__init__.py")))
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"commands/ping_command.py")))
|
||||
else:
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"events/on_ready_event.py")))
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"commands/ping_command.py")))
|
||||
|
||||
def _test_sub_project(self, project_type: str, name: str, workspace_name: str, *args, test_venv=False):
|
||||
os.chdir(os.path.abspath(os.path.join(os.getcwd(), workspace_name)))
|
||||
CLICommands.new(project_type, name, *args)
|
||||
workspace_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, workspace_name))
|
||||
self.assertTrue(os.path.exists(workspace_path))
|
||||
if test_venv:
|
||||
self.assertTrue(os.path.exists(os.path.join(workspace_path, "venv")))
|
||||
self.assertTrue(os.path.exists(os.path.join(workspace_path, "venv/bin")))
|
||||
self.assertTrue(os.path.exists(os.path.join(workspace_path, "venv/bin/python")))
|
||||
self.assertTrue(os.path.islink(os.path.join(workspace_path, "venv/bin/python")))
|
||||
|
||||
base = "src"
|
||||
if "--base" in args and "/" in name:
|
||||
base = name.split("/")[0]
|
||||
name = name.replace(f'{name.split("/")[0]}/', "")
|
||||
|
||||
project_path = os.path.abspath(
|
||||
os.path.join(PLAYGROUND_PATH, workspace_name, base, String.to_snake_case(name))
|
||||
)
|
||||
self.assertTrue(os.path.exists(project_path))
|
||||
self.assertTrue(os.path.join(project_path, f"{name}.json"))
|
||||
|
||||
if project_type == "discord-bot":
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"events/__init__.py")))
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"events/on_ready_event.py")))
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"commands/__init__.py")))
|
||||
self.assertTrue(os.path.isfile(os.path.join(project_path, f"commands/ping_command.py")))
|
||||
else:
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"events/on_ready_event.py")))
|
||||
self.assertFalse(os.path.isfile(os.path.join(project_path, f"commands/ping_command.py")))
|
||||
|
||||
def _test_sub_directory_project(self, project_type: str, directory: str, name: str, workspace_name: str, *args):
|
||||
os.chdir(os.path.abspath(os.path.join(os.getcwd(), workspace_name)))
|
||||
CLICommands.new(project_type, f"{directory}/{name}", *args)
|
||||
workspace_path = os.path.abspath(os.path.join(PLAYGROUND_PATH, workspace_name))
|
||||
self.assertTrue(os.path.exists(workspace_path))
|
||||
|
||||
project_path = os.path.abspath(
|
||||
os.path.join(PLAYGROUND_PATH, workspace_name, f"src/{directory}", String.to_snake_case(name))
|
||||
)
|
||||
self.assertTrue(os.path.exists(project_path))
|
||||
project_file = os.path.join(project_path, f"{name}.json")
|
||||
self.assertTrue(os.path.exists(project_file))
|
||||
with open(project_file, "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
project_settings = project_json["ProjectSettings"]
|
||||
build_settings = project_json["BuildSettings"]
|
||||
|
||||
self.assertEqual(project_settings["Name"], name)
|
||||
self.assertEqual(build_settings["ProjectType"], "library")
|
||||
self.assertEqual(build_settings["OutputPath"], "../../dist")
|
||||
self.assertEqual(build_settings["Main"], f"{String.to_snake_case(name)}.main")
|
||||
self.assertEqual(build_settings["EntryPoint"], name)
|
||||
|
||||
def test_console(self):
|
||||
self._test_project("console", "test-console", "--ab", "--s", "--venv", test_venv=True)
|
||||
|
||||
def test_console_with_other_base(self):
|
||||
self._test_project(
|
||||
"console", "tools/test-console", "--ab", "--s", "--venv", "--base", test_venv=True, without_ws=True
|
||||
)
|
||||
|
||||
def test_console_without_s(self):
|
||||
self._test_project("console", "test-console-without-s", "--ab")
|
||||
|
||||
def test_console_without_ab(self):
|
||||
self._test_project("console", "test-console-without-ab", "--sp")
|
||||
|
||||
def test_console_without_anything(self):
|
||||
self._test_project("console", "test-console-without-anything", "--n")
|
||||
|
||||
def test_console_sub(self):
|
||||
self._test_sub_project(
|
||||
"console", "test-sub-console", "test-console", "--ab", "--s", "--sp", "--venv", test_venv=True
|
||||
)
|
||||
|
||||
def test_console_sub_with_other_base(self):
|
||||
self._test_sub_project(
|
||||
"console",
|
||||
"tools/test-sub-console",
|
||||
"test-console",
|
||||
"--ab",
|
||||
"--s",
|
||||
"--sp",
|
||||
"--venv",
|
||||
"--base",
|
||||
test_venv=True,
|
||||
)
|
||||
|
||||
def test_discord_bot(self):
|
||||
self._test_project("discord-bot", "test-bot", "--ab", "--s", "--venv", test_venv=True)
|
||||
|
||||
def test_discord_bot_sub(self):
|
||||
self._test_sub_project("discord-bot", "test-bot-sub", "test-console", "--ab", "--s", "--venv", test_venv=True)
|
||||
|
||||
def test_library(self):
|
||||
self._test_project("library", "test-library", "--ab", "--s", "--sp")
|
||||
|
||||
def test_library_sub(self):
|
||||
self._test_sub_project("library", "test-sub-library", "test-console", "--ab", "--s", "--sp")
|
||||
|
||||
def test_library_sub_directory(self):
|
||||
self._test_sub_directory_project(
|
||||
"library", "directory", "test-sub-library", "test-console", "--ab", "--s", "--sp"
|
||||
)
|
||||
|
||||
def test_unittest(self):
|
||||
self._test_project("unittest", "test-unittest", "--ab")
|
||||
|
||||
def test_unittest_sub(self):
|
||||
self._test_sub_project("unittest", "test-unittest", "test-console", "--ab", "--s", "--sp")
|
||||
@@ -1,85 +0,0 @@
|
||||
import filecmp
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class PublishTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "publish-test-source"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
|
||||
def setUp(self):
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
|
||||
def _are_dir_trees_equal(self, dir1, dir2):
|
||||
"""
|
||||
found at https://stackoverflow.com/questions/4187564/recursively-compare-two-directories-to-ensure-they-have-the-same-files-and-subdi
|
||||
|
||||
Compare two directories recursively. Files in each directory are
|
||||
assumed to be equal if their names and contents are equal.
|
||||
|
||||
@param dir1: First directory path
|
||||
@param dir2: Second directory path
|
||||
|
||||
@return: True if the directory trees are the same and
|
||||
there were no errors while accessing the directories or files,
|
||||
False otherwise.
|
||||
"""
|
||||
|
||||
dirs_cmp = filecmp.dircmp(dir1, dir2)
|
||||
if len(dirs_cmp.left_only) > 0 or len(dirs_cmp.right_only) > 0 or len(dirs_cmp.funny_files) > 0:
|
||||
return False
|
||||
|
||||
(_, mismatch, errors) = filecmp.cmpfiles(dir1, dir2, dirs_cmp.common_files, shallow=False)
|
||||
|
||||
if len(mismatch) > 0 or len(errors) > 0:
|
||||
return False
|
||||
|
||||
for common_dir in dirs_cmp.common_dirs:
|
||||
new_dir1 = os.path.join(dir1, common_dir)
|
||||
new_dir2 = os.path.join(dir2, common_dir)
|
||||
if not self._are_dir_trees_equal(new_dir1, new_dir2):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_publish(self):
|
||||
CLICommands.publish()
|
||||
dist_path = "./dist"
|
||||
setup_path = f"{dist_path}/{self._source}/publish/setup"
|
||||
full_dist_path = f"{dist_path}/{self._source}/publish/build/lib/{String.to_snake_case(self._source)}"
|
||||
self.assertTrue(os.path.exists(dist_path))
|
||||
self.assertTrue(os.path.exists(setup_path))
|
||||
self.assertTrue(os.path.exists(os.path.join(setup_path, f"{self._source}-0.0.0.tar.gz")))
|
||||
self.assertTrue(
|
||||
os.path.exists(
|
||||
os.path.join(setup_path, f"{String.to_snake_case(self._source)}-0.0.0-py3-none-any.whl")
|
||||
)
|
||||
)
|
||||
self.assertTrue(os.path.exists(full_dist_path))
|
||||
self.assertFalse(
|
||||
self._are_dir_trees_equal(f"./src/{String.to_snake_case(self._source)}", full_dist_path)
|
||||
)
|
||||
|
||||
shutil.copyfile(os.path.join(os.getcwd(), self._project_file), f"{full_dist_path}/{self._source}.json")
|
||||
shutil.copyfile(
|
||||
os.path.join(os.getcwd(), os.path.dirname(self._project_file), "appsettings.json"),
|
||||
f"{full_dist_path}/appsettings.json",
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
self._are_dir_trees_equal(f"./src/{String.to_snake_case(self._source)}", full_dist_path)
|
||||
)
|
||||
@@ -1,50 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class RemoveTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "add-test-project"
|
||||
self._target = "add-test-library"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
|
||||
def _get_project_settings(self):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def setUp(self):
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
CLICommands.new("console", self._target, "--ab", "--s")
|
||||
CLICommands.add(self._source, self._target)
|
||||
|
||||
def test_remove(self):
|
||||
CLICommands.remove(self._target)
|
||||
path = os.path.abspath(os.path.join(os.getcwd(), f"../{String.to_snake_case(self._target)}"))
|
||||
self.assertTrue(os.path.exists(os.getcwd()))
|
||||
self.assertTrue(os.path.exists(os.path.join(os.getcwd(), self._project_file)))
|
||||
self.assertFalse(os.path.exists(path))
|
||||
settings = self._get_project_settings()
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("ProjectReferences", settings["BuildSettings"])
|
||||
self.assertIn("BuildSettings", settings)
|
||||
self.assertNotIn(
|
||||
f"../{String.to_snake_case(self._target)}/{self._target}.json",
|
||||
settings["BuildSettings"]["ProjectReferences"],
|
||||
)
|
||||
@@ -1,117 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class RunTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "run-test"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
self._application = f"src/{String.to_snake_case(self._source)}/application.py"
|
||||
self._test_code = f"""
|
||||
import json
|
||||
import os
|
||||
settings = dict()
|
||||
with open('appsettings.json', 'r', encoding='utf-8') as cfg:
|
||||
# load json
|
||||
settings = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
settings['RunTest']['WasStarted'] = 'True'
|
||||
settings['RunTest']['Path'] = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
with open('appsettings.json', 'w', encoding='utf-8') as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
"""
|
||||
|
||||
def _get_appsettings(self, is_dev=False):
|
||||
appsettings = f"dist/{self._source}/build/{String.to_snake_case(self._source)}/appsettings.json"
|
||||
if is_dev:
|
||||
appsettings = f"src/{String.to_snake_case(self._source)}/appsettings.json"
|
||||
|
||||
with open(os.path.join(os.getcwd(), appsettings), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def _save_appsettings(self, settings: dict):
|
||||
with open(
|
||||
os.path.join(os.getcwd(), f"src/{String.to_snake_case(self._source)}/appsettings.json"),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
|
||||
def setUp(self):
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
settings = {"RunTest": {"WasStarted": "False"}}
|
||||
self._save_appsettings(settings)
|
||||
with open(os.path.join(os.getcwd(), self._application), "a", encoding="utf-8") as file:
|
||||
file.write(f"\t\t{self._test_code}")
|
||||
file.close()
|
||||
|
||||
def test_run(self):
|
||||
CLICommands.run()
|
||||
settings = self._get_appsettings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
self.assertNotEqual(
|
||||
os.path.join(os.getcwd(), f"src/{String.to_snake_case(self._source)}"), settings["RunTest"]["Path"]
|
||||
)
|
||||
self.assertEqual(
|
||||
os.path.join(os.getcwd(), f"dist/{self._source}/build/{String.to_snake_case(self._source)}"),
|
||||
settings["RunTest"]["Path"],
|
||||
)
|
||||
|
||||
def test_run_by_project(self):
|
||||
CLICommands.run(self._source)
|
||||
settings = self._get_appsettings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
self.assertNotEqual(
|
||||
os.path.join(os.getcwd(), f"src/{String.to_snake_case(self._source)}"), settings["RunTest"]["Path"]
|
||||
)
|
||||
self.assertEqual(
|
||||
os.path.join(os.getcwd(), f"dist/{self._source}/build/{String.to_snake_case(self._source)}"),
|
||||
settings["RunTest"]["Path"],
|
||||
)
|
||||
|
||||
def test_run_dev(self):
|
||||
CLICommands.run(is_dev=True)
|
||||
settings = self._get_appsettings(is_dev=True)
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
self.assertEqual(
|
||||
os.path.join(os.getcwd(), f"src/{String.to_snake_case(self._source)}"), settings["RunTest"]["Path"]
|
||||
)
|
||||
|
||||
def test_run_dev_by_project(self):
|
||||
CLICommands.run(self._source, is_dev=True)
|
||||
settings = self._get_appsettings(is_dev=True)
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
self.assertEqual(
|
||||
os.path.join(os.getcwd(), f"src/{String.to_snake_case(self._source)}"), settings["RunTest"]["Path"]
|
||||
)
|
||||
@@ -1,122 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_cli.threads.start_test_thread import StartTestThread
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class StartTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "start-test"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
self._appsettings = f"src/{String.to_snake_case(self._source)}/appsettings.json"
|
||||
self._application = f"src/{String.to_snake_case(self._source)}/application.py"
|
||||
self._test_code = f"""
|
||||
import json
|
||||
import os
|
||||
settings = dict()
|
||||
with open('appsettings.json', 'r', encoding='utf-8') as cfg:
|
||||
# load json
|
||||
settings = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
if settings['RunTest']['WasStarted'] == 'True':
|
||||
settings['RunTest']['WasRestarted'] = 'True'
|
||||
|
||||
settings['RunTest']['WasStarted'] = 'True'
|
||||
settings['RunTest']['Path'] = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
with open('appsettings.json', 'w', encoding='utf-8') as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
"""
|
||||
|
||||
def _get_appsettings(self, is_dev=False):
|
||||
appsettings = f"dist/{self._source}/build/{String.to_snake_case(self._source)}/appsettings.json"
|
||||
if is_dev:
|
||||
appsettings = f"src/{String.to_snake_case(self._source)}/appsettings.json"
|
||||
|
||||
with open(os.path.join(os.getcwd(), appsettings), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def _save_appsettings(self, settings: dict):
|
||||
with open(
|
||||
os.path.join(os.getcwd(), f"src/{String.to_snake_case(self._source)}/appsettings.json"),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
|
||||
def setUp(self):
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
settings = {"RunTest": {"WasStarted": "False", "WasRestarted": "False"}}
|
||||
self._save_appsettings(settings)
|
||||
with open(os.path.join(os.getcwd(), self._application), "a", encoding="utf-8") as file:
|
||||
file.write(f"\t\t{self._test_code}")
|
||||
file.close()
|
||||
|
||||
def test_start(self):
|
||||
thread = StartTestThread()
|
||||
thread.start()
|
||||
time.sleep(5)
|
||||
settings = self._get_appsettings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
|
||||
with open(os.path.join(os.getcwd(), self._application), "a", encoding="utf-8") as file:
|
||||
file.write(f"# trigger restart (comment generated by unittest)")
|
||||
file.close()
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
settings = self._get_appsettings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertIn("WasRestarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasRestarted"])
|
||||
|
||||
def test_start_dev(self):
|
||||
thread = StartTestThread(is_dev=True)
|
||||
thread.start()
|
||||
time.sleep(1)
|
||||
settings = self._get_appsettings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
|
||||
with open(os.path.join(os.getcwd(), self._application), "a", encoding="utf-8") as file:
|
||||
file.write(f"# trigger restart (comment generated by unittest)")
|
||||
file.close()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
settings = self._get_appsettings(is_dev=True)
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("RunTest", settings)
|
||||
self.assertIn("WasStarted", settings["RunTest"])
|
||||
self.assertIn("WasRestarted", settings["RunTest"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasStarted"])
|
||||
self.assertEqual("True", settings["RunTest"]["WasRestarted"])
|
||||
@@ -1,12 +0,0 @@
|
||||
import threading
|
||||
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class StartTestThread(threading.Thread):
|
||||
def __init__(self, is_dev=False):
|
||||
threading.Thread.__init__(self, daemon=True)
|
||||
self._is_dev = is_dev
|
||||
|
||||
def run(self):
|
||||
CLICommands.start(is_dev=self._is_dev, output=True)
|
||||
@@ -1,67 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class UninstallTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "uninstall-test-source"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
self._version = "1.7.3"
|
||||
self._package_name = "discord.py"
|
||||
self._package = f"{self._package_name}=={self._version}"
|
||||
|
||||
def _get_project_settings(self):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def setUp(self):
|
||||
if not os.path.exists(PLAYGROUND_PATH):
|
||||
os.makedirs(PLAYGROUND_PATH)
|
||||
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
|
||||
def _get_installed_packages(self) -> dict:
|
||||
reqs = subprocess.check_output([sys.executable, "-m", "pip", "freeze"])
|
||||
return dict([tuple(r.decode().split("==")) for r in reqs.split()])
|
||||
|
||||
def test_uninstall(self):
|
||||
CLICommands.install(self._package)
|
||||
CLICommands.uninstall(self._package)
|
||||
settings = self._get_project_settings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertNotIn(self._package, settings["ProjectSettings"]["Dependencies"])
|
||||
self.assertNotIn(self._package, settings["ProjectSettings"]["DevDependencies"])
|
||||
packages = self._get_installed_packages()
|
||||
self.assertNotIn(self._package_name, packages)
|
||||
|
||||
def test_dev_uninstall(self):
|
||||
CLICommands.install(self._package, is_dev=True)
|
||||
CLICommands.uninstall(self._package, is_dev=True)
|
||||
settings = self._get_project_settings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertIn("DevDependencies", settings["ProjectSettings"])
|
||||
self.assertNotIn(self._package, settings["ProjectSettings"]["Dependencies"])
|
||||
self.assertNotIn(self._package, settings["ProjectSettings"]["DevDependencies"])
|
||||
packages = self._get_installed_packages()
|
||||
self.assertNotIn(self._package_name, packages)
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"Project": {
|
||||
"Name": "unittest_cli",
|
||||
"Version": {
|
||||
"Major": "2024",
|
||||
"Minor": "7",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=2024.6.2024.07.0",
|
||||
"cpl-cli>=2024.6.2024.07.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": [],
|
||||
"DevDependencies": []
|
||||
},
|
||||
"Build": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "unittest_cli.main",
|
||||
"EntryPoint": "unittest_cli",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import String
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_cli.constants import PLAYGROUND_PATH
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class UpdateTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._source = "install-test-source"
|
||||
self._project_file = f"src/{String.to_snake_case(self._source)}/{self._source}.json"
|
||||
|
||||
self._old_version = "1.7.1"
|
||||
self._old_package_name = "discord.py"
|
||||
self._old_package = f"{self._old_package_name}=={self._old_version}"
|
||||
|
||||
# todo: better way to do shit required
|
||||
self._new_version = "2.2.2"
|
||||
self._new_package_name = "discord.py"
|
||||
self._new_package = f"{self._new_package_name}=={self._new_version}"
|
||||
|
||||
def setUp(self):
|
||||
CLICommands.uninstall(self._old_package)
|
||||
CLICommands.uninstall(self._new_package)
|
||||
os.chdir(PLAYGROUND_PATH)
|
||||
# create projects
|
||||
CLICommands.new("console", self._source, "--ab", "--s")
|
||||
os.chdir(os.path.join(os.getcwd(), self._source))
|
||||
CLICommands.install(self._old_package)
|
||||
|
||||
def _get_project_settings(self):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "r", encoding="utf-8") as cfg:
|
||||
# load json
|
||||
project_json = json.load(cfg)
|
||||
cfg.close()
|
||||
|
||||
return project_json
|
||||
|
||||
def _save_project_settings(self, settings: dict):
|
||||
with open(os.path.join(os.getcwd(), self._project_file), "w", encoding="utf-8") as project_file:
|
||||
project_file.write(json.dumps(settings, indent=2))
|
||||
project_file.close()
|
||||
|
||||
def _get_installed_packages(self) -> dict:
|
||||
reqs = subprocess.check_output([sys.executable, "-m", "pip", "freeze"])
|
||||
return dict([tuple(r.decode().split("==")) for r in reqs.split()])
|
||||
|
||||
def test_install_package(self):
|
||||
settings = self._get_project_settings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertIn(self._old_package, settings["ProjectSettings"]["Dependencies"])
|
||||
packages = self._get_installed_packages()
|
||||
self.assertIn(self._old_package_name, packages)
|
||||
self.assertEqual(self._old_version, packages[self._old_package_name])
|
||||
|
||||
CLICommands.update()
|
||||
|
||||
settings = self._get_project_settings()
|
||||
self.assertNotEqual(settings, {})
|
||||
self.assertIn("ProjectSettings", settings)
|
||||
self.assertIn("Dependencies", settings["ProjectSettings"])
|
||||
self.assertIn(self._new_package, settings["ProjectSettings"]["Dependencies"])
|
||||
packages = self._get_installed_packages()
|
||||
self.assertIn(self._new_package_name, packages)
|
||||
self.assertEqual(self._new_version, packages[self._new_package_name])
|
||||
@@ -1,95 +0,0 @@
|
||||
import pkgutil
|
||||
import platform
|
||||
import sys
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
import pkg_resources
|
||||
from art import text2art
|
||||
from tabulate import tabulate
|
||||
|
||||
import cpl_cli
|
||||
from cpl.core.console import ForegroundColorEnum
|
||||
from termcolor import colored
|
||||
|
||||
from unittests_cli.abc.command_test_case import CommandTestCase
|
||||
from unittests_shared.cli_commands import CLICommands
|
||||
|
||||
|
||||
class VersionTestCase(CommandTestCase):
|
||||
def __init__(self, method_name: str):
|
||||
CommandTestCase.__init__(self, method_name)
|
||||
self._block_banner = ""
|
||||
self._block_version = ""
|
||||
self._block_package_header = ""
|
||||
self._block_cpl_packages = ""
|
||||
self._block_packages = ""
|
||||
self._name = "CPL CLI"
|
||||
|
||||
def setUp(self): ...
|
||||
|
||||
def _get_version_output(self, version: str):
|
||||
index = 0
|
||||
|
||||
for line in version.split("\n"):
|
||||
if line == "":
|
||||
continue
|
||||
|
||||
if index <= 5:
|
||||
self._block_banner += f"{line}\n"
|
||||
|
||||
if 7 <= index <= 9:
|
||||
self._block_version += f"{line}\n"
|
||||
|
||||
if 10 <= index <= 16:
|
||||
self._block_cpl_packages += f"{line}\n"
|
||||
|
||||
if index >= 18:
|
||||
self._block_packages += f"{line}\n"
|
||||
|
||||
index += 1
|
||||
|
||||
def test_version(self):
|
||||
packages = []
|
||||
cpl_packages = []
|
||||
dependencies = dict(tuple(str(ws).split()) for ws in pkg_resources.working_set)
|
||||
for p in dependencies:
|
||||
if str(p).startswith("cpl-"):
|
||||
cpl_packages.append([p, dependencies[p]])
|
||||
continue
|
||||
|
||||
packages.append([p, dependencies[p]])
|
||||
|
||||
version = CLICommands.version()
|
||||
self._get_version_output(version)
|
||||
reference_banner = colored(text2art(self._name), ForegroundColorEnum.yellow.value).split("\n")
|
||||
reference_banner = "\n".join(reference_banner[: len(reference_banner) - 1]) + "\n"
|
||||
|
||||
with self.subTest(msg="Block banner"):
|
||||
self.assertEqual(reference_banner, self._block_banner)
|
||||
|
||||
reference_version = [
|
||||
colored(f'{colored("Common Python library CLI: ")}{colored(cpl_cli.__version__)}'),
|
||||
colored(
|
||||
f'{colored("Python: ")}{colored(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")}'
|
||||
),
|
||||
colored(f'OS: {colored(f"{platform.system()} {platform.processor()}")}') + "\n",
|
||||
]
|
||||
with self.subTest(msg="Block version"):
|
||||
self.assertEqual("\n".join(reference_version), self._block_version)
|
||||
reference_cpl_packages = [
|
||||
colored(colored(f"CPL packages:")),
|
||||
colored(f'{tabulate(cpl_packages, headers=["Name", "Version"])}') + "\n",
|
||||
]
|
||||
with self.subTest(msg="Block cpl packages"):
|
||||
self.assertEqual("\n".join(reference_cpl_packages), self._block_cpl_packages)
|
||||
reference_packages = [
|
||||
colored(colored(f"Python packages:")),
|
||||
colored(f'{tabulate(packages, headers=["Name", "Version"])}'),
|
||||
"\x1b[0m\x1b[0m\n\x1b[0m\x1b[0m\n\x1b[0m\x1b[0m\n", # fix colored codes
|
||||
]
|
||||
|
||||
self.maxDiff = None
|
||||
with self.subTest(msg="Block packages"):
|
||||
ref_packages = "\n".join(reference_packages)
|
||||
self.assertEqual(ref_packages, self._block_packages)
|
||||
@@ -1,68 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import Mock, MagicMock
|
||||
|
||||
from cpl.core.configuration import Configuration, ArgumentTypeEnum
|
||||
from cpl.database import DatabaseSettings
|
||||
from cpl.dependency import ServiceProvider, ServiceCollection
|
||||
from cpl.mail import EMailClientSettings
|
||||
|
||||
|
||||
class ConfigurationTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._config = Configuration()
|
||||
|
||||
def test_env_vars(self):
|
||||
os.environ["CPLT_TESTVAR"] = "Hello World"
|
||||
os.environ["CPL_NOT_EXISTING"] = "Hello World"
|
||||
|
||||
self._config.add_environment_variables("CPLT_")
|
||||
|
||||
self.assertEqual(self._config.get_configuration("TESTVAR"), "Hello World")
|
||||
self.assertEqual(self._config.get_configuration("TESTVAR"), "Hello World")
|
||||
self.assertEqual(self._config.get_configuration("NOT_EXISTING"), None)
|
||||
|
||||
def test_add_json_file(self):
|
||||
self._config.add_json_file("unittests_core/configuration/test-settings.json")
|
||||
db = self._config.get_configuration(DatabaseSettings)
|
||||
self.assertIsNotNone(db)
|
||||
self.assertEqual("localhost", db.host)
|
||||
self.assertEqual("local", db.user)
|
||||
self.assertEqual("bG9jYWw=", db.password)
|
||||
self.assertEqual("local", db.database)
|
||||
self.assertEqual(int, type(db.port))
|
||||
self.assertEqual(3306, db.port)
|
||||
self.assertEqual("utf8mb4", db.charset)
|
||||
self.assertTrue(db.use_unicode)
|
||||
self.assertTrue(db.buffered)
|
||||
self.assertEqual("mysql_native_password", db.auth_plugin)
|
||||
self.assertIsNone(self._config.get_configuration(EMailClientSettings))
|
||||
|
||||
def test_add_config(self):
|
||||
self.assertIsNone(self._config.get_configuration("Test"))
|
||||
self._config.add_configuration("Test", "Hello World")
|
||||
self.assertIsNotNone(self._config.get_configuration("Test"))
|
||||
self.assertEqual("Hello World", self._config.get_configuration("Test"))
|
||||
|
||||
def test_console_argument(self):
|
||||
sc = ServiceCollection(self._config)
|
||||
self.assertEqual([], sys.argv[1:])
|
||||
sys.argv.append("flag")
|
||||
sys.argv.append("exec")
|
||||
sys.argv.append("var=test")
|
||||
self.assertNotEqual([], sys.argv[1:])
|
||||
|
||||
self._config.create_console_argument(ArgumentTypeEnum.Flag, "", "flag", [])
|
||||
mocked_exec = Mock()
|
||||
mocked_exec.run = MagicMock()
|
||||
sc.add_transient(mocked_exec)
|
||||
self._config.create_console_argument(ArgumentTypeEnum.Executable, "", "exec", [], Mock)
|
||||
self._config.create_console_argument(ArgumentTypeEnum.Variable, "", "var", [], "=")
|
||||
|
||||
self.assertIsNone(self._config.get_configuration("var"))
|
||||
self._config.parse_console_arguments(sc.build())
|
||||
mocked_exec.run.assert_called()
|
||||
|
||||
self.assertEqual("test", self._config.get_configuration("var"))
|
||||
self.assertIn("flag", self._config.additional_arguments)
|
||||
@@ -1,75 +0,0 @@
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import Mock, MagicMock
|
||||
|
||||
from cpl.core.configuration import Configuration, ArgumentTypeEnum
|
||||
from cpl.dependency import ServiceCollection
|
||||
|
||||
|
||||
class ConsoleArgumentsTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._config = Configuration()
|
||||
|
||||
self._config.create_console_argument(ArgumentTypeEnum.Flag, "", "flag", [])
|
||||
self._config.create_console_argument(ArgumentTypeEnum.Variable, "", "var", [], "=")
|
||||
|
||||
self._config.create_console_argument(ArgumentTypeEnum.Executable, "", "exec", [], Mock).add_console_argument(
|
||||
ArgumentTypeEnum.Flag, "--", "dev", ["d", "D"]
|
||||
).add_console_argument(ArgumentTypeEnum.Flag, "--", "virtual", ["v", "V"]).add_console_argument(
|
||||
ArgumentTypeEnum.Variable, "", "var1", [], "="
|
||||
)
|
||||
|
||||
self._config.for_each_argument(
|
||||
lambda a: a.add_console_argument(ArgumentTypeEnum.Flag, "--", "help", ["h", "H"])
|
||||
)
|
||||
|
||||
self._sc = ServiceCollection(self._config)
|
||||
self._mocked_exec = Mock()
|
||||
self._mocked_exec.run = MagicMock()
|
||||
self._sc.add_transient(self._mocked_exec)
|
||||
|
||||
def test_flag(self):
|
||||
sys.argv.append("flag")
|
||||
|
||||
self._config.parse_console_arguments(self._sc.build())
|
||||
self.assertIn("flag", self._config.additional_arguments)
|
||||
|
||||
def test_var(self):
|
||||
sys.argv.append("var=1")
|
||||
sys.argv.append("var2=1")
|
||||
|
||||
self._config.parse_console_arguments(self._sc.build())
|
||||
self.assertEqual("1", self._config.get_configuration("var"))
|
||||
self.assertIsNone(self._config.get_configuration("var1"))
|
||||
|
||||
def test_exec(self):
|
||||
sys.argv.append("exec")
|
||||
|
||||
self._config.parse_console_arguments(self._sc.build())
|
||||
self._mocked_exec.run.assert_called()
|
||||
|
||||
def test_exec_with_one_flag(self):
|
||||
sys.argv.append("exec")
|
||||
sys.argv.append("--dev")
|
||||
|
||||
self._config.parse_console_arguments(self._sc.build())
|
||||
self._mocked_exec.run.assert_called()
|
||||
self.assertIn("dev", self._config.additional_arguments)
|
||||
|
||||
def test_exec_with_one_flag_alias(self):
|
||||
sys.argv.append("exec")
|
||||
sys.argv.append("--d")
|
||||
|
||||
self._config.parse_console_arguments(self._sc.build())
|
||||
self._mocked_exec.run.assert_called()
|
||||
self.assertIn("dev", self._config.additional_arguments)
|
||||
|
||||
def test_exec_with_two_flags(self):
|
||||
sys.argv.append("exec")
|
||||
sys.argv.append("--dev")
|
||||
sys.argv.append("--virtual")
|
||||
|
||||
self._config.parse_console_arguments(self._sc.build())
|
||||
self._mocked_exec.run.assert_called()
|
||||
self.assertIn("dev", self._config.additional_arguments)
|
||||
self.assertIn("virtual", self._config.additional_arguments)
|
||||
@@ -1,52 +0,0 @@
|
||||
import os
|
||||
import unittest
|
||||
from _socket import gethostname
|
||||
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.core.environment import Environment, EnvironmentABC
|
||||
from cpl.core.environment import environment
|
||||
|
||||
|
||||
class EnvironmentTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._config = Configuration()
|
||||
self._env = self._config.environment
|
||||
|
||||
def test_app_env_created(self):
|
||||
self.assertTrue(isinstance(self._env, Environment))
|
||||
self.assertTrue(issubclass(type(self._env), EnvironmentABC))
|
||||
|
||||
def test_app_env_values_correct_when_default(self):
|
||||
self.assertEqual(self._env.environment_name, "production")
|
||||
self.assertEqual(self._env.application_name, "")
|
||||
self.assertEqual(self._env.customer, "")
|
||||
self.assertEqual(self._env.host_name, gethostname())
|
||||
self.assertEqual(self._env.cwd, os.getcwd())
|
||||
self.assertEqual(
|
||||
self._env.runtime_directory,
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(application_environment.__file__))),
|
||||
)
|
||||
|
||||
def test_app_env_values_correct_when_read_from_env(self):
|
||||
os.environ["CPLT_ENVIRONMENT"] = "development"
|
||||
os.environ["CPLT_NAME"] = "Core Tests"
|
||||
os.environ["CPLT_CUSTOMER"] = "sh-edraft.de"
|
||||
|
||||
self._config.add_environment_variables("CPLT_")
|
||||
|
||||
self.assertEqual(self._env.environment_name, "development")
|
||||
self.assertEqual(self._env.application_name, "Core Tests")
|
||||
self.assertEqual(self._env.customer, "sh-edraft.de")
|
||||
self.assertEqual(self._env.host_name, gethostname())
|
||||
self.assertEqual(self._env.cwd, os.getcwd())
|
||||
self.assertEqual(
|
||||
self._env.runtime_directory,
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(application_environment.__file__))),
|
||||
)
|
||||
|
||||
def test_app_env_set_dirs(self):
|
||||
new_cwd = os.path.join(os.getcwd(), "../")
|
||||
self._env.set_cwd(new_cwd)
|
||||
self.assertEqual(self._env.cwd, new_cwd)
|
||||
self._env.set_runtime_directory(new_cwd)
|
||||
self.assertEqual(self._env.runtime_directory, new_cwd)
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"TimeFormat": {
|
||||
"DateFormat": "%Y-%m-%d",
|
||||
"TimeFormat": "%H:%M:%S",
|
||||
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
|
||||
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
|
||||
},
|
||||
"Logging": {
|
||||
"Path": "logs/$date_now/",
|
||||
"Filename": "bot.log",
|
||||
"ConsoleLevel": "TRACE",
|
||||
"Level": "TRACE"
|
||||
},
|
||||
"Database": {
|
||||
"Host": "localhost",
|
||||
"User": "local",
|
||||
"Password": "bG9jYWw=",
|
||||
"Database": "local",
|
||||
"Port": "3306",
|
||||
"Charset": "utf8mb4",
|
||||
"UseUnicode": "true",
|
||||
"Buffered": "true",
|
||||
"AuthPlugin": "mysql_native_password"
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from unittests_core.configuration.console_arguments_test_case import ConsoleArgumentsTestCase
|
||||
from unittests_core.configuration.configuration_test_case import ConfigurationTestCase
|
||||
from unittests_core.configuration.environment_test_case import EnvironmentTestCase
|
||||
from unittests_core.di.service_collection_test_case import ServiceCollectionTestCase
|
||||
from unittests_core.di.service_provider_test_case import ServiceProviderTestCase
|
||||
from unittests_core.pipes.bool_pipe_test_case import BoolPipeTestCase
|
||||
from unittests_core.pipes.ip_address_pipe_test_case import IPAddressTestCase
|
||||
from unittests_core.pipes.version_pipe_test_case import VersionPipeTestCase
|
||||
from unittests_core.utils.credential_manager_test_case import CredentialManagerTestCase
|
||||
from unittests_core.utils.json_processor_test_case import JSONProcessorTestCase
|
||||
from unittests_core.utils.string_test_case import StringTestCase
|
||||
|
||||
|
||||
class CoreTestSuite(unittest.TestSuite):
|
||||
def __init__(self):
|
||||
unittest.TestSuite.__init__(self)
|
||||
|
||||
loader = unittest.TestLoader()
|
||||
tests = [
|
||||
# config
|
||||
ConfigurationTestCase,
|
||||
ConsoleArgumentsTestCase,
|
||||
EnvironmentTestCase,
|
||||
# di
|
||||
ServiceCollectionTestCase,
|
||||
ServiceProviderTestCase,
|
||||
# pipes
|
||||
BoolPipeTestCase,
|
||||
IPAddressTestCase,
|
||||
VersionPipeTestCase,
|
||||
# utils
|
||||
CredentialManagerTestCase,
|
||||
JSONProcessorTestCase,
|
||||
StringTestCase,
|
||||
]
|
||||
|
||||
for test in tests:
|
||||
self.addTests(loader.loadTestsFromTestCase(test))
|
||||
|
||||
def run(self, *args):
|
||||
super().run(*args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
runner = unittest.TextTestRunner()
|
||||
runner.run(CoreTestSuite())
|
||||
@@ -1,56 +0,0 @@
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.dependency import ServiceCollection, ServiceLifetimeEnum, ServiceProvider
|
||||
|
||||
|
||||
class ServiceCollectionTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._sc = ServiceCollection(Configuration())
|
||||
|
||||
def test_add_singleton_type(self):
|
||||
self._sc.add_singleton(Mock)
|
||||
|
||||
service = self._sc._service_descriptors[0]
|
||||
self.assertEqual(ServiceLifetimeEnum.singleton, service.lifetime)
|
||||
self.assertEqual(Mock, service.service_type)
|
||||
self.assertEqual(Mock, service.base_type)
|
||||
self.assertIsNone(service.implementation)
|
||||
|
||||
def test_add_singleton_instance(self):
|
||||
mock = Mock()
|
||||
self._sc.add_singleton(mock)
|
||||
|
||||
service = self._sc._service_descriptors[0]
|
||||
self.assertEqual(ServiceLifetimeEnum.singleton, service.lifetime)
|
||||
self.assertEqual(type(mock), service.service_type)
|
||||
self.assertEqual(type(mock), service.base_type)
|
||||
self.assertIsNotNone(service.implementation)
|
||||
|
||||
def test_add_transient_type(self):
|
||||
self._sc.add_transient(Mock)
|
||||
|
||||
service = self._sc._service_descriptors[0]
|
||||
self.assertEqual(ServiceLifetimeEnum.transient, service.lifetime)
|
||||
self.assertEqual(Mock, service.service_type)
|
||||
self.assertEqual(Mock, service.base_type)
|
||||
self.assertIsNone(service.implementation)
|
||||
|
||||
def test_add_scoped_type(self):
|
||||
self._sc.add_scoped(Mock)
|
||||
|
||||
service = self._sc._service_descriptors[0]
|
||||
self.assertEqual(ServiceLifetimeEnum.scoped, service.lifetime)
|
||||
self.assertEqual(Mock, service.service_type)
|
||||
self.assertEqual(Mock, service.base_type)
|
||||
self.assertIsNone(service.implementation)
|
||||
|
||||
def test_build_service_provider(self):
|
||||
self._sc.add_singleton(Mock)
|
||||
service = self._sc._service_descriptors[0]
|
||||
self.assertIsNone(service.implementation)
|
||||
sp = self._sc.build()
|
||||
self.assertTrue(isinstance(sp, ServiceProvider))
|
||||
self.assertTrue(isinstance(sp.get_service(Mock), Mock))
|
||||
self.assertIsNotNone(service.implementation)
|
||||
@@ -1,98 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from cpl.core.configuration import Configuration
|
||||
from cpl.dependency import ServiceCollection, ServiceProvider
|
||||
|
||||
|
||||
class ServiceCount:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
|
||||
|
||||
class TestService:
|
||||
def __init__(self, sp: ServiceProvider, count: ServiceCount):
|
||||
count.count += 1
|
||||
self.sp = sp
|
||||
self.id = count.count
|
||||
|
||||
|
||||
class DifferentService:
|
||||
def __init__(self, sp: ServiceProvider, count: ServiceCount):
|
||||
count.count += 1
|
||||
self.sp = sp
|
||||
self.id = count.count
|
||||
|
||||
|
||||
class MoreDifferentService:
|
||||
def __init__(self, sp: ServiceProvider, count: ServiceCount):
|
||||
count.count += 1
|
||||
self.sp = sp
|
||||
self.id = count.count
|
||||
|
||||
|
||||
class ServiceProviderTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self._services = (
|
||||
ServiceCollection(Configuration())
|
||||
.add_singleton(ServiceCount)
|
||||
.add_singleton(TestService)
|
||||
.add_singleton(TestService)
|
||||
.add_transient(DifferentService)
|
||||
.add_scoped(MoreDifferentService)
|
||||
.build()
|
||||
)
|
||||
|
||||
count = self._services.get_service(ServiceCount)
|
||||
|
||||
def test_get_singleton(self):
|
||||
x = self._services.get_service(TestService)
|
||||
self.assertIsNotNone(x)
|
||||
self.assertEqual(1, x.id)
|
||||
self.assertEqual(x, self._services.get_service(TestService))
|
||||
self.assertEqual(x, self._services.get_service(TestService))
|
||||
self.assertEqual(x, self._services.get_service(TestService))
|
||||
|
||||
def test_get_singletons(self):
|
||||
x = self._services.get_services(list[TestService])
|
||||
self.assertEqual(2, len(x))
|
||||
self.assertEqual(1, x[0].id)
|
||||
self.assertEqual(2, x[1].id)
|
||||
self.assertNotEqual(x[0], x[1])
|
||||
|
||||
def test_get_transient(self):
|
||||
x = self._services.get_service(DifferentService)
|
||||
self.assertIsNotNone(x)
|
||||
self.assertEqual(1, x.id)
|
||||
self.assertNotEqual(x, self._services.get_service(DifferentService))
|
||||
self.assertNotEqual(x, self._services.get_service(DifferentService))
|
||||
self.assertNotEqual(x, self._services.get_service(DifferentService))
|
||||
|
||||
def test_scoped(self):
|
||||
scoped_id = 0
|
||||
singleton = self._services.get_service(TestService)
|
||||
transient = self._services.get_service(DifferentService)
|
||||
with self._services.create_scope() as scope:
|
||||
sp: ServiceProvider = scope.service_provider
|
||||
self.assertNotEqual(sp, self._services)
|
||||
y = sp.get_service(DifferentService)
|
||||
self.assertIsNotNone(y)
|
||||
self.assertEqual(3, y.id)
|
||||
x = sp.get_service(MoreDifferentService)
|
||||
self.assertIsNotNone(x)
|
||||
self.assertEqual(4, x.id)
|
||||
scoped_id = 4
|
||||
self.assertEqual(singleton.sp, self._services)
|
||||
self.assertEqual(transient.sp, self._services)
|
||||
self.assertEqual(x.sp, sp)
|
||||
self.assertNotEqual(x.sp, singleton.sp)
|
||||
transient_in_scope = sp.get_service(DifferentService)
|
||||
self.assertEqual(transient_in_scope.sp, sp)
|
||||
self.assertNotEqual(transient.sp, transient_in_scope.sp)
|
||||
|
||||
self.assertEqual(x.id, sp.get_service(MoreDifferentService).id)
|
||||
self.assertEqual(x.id, sp.get_service(MoreDifferentService).id)
|
||||
self.assertNotEqual(x, self._services.get_service(MoreDifferentService))
|
||||
self.assertEqual(singleton, self._services.get_service(TestService))
|
||||
|
||||
self.assertIsNone(scope.service_provider)
|
||||
self.assertNotEqual(scoped_id, self._services.get_service(MoreDifferentService).id)
|
||||
@@ -1,11 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from cpl.core.pipes import BoolPipe
|
||||
|
||||
|
||||
class BoolPipeTestCase(unittest.TestCase):
|
||||
def setUp(self): ...
|
||||
|
||||
def test_transform(self):
|
||||
self.assertEqual("true", BoolPipe.to_str(True))
|
||||
self.assertEqual("false", BoolPipe.to_str(False))
|
||||
@@ -1,17 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from cpl.core.pipes import IPAddressPipe
|
||||
|
||||
|
||||
class IPAddressTestCase(unittest.TestCase):
|
||||
def setUp(self): ...
|
||||
|
||||
def test_transform(self):
|
||||
self.assertEqual("192.168.178.1", IPAddressPipe.to_str([192, 168, 178, 1]))
|
||||
self.assertEqual("255.255.255.255", IPAddressPipe.to_str([255, 255, 255, 255]))
|
||||
self.assertEqual("0.0.0.0", IPAddressPipe.to_str([0, 0, 0, 0]))
|
||||
|
||||
self.assertRaises(Exception, lambda: IPAddressPipe.to_str([-192, 168, 178, 1]))
|
||||
self.assertRaises(Exception, lambda: IPAddressPipe.to_str([256, 168, 178, 1]))
|
||||
self.assertRaises(Exception, lambda: IPAddressPipe.to_str([256, 168, 178]))
|
||||
self.assertRaises(Exception, lambda: IPAddressPipe.to_str([256, 168, 178, 1, 1]))
|
||||
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"Project": {
|
||||
"Name": "unittest_core",
|
||||
"Version": {
|
||||
"Major": "2024",
|
||||
"Minor": "7",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=2024.6.2024.07.0"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {},
|
||||
"Classifiers": [],
|
||||
"DevDependencies": []
|
||||
},
|
||||
"Build": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "unittest_core.main",
|
||||
"EntryPoint": "unittest_core",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import unittest
|
||||
|
||||
from cpl.core.utils import CredentialManager
|
||||
|
||||
|
||||
class CredentialManagerTestCase(unittest.TestCase):
|
||||
def setUp(self): ...
|
||||
|
||||
def test_encrypt(self): ...
|
||||
|
||||
# self.assertEqual("ZkVjSkplQUx4aW1zWHlPbA==", CredentialManager.encrypt("fEcJJeALximsXyOl"))
|
||||
# self.assertEqual("QmtVd1l4dW5Sck9jRmVTQQ==", CredentialManager.encrypt("BkUwYxunRrOcFeSA"))
|
||||
# self.assertEqual("c2FtaHF1VkNSdmZpSGxDcQ==", CredentialManager.encrypt("samhquVCRvfiHlCq"))
|
||||
# self.assertEqual("S05aWHBPYW9DbkRSV01rWQ==", CredentialManager.encrypt("KNZXpOaoCnDRWMkY"))
|
||||
# self.assertEqual("QmtUV0Zsb3h1Y254UkJWeg==", CredentialManager.encrypt("BkTWFloxucnxRBVz"))
|
||||
# self.assertEqual("VFdNTkRuYXB1b1dndXNKdw==", CredentialManager.encrypt("TWMNDnapuoWgusJw"))
|
||||
# self.assertEqual("WVRiQXVSZXRMblpicWNrcQ==", CredentialManager.encrypt("YTbAuRetLnZbqckq"))
|
||||
# self.assertEqual("bmN4aExackxhYUVVdnV2VA==", CredentialManager.encrypt("ncxhLZrLaaEUvuvT"))
|
||||
# self.assertEqual("dmpNT0J5U0lLQmFrc0pIYQ==", CredentialManager.encrypt("vjMOBySIKBaksJHa"))
|
||||
# self.assertEqual("ZHd6WHFzSlFvQlhRbGtVZw==", CredentialManager.encrypt("dwzXqsJQoBXQlkUg"))
|
||||
# self.assertEqual("Q0lmUUhOREtiUmxnY2VCbQ==", CredentialManager.encrypt("CIfQHNDKbRlgceBm"))
|
||||
|
||||
def test_decrypt(self): ...
|
||||
|
||||
# self.assertEqual("fEcJJeALximsXyOl", CredentialManager.decrypt("ZkVjSkplQUx4aW1zWHlPbA=="))
|
||||
# self.assertEqual("BkUwYxunRrOcFeSA", CredentialManager.decrypt("QmtVd1l4dW5Sck9jRmVTQQ=="))
|
||||
# self.assertEqual("samhquVCRvfiHlCq", CredentialManager.decrypt("c2FtaHF1VkNSdmZpSGxDcQ=="))
|
||||
# self.assertEqual("KNZXpOaoCnDRWMkY", CredentialManager.decrypt("S05aWHBPYW9DbkRSV01rWQ=="))
|
||||
# self.assertEqual("BkTWFloxucnxRBVz", CredentialManager.decrypt("QmtUV0Zsb3h1Y254UkJWeg=="))
|
||||
# self.assertEqual("TWMNDnapuoWgusJw", CredentialManager.decrypt("VFdNTkRuYXB1b1dndXNKdw=="))
|
||||
# self.assertEqual("YTbAuRetLnZbqckq", CredentialManager.decrypt("WVRiQXVSZXRMblpicWNrcQ=="))
|
||||
# self.assertEqual("ncxhLZrLaaEUvuvT", CredentialManager.decrypt("bmN4aExackxhYUVVdnV2VA=="))
|
||||
# self.assertEqual("vjMOBySIKBaksJHa", CredentialManager.decrypt("dmpNT0J5U0lLQmFrc0pIYQ=="))
|
||||
# self.assertEqual("dwzXqsJQoBXQlkUg", CredentialManager.decrypt("ZHd6WHFzSlFvQlhRbGtVZw=="))
|
||||
# self.assertEqual("CIfQHNDKbRlgceBm", CredentialManager.decrypt("Q0lmUUhOREtiUmxnY2VCbQ=="))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user