Files
ServerKit/backend/tests/test_container_sleep.py
Juan DenisandClaude Fable 5 9b9aa34f01 test(seeds): one-door factories + persona headers + adoption ratchet (plan 77 G1/G2)
tests/factories.py owns make_user/headers_for/make_application/
make_server/make_workspace; conftest personas (scoping_rbac,
new viewer_headers/developer_headers fixtures) ride them. The
byte-identical _seed_app pair (container scale/sleep), two variants
(image_update, waf) and three server fixtures (fleet doctor/repair)
migrate as proof. Ratchet freezes the 44-file hand-minted-JWT and
65-file direct-Application() populations.

Note: test_fleet_doctor.py::test_dns_fail_when_unresolved fails
pre-existing on this machine (warn vs fail), unrelated to this change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 01:55:18 -04:00

96 lines
4.0 KiB
Python

"""Tests for container auto-sleep (policy, manual sleep/wake, idle sweep)."""
import uuid
from datetime import datetime, timedelta
from app import db
from app.models import Application
from factories import make_application
from app.models.container_sleep_policy import ContainerSleepPolicy
from app.services.docker_service import DockerService
from app.services.container_sleep_service import ContainerSleepService
def _seed_app(**kw):
return make_application(db, **kw)
def _mock_docker(monkeypatch, ok=True):
monkeypatch.setattr(DockerService, 'compose_down', lambda *a, **k: {'success': ok})
monkeypatch.setattr(DockerService, 'compose_up', lambda *a, **k: {'success': ok})
class TestSleepPolicy:
def test_set_and_get_policy(self, app):
a = _seed_app()
p = ContainerSleepService.set_policy(a.id, enabled=True, idle_timeout_minutes=15)
assert p.enabled is True and p.idle_timeout_minutes == 15
assert ContainerSleepService.get_or_create_policy(a.id).enabled is True
def test_timeout_has_a_floor_of_one(self, app):
a = _seed_app()
p = ContainerSleepService.set_policy(a.id, idle_timeout_minutes=0)
assert p.idle_timeout_minutes == 1
class TestSleepWake:
def test_sleep_then_wake(self, app, monkeypatch):
_mock_docker(monkeypatch)
a = _seed_app()
r = ContainerSleepService.sleep_app(a.id)
assert r['success'] and r['policy']['asleep'] is True
assert Application.query.get(a.id).status == 'stopped'
r2 = ContainerSleepService.wake_app(a.id)
assert r2['success'] and r2['policy']['asleep'] is False
assert Application.query.get(a.id).status == 'running'
def test_sleep_propagates_docker_failure(self, app, monkeypatch):
_mock_docker(monkeypatch, ok=False)
a = _seed_app()
assert ContainerSleepService.sleep_app(a.id)['success'] is False
def test_remote_app_is_guarded(self, app):
a = _seed_app(server_id='srv-1')
r = ContainerSleepService.sleep_app(a.id)
assert r['success'] is False and 'remote' in r['error'].lower()
class TestSweep:
def test_sweeps_only_idle_enabled_awake(self, app, monkeypatch):
_mock_docker(monkeypatch)
idle, active, disabled = _seed_app(), _seed_app(), _seed_app()
ContainerSleepService.set_policy(idle.id, enabled=True, idle_timeout_minutes=30)
ContainerSleepPolicy.query.filter_by(application_id=idle.id).first().last_activity_at = \
datetime.utcnow() - timedelta(minutes=60)
ContainerSleepService.set_policy(active.id, enabled=True, idle_timeout_minutes=30) # activity just now
ContainerSleepService.set_policy(disabled.id, enabled=False, idle_timeout_minutes=1)
ContainerSleepPolicy.query.filter_by(application_id=disabled.id).first().last_activity_at = \
datetime.utcnow() - timedelta(minutes=60)
db.session.commit()
slept = ContainerSleepService.sweep_idle()['slept']
assert idle.id in slept
assert active.id not in slept
assert disabled.id not in slept
def test_sweep_skips_without_activity_baseline(self, app, monkeypatch):
_mock_docker(monkeypatch)
a = _seed_app()
p = ContainerSleepService.set_policy(a.id, enabled=True, idle_timeout_minutes=30)
p.last_activity_at = None
db.session.commit()
assert a.id not in ContainerSleepService.sweep_idle()['slept']
class TestSleepApi:
def test_sleep_and_wake_endpoints(self, client, auth_headers, app, monkeypatch):
_mock_docker(monkeypatch)
from app.models import User
admin = User.query.filter_by(username='testadmin').first()
a = _seed_app(user_id=admin.id)
assert client.post(f'/api/v1/apps/{a.id}/sleep', headers=auth_headers).status_code == 200
assert client.post(f'/api/v1/apps/{a.id}/wake', headers=auth_headers).status_code == 200
def test_sweep_requires_admin_auth(self, client, app):
assert client.post('/api/v1/apps/sweep-idle').status_code == 401