Compare commits

...

15 Commits

Author SHA1 Message Date
clu
ceb3957f0c fix(test): add pytest-asyncio for async test support
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 7s
Test before pr merge / test (pull_request) Successful in 35s
Add pytest-asyncio==0.26.0 to core dev requirements and configure
asyncio_mode=auto in pyproject.toml to fix failing async tests in
service_test.py.
2026-04-13 19:47:48 +02:00
clu
27205022a5 test(core): extend coverage — console, errors, log, service, time, benchmark
Some checks failed
Test before pr merge / test-lint (pull_request) Successful in 8s
Test before pr merge / test (pull_request) Failing after 36s
Add missing test modules for previously untested core areas:
- console: ForegroundColorEnum, BackgroundColorEnum, Console methods
- errors: dependency_error, module_dependency_error
- log: LogLevel ordering/values, LogSettings, Logger (should_log, format, file write, fatal)
- service: HostedService, StartupTask, CronjobABC (start/stop/loop/task cancellation)
- time: TimeFormatSettings properties and setters
- utils: Benchmark.time / .memory / .all call-count and output

Also fix existing test files: environment cleanup, cron exception specificity,
json_processor kwargs bug doc, configuration_model_abc to_dict bug doc.
All 199 tests pass, black clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:34:52 +02:00
clu
82055ca6b5 test(core): add tests for Configuration and ConfigurationModelABC
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 8s
Test before pr merge / test (pull_request) Successful in 34s
- ConfigurationModelABC: defaults, src parsing, PascalCase/snake_case
  key variants, type casting, required fields, readonly enforcement,
  env var override with prefix
- Configuration: set/get by class and string key, auto-instantiation
  of unregistered models, add_json_file loading, optional/missing files

Also documents two additional bugs found:
- cast(True, bool) fails with AttributeError (bool has no .lower())
- add_json_file does not exit on invalid JSON (swallows parse error)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 19:23:07 +02:00
clu
cdca5614e8 style: apply black formatting to src
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 7s
Test before pr merge / test (pull_request) Successful in 35s
Auto-formatted 9 files that were failing the black lint check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 18:43:12 +02:00
clu
ca58f636ee test(core): add unit tests for untested core modules
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 13s
Test before pr merge / test (pull_request) Successful in 40s
Adds 113 tests covering:
- abc: RegistryABC (concrete implementation + edge cases)
- environment: Environment get/set, EnvironmentEnum
- pipes: BoolPipe, IPAddressPipe (incl. roundtrip + error cases)
- time: Cron (next(), intervals, invalid expression)
- utils: Cache (TTL, expiry, cleanup), get_value (incl. bug
  documentation: cast result not returned for string->typed values),
  JSONProcessor (nested objects, enums, defaults)
