"""Tests for Nextcloud playbook routes.""" import uuid import pytest from fastapi import HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.agent import Agent from app.models.task import Task from app.models.tenant import Tenant from app.playbooks.nextcloud import NEXTCLOUD_STACK_DIR from app.routes.playbooks import ( NextcloudSetDomainRequest, nextcloud_set_domain, ) @pytest.mark.asyncio class TestNextcloudSetDomainEndpoint: """Tests for the nextcloud_set_domain endpoint.""" async def test_happy_path_creates_task( self, db: AsyncSession, test_tenant: Tenant, test_agent_for_tenant: Agent ): """POST /tenants/{id}/nextcloud/set-domain returns 201 with COMPOSITE task.""" request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=False, ) task = await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) assert task.id is not None assert task.tenant_id == test_tenant.id assert task.agent_id == test_agent_for_tenant.id assert task.type == "COMPOSITE" assert task.status == "pending" async def test_task_has_both_steps( self, db: AsyncSession, test_tenant: Tenant, test_agent_for_tenant: Agent ): """Verify response task payload contains both steps in correct order.""" request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=True, ) task = await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) assert "steps" in task.payload assert len(task.payload["steps"]) == 2 # First step: NEXTCLOUD_SET_DOMAIN step0 = task.payload["steps"][0] assert step0["type"] == "NEXTCLOUD_SET_DOMAIN" assert step0["payload"]["public_url"] == "https://cloud.example.com" # Second step: DOCKER_RELOAD step1 = task.payload["steps"][1] assert step1["type"] == "DOCKER_RELOAD" assert step1["payload"]["compose_dir"] == NEXTCLOUD_STACK_DIR assert step1["payload"]["pull"] is True async def test_pull_flag_default_false( self, db: AsyncSession, test_tenant: Tenant, test_agent_for_tenant: Agent ): """Verify pull defaults to False when not specified.""" request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", ) task = await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) step = task.payload["steps"][1] assert step["payload"]["pull"] is False async def test_tenant_not_found_returns_404(self, db: AsyncSession): """Non-existent tenant_id returns 404.""" fake_tenant_id = uuid.uuid4() request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=False, ) with pytest.raises(HTTPException) as exc_info: await nextcloud_set_domain( tenant_id=fake_tenant_id, request=request, db=db ) assert exc_info.value.status_code == 404 assert f"Tenant {fake_tenant_id} not found" in str(exc_info.value.detail) async def test_no_online_agent_returns_404( self, db: AsyncSession, test_tenant: Tenant ): """Tenant with no online agent returns 404.""" request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=False, ) with pytest.raises(HTTPException) as exc_info: await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) assert exc_info.value.status_code == 404 assert "No online agent found" in str(exc_info.value.detail) async def test_offline_agent_not_resolved( self, db: AsyncSession, test_tenant: Tenant ): """Offline agent should not be auto-resolved.""" # Create an offline agent for the tenant offline_agent = Agent( id=uuid.uuid4(), tenant_id=test_tenant.id, name="offline-agent", version="1.0.0", status="offline", token="offline-token", ) db.add(offline_agent) await db.commit() request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=False, ) with pytest.raises(HTTPException) as exc_info: await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) assert exc_info.value.status_code == 404 assert "No online agent found" in str(exc_info.value.detail) async def test_task_persisted_to_database( self, db: AsyncSession, test_tenant: Tenant, test_agent_for_tenant: Agent ): """Verify the task is actually persisted and can be retrieved.""" request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=False, ) task = await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) # Query the task back from the database result = await db.execute(select(Task).where(Task.id == task.id)) retrieved_task = result.scalar_one_or_none() assert retrieved_task is not None assert retrieved_task.type == "COMPOSITE" assert retrieved_task.tenant_id == test_tenant.id assert retrieved_task.agent_id == test_agent_for_tenant.id async def test_auto_resolves_first_online_agent( self, db: AsyncSession, test_tenant: Tenant ): """Verify that the first online agent is auto-resolved.""" # Create two agents for the tenant - one offline, one online offline_agent = Agent( id=uuid.uuid4(), tenant_id=test_tenant.id, name="offline-agent", version="1.0.0", status="offline", token="offline-token", ) online_agent = Agent( id=uuid.uuid4(), tenant_id=test_tenant.id, name="online-agent", version="1.0.0", status="online", token="online-token", ) db.add(offline_agent) db.add(online_agent) await db.commit() request = NextcloudSetDomainRequest( public_url="https://cloud.example.com", pull=False, ) task = await nextcloud_set_domain( tenant_id=test_tenant.id, request=request, db=db ) # Should have resolved to the online agent assert task.agent_id == online_agent.id