2025-12-04 14:04:21 +01:00
|
|
|
"""Tests for the Nextcloud playbook module."""
|
|
|
|
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
import pytest_asyncio
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
from app.models.task import Task, TaskStatus
|
|
|
|
|
from app.models.tenant import Tenant
|
|
|
|
|
from app.playbooks.nextcloud import (
|
|
|
|
|
NEXTCLOUD_STACK_DIR,
|
|
|
|
|
CompositeStep,
|
2025-12-08 20:20:31 +01:00
|
|
|
build_nextcloud_initial_setup_step,
|
2025-12-04 14:04:21 +01:00
|
|
|
build_nextcloud_set_domain_steps,
|
2025-12-08 20:20:31 +01:00
|
|
|
create_nextcloud_initial_setup_task,
|
2025-12-04 14:04:21 +01:00
|
|
|
create_nextcloud_set_domain_task,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-12-08 20:20:31 +01:00
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for Playwright Initial Setup
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestBuildNextcloudInitialSetupStep:
|
|
|
|
|
"""Tests for the build_nextcloud_initial_setup_step function."""
|
|
|
|
|
|
|
|
|
|
def test_returns_playwright_payload(self):
|
|
|
|
|
"""Verify that build_nextcloud_initial_setup_step returns correct structure."""
|
|
|
|
|
payload = build_nextcloud_initial_setup_step(
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="securepassword123",
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-08 22:23:18 +01:00
|
|
|
assert payload["scenario"] == "nextcloud_initial_setup"
|
2025-12-08 20:20:31 +01:00
|
|
|
assert "inputs" in payload
|
|
|
|
|
assert "timeout" in payload
|
|
|
|
|
|
|
|
|
|
def test_inputs_contain_required_fields(self):
|
|
|
|
|
"""Verify that inputs contain all required fields."""
|
|
|
|
|
payload = build_nextcloud_initial_setup_step(
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="securepassword123",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
inputs = payload["inputs"]
|
|
|
|
|
assert inputs["base_url"] == "https://cloud.example.com"
|
|
|
|
|
assert inputs["admin_username"] == "admin"
|
|
|
|
|
assert inputs["admin_password"] == "securepassword123"
|
2025-12-08 22:23:18 +01:00
|
|
|
# allowed_domains should be in options, not inputs
|
|
|
|
|
assert "options" in payload
|
|
|
|
|
assert "allowed_domains" in payload["options"]
|
2025-12-08 20:20:31 +01:00
|
|
|
|
|
|
|
|
def test_allowed_domains_extracted_from_url(self):
|
|
|
|
|
"""Verify that allowed_domains is extracted from base_url."""
|
|
|
|
|
payload = build_nextcloud_initial_setup_step(
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="password",
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-08 22:23:18 +01:00
|
|
|
assert payload["options"]["allowed_domains"] == ["cloud.example.com"]
|
2025-12-08 20:20:31 +01:00
|
|
|
|
|
|
|
|
def test_allowed_domains_with_port(self):
|
|
|
|
|
"""Verify that allowed_domains handles URLs with ports."""
|
|
|
|
|
payload = build_nextcloud_initial_setup_step(
|
|
|
|
|
base_url="https://cloud.example.com:8443",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="password",
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-08 22:23:18 +01:00
|
|
|
assert payload["options"]["allowed_domains"] == ["cloud.example.com:8443"]
|
2025-12-08 20:20:31 +01:00
|
|
|
|
|
|
|
|
def test_timeout_is_set(self):
|
|
|
|
|
"""Verify that timeout is set in the payload."""
|
|
|
|
|
payload = build_nextcloud_initial_setup_step(
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="password",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert payload["timeout"] == 120
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
class TestCreateNextcloudInitialSetupTask:
|
|
|
|
|
"""Tests for the create_nextcloud_initial_setup_task function."""
|
|
|
|
|
|
|
|
|
|
async def test_persists_playwright_task(self, db: AsyncSession, test_tenant: Tenant):
|
|
|
|
|
"""Verify that create_nextcloud_initial_setup_task persists a PLAYWRIGHT task."""
|
|
|
|
|
agent_id = uuid.uuid4()
|
|
|
|
|
|
|
|
|
|
task = await create_nextcloud_initial_setup_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="securepassword123",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert task.id is not None
|
|
|
|
|
assert task.tenant_id == test_tenant.id
|
|
|
|
|
assert task.agent_id == agent_id
|
|
|
|
|
assert task.type == "PLAYWRIGHT"
|
|
|
|
|
assert task.status == TaskStatus.PENDING.value
|
|
|
|
|
|
|
|
|
|
async def test_task_payload_contains_scenario(
|
|
|
|
|
self, db: AsyncSession, test_tenant: Tenant
|
|
|
|
|
):
|
|
|
|
|
"""Verify that the task payload contains the scenario field."""
|
|
|
|
|
task = await create_nextcloud_initial_setup_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="password",
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-08 22:23:18 +01:00
|
|
|
assert task.payload["scenario"] == "nextcloud_initial_setup"
|
2025-12-08 20:20:31 +01:00
|
|
|
|
|
|
|
|
async def test_task_payload_contains_inputs(
|
|
|
|
|
self, db: AsyncSession, test_tenant: Tenant
|
|
|
|
|
):
|
|
|
|
|
"""Verify that the task payload contains the inputs field."""
|
|
|
|
|
task = await create_nextcloud_initial_setup_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="testadmin",
|
|
|
|
|
admin_password="testpassword123",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
inputs = task.payload["inputs"]
|
|
|
|
|
assert inputs["base_url"] == "https://cloud.example.com"
|
|
|
|
|
assert inputs["admin_username"] == "testadmin"
|
|
|
|
|
assert inputs["admin_password"] == "testpassword123"
|
2025-12-08 22:23:18 +01:00
|
|
|
# allowed_domains should be in options, not inputs
|
|
|
|
|
assert task.payload["options"]["allowed_domains"] == ["cloud.example.com"]
|
2025-12-08 20:20:31 +01:00
|
|
|
|
|
|
|
|
async def test_task_persisted_to_database(
|
|
|
|
|
self, db: AsyncSession, test_tenant: Tenant
|
|
|
|
|
):
|
|
|
|
|
"""Verify the task is actually persisted and can be retrieved."""
|
|
|
|
|
task = await create_nextcloud_initial_setup_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
base_url="https://cloud.example.com",
|
|
|
|
|
admin_username="admin",
|
|
|
|
|
admin_password="password",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 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 == "PLAYWRIGHT"
|
|
|
|
|
assert retrieved_task.tenant_id == test_tenant.id
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================================================================
|
|
|
|
|
# Tests for Set Domain Playbook
|
|
|
|
|
# =============================================================================
|
|
|
|
|
|
|
|
|
|
|
2025-12-04 14:04:21 +01:00
|
|
|
class TestBuildNextcloudSetDomainSteps:
|
|
|
|
|
"""Tests for the build_nextcloud_set_domain_steps function."""
|
|
|
|
|
|
|
|
|
|
def test_returns_two_steps(self):
|
|
|
|
|
"""Verify that build_nextcloud_set_domain_steps returns exactly 2 steps."""
|
|
|
|
|
steps = build_nextcloud_set_domain_steps(
|
|
|
|
|
public_url="https://cloud.example.com", pull=False
|
|
|
|
|
)
|
|
|
|
|
assert len(steps) == 2
|
|
|
|
|
assert all(isinstance(step, CompositeStep) for step in steps)
|
|
|
|
|
|
|
|
|
|
def test_first_step_is_nextcloud_set_domain(self):
|
|
|
|
|
"""Verify the first step is NEXTCLOUD_SET_DOMAIN."""
|
|
|
|
|
steps = build_nextcloud_set_domain_steps(
|
|
|
|
|
public_url="https://cloud.example.com", pull=False
|
|
|
|
|
)
|
|
|
|
|
assert steps[0].type == "NEXTCLOUD_SET_DOMAIN"
|
|
|
|
|
|
|
|
|
|
def test_nextcloud_set_domain_payload(self):
|
|
|
|
|
"""Verify NEXTCLOUD_SET_DOMAIN step has correct payload."""
|
|
|
|
|
public_url = "https://cloud.example.com"
|
|
|
|
|
steps = build_nextcloud_set_domain_steps(public_url=public_url, pull=False)
|
|
|
|
|
assert steps[0].payload["public_url"] == public_url
|
|
|
|
|
|
|
|
|
|
def test_docker_reload_payload(self):
|
|
|
|
|
"""Verify the DOCKER_RELOAD step has the correct payload structure."""
|
|
|
|
|
steps = build_nextcloud_set_domain_steps(
|
|
|
|
|
public_url="https://cloud.example.com", pull=False
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
docker_step = steps[1]
|
|
|
|
|
assert docker_step.type == "DOCKER_RELOAD"
|
|
|
|
|
assert docker_step.payload["compose_dir"] == NEXTCLOUD_STACK_DIR
|
|
|
|
|
assert docker_step.payload["pull"] is False
|
|
|
|
|
|
|
|
|
|
def test_pull_flag_true(self):
|
|
|
|
|
"""Verify that pull=True is passed correctly."""
|
|
|
|
|
steps = build_nextcloud_set_domain_steps(
|
|
|
|
|
public_url="https://cloud.example.com", pull=True
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
docker_step = steps[1]
|
|
|
|
|
assert docker_step.payload["pull"] is True
|
|
|
|
|
|
|
|
|
|
def test_pull_flag_false(self):
|
|
|
|
|
"""Verify that pull=False is passed correctly."""
|
|
|
|
|
steps = build_nextcloud_set_domain_steps(
|
|
|
|
|
public_url="https://cloud.example.com", pull=False
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
docker_step = steps[1]
|
|
|
|
|
assert docker_step.payload["pull"] is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
class TestCreateNextcloudSetDomainTask:
|
|
|
|
|
"""Tests for the create_nextcloud_set_domain_task function."""
|
|
|
|
|
|
|
|
|
|
async def test_persists_composite_task(self, db: AsyncSession, test_tenant: Tenant):
|
|
|
|
|
"""Verify that create_nextcloud_set_domain_task persists a COMPOSITE task."""
|
|
|
|
|
agent_id = uuid.uuid4()
|
|
|
|
|
|
|
|
|
|
task = await create_nextcloud_set_domain_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=agent_id,
|
|
|
|
|
public_url="https://cloud.example.com",
|
|
|
|
|
pull=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert task.id is not None
|
|
|
|
|
assert task.tenant_id == test_tenant.id
|
|
|
|
|
assert task.agent_id == agent_id
|
|
|
|
|
assert task.type == "COMPOSITE"
|
|
|
|
|
assert task.status == TaskStatus.PENDING.value
|
|
|
|
|
|
|
|
|
|
async def test_task_payload_contains_steps(
|
|
|
|
|
self, db: AsyncSession, test_tenant: Tenant
|
|
|
|
|
):
|
|
|
|
|
"""Verify that the task payload contains the steps array."""
|
|
|
|
|
task = await create_nextcloud_set_domain_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
public_url="https://cloud.example.com",
|
|
|
|
|
pull=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert "steps" in task.payload
|
|
|
|
|
assert len(task.payload["steps"]) == 2
|
|
|
|
|
|
|
|
|
|
async def test_task_steps_structure(self, db: AsyncSession, test_tenant: Tenant):
|
|
|
|
|
"""Verify that the steps in the payload have the correct structure."""
|
|
|
|
|
task = await create_nextcloud_set_domain_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
public_url="https://cloud.example.com",
|
|
|
|
|
pull=True,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
steps = task.payload["steps"]
|
|
|
|
|
|
|
|
|
|
# First step should be NEXTCLOUD_SET_DOMAIN
|
|
|
|
|
assert steps[0]["type"] == "NEXTCLOUD_SET_DOMAIN"
|
|
|
|
|
assert steps[0]["payload"]["public_url"] == "https://cloud.example.com"
|
|
|
|
|
|
|
|
|
|
# Second step should be DOCKER_RELOAD
|
|
|
|
|
assert steps[1]["type"] == "DOCKER_RELOAD"
|
|
|
|
|
assert steps[1]["payload"]["compose_dir"] == NEXTCLOUD_STACK_DIR
|
|
|
|
|
assert steps[1]["payload"]["pull"] is True
|
|
|
|
|
|
|
|
|
|
async def test_task_with_pull_false(self, db: AsyncSession, test_tenant: Tenant):
|
|
|
|
|
"""Verify that pull=False is correctly stored in the task payload."""
|
|
|
|
|
task = await create_nextcloud_set_domain_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
public_url="https://cloud.example.com",
|
|
|
|
|
pull=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
steps = task.payload["steps"]
|
|
|
|
|
assert steps[1]["payload"]["pull"] is False
|
|
|
|
|
|
|
|
|
|
async def test_task_persisted_to_database(
|
|
|
|
|
self, db: AsyncSession, test_tenant: Tenant
|
|
|
|
|
):
|
|
|
|
|
"""Verify the task is actually persisted and can be retrieved."""
|
|
|
|
|
task = await create_nextcloud_set_domain_task(
|
|
|
|
|
db=db,
|
|
|
|
|
tenant_id=test_tenant.id,
|
|
|
|
|
agent_id=uuid.uuid4(),
|
|
|
|
|
public_url="https://cloud.example.com",
|
|
|
|
|
pull=False,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 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
|