- property: classproperty (class access, instance access, subclass)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 18:40:01 +02:00
bcca7090d3 Updated requirements
All checks were successful
Build on push / prepare (push) Successful in 10s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 23s
Build on push / cli (push) Successful in 16s
Build on push / dependency (push) Successful in 18s
Build on push / translation (push) Successful in 21s
Build on push / mail (push) Successful in 22s
Build on push / application (push) Successful in 22s
Build on push / database (push) Successful in 22s
Build on push / auth (push) Successful in 16s
Build on push / api (push) Successful in 16s
2026-01-16 16:41:04 +01:00
8aeb381a91 Add .cpl to build
All checks were successful
Build on push / prepare (push) Successful in 10s
Build on push / query (push) Successful in 19s
Build on push / core (push) Successful in 20s
Build on push / dependency (push) Successful in 15s
Build on push / cli (push) Successful in 19s
Build on push / translation (push) Successful in 16s
Build on push / application (push) Successful in 20s
Build on push / database (push) Successful in 21s
Build on push / mail (push) Successful in 20s
Build on push / auth (push) Successful in 15s
Build on push / api (push) Successful in 15s
2026-01-16 16:35:45 +01:00
9cf5886902 Fixed master build
All checks were successful
Build on push / prepare (push) Successful in 23s
Build on push / core (push) Successful in 19s
Build on push / query (push) Successful in 23s
Build on push / cli (push) Successful in 15s
Build on push / dependency (push) Successful in 47s
Build on push / application (push) Successful in 15s
Build on push / mail (push) Successful in 19s
Build on push / translation (push) Successful in 19s
Build on push / database (push) Successful in 46s
Build on push / auth (push) Successful in 17s
Build on push / api (push) Successful in 16s
2026-01-16 16:27:32 +01:00
d60b281d6a Merge pull request 'dev into master' (#184) from dev into master
Some checks failed
Build on push / core (push) Has been cancelled
Build on push / query (push) Has been cancelled
Build on push / translation (push) Has been cancelled
Build on push / mail (push) Has been cancelled
Build on push / prepare (push) Has been cancelled
Reviewed-on: #184
2026-01-16 16:25:37 +01:00
6eae7c7b98 Merge pull request '#191_tests' (#201) from #191_tests into dev
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 9s
Build on push / prepare (push) Successful in 25s
Test before pr merge / test (pull_request) Successful in 27s
Build on push / query (push) Successful in 49s
Build on push / core (push) Successful in 50s
Build on push / cli (push) Successful in 44s
Build on push / dependency (push) Successful in 45s
Build on push / database (push) Successful in 17s
Build on push / application (push) Successful in 17s
Build on push / mail (push) Successful in 18s
Build on push / translation (push) Successful in 18s
Build on push / auth (push) Successful in 1m7s
Build on push / api (push) Successful in 16s
Reviewed-on: #201
2026-01-16 16:21:25 +01:00
638434af76 Fixed test action installation
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 17s
Test before pr merge / test (pull_request) Successful in 31s
2026-01-16 16:20:41 +01:00
c18777656c Added test action
Some checks failed
Test before pr merge / test (pull_request) Failing after 4s
Test before pr merge / test-lint (pull_request) Successful in 15s
2026-01-16 16:07:09 +01:00
c8de1284fb Fixed formatting
All checks were successful
Test before pr merge / test-lint (pull_request) Successful in 7s
2026-01-16 15:55:28 +01:00
17408d5cd2 Added first tests #191
Some checks failed
Test before pr merge / test-lint (pull_request) Failing after 7s
2025-12-10 23:21:52 +01:00
cc76227199 Moved hosted service base
All checks were successful
Build on push / prepare (push) Successful in 13s
Build on push / query (push) Successful in 24s
Build on push / core (push) Successful in 25s
Build on push / dependency (push) Successful in 18s
Build on push / cli (push) Successful in 24s
Build on push / application (push) Successful in 18s
Build on push / mail (push) Successful in 19s
Build on push / database (push) Successful in 23s
Build on push / translation (push) Successful in 26s
Build on push / auth (push) Successful in 17s
Build on push / api (push) Successful in 22s
Test before pr merge / test-lint (pull_request) Successful in 13s
2025-10-22 11:40:25 +02:00
116 changed files with 1913 additions and 3440 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -3,7 +3,8 @@
"projects": [
"src/cli/cpl.project.json",
"src/core/cpl.project.json",
"src/mail/cpl.project.json"
"src/mail/cpl.project.json",
"test/cpl.project.json"
],
"defaultProject": "cpl-cli",
"scripts": {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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):

View File

@@ -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]

View File

@@ -0,0 +1,9 @@
from cpl.core.console import Console
def main():
Console.write_line("Hello, World!")
if __name__ == "__main__":
main()

View File

@@ -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)

View File

@@ -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/"

View File

@@ -33,10 +33,11 @@ class Workspace(CPLStructureModel):
self._actual_projects = []
self._project_names = []
for project in projects:
if Path(project).is_dir() or not Path(project).exists():
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(project)
p = Project.from_file(p_path)
self._actual_projects.append(p)
self._project_names.append(p.name)

View File

@@ -50,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"
@@ -62,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.")
@@ -86,9 +84,10 @@ class Structure:
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}")

View File

@@ -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"] }

View File

@@ -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):

View File

@@ -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:

View File

@@ -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:

View File

@@ -1 +1,2 @@
black==25.1.0
black==25.1.0
pytest-asyncio==0.26.0

View File

@@ -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

View File

@@ -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]

View File

@@ -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};
"""
)
""")

View File

@@ -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()

View File

@@ -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):

View File

