190 lines
5.6 KiB
Python
190 lines
5.6 KiB
Python
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app import app, PIPELINES_DIR, LOGS_DIR
|
|
|
|
BASE_DIR = Path(__file__).parent.parent
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clean_storage():
|
|
"""Clean storage before and after each test."""
|
|
if PIPELINES_DIR.exists():
|
|
shutil.rmtree(PIPELINES_DIR)
|
|
if LOGS_DIR.exists():
|
|
shutil.rmtree(LOGS_DIR)
|
|
PIPELINES_DIR.mkdir(parents=True, exist_ok=True)
|
|
LOGS_DIR.mkdir(parents=True, exist_ok=True)
|
|
yield
|
|
if PIPELINES_DIR.exists():
|
|
shutil.rmtree(PIPELINES_DIR)
|
|
if LOGS_DIR.exists():
|
|
shutil.rmtree(LOGS_DIR)
|
|
|
|
def make_pipeline(name="test", prompt_count=1):
|
|
return {
|
|
"id": "test-pipeline-id",
|
|
"name": name,
|
|
"prompts": [
|
|
{
|
|
"id": f"prompt-{i}",
|
|
"text": f"test prompt {i}",
|
|
"title": f"Test Prompt {i}",
|
|
"order": i
|
|
}
|
|
for i in range(prompt_count)
|
|
],
|
|
"created_at": "2024-01-01T00:00:00+00:00",
|
|
"updated_at": "2024-01-01T00:00:00+00:00"
|
|
}
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
return TestClient(app)
|
|
|
|
class TestListPipelines:
|
|
def test_empty(self, client):
|
|
resp = client.get("/api/pipelines")
|
|
assert resp.status_code == 200
|
|
assert resp.json() == []
|
|
|
|
def test_with_pipelines(self, client):
|
|
p = make_pipeline()
|
|
client.post("/api/pipelines", json=p)
|
|
resp = client.get("/api/pipelines")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert len(data) == 1
|
|
assert data[0]["name"] == "test"
|
|
|
|
class TestCreatePipeline:
|
|
def test_create(self, client):
|
|
p = make_pipeline()
|
|
resp = client.post("/api/pipelines", json=p)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert data["name"] == "test"
|
|
assert data["id"] == "test-pipeline-id"
|
|
|
|
def test_duplicate_id(self, client):
|
|
p = make_pipeline()
|
|
client.post("/api/pipelines", json=p)
|
|
resp = client.post("/api/pipelines", json=p)
|
|
assert resp.status_code == 409
|
|
|
|
class TestDeletePipeline:
|
|
def test_delete(self, client):
|
|
p = make_pipeline()
|
|
client.post("/api/pipelines", json=p)
|
|
resp = client.delete("/api/pipelines/test-pipeline-id")
|
|
assert resp.status_code == 200
|
|
assert resp.json()["deleted"] == "test-pipeline-id"
|
|
|
|
def test_delete_not_found(self, client):
|
|
resp = client.delete("/api/pipelines/nonexistent")
|
|
assert resp.status_code == 404
|
|
|
|
class TestExecutePipeline:
|
|
def test_not_found(self, client):
|
|
resp = client.post("/api/pipelines/nonexistent/execute")
|
|
assert resp.status_code == 404
|
|
|
|
def test_execute(self, client, monkeypatch):
|
|
"""Test pipeline execution by patching opencode with echo."""
|
|
import asyncio
|
|
|
|
class FakeStream:
|
|
async def readline(self):
|
|
return b""
|
|
|
|
class FakeProcess:
|
|
returncode = 0
|
|
|
|
async def wait(self):
|
|
return 0
|
|
|
|
async def fake_create_subprocess_exec(*args, **kwargs):
|
|
proc = FakeProcess()
|
|
proc.stdout = FakeStream()
|
|
proc.stderr = FakeStream()
|
|
return proc
|
|
|
|
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
|
|
|
|
p = make_pipeline()
|
|
client.post("/api/pipelines", json=p)
|
|
|
|
resp = client.post("/api/pipelines/test-pipeline-id/execute")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "run_id" in data
|
|
assert "log_file" in data
|
|
|
|
def test_log_file_created(self, client, monkeypatch):
|
|
"""Test that log file is created after execution."""
|
|
import asyncio
|
|
|
|
class FakeStream:
|
|
async def readline(self):
|
|
return b""
|
|
|
|
class FakeProcess:
|
|
returncode = 0
|
|
|
|
async def wait(self):
|
|
return 0
|
|
|
|
async def fake_create_subprocess_exec(*args, **kwargs):
|
|
proc = FakeProcess()
|
|
proc.stdout = FakeStream()
|
|
proc.stderr = FakeStream()
|
|
return proc
|
|
|
|
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
|
|
|
|
p = make_pipeline()
|
|
client.post("/api/pipelines", json=p)
|
|
|
|
resp = client.post("/api/pipelines/test-pipeline-id/execute")
|
|
data = resp.json()
|
|
log_path = Path(data["log_file"])
|
|
assert log_path.exists()
|
|
content = log_path.read_text()
|
|
assert "Starting pipeline" in content
|
|
assert "Pipeline finished" in content
|
|
|
|
def test_error_step_stops_pipeline(self, client, monkeypatch):
|
|
"""Test that pipeline stops on first error."""
|
|
import asyncio
|
|
|
|
class FakeStream:
|
|
async def readline(self):
|
|
return b""
|
|
|
|
class FakeProcess:
|
|
returncode = 1
|
|
|
|
async def wait(self):
|
|
return 1
|
|
|
|
async def fake_create_subprocess_exec(*args, **kwargs):
|
|
proc = FakeProcess()
|
|
proc.stdout = FakeStream()
|
|
proc.stderr = FakeStream()
|
|
return proc
|
|
|
|
monkeypatch.setattr(asyncio, "create_subprocess_exec", fake_create_subprocess_exec)
|
|
|
|
p = make_pipeline(prompt_count=3)
|
|
client.post("/api/pipelines", json=p)
|
|
|
|
resp = client.post("/api/pipelines/test-pipeline-id/execute")
|
|
data = resp.json()
|
|
log_path = Path(data["log_file"])
|
|
content = log_path.read_text()
|
|
assert "Pipeline failed at step 1" in content
|
|
assert "Step 2" not in content
|