feat: 添加自动化任务(automation_jobs)功能模块
This commit is contained in:
@@ -0,0 +1,371 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, time, 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"),
|
||||
schedule_type=overrides.get("schedule_type", "daily"),
|
||||
run_at=overrides.get("run_at", time(9, 0, 0)),
|
||||
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": {}}
|
||||
),
|
||||
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",
|
||||
"schedule_type": "daily",
|
||||
"run_at": "09:00:00",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"config": {
|
||||
"input_template": "Hello",
|
||||
"enabled_tools": [],
|
||||
"context": {},
|
||||
},
|
||||
},
|
||||
)
|
||||
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",
|
||||
"schedule_type": "daily",
|
||||
"run_at": "09:00:00",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"status": "active",
|
||||
"config": {
|
||||
"input_template": "Hello",
|
||||
"enabled_tools": [],
|
||||
"context": {},
|
||||
},
|
||||
},
|
||||
)
|
||||
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",
|
||||
"schedule_type": "daily",
|
||||
"run_at": "09:00:00",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"status": "active",
|
||||
"config": {
|
||||
"input_template": "Hello",
|
||||
"enabled_tools": [],
|
||||
"context": {},
|
||||
},
|
||||
},
|
||||
)
|
||||
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(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(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 = {}
|
||||
Reference in New Issue
Block a user