@@ -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[

View File

@@ -1,7 +1,6 @@
import contextvars
from contextlib import contextmanager
_current_provider = contextvars.ContextVar("current_provider", default=None)

View File

@@ -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

View File

@@ -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]

View File

@@ -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>
"""
)
""")

View File

@@ -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>
"""
)
""")

View 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()

View 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()

View 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

View 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

View 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
View 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
View 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

View 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

View 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

View 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"

View 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()

View 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")

View 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

View 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??")

View 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

View 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()

View 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"]

View 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

View 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

View 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"})

View 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"

View 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
View 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": []
}
}

View File

@@ -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())

View File

@@ -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()

View File

@@ -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": []
}
}

View File

@@ -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()}")

View File

@@ -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"],
)

View File

@@ -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)
)

View File

@@ -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())

View File

@@ -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"))

View File

@@ -1,7 +0,0 @@
from unittests_cli.abc.command_test_case import CommandTestCase
class CustomTestCase(CommandTestCase):
def setUp(self): ...
def test_equal(self): ...

View File

@@ -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)

View File

@@ -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])

View File

@@ -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")

View File

@@ -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)
)

View File

@@ -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"],
)

View File

@@ -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"]
)

View File

@@ -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"])

View File

@@ -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)

View File

@@ -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)

View File

@@ -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": []
}
}

View File

@@ -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])

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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"
}
}

View File

@@ -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())

View File

@@ -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)

View File

@@ -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)

View File

@@ -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))

View File

@@ -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]))

View File

@@ -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": []
}
}

View File

@@ -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=="))

View File

@@ -1,37 +0,0 @@
import unittest
from cpl.core.utils.json_processor import JSONProcessor
class SubTestClass:
def __init__(self, value: str = None):
self.value = value
class TestClass:
def __init__(self, i: int = None, s: str = None, d: dict = None, l: list = None, value: SubTestClass = None):
self.i = i
self.s = s
self.d = d
self.l = l
self.value = value
class JSONProcessorTestCase(unittest.TestCase):
def setUp(self): ...
def test_process(self):
test_dict = {
"i": 10,
"s": "Hello World",
"d": {"test": "Test"},
"l": list(range(0, 11)),
"value": {"value": "Hello World"},
}
test: TestClass = JSONProcessor.process(TestClass, test_dict)
self.assertEqual(test.i, test_dict["i"])
self.assertEqual(test.s, test_dict["s"])
self.assertEqual(test.d, test_dict["d"])
self.assertEqual(test.l, test_dict["l"])
self.assertEqual(test.value.value, test_dict["value"]["value"])

View File

@@ -1,57 +0,0 @@
import string
import unittest
from cpl.core.utils import String
class StringTestCase(unittest.TestCase):
def setUp(self): ...
def test_convert_to_camel_case(self):
expected = "HelloWorld"
self.assertEqual(expected, String.to_camel_case("hello-world"))
self.assertEqual(expected, String.to_camel_case("hello-World"))
self.assertEqual(expected, String.to_camel_case("hello_world"))
self.assertEqual("helloWorld", String.to_camel_case("helloWorld"))
self.assertEqual(expected, String.to_camel_case("Hello_world"))
self.assertEqual(expected, String.to_camel_case("Hello_World"))
self.assertEqual(expected, String.to_camel_case("hello world"))
def test_convert_to_snake_case(self):
expected = "hello_world"
self.assertEqual(expected, String.to_snake_case("Hello World"))
self.assertEqual(expected, String.to_snake_case("hello-world"))
self.assertEqual(expected, String.to_snake_case("hello_world"))
self.assertEqual(expected, String.to_snake_case("helloWorld"))
self.assertEqual(expected, String.to_snake_case("Hello_world"))
self.assertEqual(expected, String.to_snake_case("Hello_World"))
self.assertEqual(expected, String.to_snake_case("hello world"))
def test_first_to_upper(self):
expected = "HelloWorld"
self.assertEqual(expected, String.first_to_upper("helloWorld"))
self.assertEqual(expected, String.first_to_upper("HelloWorld"))
def test_first_to_lower(self):
expected = "helloWorld"
self.assertEqual(expected, String.first_to_lower("helloWorld"))
self.assertEqual(expected, String.first_to_lower("HelloWorld"))
def test_random_string(self):
expected = ""
for x in range(0, 100):
rstr = String.random_string(string.ascii_letters, 4)
self.assertNotEqual(expected, rstr)
self.assertEqual(4, len(rstr))
expected = rstr
for x in range(0, 100):
rstr = String.random_string(string.ascii_letters, 16)
self.assertNotEqual(expected, rstr)
self.assertEqual(16, len(rstr))
expected = rstr

