simple-module-test 0.0.32__tar.gz → 0.0.33__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.32 → simple_module_test-0.0.33}/PKG-INFO +4 -4
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/pyproject.toml +4 -4
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/fixtures.py +76 -16
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/plugin.py +1 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/.gitignore +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/LICENSE +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/README.md +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/__init__.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/app_factory.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/fake_events.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/routes.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/simple_module_test/session_cookie.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/tests/test_app_factory.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/tests/test_fake_events.py +0 -0
- {simple_module_test-0.0.32 → simple_module_test-0.0.33}/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.33
|
|
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.33
|
|
29
|
+
Requires-Dist: simple-module-db==0.0.33
|
|
30
|
+
Requires-Dist: simple-module-hosting==0.0.33
|
|
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.33"
|
|
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.33",
|
|
29
|
+
"simple_module_db==0.0.33",
|
|
30
|
+
"simple_module_hosting==0.0.33",
|
|
31
31
|
"sqlalchemy>=2.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -118,17 +118,24 @@ def _ensure_models_imported() -> list:
|
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
@lru_cache(maxsize=1)
|
|
121
|
-
def
|
|
122
|
-
"""Cached head
|
|
123
|
-
|
|
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
|
|
124
131
|
|
|
125
|
-
return
|
|
132
|
+
return resolve_head_revisions()
|
|
126
133
|
|
|
127
134
|
|
|
128
135
|
async def _create_all_tables(engine) -> None:
|
|
129
136
|
"""Create all module tables in a single connection.
|
|
130
137
|
|
|
131
|
-
Also stamps the alembic_version table at
|
|
138
|
+
Also stamps the alembic_version table at heads so the app's startup
|
|
132
139
|
migration check (``check_migrations``) treats the test DB as current.
|
|
133
140
|
Without the stamp the check would raise because ``create_all`` doesn't
|
|
134
141
|
touch alembic_version.
|
|
@@ -136,7 +143,7 @@ async def _create_all_tables(engine) -> None:
|
|
|
136
143
|
from sqlalchemy import text
|
|
137
144
|
|
|
138
145
|
bases = _ensure_models_imported()
|
|
139
|
-
|
|
146
|
+
heads = _alembic_heads()
|
|
140
147
|
|
|
141
148
|
async with engine.begin() as conn:
|
|
142
149
|
|
|
@@ -146,7 +153,7 @@ async def _create_all_tables(engine) -> None:
|
|
|
146
153
|
|
|
147
154
|
await conn.run_sync(_sync_create_all)
|
|
148
155
|
|
|
149
|
-
if
|
|
156
|
+
if heads:
|
|
150
157
|
await conn.execute(
|
|
151
158
|
text(
|
|
152
159
|
"CREATE TABLE IF NOT EXISTS alembic_version "
|
|
@@ -154,10 +161,11 @@ async def _create_all_tables(engine) -> None:
|
|
|
154
161
|
)
|
|
155
162
|
)
|
|
156
163
|
await conn.execute(text("DELETE FROM alembic_version"))
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
for head in heads:
|
|
165
|
+
await conn.execute(
|
|
166
|
+
text("INSERT INTO alembic_version (version_num) VALUES (:v)"),
|
|
167
|
+
{"v": head},
|
|
168
|
+
)
|
|
161
169
|
|
|
162
170
|
|
|
163
171
|
@pytest.fixture
|
|
@@ -169,14 +177,47 @@ async def db_session(db_state: DatabaseState) -> AsyncGenerator[AsyncSession, No
|
|
|
169
177
|
yield session
|
|
170
178
|
|
|
171
179
|
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
|
|
180
|
+
SETUP_ADMIN_EMAIL = "admin@test"
|
|
181
|
+
SETUP_ADMIN_PASSWORD = "test-password"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
async def _seed_setup_admin(application) -> None:
|
|
185
|
+
"""Make the ``app`` fixture model a *configured* install.
|
|
186
|
+
|
|
187
|
+
``SetupMiddleware`` redirects every request to ``/setup`` until an
|
|
188
|
+
administrator exists. That is right for a fresh deployment and wrong for a
|
|
189
|
+
suite asserting on ordinary pages — without this, every test using ``app``
|
|
190
|
+
gets a 302.
|
|
191
|
+
|
|
192
|
+
Seeds the same admin ``authenticated_client`` uses, so that fixture's own
|
|
193
|
+
``create_admin`` call stays idempotent rather than creating a second one.
|
|
194
|
+
|
|
195
|
+
Skipped when the ``users`` module isn't installed: an install with no
|
|
196
|
+
local-accounts provider registers no setup step and is never gated, so
|
|
197
|
+
there is nothing to satisfy. Use ``setup_pending_app`` to exercise the gate.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
from users.bootstrap import create_admin
|
|
201
|
+
except ImportError:
|
|
202
|
+
return
|
|
203
|
+
|
|
204
|
+
async with application.state.sm.db.session_factory() as session:
|
|
205
|
+
await create_admin(
|
|
206
|
+
session,
|
|
207
|
+
email=SETUP_ADMIN_EMAIL,
|
|
208
|
+
password=SETUP_ADMIN_PASSWORD,
|
|
209
|
+
full_name="Test Admin",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def _build_app(settings: Settings, *, seed_admin: bool):
|
|
175
214
|
from simple_module_hosting.app_builder import create_app
|
|
176
215
|
|
|
177
216
|
application = create_app(settings)
|
|
178
217
|
|
|
179
218
|
await _create_all_tables(application.state.sm.db.engine)
|
|
219
|
+
if seed_admin:
|
|
220
|
+
await _seed_setup_admin(application)
|
|
180
221
|
|
|
181
222
|
# Trigger lifespan startup so app.state.migration is populated
|
|
182
223
|
ctx = application.router.lifespan_context(application)
|
|
@@ -188,6 +229,23 @@ async def app(settings: Settings):
|
|
|
188
229
|
await ctx.__aexit__(None, None, None)
|
|
189
230
|
|
|
190
231
|
|
|
232
|
+
@pytest.fixture
|
|
233
|
+
async def app(settings: Settings):
|
|
234
|
+
"""A configured app: tables created, an admin seeded, lifespan triggered."""
|
|
235
|
+
async for application in _build_app(settings, seed_admin=True):
|
|
236
|
+
yield application
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@pytest.fixture
|
|
240
|
+
async def setup_pending_app(settings: Settings):
|
|
241
|
+
"""An app with no administrator, so the first-run setup gate is engaged.
|
|
242
|
+
|
|
243
|
+
The counterpart to ``app``: use this to assert on setup-mode behaviour.
|
|
244
|
+
"""
|
|
245
|
+
async for application in _build_app(settings, seed_admin=False):
|
|
246
|
+
yield application
|
|
247
|
+
|
|
248
|
+
|
|
191
249
|
@pytest.fixture
|
|
192
250
|
async def client(app) -> AsyncGenerator[httpx.AsyncClient, None]:
|
|
193
251
|
"""Unauthenticated async HTTP client."""
|
|
@@ -211,10 +269,12 @@ async def authenticated_client(app) -> AsyncGenerator[httpx.AsyncClient, None]:
|
|
|
211
269
|
from users.bootstrap import create_admin
|
|
212
270
|
|
|
213
271
|
async with app.state.sm.db.session_factory() as session:
|
|
272
|
+
# Idempotent: the `app` fixture already seeded this same admin so the
|
|
273
|
+
# setup gate would release. This resolves its id for the cookie.
|
|
214
274
|
result = await create_admin(
|
|
215
275
|
session,
|
|
216
|
-
email=
|
|
217
|
-
password=
|
|
276
|
+
email=SETUP_ADMIN_EMAIL,
|
|
277
|
+
password=SETUP_ADMIN_PASSWORD,
|
|
218
278
|
full_name="Test Admin",
|
|
219
279
|
)
|
|
220
280
|
user_id = str(result.user.id)
|
|
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.32 → simple_module_test-0.0.33}/simple_module_test/session_cookie.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|