simple-module-test 0.0.33__tar.gz → 0.0.34__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.33 → simple_module_test-0.0.34}/PKG-INFO +4 -4
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/pyproject.toml +4 -4
- simple_module_test-0.0.34/simple_module_test/_schema.py +77 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/fixtures.py +29 -67
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/.gitignore +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/LICENSE +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/README.md +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/__init__.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/app_factory.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/fake_events.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/plugin.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/routes.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/simple_module_test/session_cookie.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/tests/test_app_factory.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/tests/test_fake_events.py +0 -0
- {simple_module_test-0.0.33 → simple_module_test-0.0.34}/tests/test_plugin_fixtures.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.34
|
|
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
|
|
@@ -25,9 +25,9 @@ Requires-Dist: fastapi>=0.115
|
|
|
25
25
|
Requires-Dist: httpx>=0.27
|
|
26
26
|
Requires-Dist: pytest-asyncio>=0.24
|
|
27
27
|
Requires-Dist: pytest>=8.0
|
|
28
|
-
Requires-Dist: simple-module-core==0.0.
|
|
29
|
-
Requires-Dist: simple-module-db==0.0.
|
|
30
|
-
Requires-Dist: simple-module-hosting==0.0.
|
|
28
|
+
Requires-Dist: simple-module-core==0.0.34
|
|
29
|
+
Requires-Dist: simple-module-db==0.0.34
|
|
30
|
+
Requires-Dist: simple-module-hosting==0.0.34
|
|
31
31
|
Requires-Dist: sqlalchemy>=2.0
|
|
32
32
|
Description-Content-Type: text/markdown
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "simple_module_test"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.34"
|
|
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"
|
|
@@ -25,9 +25,9 @@ dependencies = [
|
|
|
25
25
|
"httpx>=0.27",
|
|
26
26
|
"pytest>=8.0",
|
|
27
27
|
"pytest-asyncio>=0.24",
|
|
28
|
-
"simple_module_core==0.0.
|
|
29
|
-
"simple_module_db==0.0.
|
|
30
|
-
"simple_module_hosting==0.0.
|
|
28
|
+
"simple_module_core==0.0.34",
|
|
29
|
+
"simple_module_db==0.0.34",
|
|
30
|
+
"simple_module_hosting==0.0.34",
|
|
31
31
|
"sqlalchemy>=2.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Schema construction for the plugin's app fixtures.
|
|
2
|
+
|
|
3
|
+
Split out of :mod:`simple_module_test.fixtures` so that module declares
|
|
4
|
+
fixtures and this one holds the machinery underneath them: importing every
|
|
5
|
+
installed module's models, resolving the migration heads, creating the tables
|
|
6
|
+
and stamping ``alembic_version`` so the boot-time migration check passes.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import importlib
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
|
|
15
|
+
from simple_module_core.discovery import discover_modules
|
|
16
|
+
from simple_module_db.base import all_module_bases
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lru_cache(maxsize=1)
|
|
20
|
+
def _ensure_models_imported() -> list:
|
|
21
|
+
"""Import all module models so all_module_bases is populated (cached)."""
|
|
22
|
+
for mod in discover_modules():
|
|
23
|
+
pkg = type(mod).__module__.split(".")[0]
|
|
24
|
+
with contextlib.suppress(ModuleNotFoundError):
|
|
25
|
+
importlib.import_module(f"{pkg}.models")
|
|
26
|
+
return list(all_module_bases)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lru_cache(maxsize=1)
|
|
30
|
+
def _alembic_heads() -> tuple[str, ...]:
|
|
31
|
+
"""Cached head revisions — cannot change within a pytest run.
|
|
32
|
+
|
|
33
|
+
Plural: each module's first migration sets its own ``branch_labels``, so
|
|
34
|
+
the history has one head per module and a real upgraded database carries
|
|
35
|
+
an ``alembic_version`` row for each. Stamping only one leaves the rest
|
|
36
|
+
looking un-applied, which now reads as a behind-head schema and puts every
|
|
37
|
+
test app behind the setup gate.
|
|
38
|
+
"""
|
|
39
|
+
from simple_module_hosting.migrations import resolve_head_revisions
|
|
40
|
+
|
|
41
|
+
return resolve_head_revisions()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def _create_all_tables(engine) -> None:
|
|
45
|
+
"""Create all module tables in a single connection.
|
|
46
|
+
|
|
47
|
+
Also stamps the alembic_version table at heads so the app's startup
|
|
48
|
+
migration check (``check_migrations``) treats the test DB as current.
|
|
49
|
+
Without the stamp the check would raise because ``create_all`` doesn't
|
|
50
|
+
touch alembic_version.
|
|
51
|
+
"""
|
|
52
|
+
from sqlalchemy import text
|
|
53
|
+
|
|
54
|
+
bases = _ensure_models_imported()
|
|
55
|
+
heads = _alembic_heads()
|
|
56
|
+
|
|
57
|
+
async with engine.begin() as conn:
|
|
58
|
+
|
|
59
|
+
def _sync_create_all(sync_conn):
|
|
60
|
+
for base in bases:
|
|
61
|
+
base.metadata.create_all(sync_conn)
|
|
62
|
+
|
|
63
|
+
await conn.run_sync(_sync_create_all)
|
|
64
|
+
|
|
65
|
+
if heads:
|
|
66
|
+
await conn.execute(
|
|
67
|
+
text(
|
|
68
|
+
"CREATE TABLE IF NOT EXISTS alembic_version "
|
|
69
|
+
"(version_num VARCHAR(32) NOT NULL PRIMARY KEY)"
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
await conn.execute(text("DELETE FROM alembic_version"))
|
|
73
|
+
for head in heads:
|
|
74
|
+
await conn.execute(
|
|
75
|
+
text("INSERT INTO alembic_version (version_num) VALUES (:v)"),
|
|
76
|
+
{"v": head},
|
|
77
|
+
)
|
|
@@ -20,20 +20,17 @@ which soft-imports ``background_tasks``).
|
|
|
20
20
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
|
-
import contextlib
|
|
24
|
-
import importlib
|
|
25
23
|
import os
|
|
26
24
|
from collections.abc import AsyncGenerator, Iterator
|
|
27
|
-
from functools import lru_cache
|
|
28
25
|
|
|
29
26
|
import httpx
|
|
30
27
|
import pytest
|
|
31
|
-
from simple_module_core.discovery import DEFAULT_AUTH_PROVIDER
|
|
32
|
-
from simple_module_db.base import all_module_bases
|
|
28
|
+
from simple_module_core.discovery import DEFAULT_AUTH_PROVIDER
|
|
33
29
|
from simple_module_db.session import DatabaseState, init_db
|
|
34
30
|
from simple_module_hosting.settings import Settings
|
|
35
31
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
|
36
32
|
|
|
33
|
+
from simple_module_test._schema import _create_all_tables
|
|
37
34
|
from simple_module_test.session_cookie import forge_session_cookie
|
|
38
35
|
|
|
39
36
|
_AUTH_PROVIDER_ENV = "SM_AUTH_PROVIDER"
|
|
@@ -107,67 +104,6 @@ async def engine(db_state: DatabaseState) -> AsyncEngine:
|
|
|
107
104
|
return db_state.engine
|
|
108
105
|
|
|
109
106
|
|
|
110
|
-
@lru_cache(maxsize=1)
|
|
111
|
-
def _ensure_models_imported() -> list:
|
|
112
|
-
"""Import all module models so all_module_bases is populated (cached)."""
|
|
113
|
-
for mod in discover_modules():
|
|
114
|
-
pkg = type(mod).__module__.split(".")[0]
|
|
115
|
-
with contextlib.suppress(ModuleNotFoundError):
|
|
116
|
-
importlib.import_module(f"{pkg}.models")
|
|
117
|
-
return list(all_module_bases)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
@lru_cache(maxsize=1)
|
|
121
|
-
def _alembic_heads() -> tuple[str, ...]:
|
|
122
|
-
"""Cached head revisions — cannot change within a pytest run.
|
|
123
|
-
|
|
124
|
-
Plural: each module's first migration sets its own ``branch_labels``, so
|
|
125
|
-
the history has one head per module and a real upgraded database carries
|
|
126
|
-
an ``alembic_version`` row for each. Stamping only one leaves the rest
|
|
127
|
-
looking un-applied, which now reads as a behind-head schema and puts every
|
|
128
|
-
test app behind the setup gate.
|
|
129
|
-
"""
|
|
130
|
-
from simple_module_hosting.migrations import resolve_head_revisions
|
|
131
|
-
|
|
132
|
-
return resolve_head_revisions()
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async def _create_all_tables(engine) -> None:
|
|
136
|
-
"""Create all module tables in a single connection.
|
|
137
|
-
|
|
138
|
-
Also stamps the alembic_version table at heads so the app's startup
|
|
139
|
-
migration check (``check_migrations``) treats the test DB as current.
|
|
140
|
-
Without the stamp the check would raise because ``create_all`` doesn't
|
|
141
|
-
touch alembic_version.
|
|
142
|
-
"""
|
|
143
|
-
from sqlalchemy import text
|
|
144
|
-
|
|
145
|
-
bases = _ensure_models_imported()
|
|
146
|
-
heads = _alembic_heads()
|
|
147
|
-
|
|
148
|
-
async with engine.begin() as conn:
|
|
149
|
-
|
|
150
|
-
def _sync_create_all(sync_conn):
|
|
151
|
-
for base in bases:
|
|
152
|
-
base.metadata.create_all(sync_conn)
|
|
153
|
-
|
|
154
|
-
await conn.run_sync(_sync_create_all)
|
|
155
|
-
|
|
156
|
-
if heads:
|
|
157
|
-
await conn.execute(
|
|
158
|
-
text(
|
|
159
|
-
"CREATE TABLE IF NOT EXISTS alembic_version "
|
|
160
|
-
"(version_num VARCHAR(32) NOT NULL PRIMARY KEY)"
|
|
161
|
-
)
|
|
162
|
-
)
|
|
163
|
-
await conn.execute(text("DELETE FROM alembic_version"))
|
|
164
|
-
for head in heads:
|
|
165
|
-
await conn.execute(
|
|
166
|
-
text("INSERT INTO alembic_version (version_num) VALUES (:v)"),
|
|
167
|
-
{"v": head},
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
|
|
171
107
|
@pytest.fixture
|
|
172
108
|
async def db_session(db_state: DatabaseState) -> AsyncGenerator[AsyncSession, None]:
|
|
173
109
|
"""Yield an async session backed by in-memory SQLite."""
|
|
@@ -236,12 +172,38 @@ async def app(settings: Settings):
|
|
|
236
172
|
yield application
|
|
237
173
|
|
|
238
174
|
|
|
175
|
+
def _disable_env_bootstrap(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
176
|
+
"""Stop ``bootstrap_admin_from_env`` seeding the app being built.
|
|
177
|
+
|
|
178
|
+
``UsersModule.on_startup`` creates an administrator from
|
|
179
|
+
``SM_USERS_BOOTSTRAP_*``, read from the environment *and* from a ``.env`` on
|
|
180
|
+
disk. A developer who followed ``.env.example`` has those set, so every app
|
|
181
|
+
this plugin builds gets an admin whether the fixture asked for one or not.
|
|
182
|
+
For ``setup_pending_app`` that is not a nuisance but the opposite of its
|
|
183
|
+
contract: the setup gate releases the moment an administrator exists, so the
|
|
184
|
+
wizard routes 404 and every test using it fails.
|
|
185
|
+
|
|
186
|
+
They fail *locally only* — CI has no ``.env`` — which is the worst shape for
|
|
187
|
+
a fixture to be wrong in, and left the failure looking like test-ordering
|
|
188
|
+
noise rather than a fixture that does not do what it says.
|
|
189
|
+
"""
|
|
190
|
+
for key in list(os.environ):
|
|
191
|
+
if key.startswith("SM_USERS_BOOTSTRAP_"):
|
|
192
|
+
monkeypatch.delenv(key, raising=False)
|
|
193
|
+
try:
|
|
194
|
+
from users import bootstrap as bootstrap_module
|
|
195
|
+
except ImportError:
|
|
196
|
+
return # No local-accounts provider installed; nothing bootstraps.
|
|
197
|
+
monkeypatch.setattr(bootstrap_module, "_read_dotenv_bootstrap_vars", dict)
|
|
198
|
+
|
|
199
|
+
|
|
239
200
|
@pytest.fixture
|
|
240
|
-
async def setup_pending_app(settings: Settings):
|
|
201
|
+
async def setup_pending_app(settings: Settings, monkeypatch: pytest.MonkeyPatch):
|
|
241
202
|
"""An app with no administrator, so the first-run setup gate is engaged.
|
|
242
203
|
|
|
243
204
|
The counterpart to ``app``: use this to assert on setup-mode behaviour.
|
|
244
205
|
"""
|
|
206
|
+
_disable_env_bootstrap(monkeypatch)
|
|
245
207
|
async for application in _build_app(settings, seed_admin=False):
|
|
246
208
|
yield application
|
|
247
209
|
|
|
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.33 → simple_module_test-0.0.34}/simple_module_test/session_cookie.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|