mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-04 02:56:54 +09:00
The drawer showed '<name>.<base> — Published automatically once the deploy finishes' whenever a managed base domain existed, but publishing was gated on a per-template auto_domain flag the operator can't see. For most of the catalog the install landed on host:port instead — a link a default firewall doesn't even expose — and the domain had to be redone by hand in the service's Domain settings. The promise on screen is now the contract: when the drawer displays a domain it sends auto_domain: true, which rides the install plan to the finalizer and overrides the template's silence (the forced flag also feeds the SERVICE_FQDN magic context, so tokens and the published host agree). Absent flag = the template YAML still decides, as before. The drawer also stops promising what can't be delivered: the domain field only renders for compose templates targeting the local server — the panel's nginx can't proxy a remote container, and the repo pipeline has no auto-publish step. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
91 lines
3.9 KiB
Python
91 lines
3.9 KiB
Python
"""The deploy drawer's domain promise must be the install's contract.
|
|
|
|
The drawer shows the operator the exact ``<name>.<base>`` hostname before they
|
|
click Deploy ("Published automatically once the deploy finishes"). Publishing,
|
|
however, was gated on a per-template ``auto_domain: true`` flag the operator
|
|
cannot see — so for most of the catalog the install silently landed on
|
|
``host:port`` instead (which a default firewall doesn't even expose), and the
|
|
domain had to be redone by hand in the service's settings.
|
|
|
|
These tests pin the fix: the install request can opt in explicitly, the flag
|
|
rides the plan to the finalizer, and an absent flag still lets the template
|
|
YAML decide.
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from app.services.template_service import TemplateService
|
|
|
|
# A real bundled catalog template that does NOT declare auto_domain — the
|
|
# common case the drawer was over-promising for.
|
|
TEMPLATE_ID = 'actualbudget'
|
|
|
|
|
|
@pytest.fixture
|
|
def plain_template(app):
|
|
result = TemplateService.get_template(TEMPLATE_ID)
|
|
assert result.get('success'), 'bundled template missing'
|
|
assert not result['template'].get('auto_domain'), (
|
|
'test premise broken: template now opts in by itself')
|
|
return result['template']
|
|
|
|
|
|
class TestPlanFlag:
|
|
def test_request_opt_in_reaches_the_plan(self, app, plain_template):
|
|
result = TemplateService.build_install_plan(
|
|
TEMPLATE_ID, 'promised-app', auto_domain=True)
|
|
assert result['success'] is True
|
|
assert result['plan']['auto_domain'] is True
|
|
|
|
def test_without_a_request_flag_the_template_decides(self, app, plain_template):
|
|
result = TemplateService.build_install_plan(
|
|
TEMPLATE_ID, 'quiet-app')
|
|
assert result['success'] is True
|
|
assert result['plan']['auto_domain'] is False
|
|
|
|
def test_the_cached_template_is_not_mutated(self, app, plain_template):
|
|
TemplateService.build_install_plan(
|
|
TEMPLATE_ID, 'copy-check-app', auto_domain=True)
|
|
again = TemplateService.get_template(TEMPLATE_ID)['template']
|
|
assert not again.get('auto_domain')
|
|
|
|
|
|
class TestApiPassThrough:
|
|
def test_install_endpoint_forwards_the_flag(self, app, client, auth_headers,
|
|
monkeypatch):
|
|
from app.services.deployment_job_service import DeploymentJobService
|
|
seen = {}
|
|
|
|
def fake_install(**kwargs):
|
|
seen.update(kwargs)
|
|
return {'success': True, 'job_id': 'j1',
|
|
'job': {'id': 'j1', 'status': 'pending'}}
|
|
|
|
monkeypatch.setattr(DeploymentJobService, 'install_template',
|
|
staticmethod(fake_install))
|
|
res = client.post(f'/api/v1/templates/{TEMPLATE_ID}/install',
|
|
headers=auth_headers,
|
|
json={'app_name': 'promised-app',
|
|
'server_id': 'local', 'auto_domain': True})
|
|
assert res.status_code in (201, 202), res.get_json()
|
|
assert seen['auto_domain'] is True
|
|
|
|
def test_absent_flag_stays_absent(self, app, client, auth_headers, monkeypatch):
|
|
# None, not False: the service must be able to tell "the drawer said
|
|
# nothing" apart from an explicit opt-out.
|
|
from app.services.deployment_job_service import DeploymentJobService
|
|
seen = {}
|
|
|
|
def fake_install(**kwargs):
|
|
seen.update(kwargs)
|
|
return {'success': True, 'job_id': 'j2',
|
|
'job': {'id': 'j2', 'status': 'pending'}}
|
|
|
|
monkeypatch.setattr(DeploymentJobService, 'install_template',
|
|
staticmethod(fake_install))
|
|
res = client.post(f'/api/v1/templates/{TEMPLATE_ID}/install',
|
|
headers=auth_headers,
|
|
json={'app_name': 'quiet-app', 'server_id': 'local'})
|
|
assert res.status_code in (201, 202), res.get_json()
|
|
assert seen['auto_domain'] is None
|