From 87d6d421365cfb2ebaecdc7b0b14f98be10880a9 Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 7 Oct 2026 13:30:22 -0600 Subject: [PATCH] collections: refuse domain changes until domains.yaml is imported A domain created, changed, tagged or deleted through the API before domains.yaml was imported (e.g. while WROLPi starts) was undone by the import. These now wait for the import (503, "still starting"). Co-Authored-By: Claude Opus 5.5 --- wrolpi/collections/lib.py | 4 ++ .../collections/test/test_domains_config.py | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/wrolpi/collections/lib.py b/wrolpi/collections/lib.py index 07ec007d..990136ea 100644 --- a/wrolpi/collections/lib.py +++ b/wrolpi/collections/lib.py @@ -45,6 +45,10 @@ def check_config_imported(kind: str): if kind == 'playlist': from .config import get_playlists_config get_playlists_config().check_imported() + elif kind == 'domain': + # Local import to avoid circular import: collections -> archive -> collections + from modules.archive.lib import get_domains_config + get_domains_config().check_imported() def _activate_config_save(kind: str): diff --git a/wrolpi/collections/test/test_domains_config.py b/wrolpi/collections/test/test_domains_config.py index d96b096b..398f04f0 100644 --- a/wrolpi/collections/test/test_domains_config.py +++ b/wrolpi/collections/test/test_domains_config.py @@ -1,6 +1,9 @@ """Tests for DomainsConfig functionality.""" +import json import pathlib +from http import HTTPStatus +import pytest import yaml from sqlalchemy.orm import Session @@ -256,3 +259,40 @@ class TestDomainsConfig: assert not config['directory'].startswith('/'), \ f"Expected relative path but got absolute: {config['directory']}" assert config['directory'] == 'archive/example.com' + + +@pytest.mark.asyncio +async def test_domain_changes_wait_for_config_import(test_session: Session, test_directory: pathlib.Path, + async_client): + """Until domains.yaml is imported (e.g. while WROLPi starts), domains cannot be changed through the API; the + import would undo the change (or delete a new domain).""" + domain = Collection.from_config(test_session, {'name': 'kept.com', 'kind': 'domain'}) + test_session.commit() + domains_config.dump_config() + assert domains_config.get_file().is_file() + domains_config.successful_import = False + + did = domain.id + requests = [ + ('post', '/api/collections', dict(name='new.com', kind='domain')), + ('put', f'/api/collections/{did}', dict(description='changed')), + ('post', f'/api/collections/{did}/tag', dict(tag_name='Tagged', directory='archive/Tagged/kept.com')), + ('delete', f'/api/collections/{did}', None), + ] + for method, url, body in requests: + kwargs = dict(content=json.dumps(body)) if body else {} + request, response = await getattr(async_client, method)(url, **kwargs) + assert response.status_code == HTTPStatus.SERVICE_UNAVAILABLE, (method, url, response.json) + assert response.json['code'] == 'CONFIG_NOT_IMPORTED' + + test_session.expire_all() + domain = test_session.query(Collection).one() + assert domain.name == 'kept.com' + assert domain.description is None + assert domain.tag is None + assert domain.directory is None + + # Once imported, domains can change again. + domains_config.import_config() + request, response = await async_client.put(f'/api/collections/{did}', content=json.dumps(dict(description='x'))) + assert response.status_code == HTTPStatus.OK, response.json