simple-module-test 0.0.38__tar.gz → 0.0.40__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.
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/PKG-INFO +9 -9
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/pyproject.toml +9 -9
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/_schema.py +17 -3
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/fixtures.py +3 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/mail.py +2 -11
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/plugin.py +2 -1
- simple_module_test-0.0.40/simple_module_test/process_state.py +24 -0
- simple_module_test-0.0.40/tests/test_testing_process_state.py +94 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/.gitignore +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/LICENSE +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/README.md +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/__init__.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/app_factory.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/database.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/fake_events.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/redis_server.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/routes.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/session_cookie.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/setup_wizard.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/tenant_client.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/waiting.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/tests/test_app_factory.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/tests/test_fake_events.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/tests/test_plugin_fixtures.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/tests/test_schema_reuse_pg.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/tests/test_waiting_helpers.py +0 -0
- {simple_module_test-0.0.38 → simple_module_test-0.0.40}/tests/test_worker_database.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simple_module_test
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.40
|
|
4
4
|
Summary: Shared pytest fixtures (app, client, db_session, authenticated_client) for writing simple_module module tests
|
|
5
5
|
Project-URL: Homepage, https://github.com/antosubash/simple_module_python
|
|
6
6
|
Project-URL: Repository, https://github.com/antosubash/simple_module_python
|
|
@@ -21,14 +21,14 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Testing
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.12
|
|
24
|
-
Requires-Dist: fastapi<0.
|
|
25
|
-
Requires-Dist: httpx
|
|
26
|
-
Requires-Dist: pytest-asyncio
|
|
27
|
-
Requires-Dist: pytest
|
|
28
|
-
Requires-Dist: simple-module-core==0.0.
|
|
29
|
-
Requires-Dist: simple-module-db==0.0.
|
|
30
|
-
Requires-Dist: simple-module-hosting==0.0.
|
|
31
|
-
Requires-Dist: sqlalchemy
|
|
24
|
+
Requires-Dist: fastapi<0.159,>=0.140
|
|
25
|
+
Requires-Dist: httpx<0.29,>=0.27
|
|
26
|
+
Requires-Dist: pytest-asyncio<2,>=0.24
|
|
27
|
+
Requires-Dist: pytest<10,>=8.0
|
|
28
|
+
Requires-Dist: simple-module-core==0.0.40
|
|
29
|
+
Requires-Dist: simple-module-db==0.0.40
|
|
30
|
+
Requires-Dist: simple-module-hosting==0.0.40
|
|
31
|
+
Requires-Dist: sqlalchemy<3,>=2.0
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
34
34
|
# simple_module_test
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_test"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.40"
|
|
4
4
|
description = "Shared pytest fixtures (app, client, db_session, authenticated_client) for writing simple_module module tests"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -21,14 +21,14 @@ classifiers = [
|
|
|
21
21
|
"Typing :: Typed",
|
|
22
22
|
]
|
|
23
23
|
dependencies = [
|
|
24
|
-
"fastapi>=0.140,<0.
|
|
25
|
-
"httpx>=0.27",
|
|
26
|
-
"pytest>=8.0",
|
|
27
|
-
"pytest-asyncio>=0.24",
|
|
28
|
-
"simple_module_core==0.0.
|
|
29
|
-
"simple_module_db==0.0.
|
|
30
|
-
"simple_module_hosting==0.0.
|
|
31
|
-
"sqlalchemy>=2.0",
|
|
24
|
+
"fastapi>=0.140,<0.159",
|
|
25
|
+
"httpx>=0.27,<0.29",
|
|
26
|
+
"pytest>=8.0,<10",
|
|
27
|
+
"pytest-asyncio>=0.24,<2",
|
|
28
|
+
"simple_module_core==0.0.40",
|
|
29
|
+
"simple_module_db==0.0.40",
|
|
30
|
+
"simple_module_hosting==0.0.40",
|
|
31
|
+
"sqlalchemy>=2.0,<3",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.entry-points.pytest11]
|
|
@@ -29,9 +29,17 @@ def _ensure_models_imported() -> list:
|
|
|
29
29
|
return list(all_module_bases)
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
_heads_cache: tuple[str, ...] | None = None
|
|
33
|
+
|
|
34
|
+
|
|
33
35
|
def _alembic_heads() -> tuple[str, ...]:
|
|
34
|
-
"""
|
|
36
|
+
"""Head revisions — cannot change within a pytest run, once found.
|
|
37
|
+
|
|
38
|
+
Only a non-empty result is cached. ``resolve_head_revisions`` returns ``()``
|
|
39
|
+
when ``alembic.ini`` is not found, which is what happens under a test that
|
|
40
|
+
points ``SM_PROJECT_ROOT`` at a tmp dir; caching that would leave every
|
|
41
|
+
later test in the worker unstamped while its app, resolving the real ini,
|
|
42
|
+
demands the heads ("Database is N revision(s) behind").
|
|
35
43
|
|
|
36
44
|
Plural: each module's first migration sets its own ``branch_labels``, so
|
|
37
45
|
the history has one head per module and a real upgraded database carries
|
|
@@ -39,9 +47,15 @@ def _alembic_heads() -> tuple[str, ...]:
|
|
|
39
47
|
looking un-applied, which now reads as a behind-head schema and puts every
|
|
40
48
|
test app behind the setup gate.
|
|
41
49
|
"""
|
|
50
|
+
global _heads_cache
|
|
42
51
|
from simple_module_hosting.migrations import resolve_head_revisions
|
|
43
52
|
|
|
44
|
-
|
|
53
|
+
if _heads_cache is None:
|
|
54
|
+
heads = resolve_head_revisions()
|
|
55
|
+
if not heads:
|
|
56
|
+
return heads
|
|
57
|
+
_heads_cache = heads
|
|
58
|
+
return _heads_cache
|
|
45
59
|
|
|
46
60
|
|
|
47
61
|
async def _create_all_tables(engine) -> None:
|
|
@@ -161,6 +161,9 @@ async def _seed_setup_admin(application) -> None:
|
|
|
161
161
|
async def _build_app(settings: Settings, *, seed_admin: bool):
|
|
162
162
|
from simple_module_hosting.app_builder import create_app
|
|
163
163
|
|
|
164
|
+
# Process-wide state is reset per test by the autouse fixture in
|
|
165
|
+
# ``process_state``, not here: a second app built inside one test must not
|
|
166
|
+
# reset the live first app's mail sender or lease backend.
|
|
164
167
|
application = create_app(settings)
|
|
165
168
|
|
|
166
169
|
await _create_all_tables(application.state.sm.db.engine)
|
|
@@ -10,8 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from collections.abc import Iterator
|
|
11
11
|
|
|
12
12
|
import pytest
|
|
13
|
-
from simple_module_core import
|
|
14
|
-
from simple_module_core.mail import MailMessage
|
|
13
|
+
from simple_module_core.mail import MailMessage, install_mail_sender
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
class CapturedMail:
|
|
@@ -28,14 +27,6 @@ class CapturedMail:
|
|
|
28
27
|
return [m for m in self.messages if m.to == address]
|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
@pytest.fixture(autouse=True)
|
|
32
|
-
def _reset_process_mail_sender() -> Iterator[None]:
|
|
33
|
-
sender, resolver = core_mail._sender, core_mail._resolver
|
|
34
|
-
yield
|
|
35
|
-
core_mail.install_mail_sender(sender)
|
|
36
|
-
core_mail.install_mail_resolver(resolver)
|
|
37
|
-
|
|
38
|
-
|
|
39
30
|
@pytest.fixture
|
|
40
31
|
def captured_mail() -> Iterator[CapturedMail]:
|
|
41
32
|
"""Install a recording sender that claims to deliver; yields it.
|
|
@@ -45,5 +36,5 @@ def captured_mail() -> Iterator[CapturedMail]:
|
|
|
45
36
|
reinstalls the real one; reinstall with ``install_mail_sender(captured)``.
|
|
46
37
|
"""
|
|
47
38
|
captured = CapturedMail()
|
|
48
|
-
|
|
39
|
+
install_mail_sender(captured)
|
|
49
40
|
yield captured
|
|
@@ -33,7 +33,8 @@ from simple_module_test.fixtures import ( # noqa: F401
|
|
|
33
33
|
settings,
|
|
34
34
|
setup_pending_app,
|
|
35
35
|
)
|
|
36
|
-
from simple_module_test.mail import
|
|
36
|
+
from simple_module_test.mail import captured_mail # noqa: F401
|
|
37
|
+
from simple_module_test.process_state import _reset_process_state # noqa: F401
|
|
37
38
|
from simple_module_test.redis_server import redis_server # noqa: F401
|
|
38
39
|
from simple_module_test.tenant_client import tenant_client # noqa: F401
|
|
39
40
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Reset process services between tests and apps built by the test plugin."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from simple_module_core.leases import reset_lease_backend
|
|
7
|
+
from simple_module_core.mail import reset_mail_sender
|
|
8
|
+
from simple_module_hosting.sentry import reset_sentry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def reset_process_state() -> None:
|
|
12
|
+
"""Restore unconfigured services without retaining another app's resources."""
|
|
13
|
+
reset_mail_sender()
|
|
14
|
+
reset_lease_backend()
|
|
15
|
+
reset_sentry()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(autouse=True)
|
|
19
|
+
def _reset_process_state() -> Iterator[None]:
|
|
20
|
+
reset_process_state()
|
|
21
|
+
try:
|
|
22
|
+
yield
|
|
23
|
+
finally:
|
|
24
|
+
reset_process_state()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Process services are isolated between plugin-built apps and tests."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import Mock
|
|
4
|
+
|
|
5
|
+
from simple_module_core.leases import (
|
|
6
|
+
InProcessLeaseBackend,
|
|
7
|
+
configure_leases,
|
|
8
|
+
get_leases,
|
|
9
|
+
reset_lease_backend,
|
|
10
|
+
)
|
|
11
|
+
from simple_module_core.mail import (
|
|
12
|
+
ConsoleMailSender,
|
|
13
|
+
current_mail_sender,
|
|
14
|
+
install_mail_resolver,
|
|
15
|
+
install_mail_sender,
|
|
16
|
+
mail_transport_installed,
|
|
17
|
+
reset_mail_sender,
|
|
18
|
+
resolve_mail_sender,
|
|
19
|
+
)
|
|
20
|
+
from simple_module_hosting import sentry
|
|
21
|
+
from simple_module_test.mail import CapturedMail
|
|
22
|
+
from simple_module_test.process_state import reset_process_state
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def test_reset_mail_clears_sender_and_worker_resolver() -> None:
|
|
26
|
+
sender = CapturedMail()
|
|
27
|
+
|
|
28
|
+
async def resolver(db):
|
|
29
|
+
return sender
|
|
30
|
+
|
|
31
|
+
install_mail_sender(sender)
|
|
32
|
+
install_mail_resolver(resolver)
|
|
33
|
+
reset_mail_sender()
|
|
34
|
+
assert not mail_transport_installed()
|
|
35
|
+
assert isinstance(current_mail_sender(), ConsoleMailSender)
|
|
36
|
+
assert isinstance(await resolve_mail_sender(object()), ConsoleMailSender)
|
|
37
|
+
assert current_mail_sender().include_body is False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def test_reset_leases_discards_configuration_and_held_names() -> None:
|
|
41
|
+
original = InProcessLeaseBackend()
|
|
42
|
+
configure_leases(original)
|
|
43
|
+
assert await original.acquire("held", "previous-app", 60) == 1
|
|
44
|
+
reset_lease_backend()
|
|
45
|
+
replacement = get_leases()
|
|
46
|
+
assert replacement is not original
|
|
47
|
+
assert isinstance(replacement, InProcessLeaseBackend)
|
|
48
|
+
assert await replacement.acquire("held", "next-app", 60) == 1
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_reset_sentry_closes_and_unbinds_client(monkeypatch) -> None:
|
|
52
|
+
import pytest
|
|
53
|
+
|
|
54
|
+
sdk = pytest.importorskip("sentry_sdk")
|
|
55
|
+
scope = Mock()
|
|
56
|
+
monkeypatch.setattr(sdk, "get_global_scope", lambda: scope)
|
|
57
|
+
monkeypatch.setattr(sentry, "_enabled", True)
|
|
58
|
+
sentry._request_scope.set({"type": "http"})
|
|
59
|
+
sentry.reset_sentry()
|
|
60
|
+
scope.client.close.assert_called_once_with()
|
|
61
|
+
scope.set_client.assert_called_once_with(None)
|
|
62
|
+
assert not sentry.is_enabled()
|
|
63
|
+
assert sentry._request_scope.get() is None
|
|
64
|
+
sentry.reset_sentry()
|
|
65
|
+
scope.client.close.assert_called_once_with()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def test_plugin_second_app_keeps_first_apps_services(settings, monkeypatch) -> None:
|
|
69
|
+
"""Resets are per test (autouse fixture), so a second app in one test must not
|
|
70
|
+
wipe the first app's installed mail sender."""
|
|
71
|
+
from fastapi import FastAPI
|
|
72
|
+
from simple_module_hosting import app_builder
|
|
73
|
+
from simple_module_test import fixtures
|
|
74
|
+
|
|
75
|
+
def create_app(settings):
|
|
76
|
+
app = FastAPI()
|
|
77
|
+
app.state.sm = Mock()
|
|
78
|
+
return app
|
|
79
|
+
|
|
80
|
+
async def create_tables(engine):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
monkeypatch.setattr(app_builder, "create_app", create_app)
|
|
84
|
+
monkeypatch.setattr(fixtures, "_create_all_tables", create_tables)
|
|
85
|
+
install_mail_sender(CapturedMail())
|
|
86
|
+
async for _app in fixtures._build_app(settings, seed_admin=False):
|
|
87
|
+
assert mail_transport_installed()
|
|
88
|
+
reset_process_state()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_autouse_reset_clears_state_left_by_a_previous_test() -> None:
|
|
92
|
+
# The autouse ``_reset_process_state`` fixture ran before this test.
|
|
93
|
+
assert not mail_transport_installed()
|
|
94
|
+
assert not sentry.is_enabled()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{simple_module_test-0.0.38 → simple_module_test-0.0.40}/simple_module_test/session_cookie.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|