mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-31 00:57:22 +09:00
92 lines
3.8 KiB
Python
92 lines
3.8 KiB
Python
"""PUT /apps/<id>/env/<key> partial-update semantics at the route boundary.
|
|
|
|
The service door (EnvService.update_env_var) distinguishes "field absent"
|
|
(_UNSET, leave untouched) from "field explicitly null" (clear it). The route
|
|
must preserve that distinction for description: a JSON null clears, an omitted
|
|
key leaves the stored text alone.
|
|
"""
|
|
from app import db
|
|
from app.models import Application
|
|
|
|
|
|
def _seed_var(app):
|
|
from app.services.env_service import EnvService
|
|
from factories import make_application, make_user
|
|
|
|
user = make_user(db, role='admin')
|
|
application = make_application(db, name='env-api-app', user_id=user.id)
|
|
EnvService.set_env_var(
|
|
application.id, 'API_URL', 'https://api.example.test',
|
|
description='where the API lives', user_id=user.id,
|
|
)
|
|
return application, user
|
|
|
|
|
|
def test_null_description_clears_but_omitted_leaves_it(app, client):
|
|
from factories import headers_for
|
|
|
|
with app.app_context():
|
|
application, user = _seed_var(app)
|
|
headers = headers_for(user)
|
|
app_id = application.id
|
|
|
|
# Omitting description entirely must not touch it.
|
|
res = client.put(f'/api/v1/apps/{app_id}/env/API_URL',
|
|
json={'value': 'https://api2.example.test'},
|
|
headers=headers)
|
|
assert res.status_code == 200
|
|
assert res.get_json()['env_var']['description'] == 'where the API lives'
|
|
|
|
# An explicit JSON null clears it.
|
|
res = client.put(f'/api/v1/apps/{app_id}/env/API_URL',
|
|
json={'description': None},
|
|
headers=headers)
|
|
assert res.status_code == 200
|
|
assert res.get_json()['env_var']['description'] is None
|
|
|
|
|
|
def test_effective_view_is_value_free_and_app_scoped(app, client):
|
|
from factories import headers_for, make_user
|
|
with app.app_context():
|
|
application, owner = _seed_var(app)
|
|
app_id = application.id
|
|
owner_headers = headers_for(owner)
|
|
stranger = make_user(db, role='viewer')
|
|
stranger_headers = headers_for(stranger)
|
|
url = f'/api/v1/apps/{app_id}/env?view=effective'
|
|
response = client.get(url, headers=owner_headers)
|
|
assert response.status_code == 200
|
|
data = response.get_json()
|
|
assert data['values_hidden'] and data['complete']
|
|
assert data['env_vars'][0]['key'] == 'API_URL'
|
|
assert 'https://api.example.test' not in response.get_data(as_text=True)
|
|
assert 'value' not in data['env_vars'][0]
|
|
assert client.get(url, headers=stranger_headers).status_code in (403, 404)
|
|
assert client.get(url).status_code == 401
|
|
assert client.get(url + '&service=bad/name', headers=owner_headers).status_code == 400
|
|
|
|
|
|
def test_effective_view_grant_never_reveals_values_and_tombstone_denied(app, client):
|
|
from datetime import datetime
|
|
from factories import headers_for, make_user
|
|
from app.services.resource_grant_service import ResourceGrantService
|
|
from app.services.env_service import EnvService
|
|
with app.app_context():
|
|
application, owner = _seed_var(app)
|
|
EnvService.set_env_var(application.id, 'PASSWORD', 'super-private', is_secret=True)
|
|
viewer = make_user(db, role='viewer')
|
|
ResourceGrantService.grant(user_id=viewer.id, resource_type='application',
|
|
resource_id=application.id, granted_by=owner.id, role='viewer')
|
|
headers = headers_for(viewer)
|
|
app_id = application.id
|
|
url = f'/api/v1/apps/{app_id}/env?view=effective&mask=false'
|
|
response = client.get(url, headers=headers)
|
|
assert response.status_code == 200
|
|
assert 'super-private' not in response.get_data(as_text=True)
|
|
assert all('value' not in row for row in response.get_json()['env_vars'])
|
|
with app.app_context():
|
|
row = db.session.get(Application, app_id)
|
|
row.deleted_at = datetime.utcnow()
|
|
db.session.commit()
|
|
assert client.get(url, headers=headers).status_code == 404
|