405 lines
12 KiB
Python
405 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from uuid import UUID, uuid4
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app import app
|
|
from core.auth.models import CurrentUser
|
|
from v1.automation_jobs.dependencies import get_automation_jobs_service
|
|
from v1.automation_jobs.service import (
|
|
AutomationJobLimitExceeded,
|
|
AutomationJobNotFound,
|
|
)
|
|
from v1.automation_jobs.schemas import (
|
|
AutomationJobCreateRequest,
|
|
AutomationJobListResponse,
|
|
AutomationJobResponse,
|
|
AutomationJobUpdateRequest,
|
|
)
|
|
from v1.users.dependencies import get_current_user
|
|
|
|
|
|
def _make_job_response(
|
|
job_id: UUID | None = None, owner_id: UUID | None = None, **overrides
|
|
) -> AutomationJobResponse:
|
|
now = datetime.now(timezone.utc)
|
|
return AutomationJobResponse(
|
|
id=job_id or uuid4(),
|
|
owner_id=owner_id or uuid4(),
|
|
title=overrides.get("title", "Test Job"),
|
|
timezone=overrides.get("timezone", "Asia/Shanghai"),
|
|
status=overrides.get("status", "active"),
|
|
is_system=overrides.get("is_system", False),
|
|
config=overrides.get(
|
|
"config",
|
|
{
|
|
"input_template": "Hello",
|
|
"enabled_tools": [],
|
|
"context": {
|
|
"source": "latest_chat",
|
|
"window_mode": "day",
|
|
"window_count": 2,
|
|
},
|
|
"schedule": {
|
|
"type": "daily",
|
|
"run_at": {"hour": 9, "minute": 0},
|
|
},
|
|
},
|
|
),
|
|
next_run_at=overrides.get("next_run_at", now),
|
|
created_at=overrides.get("created_at", now),
|
|
updated_at=overrides.get("updated_at", now),
|
|
)
|
|
|
|
|
|
def test_list_automation_jobs_requires_auth() -> None:
|
|
client = TestClient(app)
|
|
response = client.get("/api/v1/automation-jobs")
|
|
assert response.status_code == 401
|
|
|
|
|
|
def test_list_automation_jobs_returns_empty_when_no_jobs() -> None:
|
|
class FakeService:
|
|
async def list_by_owner(self, *, owner_id: UUID) -> AutomationJobListResponse:
|
|
return AutomationJobListResponse(items=[])
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides.pop(get_current_user, None)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.get("/api/v1/automation-jobs")
|
|
assert response.status_code == 401
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_list_automation_jobs_returns_jobs() -> None:
|
|
user_id = uuid4()
|
|
job = _make_job_response(owner_id=user_id)
|
|
|
|
class FakeService:
|
|
async def list_by_owner(self, *, owner_id: UUID) -> AutomationJobListResponse:
|
|
if owner_id == user_id:
|
|
return AutomationJobListResponse(items=[job])
|
|
return AutomationJobListResponse(items=[])
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.get("/api/v1/automation-jobs")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert len(data["items"]) == 1
|
|
assert data["items"][0]["title"] == "Test Job"
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_create_automation_job_requires_auth() -> None:
|
|
class FakeService:
|
|
pass
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides.pop(get_current_user, None)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.post(
|
|
"/api/v1/automation-jobs",
|
|
json={
|
|
"title": "New Job",
|
|
"timezone": "Asia/Shanghai",
|
|
"config": {
|
|
"input_template": "Hello",
|
|
"enabled_tools": [],
|
|
"context": {
|
|
"source": "latest_chat",
|
|
"window_mode": "day",
|
|
"window_count": 2,
|
|
},
|
|
"schedule": {
|
|
"type": "daily",
|
|
"run_at": {"hour": 9, "minute": 0},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
assert response.status_code == 401
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_create_automation_job_succeeds() -> None:
|
|
user_id = uuid4()
|
|
new_job = _make_job_response(owner_id=user_id, title="New Job")
|
|
|
|
class FakeService:
|
|
async def create(
|
|
self, *, owner_id: UUID, data: AutomationJobCreateRequest
|
|
) -> AutomationJobResponse:
|
|
return new_job
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.post(
|
|
"/api/v1/automation-jobs",
|
|
json={
|
|
"title": "New Job",
|
|
"timezone": "Asia/Shanghai",
|
|
"status": "active",
|
|
"config": {
|
|
"input_template": "Hello",
|
|
"enabled_tools": [],
|
|
"context": {
|
|
"source": "latest_chat",
|
|
"window_mode": "day",
|
|
"window_count": 2,
|
|
},
|
|
"schedule": {
|
|
"type": "daily",
|
|
"run_at": {"hour": 9, "minute": 0},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
assert response.status_code == 201
|
|
data = response.json()
|
|
assert data["title"] == "New Job"
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_create_automation_job_respects_limit() -> None:
|
|
user_id = uuid4()
|
|
|
|
class FakeService:
|
|
async def create(
|
|
self, *, owner_id: UUID, data: AutomationJobCreateRequest
|
|
) -> AutomationJobResponse:
|
|
raise AutomationJobLimitExceeded()
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.post(
|
|
"/api/v1/automation-jobs",
|
|
json={
|
|
"title": "New Job",
|
|
"timezone": "Asia/Shanghai",
|
|
"status": "active",
|
|
"config": {
|
|
"input_template": "Hello",
|
|
"enabled_tools": [],
|
|
"context": {
|
|
"source": "latest_chat",
|
|
"window_mode": "day",
|
|
"window_count": 2,
|
|
},
|
|
"schedule": {
|
|
"type": "daily",
|
|
"run_at": {"hour": 9, "minute": 0},
|
|
},
|
|
},
|
|
},
|
|
)
|
|
assert response.status_code == 400
|
|
assert "maximum" in response.json()["detail"].lower()
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_get_automation_job_requires_auth() -> None:
|
|
client = TestClient(app)
|
|
response = client.get(f"/api/v1/automation-jobs/{uuid4()}")
|
|
assert response.status_code == 401
|
|
|
|
|
|
def test_get_automation_job_returns_job() -> None:
|
|
user_id = uuid4()
|
|
job_id = uuid4()
|
|
job = _make_job_response(job_id=job_id, owner_id=user_id)
|
|
|
|
captured_job_id = job_id
|
|
captured_owner_id = user_id
|
|
|
|
class FakeService:
|
|
async def get_by_id(
|
|
self, *, job_id: UUID, owner_id: UUID
|
|
) -> AutomationJobResponse:
|
|
if job_id == captured_job_id and owner_id == captured_owner_id:
|
|
return job
|
|
raise AutomationJobNotFound()
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.get(f"/api/v1/automation-jobs/{job_id}")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["title"] == "Test Job"
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_get_automation_job_returns_404_when_not_found() -> None:
|
|
user_id = uuid4()
|
|
|
|
class FakeService:
|
|
async def get_by_id(
|
|
self, *, job_id: UUID, owner_id: UUID
|
|
) -> AutomationJobResponse:
|
|
raise AutomationJobNotFound()
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.get(f"/api/v1/automation-jobs/{uuid4()}")
|
|
assert response.status_code == 404
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_update_automation_job_requires_auth() -> None:
|
|
client = TestClient(app)
|
|
response = client.patch(
|
|
f"/api/v1/automation-jobs/{uuid4()}",
|
|
json={"title": "Updated"},
|
|
)
|
|
assert response.status_code == 401
|
|
|
|
|
|
def test_update_automation_job_succeeds() -> None:
|
|
user_id = uuid4()
|
|
job_id = uuid4()
|
|
updated_job = _make_job_response(
|
|
job_id=job_id,
|
|
owner_id=user_id,
|
|
title="Updated Title",
|
|
)
|
|
|
|
class FakeService:
|
|
async def update(
|
|
self,
|
|
*,
|
|
job_id: UUID,
|
|
owner_id: UUID,
|
|
data: AutomationJobUpdateRequest,
|
|
) -> AutomationJobResponse:
|
|
return updated_job
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.patch(
|
|
f"/api/v1/automation-jobs/{job_id}",
|
|
json={"title": "Updated Title"},
|
|
)
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["title"] == "Updated Title"
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_update_automation_job_returns_404_when_not_found() -> None:
|
|
user_id = uuid4()
|
|
|
|
class FakeService:
|
|
async def update(
|
|
self,
|
|
*,
|
|
job_id: UUID,
|
|
owner_id: UUID,
|
|
data: AutomationJobUpdateRequest,
|
|
) -> AutomationJobResponse:
|
|
raise AutomationJobNotFound()
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.patch(
|
|
f"/api/v1/automation-jobs/{uuid4()}", json={"title": "Updated"}
|
|
)
|
|
assert response.status_code == 404
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_delete_automation_job_requires_auth() -> None:
|
|
client = TestClient(app)
|
|
response = client.delete(f"/api/v1/automation-jobs/{uuid4()}")
|
|
assert response.status_code == 401
|
|
|
|
|
|
def test_delete_automation_job_succeeds() -> None:
|
|
user_id = uuid4()
|
|
job_id = uuid4()
|
|
|
|
class FakeService:
|
|
async def delete(self, *, job_id: UUID, owner_id: UUID) -> None:
|
|
pass
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.delete(f"/api/v1/automation-jobs/{job_id}")
|
|
assert response.status_code == 204
|
|
finally:
|
|
app.dependency_overrides = {}
|
|
|
|
|
|
def test_delete_automation_job_returns_404_when_not_found() -> None:
|
|
user_id = uuid4()
|
|
|
|
class FakeService:
|
|
async def delete(self, *, job_id: UUID, owner_id: UUID) -> None:
|
|
raise AutomationJobNotFound()
|
|
|
|
app.dependency_overrides[get_automation_jobs_service] = lambda: FakeService()
|
|
app.dependency_overrides[get_current_user] = lambda: CurrentUser(
|
|
id=user_id, phone="+8613812345678"
|
|
)
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.delete(f"/api/v1/automation-jobs/{uuid4()}")
|
|
assert response.status_code == 404
|
|
finally:
|
|
app.dependency_overrides = {}
|