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