View File

@@ -1,346 +0,0 @@
import string
import unittest
from random import randint
from cpl.core.utils import String
from cpl.query.enumerable.enumerable import Enumerable
from cpl.query.exceptions import InvalidTypeException, ArgumentNoneException, IndexOutOfRangeException
from unittests_query.models import User, Address
class EnumerableQueryTestCase(unittest.TestCase):
def setUp(self) -> None:
users = []
for i in range(0, 100):
users.append(
User(
String.random_string(string.ascii_letters, 8).lower(),
Address(String.random_string(string.ascii_letters, 10).lower(), randint(1, 10)),
)
)
self._t_user = User("Test user", Address("teststr.", 15))
self._t_user2 = User("Test user", Address("teststr.", 14))
users.append(self._t_user)
users.append(self._t_user2)
self._tests = Enumerable(User, users)
def test_any(self):
results = []
for user in self._tests:
if user.address.nr == 10:
results.append(user)
res = self._tests.any(lambda u: u.address.nr == 10)
n_res = self._tests.any(lambda u: u.address.nr == 100)
self.assertTrue(res)
self.assertFalse(n_res)
def test_all(self):
results = []
for user in self._tests:
if user.address.nr == 10:
results.append(user)
res = self._tests.all(lambda u: u.address is not None)
n_res = self._tests.all(lambda u: u.address.nr == 100)
self.assertTrue(res)
self.assertFalse(n_res)
def test_avg(self):
avg = 0
for user in self._tests:
avg += user.address.nr
avg = avg / len(self._tests)
res = self._tests.average(lambda u: u.address.nr)
self.assertEqual(avg, res)
def invalid():
tests = Enumerable(str, ["hello", "world"])
e_res = tests.average()
self.assertRaises(InvalidTypeException, invalid)
tests = Enumerable(int, list(range(0, 100)))
self.assertEqual(sum(tests) / len(tests), tests.average())
def wrong2():
tests2 = Enumerable(int, values=list(range(0, 100)))
e_res = tests2.average(lambda u: u.address.nr)
self.assertRaises(AttributeError, wrong2)
def test_contains(self):
self.assertTrue(self._tests.contains(self._t_user))
self.assertFalse(self._tests.contains(User("Test", None)))
def test_count(self):
self.assertEqual(len(self._tests), self._tests.count())
self.assertEqual(1, self._tests.count(lambda u: u == self._t_user))
def test_distinct(self):
res = self._tests.distinct(lambda u: u.address.nr).where(lambda u: u.address.nr == 5)
self.assertEqual(1, len(res))
def test_element_at(self):
index = randint(0, len(self._tests) - 1)
self.assertEqual(self._tests.element_at(index), self._tests.element_at(index))
def invalid():
result = self._tests.element_at(len(self._tests))
self.assertRaises(IndexOutOfRangeException, invalid)
def test_element_at_or_default(self):
index = randint(0, len(self._tests) - 1)
self.assertEqual(self._tests.element_at(index), self._tests.element_at_or_default(index))
self.assertIsNone(self._tests.element_at_or_default(len(self._tests)))
def test_last(self):
results = []
for user in self._tests:
if user.address.nr == 10:
results.append(user)
res = self._tests.where(lambda u: u.address.nr == 10)
s_res = self._tests.where(lambda u: u.address.nr == 10).last()
self.assertEqual(len(res), len(results))
self.assertEqual(res.element_at(len(res) - 1), s_res)
def test_last_or_default(self):
results = []
for user in self._tests:
if user.address.nr == 10:
results.append(user)
res = self._tests.where(lambda u: u.address.nr == 10)
s_res = self._tests.where(lambda u: u.address.nr == 10).last_or_default()
sn_res = self._tests.where(lambda u: u.address.nr == 11).last_or_default()
self.assertEqual(len(res), len(results))
self.assertEqual(res.element_at(len(res) - 1), s_res)
self.assertIsNone(sn_res)
def test_first(self):
results = []
for user in self._tests:
if user.address.nr == 10:
results.append(user)
res = self._tests.where(lambda u: u.address.nr == 10)
s_res = self._tests.where(lambda u: u.address.nr == 10).first()
self.assertEqual(len(res), len(results))
self.assertEqual(res.element_at(0), s_res)
self.assertEqual(res.element_at(0), res.first())
self.assertEqual(res.first(), res.first())
def test_first_or_default(self):
results = []
for user in self._tests:
if user.address.nr == 10:
results.append(user)
res = self._tests.where(lambda u: u.address.nr == 10)
s_res = self._tests.where(lambda u: u.address.nr == 10).first_or_default()
sn_res = self._tests.where(lambda u: u.address.nr == 11).first_or_default()
self.assertEqual(len(res), len(results))
self.assertEqual(res.element_at(0), s_res)
self.assertIsNone(sn_res)
def test_for_each(self):
users = []
self._tests.for_each(lambda user: (users.append(user)))
self.assertEqual(len(users), len(self._tests))
def test_max(self):
res = self._tests.max(lambda u: u.address.nr)
self.assertEqual(res, self._t_user.address.nr)
tests = Enumerable(int, list(range(0, 100)))
self.assertEqual(99, tests.max())
def invalid():
tests = Enumerable(str, ["hello", "world"])
e_res = tests.average()
self.assertRaises(InvalidTypeException, invalid)
def test_min(self):
res = self._tests.min(lambda u: u.address.nr)
self.assertEqual(1, res)
tests = Enumerable(int, list(range(0, 100)))
self.assertEqual(0, tests.min())
def invalid():
tests = Enumerable(str, ["hello", "world"])
e_res = tests.average()
self.assertRaises(InvalidTypeException, invalid)
def test_order_by(self):
res = self._tests.order_by(lambda user: user.address.street)
res2 = self._tests.order_by(lambda user: user.address.nr).to_list()
s_res = self._tests.to_list()
s_res.sort(key=lambda user: user.address.street)
self.assertEqual(res.to_list(), s_res)
s_res = self._tests.to_list()
s_res.sort(key=lambda user: user.address.nr)
self.assertEqual(res2, s_res)
self.assertEqual(self._t_user, res.where(lambda u: u.address.nr == self._t_user.address.nr).single())
def test_order_by_descending(self):
res = self._tests.order_by_descending(lambda user: user.address.street).to_list()
res2 = self._tests.order_by_descending(lambda user: user.address.nr).to_list()
s_res = self._tests.to_list()
s_res.sort(key=lambda user: user.address.street, reverse=True)
self.assertEqual(res, s_res)
s_res = self._tests.to_list()
s_res.sort(key=lambda user: user.address.nr, reverse=True)
self.assertEqual(res2, s_res)
def test_then_by(self):
res = self._tests.order_by(lambda user: user.address.street).then_by(lambda user: user.address.nr).to_list()
s_res = self._tests.to_list()
s_res.sort(key=lambda user: (user.address.street, user.address.nr))
self.assertEqual(res, s_res)
def test_then_by_descending(self):
res = (
self._tests.order_by_descending(lambda user: user.address.street)
.then_by_descending(lambda user: user.address.nr)
.to_list()
)
s_res = self._tests.to_list()
s_res.sort(key=lambda user: (user.address.street, user.address.nr), reverse=True)
self.assertEqual(res, s_res)
def test_reverse(self):
res = self._tests.reverse().to_list()
l_res = self._tests.to_list()
l_res.reverse()
self.assertEqual(res, l_res)
def test_select(self):
range_list = Enumerable(int, range(0, 100))
selected_range = range_list.select(lambda x: x + 1)
modulo_range = []
for x in range(0, 100):
if x % 2 == 0:
modulo_range.append(x)
self.assertEqual(selected_range.to_list(), list(range(1, 101)))
self.assertEqual(range_list.where(lambda x: x % 2 == 0).to_list(), modulo_range)
def test_select_many(self):
range_list = Enumerable(int, list(range(0, 100)))
selected_range = range_list.select(lambda x: [x, x])
self.assertEqual(selected_range.to_list(), [[x, x] for x in range(0, 100)])
self.assertEqual(
selected_range.select_many(lambda x: x).to_list(),
[_x for _l in [2 * [x] for x in range(0, 100)] for _x in _l],
)
class TestClass:
def __init__(self, i, is_sub=False):
self.i = i
if is_sub:
return
self.elements = [TestClass(x, True) for x in range(0, 10)]
elements = Enumerable(TestClass, [TestClass(i) for i in range(0, 100)])
selected_elements = elements.select_many(lambda x: x.elements).select(lambda x: x.i)
self.assertEqual(selected_elements.where(lambda x: x == 0).count(), 100)
def test_single(self):
res = self._tests.where(lambda u: u.address.nr == self._t_user.address.nr)
s_res = self._tests.where(lambda u: u.address.nr == self._t_user.address.nr).single()
self.assertEqual(len(res), 1)
self.assertEqual(self._t_user, s_res)
def test_single_or_default(self):
res = self._tests.where(lambda u: u.address.nr == self._t_user.address.nr)
s_res = self._tests.where(lambda u: u.address.nr == self._t_user.address.nr).single_or_default()
sn_res = self._tests.where(lambda u: u.address.nr == self._t_user.address.nr + 1).single_or_default()
self.assertEqual(len(res), 1)
self.assertEqual(self._t_user, s_res)
self.assertIsNone(sn_res)
def test_skip(self):
skipped = self._tests.skip(5).to_list()
self.assertEqual(len(skipped), len(self._tests) - 5)
self.assertEqual(skipped, self._tests.to_list()[5:])
def test_skip_last(self):
skipped = self._tests.skip_last(5)
self.assertEqual(skipped.count(), len(self._tests) - 5)
self.assertEqual(skipped.to_list(), self._tests.to_list()[:-5])
self.assertEqual(skipped.last(), self._tests.to_list()[:-5][len(self._tests.to_list()[:-5]) - 1])
def test_sum(self):
res = self._tests.sum(lambda u: u.address.nr)
s_res = 0
for user in self._tests:
s_res += user.address.nr
self.assertEqual(s_res, res)
tests = Enumerable(int, list(range(0, 100)))
self.assertEqual(0, tests.min())
def invalid():
tests2 = Enumerable(str, ["hello", "world"])
e_res = tests2.average()
self.assertRaises(InvalidTypeException, invalid)
def test_take(self):
skipped = self._tests.take(5)
self.assertEqual(skipped.count(), 5)
self.assertEqual(skipped.to_list(), self._tests.to_list()[:5])
def test_take_last(self):
skipped = self._tests.take_last(5)
self.assertEqual(skipped.count(), 5)
self.assertEqual(skipped.to_list(), self._tests.to_list()[-5:])
self.assertEqual(skipped.last(), self._tests.to_list()[len(self._tests) - 1])
def test_where(self):
results = []
for user in self._tests:
if user.address.nr == 5:
results.append(user)
res = self._tests.where(lambda u: u.address.nr == 5)
self.assertEqual(len(results), len(res))
def ex():
e_res = self._tests.where(None)
self.assertRaises(ArgumentNoneException, ex)

View File

@@ -1,35 +0,0 @@
import unittest
from cpl.query.enumerable.enumerable import Enumerable
class EnumerableTestCase(unittest.TestCase):
def setUp(self) -> None:
self._list = Enumerable(int, list(range(1, 4)))
def test_append(self):
self.assertEqual(self._list.to_list(), [1, 2, 3])
self.assertRaises(Exception, lambda v: self._list.add(v), "3")
def test_default(self):
self.assertEqual(Enumerable.empty().to_list(), [])
self.assertEqual(Enumerable.range(0, 100).to_list(), list(range(0, 100)))
# def test_iter(self):
# n = 0
# elements = Enumerable.range(0, 100)
# while n < 100:
# self.assertEqual(elements.next(), n)
# n += 1
def test_for(self):
n = 0
for i in Enumerable.range(0, 100):
self.assertEqual(i, n)
n += 1
def test_get(self):
self.assertEqual(self._list.element_at(2), [1, 2, 3][2])
def test_count(self):
self.assertEqual(self._list.count(), 3)

Some files were not shown because too many files have changed in this diff Show More