Make a partly-undone import retryable, and refuse a plan it cannot deliver

This commit is contained in:
2026-09-03 09:10:13 -04:00
parent 8ad6511f14
commit cf95daf3bb
20 changed files with 588 additions and 37 deletions

View File

@@ -0,0 +1,69 @@
"""BaseModel.to_dict never publishes a secret-bearing column.
The default serialiser walks __table__.columns and emits every one, so a model
that does not override it publishes whatever it happens to carry - including
columns added later by someone not thinking about this method. User is the live
example: it has no override, so User.to_dict() returned its bcrypt hash. Nothing
called it in a response path, which was luck.
A blanket allowlist is the stronger rule and would empty the output of every
model relying on the default, so this pins the floor instead: named columns are
never emitted, and a model may extend the set.
"""
import pytest
from shopdb.core.models import User
from shopdb.core.models.base import BaseModel
def test_the_default_serialiser_hides_a_password_hash(app):
with app.app_context():
user = User(username='floor', email='floor@example.invalid',
passwordhash='$2b$12$notarealhashbutlookslikeone')
data = user.to_dict()
assert 'passwordhash' not in data
# Still a useful dict - this is a filter, not a mute button.
assert data['username'] == 'floor'
assert data['email'] == 'floor@example.invalid'
def test_every_hidden_name_is_actually_dropped(app):
"""Each entry earns its place: drop one and the column comes back."""
with app.app_context():
user = User(username='floor2', email='f2@example.invalid',
passwordhash='x')
assert 'passwordhash' in {c.name for c in User.__table__.columns}
assert 'passwordhash' in BaseModel.HIDDEN_COLUMNS
assert 'passwordhash' not in user.to_dict()
def test_a_model_can_extend_the_hidden_set(app):
"""A model with its own secret adds to the floor rather than replacing
to_dict wholesale."""
with app.app_context():
class Extended(User):
__abstract__ = True
HIDDEN_COLUMNS = BaseModel.HIDDEN_COLUMNS | {'email'}
assert 'passwordhash' in Extended.HIDDEN_COLUMNS
assert 'email' in Extended.HIDDEN_COLUMNS
@pytest.mark.parametrize('name', sorted(BaseModel.HIDDEN_COLUMNS))
def test_no_model_publishes_a_hidden_column_through_the_default(name, app):
"""Sweep every mapped model: if it carries a hidden column AND relies on the
default to_dict, the column must not come out."""
with app.app_context():
from shopdb.extensions import db
for mapper in db.Model.registry.mappers:
model = mapper.class_
table = getattr(model, '__table__', None)
if table is None or name not in table.columns:
continue
if 'to_dict' in vars(model):
continue # overrides the default; its own business
assert name in getattr(model, 'HIDDEN_COLUMNS', frozenset()), (
'%s carries %s and uses the default to_dict, so it would be '
'published' % (model.__name__, name))