Files
ServerKit/backend/tests/test_restore_point_env.py
Juan DenisandClaude Fable 5 e29872f0cd fix(restore-points): satisfy jwt-only and seed-factory ratchets
The three restore-point read routes authenticated with bare
@jwt_required(), pushing the census to 579 against a 577 ceiling.
They now use the auth_required() policy door like the file's mutating
routes (which already accept API keys via @developer_required), keeping
read access consistent with write access for API-key callers; scope
authorization stays in-route. test_restore_point_env.py hand-rolled
Application/User seeds — switched to the make_application/make_user
factories. Migration inventory regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 02:52:59 -04:00

258 lines
8.6 KiB
Python

"""Plan 81 M2: environment restore-point adapter and mutation doors."""
import json
import pytest
from app.models import EnvironmentVariableHistory, RestorePoint
from app.services import restore_point_adapter_env as env_adapter
from app.utils.sensitive_data_filter import MASK
@pytest.fixture
def registered_env_adapter():
from app.services import restore_point_service
existing = dict(restore_point_service.ADAPTERS)
restore_point_service.register_adapter('env', env_adapter)
try:
yield restore_point_service
finally:
restore_point_service.clear_adapters()
restore_point_service.ADAPTERS.update(existing)
def _make_app():
from app import db
from factories import make_application, make_user
user = make_user(
db,
username='restore-env-owner',
email='restore-env-owner@example.test',
role='admin',
)
application = make_application(
db,
name='restore-env-app',
user_id=user.id,
status='running',
)
return application, user
def test_capture_masks_flags_sensitive_names_and_keeps_reference_metadata(
app, registered_env_adapter):
from app.services.env_service import EnvService
application, user = _make_app()
EnvService.set_env_var(
application.id, 'PUBLIC_URL', 'https://example.test', user_id=user.id,
)
EnvService.set_env_var(
application.id, 'API_TOKEN', 'raw-token', is_secret=False,
user_id=user.id,
)
EnvService.set_env_var(
application.id, 'INTERNAL', 'raw-secret', is_secret=True,
user_id=user.id,
)
reference = {'kind': 'secret', 'secret': 'stripe_prod'}
EnvService.set_env_reference(
application.id, 'STRIPE_KEY', reference, user.id,
target_service='web', description='Stripe credential',
)
point = registered_env_adapter.capture(
'env', application.id, 'manual', label='mask-proof', actor=user.id,
)
payload = json.loads(point.payload_json)
assert payload['env']['PUBLIC_URL']['value'] == 'https://example.test'
assert payload['env']['API_TOKEN']['value'] == MASK
assert payload['env']['INTERNAL']['value'] == MASK
assert payload['env']['STRIPE_KEY'] == {
'value': MASK,
'is_secret': True,
'description': 'Stripe credential',
'target_service': 'web',
'value_from': reference,
}
serialized = json.dumps(payload)
assert 'raw-token' not in serialized
assert 'raw-secret' not in serialized
def test_every_env_mutation_door_auto_captures_once(
app, monkeypatch, registered_env_adapter):
from app.services import restore_point_service
from app.services.env_service import EnvService
application, user = _make_app()
captures = []
def fake_capture(scope_type, scope_id, trigger, **kwargs):
captures.append((scope_type, scope_id, trigger, kwargs.get('label')))
return object()
monkeypatch.setattr(restore_point_service, 'capture', fake_capture)
first, _, _ = EnvService.set_env_var(
application.id, 'FIRST', '1', user_id=user.id,
)
EnvService.update_env_var(
application.id, 'FIRST', value='2', user_id=user.id,
)
EnvService.set_env_reference(
application.id, 'REF', {'kind': 'secret', 'secret': 'one'}, user.id,
)
EnvService.delete_env_var(application.id, 'REF', user.id)
EnvService.delete_env_var_by_id(first.id, user.id)
EnvService.bulk_set_env_vars(
application.id, {'SECOND': '2', 'THIRD': '3'}, user.id,
)
EnvService.clear_all(application.id, user.id)
assert [item[3] for item in captures] == [
'before env.set_env_var',
'before env.update_env_var',
'before env.set_env_reference',
'before env.delete_env_var',
'before env.delete_env_var_by_id',
'before env.bulk_set_env_vars',
'before env.clear_all',
]
assert all(item[:3] == (
'env', str(application.id), 'pre_mutation',
) for item in captures)
def test_bulk_and_clear_history_rows_share_operation_batch(
app, registered_env_adapter):
from app.services.env_service import EnvService
application, user = _make_app()
EnvService.bulk_set_env_vars(
application.id, {'ONE': '1', 'TWO': '2'}, user.id,
)
created = EnvironmentVariableHistory.query.filter_by(
application_id=application.id,
action='created',
).order_by(EnvironmentVariableHistory.id).all()
assert len(created) == 2
assert created[0].batch_id
assert {row.batch_id for row in created} == {created[0].batch_id}
EnvService.clear_all(application.id, user.id)
deleted = EnvironmentVariableHistory.query.filter_by(
application_id=application.id,
action='deleted',
).order_by(EnvironmentVariableHistory.id).all()
assert len(deleted) == 2
assert deleted[0].batch_id
assert {row.batch_id for row in deleted} == {deleted[0].batch_id}
assert deleted[0].batch_id != created[0].batch_id
assert all(row.to_dict()['batch_id'] == row.batch_id for row in deleted)
def test_restore_round_trip_reconverges_and_groups_history(
app, registered_env_adapter):
from app.services.env_service import EnvService
application, user = _make_app()
EnvService.set_env_var(
application.id, 'FEATURE_FLAG', 'on', user_id=user.id,
)
point = registered_env_adapter.capture(
'env', application.id, 'manual', label='known-good', actor=user.id,
)
EnvService.set_env_var(
application.id, 'FEATURE_FLAG', 'off', user_id=user.id,
)
EnvService.set_env_var(
application.id, 'TEMPORARY', 'remove-me', user_id=user.id,
)
points_before_restore = RestorePoint.query.filter_by(
scope_type='env', scope_id=str(application.id),
).count()
result = registered_env_adapter.restore(point.id, actor=user)
assert result['success'] is True
assert EnvService.get_env_var(application.id, 'FEATURE_FLAG').value == 'on'
assert EnvService.get_env_var(application.id, 'TEMPORARY') is None
assert RestorePoint.query.filter_by(
scope_type='env', scope_id=str(application.id),
).count() == points_before_restore + 1
replay_rows = EnvironmentVariableHistory.query.filter_by(
application_id=application.id,
batch_id=result['batch_id'],
).all()
assert {row.key for row in replay_rows} == {'FEATURE_FLAG', 'TEMPORARY'}
assert {row.batch_id for row in replay_rows} == {result['batch_id']}
def test_restore_keeps_live_masked_values_and_replays_references(
app, registered_env_adapter):
from app.services.env_service import EnvService
application, user = _make_app()
EnvService.set_env_var(
application.id, 'API_TOKEN', 'old-token', is_secret=True,
user_id=user.id,
)
original_reference = {'kind': 'secret', 'secret': 'stripe_old'}
EnvService.set_env_reference(
application.id, 'STRIPE_KEY', original_reference, user.id,
)
point = registered_env_adapter.capture(
'env', application.id, 'manual', label='with-secrets', actor=user.id,
)
EnvService.set_env_var(
application.id, 'API_TOKEN', 'rotated-token', is_secret=True,
user_id=user.id,
)
EnvService.set_env_reference(
application.id, 'STRIPE_KEY',
{'kind': 'secret', 'secret': 'stripe_new'}, user.id,
)
EnvService.set_env_reference(
application.id, 'LIVE_ONLY_REF',
{'kind': 'secret', 'secret': 'keep_me'}, user.id,
)
result = registered_env_adapter.restore(point.id, actor=user)
token = EnvService.get_env_var(application.id, 'API_TOKEN')
stripe = EnvService.get_env_var(application.id, 'STRIPE_KEY')
live_only = EnvService.get_env_var(application.id, 'LIVE_ONLY_REF')
assert token.value == 'rotated-token'
assert stripe.get_reference() == original_reference
assert live_only.get_reference() == {
'kind': 'secret', 'secret': 'keep_me',
}
assert 'API_TOKEN' in result['skipped_secrets']
assert 'LIVE_ONLY_REF' in result['preserved_references']
assert MASK not in {token.value, stripe.value, live_only.value}
def test_env_diff_is_secret_safe_and_key_granular():
old = {'env': {
'TOKEN': {'value': MASK, 'is_secret': True},
'FLAG': {'value': 'on', 'is_secret': False},
}}
new = {'env': {
'TOKEN': {'value': MASK, 'is_secret': True},
'FLAG': {'value': 'off', 'is_secret': False},
'ADDED': {'value': 'yes', 'is_secret': False},
}}
result = env_adapter.diff(old, new)
assert set(result['added']) == {'ADDED'}
assert set(result['changed']) == {'FLAG'}
assert result['removed'] == {}
assert 'TOKEN' not in result['changed']