splent-framework 1.6.0__tar.gz → 1.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {splent_framework-1.6.0/src/splent_framework.egg-info → splent_framework-1.6.2}/PKG-INFO +1 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2}/pyproject.toml +1 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/path_utils.py +18 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2/src/splent_framework.egg-info}/PKG-INFO +1 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_base_blueprint_security.py +7 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_base_repository.py +5 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_config_manager.py +2 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_context_manager.py +2 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_default_config.py +1 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_error_handler_manager.py +14 -5
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_feature_manager_parse.py +2 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_feature_order.py +7 -1
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_feature_utils.py +4 -4
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_generic_resource.py +13 -7
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_migration_manager.py +36 -21
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_namespace_manager.py +3 -2
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_pyproject_reader.py +23 -11
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_pyproject_reader_env.py +8 -4
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_refinement.py +72 -44
- {splent_framework-1.6.0 → splent_framework-1.6.2}/LICENSE +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/README.md +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/setup.cfg +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/_init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/app_factory.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/blueprints/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/blueprints/base_blueprint.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/bootstraps/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/bootstraps/locustfile_bootstrap.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/configuration/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/configuration/configuration.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/configuration/default_config.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/context/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/context/context_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/db.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/decorators/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/decorators/decorators.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/environment/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/environment/host.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/fixtures/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/fixtures/fixtures.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/helpers/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/helpers/test_helpers_auth.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/hooks/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/hooks/template_hooks.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/locust/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/locust/common.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/config_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/db_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/error_handler_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/feature_loader.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/feature_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/feature_order.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/jinja_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/locale_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/logging_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/migration_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/namespace_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/session_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/task_queue_manager.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/migrations/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/migrations/feature_env.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/namespaces/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/model_extender.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/parser.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/registry.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/validator.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/repositories/BaseRepository.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/repositories/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/resources/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/resources/generic_resource.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/seeders/BaseSeeder.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/seeders/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/selenium/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/selenium/common.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/serialisers/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/serialisers/serializer.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/services/BaseService.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/services/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/services/service_locator.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/signals/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/signals/registry.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/signals/signal_utils.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/__init__.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/app_loader.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/feature_utils.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/form_helpers.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/pyproject_reader.py +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/SOURCES.txt +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/dependency_links.txt +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/requires.txt +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/top_level.txt +0 -0
- {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_config_manager_return.py +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_framework
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: SPLENT-FRAMEWORK is a set of libraries for agile product development within SPLENT.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_framework
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splent_framework"
|
|
7
|
-
version = "1.6.
|
|
7
|
+
version = "1.6.2"
|
|
8
8
|
description = "SPLENT-FRAMEWORK is a set of libraries for agile product development within SPLENT."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -29,7 +29,24 @@ class PathUtils:
|
|
|
29
29
|
def get_app_env_file():
|
|
30
30
|
working_dir = PathUtils.get_working_dir()
|
|
31
31
|
splent_app = os.getenv("SPLENT_APP", "splent_app")
|
|
32
|
-
|
|
32
|
+
docker_dir = os.path.join(working_dir, splent_app, "docker")
|
|
33
|
+
|
|
34
|
+
# In production the container is configured through the deploy env_file
|
|
35
|
+
# (``.env.deploy``), which is the single source of truth for prod values
|
|
36
|
+
# such as ``MARIADB_HOSTNAME``. The development ``.env`` is baked into the
|
|
37
|
+
# production image at build time (it points at the *dev* database host),
|
|
38
|
+
# so loading it with ``override=True`` would clobber the correct deploy
|
|
39
|
+
# values that the container already received from its ``env_file``.
|
|
40
|
+
#
|
|
41
|
+
# When running in production, return the ``.env.deploy`` path. It is not
|
|
42
|
+
# baked into the image (it is generated after the build), so callers that
|
|
43
|
+
# guard with ``os.path.exists(...)`` simply skip the load and keep the
|
|
44
|
+
# authoritative process environment untouched. If it ever is present, it
|
|
45
|
+
# carries the correct production values, so loading it is still right.
|
|
46
|
+
if os.getenv("SPLENT_ENV", "dev").lower() == "prod":
|
|
47
|
+
return os.path.join(docker_dir, ".env.deploy")
|
|
48
|
+
|
|
49
|
+
return os.path.join(docker_dir, ".env")
|
|
33
50
|
|
|
34
51
|
@staticmethod
|
|
35
52
|
def get_modules_dir():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splent_framework
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: SPLENT-FRAMEWORK is a set of libraries for agile product development within SPLENT.
|
|
5
5
|
Author-email: DiversoLab <diversolab@us.es>
|
|
6
6
|
Project-URL: Homepage, https://github.com/diverso-lab/splent_framework
|
|
@@ -8,6 +8,7 @@ assets directory, and verify:
|
|
|
8
8
|
- Filenames with path traversal sequences → 403
|
|
9
9
|
- Non-existent files → 404
|
|
10
10
|
"""
|
|
11
|
+
|
|
11
12
|
import os
|
|
12
13
|
import pytest
|
|
13
14
|
from flask import Flask
|
|
@@ -20,6 +21,7 @@ from splent_framework.blueprints.base_blueprint import BaseBlueprint
|
|
|
20
21
|
# Helpers
|
|
21
22
|
# ---------------------------------------------------------------------------
|
|
22
23
|
|
|
24
|
+
|
|
23
25
|
def make_asset_tree(base: str):
|
|
24
26
|
"""Create a small asset tree under base/assets/."""
|
|
25
27
|
for subfolder in ("js", "css", "dist"):
|
|
@@ -43,6 +45,7 @@ def make_asset_tree(base: str):
|
|
|
43
45
|
# Fixtures
|
|
44
46
|
# ---------------------------------------------------------------------------
|
|
45
47
|
|
|
48
|
+
|
|
46
49
|
@pytest.fixture
|
|
47
50
|
def feature_dir(tmp_path):
|
|
48
51
|
"""Temp directory acting as a feature package root."""
|
|
@@ -77,6 +80,7 @@ def client(app):
|
|
|
77
80
|
# Serving legitimate files
|
|
78
81
|
# ---------------------------------------------------------------------------
|
|
79
82
|
|
|
83
|
+
|
|
80
84
|
class TestServeLegitimateFiles:
|
|
81
85
|
def test_serves_js_with_correct_mimetype(self, client):
|
|
82
86
|
resp = client.get("/test_feature/js/app.js")
|
|
@@ -101,6 +105,7 @@ class TestServeLegitimateFiles:
|
|
|
101
105
|
# Security: path traversal blocked
|
|
102
106
|
# ---------------------------------------------------------------------------
|
|
103
107
|
|
|
108
|
+
|
|
104
109
|
class TestPathTraversalBlocked:
|
|
105
110
|
def test_traversal_in_filename_blocked(self, client):
|
|
106
111
|
# Attempt to escape assets/js/ via ../
|
|
@@ -120,6 +125,7 @@ class TestPathTraversalBlocked:
|
|
|
120
125
|
# Security: subfolder whitelist enforced
|
|
121
126
|
# ---------------------------------------------------------------------------
|
|
122
127
|
|
|
128
|
+
|
|
123
129
|
class TestSubfolderWhitelist:
|
|
124
130
|
def test_invalid_subfolder_returns_404(self, client):
|
|
125
131
|
resp = client.get("/test_feature/templates/base.html")
|
|
@@ -139,6 +145,7 @@ class TestSubfolderWhitelist:
|
|
|
139
145
|
# Non-existent files
|
|
140
146
|
# ---------------------------------------------------------------------------
|
|
141
147
|
|
|
148
|
+
|
|
142
149
|
class TestNonExistentFiles:
|
|
143
150
|
def test_missing_file_returns_404(self, client):
|
|
144
151
|
resp = client.get("/test_feature/js/nonexistent.js")
|
|
@@ -4,6 +4,7 @@ Tests for splent_framework.repositories.BaseRepository
|
|
|
4
4
|
Uses SQLite in-memory via a minimal Flask app.
|
|
5
5
|
Covers all CRUD methods and edge cases.
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import pytest
|
|
8
9
|
from flask import Flask
|
|
9
10
|
from sqlalchemy import String
|
|
@@ -17,6 +18,7 @@ from splent_framework.repositories.BaseRepository import BaseRepository
|
|
|
17
18
|
# Minimal model for testing
|
|
18
19
|
# ---------------------------------------------------------------------------
|
|
19
20
|
|
|
21
|
+
|
|
20
22
|
class Base(DeclarativeBase):
|
|
21
23
|
pass
|
|
22
24
|
|
|
@@ -32,6 +34,7 @@ class Widget(_db.Model):
|
|
|
32
34
|
# Fixtures
|
|
33
35
|
# ---------------------------------------------------------------------------
|
|
34
36
|
|
|
37
|
+
|
|
35
38
|
@pytest.fixture(scope="function")
|
|
36
39
|
def app():
|
|
37
40
|
application = Flask(__name__)
|
|
@@ -60,6 +63,7 @@ def one_widget(repo):
|
|
|
60
63
|
# Tests
|
|
61
64
|
# ---------------------------------------------------------------------------
|
|
62
65
|
|
|
66
|
+
|
|
63
67
|
class TestCreate:
|
|
64
68
|
def test_creates_and_returns_instance(self, repo):
|
|
65
69
|
w = repo.create(name="Bolt", color="silver")
|
|
@@ -108,6 +112,7 @@ class TestGetOr404:
|
|
|
108
112
|
|
|
109
113
|
def test_raises_404_when_not_found(self, repo, app):
|
|
110
114
|
from werkzeug.exceptions import NotFound
|
|
115
|
+
|
|
111
116
|
with app.test_request_context():
|
|
112
117
|
with pytest.raises(NotFound):
|
|
113
118
|
repo.get_or_404(9999)
|
|
@@ -11,6 +11,7 @@ from splent_framework.managers.config_manager import ConfigManager
|
|
|
11
11
|
# Helpers
|
|
12
12
|
# ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
def _make_app():
|
|
15
16
|
return Flask(__name__)
|
|
16
17
|
|
|
@@ -19,6 +20,7 @@ def _make_app():
|
|
|
19
20
|
# load_config — uses splent_framework default config (no product module)
|
|
20
21
|
# ---------------------------------------------------------------------------
|
|
21
22
|
|
|
23
|
+
|
|
22
24
|
class TestLoadConfigDefaults:
|
|
23
25
|
def test_development_sets_debug_true(self, monkeypatch):
|
|
24
26
|
monkeypatch.setenv("SPLENT_APP", "nonexistent_product_xyz")
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Tests for context_manager.build_jinja_context.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import pytest
|
|
6
5
|
from flask import Flask
|
|
7
6
|
from splent_framework.context.context_manager import build_jinja_context
|
|
8
7
|
|
|
@@ -11,6 +10,7 @@ from splent_framework.context.context_manager import build_jinja_context
|
|
|
11
10
|
# Helpers
|
|
12
11
|
# ---------------------------------------------------------------------------
|
|
13
12
|
|
|
13
|
+
|
|
14
14
|
def _app():
|
|
15
15
|
app = Flask(__name__)
|
|
16
16
|
app.context_processors = []
|
|
@@ -21,6 +21,7 @@ def _app():
|
|
|
21
21
|
# Basic behaviour
|
|
22
22
|
# ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
|
+
|
|
24
25
|
class TestBuildJinjaContext:
|
|
25
26
|
def test_returns_base_context_unchanged_when_no_processors(self):
|
|
26
27
|
app = _app()
|
|
@@ -3,8 +3,7 @@ Tests for ErrorHandlerManager.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import types
|
|
6
|
-
import
|
|
7
|
-
from unittest.mock import MagicMock, patch
|
|
6
|
+
from unittest.mock import patch
|
|
8
7
|
from flask import Flask
|
|
9
8
|
from splent_framework.managers.error_handler_manager import ErrorHandlerManager
|
|
10
9
|
|
|
@@ -13,6 +12,7 @@ from splent_framework.managers.error_handler_manager import ErrorHandlerManager
|
|
|
13
12
|
# Helpers
|
|
14
13
|
# ---------------------------------------------------------------------------
|
|
15
14
|
|
|
15
|
+
|
|
16
16
|
def _make_app():
|
|
17
17
|
app = Flask(__name__)
|
|
18
18
|
app.config["TESTING"] = True
|
|
@@ -23,6 +23,7 @@ def _make_app():
|
|
|
23
23
|
# _import_custom_handlers
|
|
24
24
|
# ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
|
+
|
|
26
27
|
class TestImportCustomHandlers:
|
|
27
28
|
def test_returns_none_when_module_not_found(self, monkeypatch):
|
|
28
29
|
monkeypatch.setenv("SPLENT_APP", "nonexistent_app_xyz")
|
|
@@ -46,6 +47,7 @@ class TestImportCustomHandlers:
|
|
|
46
47
|
# _get_handler
|
|
47
48
|
# ---------------------------------------------------------------------------
|
|
48
49
|
|
|
50
|
+
|
|
49
51
|
class TestGetHandler:
|
|
50
52
|
def _manager_with_handlers(self, handlers_dict):
|
|
51
53
|
mod = types.SimpleNamespace(**handlers_dict)
|
|
@@ -55,19 +57,25 @@ class TestGetHandler:
|
|
|
55
57
|
return manager
|
|
56
58
|
|
|
57
59
|
def test_returns_custom_handler_when_present(self):
|
|
58
|
-
custom
|
|
60
|
+
def custom(app, e):
|
|
61
|
+
return ("custom", 404)
|
|
62
|
+
|
|
59
63
|
manager = self._manager_with_handlers({"handle_404": custom})
|
|
60
64
|
result = manager._get_handler("handle_404", lambda a, e: ("default", 404))
|
|
61
65
|
assert result is custom
|
|
62
66
|
|
|
63
67
|
def test_returns_fallback_when_custom_missing(self):
|
|
64
|
-
fallback
|
|
68
|
+
def fallback(app, e):
|
|
69
|
+
return ("default", 404)
|
|
70
|
+
|
|
65
71
|
manager = self._manager_with_handlers({})
|
|
66
72
|
result = manager._get_handler("handle_404", fallback)
|
|
67
73
|
assert result is fallback
|
|
68
74
|
|
|
69
75
|
def test_returns_fallback_when_no_custom_handlers_module(self):
|
|
70
|
-
fallback
|
|
76
|
+
def fallback(app, e):
|
|
77
|
+
return ("default", 500)
|
|
78
|
+
|
|
71
79
|
manager = ErrorHandlerManager.__new__(ErrorHandlerManager)
|
|
72
80
|
manager.app = _make_app()
|
|
73
81
|
manager.custom_handlers = None
|
|
@@ -79,6 +87,7 @@ class TestGetHandler:
|
|
|
79
87
|
# register_error_handlers + default handlers
|
|
80
88
|
# ---------------------------------------------------------------------------
|
|
81
89
|
|
|
90
|
+
|
|
82
91
|
class TestRegisterErrorHandlers:
|
|
83
92
|
def test_registers_500_handler(self, monkeypatch):
|
|
84
93
|
monkeypatch.setenv("SPLENT_APP", "nonexistent_xyz")
|
|
@@ -4,8 +4,9 @@ Tests for FeatureEntryParser and FeatureRef.
|
|
|
4
4
|
Covers all supported entry formats:
|
|
5
5
|
org/name@version | name@version | org/name | name
|
|
6
6
|
"""
|
|
7
|
+
|
|
7
8
|
import pytest
|
|
8
|
-
from splent_framework.managers.feature_loader import FeatureEntryParser,
|
|
9
|
+
from splent_framework.managers.feature_loader import FeatureEntryParser, FeatureError
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@pytest.fixture
|
|
@@ -70,6 +70,7 @@ def pkg(name: str) -> str:
|
|
|
70
70
|
# UVL parsing
|
|
71
71
|
# ---------------------------------------------------------------------------
|
|
72
72
|
|
|
73
|
+
|
|
73
74
|
class TestParsePackageMap:
|
|
74
75
|
def test_extracts_short_to_package_mapping(self):
|
|
75
76
|
mapping = FeatureLoadOrderResolver._parse_package_map(SAMPLE_UVL)
|
|
@@ -102,6 +103,7 @@ class TestParseConstraints:
|
|
|
102
103
|
# Core ordering — single constraint
|
|
103
104
|
# ---------------------------------------------------------------------------
|
|
104
105
|
|
|
106
|
+
|
|
105
107
|
class TestSingleDependency:
|
|
106
108
|
def test_profile_loads_after_auth_when_profile_declared_first(self, tmp_path):
|
|
107
109
|
uvl = write_uvl(tmp_path, SAMPLE_UVL)
|
|
@@ -132,6 +134,7 @@ class TestSingleDependency:
|
|
|
132
134
|
# Full product set (mirrors sample_splent_app)
|
|
133
135
|
# ---------------------------------------------------------------------------
|
|
134
136
|
|
|
137
|
+
|
|
135
138
|
class TestFullProductOrder:
|
|
136
139
|
FULL = [
|
|
137
140
|
pkg("profile"),
|
|
@@ -174,6 +177,7 @@ class TestFullProductOrder:
|
|
|
174
177
|
# Fallback behaviour
|
|
175
178
|
# ---------------------------------------------------------------------------
|
|
176
179
|
|
|
180
|
+
|
|
177
181
|
class TestFallback:
|
|
178
182
|
def test_returns_original_order_when_uvl_path_is_none(self):
|
|
179
183
|
features = [pkg("profile"), pkg("auth")]
|
|
@@ -188,7 +192,7 @@ class TestFallback:
|
|
|
188
192
|
def test_returns_original_order_when_no_constraints_apply(self, tmp_path):
|
|
189
193
|
"""Features not mentioned in UVL constraints → pyproject order."""
|
|
190
194
|
uvl = write_uvl(tmp_path, SAMPLE_UVL)
|
|
191
|
-
features = [pkg("public"), pkg("redis")]
|
|
195
|
+
features = [pkg("public"), pkg("redis")] # neither in UVL constraints
|
|
192
196
|
result = RESOLVER.resolve(features, uvl)
|
|
193
197
|
assert result == features
|
|
194
198
|
|
|
@@ -201,6 +205,7 @@ class TestFallback:
|
|
|
201
205
|
# Cycle detection
|
|
202
206
|
# ---------------------------------------------------------------------------
|
|
203
207
|
|
|
208
|
+
|
|
204
209
|
class TestCycleDetection:
|
|
205
210
|
def test_raises_on_direct_cycle(self, tmp_path):
|
|
206
211
|
uvl = write_uvl(tmp_path, CYCLIC_UVL)
|
|
@@ -221,6 +226,7 @@ class TestCycleDetection:
|
|
|
221
226
|
# Stability (original order preserved for ties)
|
|
222
227
|
# ---------------------------------------------------------------------------
|
|
223
228
|
|
|
229
|
+
|
|
224
230
|
class TestStableOrdering:
|
|
225
231
|
def test_three_independents_keep_original_order(self, tmp_path):
|
|
226
232
|
"""No constraints between auth, public, redis → pyproject order preserved."""
|
|
@@ -7,6 +7,7 @@ Covers:
|
|
|
7
7
|
- Returns [] when [project.optional-dependencies] key is missing
|
|
8
8
|
- Raises RuntimeError on malformed TOML (not silent swallow)
|
|
9
9
|
"""
|
|
10
|
+
|
|
10
11
|
import pytest
|
|
11
12
|
from splent_framework.utils.feature_utils import get_features_from_pyproject
|
|
12
13
|
|
|
@@ -27,7 +28,7 @@ def app_dir(tmp_path):
|
|
|
27
28
|
class TestGetFeaturesFromPyproject:
|
|
28
29
|
def test_returns_features_list(self, app_dir):
|
|
29
30
|
(app_dir / "pyproject.toml").write_text(
|
|
30
|
-
|
|
31
|
+
"[project.optional-dependencies]\n"
|
|
31
32
|
'features = ["splent_feature_auth@v1.0.0", "splent_feature_public@v1.0.0"]\n'
|
|
32
33
|
)
|
|
33
34
|
result = get_features_from_pyproject()
|
|
@@ -35,8 +36,7 @@ class TestGetFeaturesFromPyproject:
|
|
|
35
36
|
|
|
36
37
|
def test_returns_empty_list_when_features_key_missing(self, app_dir):
|
|
37
38
|
(app_dir / "pyproject.toml").write_text(
|
|
38
|
-
'[project.optional-dependencies]\n'
|
|
39
|
-
'core = ["flask"]\n'
|
|
39
|
+
'[project.optional-dependencies]\ncore = ["flask"]\n'
|
|
40
40
|
)
|
|
41
41
|
result = get_features_from_pyproject()
|
|
42
42
|
assert result == []
|
|
@@ -60,7 +60,7 @@ class TestGetFeaturesFromPyproject:
|
|
|
60
60
|
|
|
61
61
|
def test_returns_empty_list_for_empty_features(self, app_dir):
|
|
62
62
|
(app_dir / "pyproject.toml").write_text(
|
|
63
|
-
|
|
63
|
+
"[project.optional-dependencies]\nfeatures = []\n"
|
|
64
64
|
)
|
|
65
65
|
result = get_features_from_pyproject()
|
|
66
66
|
assert result == []
|
|
@@ -2,21 +2,27 @@
|
|
|
2
2
|
Tests for GenericResource — verifies both Serializer objects and plain
|
|
3
3
|
field lists work correctly as the serializer parameter.
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
import pytest
|
|
7
7
|
from flask import Flask
|
|
8
8
|
|
|
9
9
|
flask_restful = pytest.importorskip("flask_restful")
|
|
10
10
|
Api = flask_restful.Api
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
# Imported after importorskip on purpose: generic_resource pulls in
|
|
13
|
+
# flask_restful, so the module must be skipped first when it is absent.
|
|
14
|
+
from splent_framework.db import db as _db # noqa: E402
|
|
15
|
+
from splent_framework.resources.generic_resource import ( # noqa: E402
|
|
16
|
+
GenericResource,
|
|
17
|
+
create_resource,
|
|
18
|
+
)
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
# ---------------------------------------------------------------------------
|
|
17
22
|
# Model fixture
|
|
18
23
|
# ---------------------------------------------------------------------------
|
|
19
24
|
|
|
25
|
+
|
|
20
26
|
class Item(_db.Model):
|
|
21
27
|
__tablename__ = "item"
|
|
22
28
|
id = _db.Column(_db.Integer, primary_key=True)
|
|
@@ -46,8 +52,8 @@ def client(app):
|
|
|
46
52
|
# Tests: create_resource with field list
|
|
47
53
|
# ---------------------------------------------------------------------------
|
|
48
54
|
|
|
49
|
-
class TestCreateResourceWithFieldList:
|
|
50
55
|
|
|
56
|
+
class TestCreateResourceWithFieldList:
|
|
51
57
|
def test_post_filters_by_allowed_fields(self, app, client):
|
|
52
58
|
api = Api(app)
|
|
53
59
|
ResourceClass = create_resource(Item, ["name"])
|
|
@@ -97,7 +103,9 @@ class TestCreateResourceWithFieldList:
|
|
|
97
103
|
_db.session.commit()
|
|
98
104
|
item_id = item.id
|
|
99
105
|
|
|
100
|
-
resp = client.put(
|
|
106
|
+
resp = client.put(
|
|
107
|
+
f"/items/{item_id}", json={"name": "new", "secret": "changed"}
|
|
108
|
+
)
|
|
101
109
|
assert resp.status_code == 200
|
|
102
110
|
|
|
103
111
|
with app.app_context():
|
|
@@ -121,7 +129,6 @@ class TestCreateResourceWithFieldList:
|
|
|
121
129
|
|
|
122
130
|
|
|
123
131
|
class TestCreateResourceWithNone:
|
|
124
|
-
|
|
125
132
|
def test_post_with_no_fields_accepts_all(self, app, client):
|
|
126
133
|
api = Api(app)
|
|
127
134
|
ResourceClass = create_resource(Item)
|
|
@@ -148,7 +155,6 @@ class TestCreateResourceWithNone:
|
|
|
148
155
|
|
|
149
156
|
|
|
150
157
|
class TestCreateResourceWithSerializer:
|
|
151
|
-
|
|
152
158
|
def test_works_with_serializer_object(self, app, client):
|
|
153
159
|
from splent_framework.serialisers.serializer import Serializer
|
|
154
160
|
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
Tests for MigrationManager — filesystem-based migration directory resolution
|
|
3
3
|
and UVL-ordered feature discovery.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
import os
|
|
6
7
|
import pytest
|
|
7
|
-
from unittest.mock import patch
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# ---------------------------------------------------------------------------
|
|
11
11
|
# Fixtures
|
|
12
12
|
# ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
@pytest.fixture
|
|
15
16
|
def feature_workspace(tmp_path, monkeypatch):
|
|
16
17
|
"""
|
|
@@ -30,11 +31,11 @@ def feature_workspace(tmp_path, monkeypatch):
|
|
|
30
31
|
pyproject = product / "pyproject.toml"
|
|
31
32
|
pyproject.write_text(
|
|
32
33
|
'[project]\nname = "test_app"\nversion = "1.0.0"\n'
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
"[project.optional-dependencies]\n"
|
|
35
|
+
"features = [\n"
|
|
35
36
|
' "splent-io/splent_feature_auth@v1.0.0",\n'
|
|
36
37
|
' "splent-io/splent_feature_profile@v1.0.0",\n'
|
|
37
|
-
|
|
38
|
+
"]\n"
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
# Create cached features with migrations
|
|
@@ -47,9 +48,7 @@ def feature_workspace(tmp_path, monkeypatch):
|
|
|
47
48
|
|
|
48
49
|
# Create relative symlinks (like product:sync does)
|
|
49
50
|
for feat in ("splent_feature_auth", "splent_feature_profile"):
|
|
50
|
-
target = os.path.relpath(
|
|
51
|
-
cache / f"{feat}@v1.0.0", features_dir
|
|
52
|
-
)
|
|
51
|
+
target = os.path.relpath(cache / f"{feat}@v1.0.0", features_dir)
|
|
53
52
|
(features_dir / f"{feat}@v1.0.0").symlink_to(target)
|
|
54
53
|
|
|
55
54
|
return tmp_path
|
|
@@ -71,17 +70,21 @@ def feature_workspace_no_migrations(tmp_path, monkeypatch):
|
|
|
71
70
|
pyproject = product / "pyproject.toml"
|
|
72
71
|
pyproject.write_text(
|
|
73
72
|
'[project]\nname = "test_app"\nversion = "1.0.0"\n'
|
|
74
|
-
|
|
73
|
+
"[project.optional-dependencies]\n"
|
|
75
74
|
'features = ["splent-io/splent_feature_nomigrations@v1.0.0"]\n'
|
|
76
75
|
)
|
|
77
76
|
|
|
78
|
-
feat_dir =
|
|
77
|
+
feat_dir = (
|
|
78
|
+
cache
|
|
79
|
+
/ "splent_feature_nomigrations@v1.0.0"
|
|
80
|
+
/ "src"
|
|
81
|
+
/ "splent_io"
|
|
82
|
+
/ "splent_feature_nomigrations"
|
|
83
|
+
)
|
|
79
84
|
feat_dir.mkdir(parents=True)
|
|
80
85
|
# No migrations/ directory
|
|
81
86
|
|
|
82
|
-
target = os.path.relpath(
|
|
83
|
-
cache / "splent_feature_nomigrations@v1.0.0", features_dir
|
|
84
|
-
)
|
|
87
|
+
target = os.path.relpath(cache / "splent_feature_nomigrations@v1.0.0", features_dir)
|
|
85
88
|
(features_dir / "splent_feature_nomigrations@v1.0.0").symlink_to(target)
|
|
86
89
|
|
|
87
90
|
return tmp_path
|
|
@@ -91,8 +94,8 @@ def feature_workspace_no_migrations(tmp_path, monkeypatch):
|
|
|
91
94
|
# Tests: get_feature_migration_dir
|
|
92
95
|
# ---------------------------------------------------------------------------
|
|
93
96
|
|
|
94
|
-
class TestGetFeatureMigrationDir:
|
|
95
97
|
|
|
98
|
+
class TestGetFeatureMigrationDir:
|
|
96
99
|
def test_finds_migration_dir_via_filesystem(self, feature_workspace):
|
|
97
100
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
98
101
|
|
|
@@ -104,26 +107,34 @@ class TestGetFeatureMigrationDir:
|
|
|
104
107
|
def test_returns_none_when_no_migrations(self, feature_workspace_no_migrations):
|
|
105
108
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
106
109
|
|
|
107
|
-
result = MigrationManager.get_feature_migration_dir(
|
|
110
|
+
result = MigrationManager.get_feature_migration_dir(
|
|
111
|
+
"splent_feature_nomigrations"
|
|
112
|
+
)
|
|
108
113
|
assert result is None
|
|
109
114
|
|
|
110
115
|
def test_returns_none_for_unknown_feature(self, feature_workspace):
|
|
111
116
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
112
117
|
|
|
113
|
-
result = MigrationManager.get_feature_migration_dir(
|
|
118
|
+
result = MigrationManager.get_feature_migration_dir(
|
|
119
|
+
"splent_feature_nonexistent"
|
|
120
|
+
)
|
|
114
121
|
assert result is None
|
|
115
122
|
|
|
116
123
|
def test_strips_version_from_name(self, feature_workspace):
|
|
117
124
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
118
125
|
|
|
119
|
-
result = MigrationManager.get_feature_migration_dir(
|
|
126
|
+
result = MigrationManager.get_feature_migration_dir(
|
|
127
|
+
"splent_feature_auth@v1.0.0"
|
|
128
|
+
)
|
|
120
129
|
assert result is not None
|
|
121
130
|
assert "migrations" in result
|
|
122
131
|
|
|
123
132
|
def test_strips_org_from_name(self, feature_workspace):
|
|
124
133
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
125
134
|
|
|
126
|
-
result = MigrationManager.get_feature_migration_dir(
|
|
135
|
+
result = MigrationManager.get_feature_migration_dir(
|
|
136
|
+
"splent-io/splent_feature_auth"
|
|
137
|
+
)
|
|
127
138
|
assert result is not None
|
|
128
139
|
|
|
129
140
|
|
|
@@ -131,8 +142,8 @@ class TestGetFeatureMigrationDir:
|
|
|
131
142
|
# Tests: get_all_feature_migration_dirs
|
|
132
143
|
# ---------------------------------------------------------------------------
|
|
133
144
|
|
|
134
|
-
class TestGetAllFeatureMigrationDirs:
|
|
135
145
|
|
|
146
|
+
class TestGetAllFeatureMigrationDirs:
|
|
136
147
|
def test_finds_all_features_with_migrations(self, feature_workspace):
|
|
137
148
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
138
149
|
|
|
@@ -149,10 +160,11 @@ class TestGetAllFeatureMigrationDirs:
|
|
|
149
160
|
product.mkdir()
|
|
150
161
|
(product / "pyproject.toml").write_text(
|
|
151
162
|
'[project]\nname = "test_app"\nversion = "1.0.0"\n'
|
|
152
|
-
|
|
163
|
+
"[project.optional-dependencies]\nfeatures = []\n"
|
|
153
164
|
)
|
|
154
165
|
|
|
155
166
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
167
|
+
|
|
156
168
|
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
157
169
|
assert dirs == {}
|
|
158
170
|
|
|
@@ -160,6 +172,7 @@ class TestGetAllFeatureMigrationDirs:
|
|
|
160
172
|
monkeypatch.delenv("SPLENT_APP", raising=False)
|
|
161
173
|
|
|
162
174
|
from splent_framework.managers.migration_manager import MigrationManager
|
|
175
|
+
|
|
163
176
|
dirs = MigrationManager.get_all_feature_migration_dirs()
|
|
164
177
|
assert dirs == {}
|
|
165
178
|
|
|
@@ -188,14 +201,16 @@ class TestGetAllFeatureMigrationDirs:
|
|
|
188
201
|
# Tests: relative symlinks work correctly
|
|
189
202
|
# ---------------------------------------------------------------------------
|
|
190
203
|
|
|
191
|
-
class TestSymlinkResolution:
|
|
192
204
|
|
|
205
|
+
class TestSymlinkResolution:
|
|
193
206
|
def test_symlinks_are_relative(self, feature_workspace):
|
|
194
207
|
features_dir = feature_workspace / "test_app" / "features" / "splent_io"
|
|
195
208
|
for link in features_dir.iterdir():
|
|
196
209
|
assert link.is_symlink()
|
|
197
210
|
target = os.readlink(str(link))
|
|
198
|
-
assert not os.path.isabs(target),
|
|
211
|
+
assert not os.path.isabs(target), (
|
|
212
|
+
f"Symlink {link} has absolute target: {target}"
|
|
213
|
+
)
|
|
199
214
|
|
|
200
215
|
def test_relative_symlinks_resolve_correctly(self, feature_workspace):
|
|
201
216
|
features_dir = feature_workspace / "test_app" / "features" / "splent_io"
|
|
@@ -5,9 +5,7 @@ NamespaceManager.init_app is integration-only (touches sys.path and importlib),
|
|
|
5
5
|
so we test the four extracted static methods in isolation using tmp_path.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import os
|
|
9
8
|
import sys
|
|
10
|
-
import pytest
|
|
11
9
|
from splent_framework.managers.namespace_manager import NamespaceManager
|
|
12
10
|
|
|
13
11
|
|
|
@@ -15,6 +13,7 @@ from splent_framework.managers.namespace_manager import NamespaceManager
|
|
|
15
13
|
# _detect_orgs
|
|
16
14
|
# ---------------------------------------------------------------------------
|
|
17
15
|
|
|
16
|
+
|
|
18
17
|
class TestDetectOrgs:
|
|
19
18
|
def test_returns_all_subdirectories(self, tmp_path):
|
|
20
19
|
(tmp_path / "org_a").mkdir()
|
|
@@ -36,6 +35,7 @@ class TestDetectOrgs:
|
|
|
36
35
|
# _ensure_init_files
|
|
37
36
|
# ---------------------------------------------------------------------------
|
|
38
37
|
|
|
38
|
+
|
|
39
39
|
class TestEnsureInitFiles:
|
|
40
40
|
def test_creates_init_py_when_missing(self, tmp_path):
|
|
41
41
|
(tmp_path / "myorg").mkdir()
|
|
@@ -65,6 +65,7 @@ class TestEnsureInitFiles:
|
|
|
65
65
|
# _add_to_syspath
|
|
66
66
|
# ---------------------------------------------------------------------------
|
|
67
67
|
|
|
68
|
+
|
|
68
69
|
class TestAddToSyspath:
|
|
69
70
|
def test_adds_src_dirs_to_syspath(self, tmp_path):
|
|
70
71
|
src = tmp_path / "org_a" / "feature_x" / "src"
|