Files
ServerKit/backend/tests/test_template_auto_domain.py
Juan DenisandClaude Fable 5 fb8d6bfbf8 fix(templates): the deploy drawer's domain promise now sticks
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>
2026-08-28 21:59:26 -04:00

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