mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-02 18:17:11 +09:00
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>
258 lines
8.6 KiB
Python
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']
|