批次1: 项目结构 + SQLite 存储层 + 数据模型 批次2: REST API (http.server) 批次3: LLM 编译器 (支持 OpenAI/Anthropic) 批次4: RestrictedPython 规则执行器 批次5: 规则匹配器 + LLM Callback 兜底 批次6: 冲突检测器 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
142 lines
4.0 KiB
Python
142 lines
4.0 KiB
Python
"""Tests for REST API."""
|
|
import pytest
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from http.client import HTTPConnection
|
|
from threading import Thread
|
|
from time import sleep
|
|
|
|
from rule_engine.api import create_app
|
|
from rule_engine.store import RuleStore
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_db():
|
|
fd, path = tempfile.mkstemp(suffix=".db")
|
|
os.close(fd)
|
|
yield path
|
|
os.unlink(path)
|
|
|
|
|
|
@pytest.fixture
|
|
def store(temp_db):
|
|
return RuleStore(temp_db)
|
|
|
|
|
|
@pytest.fixture
|
|
def server(store):
|
|
srv = create_app(store, host="127.0.0.1", port=18000)
|
|
thread = Thread(target=srv.serve_forever, daemon=True)
|
|
thread.start()
|
|
sleep(0.1) # 等待服务器启动
|
|
yield srv
|
|
srv.shutdown()
|
|
|
|
|
|
@pytest.fixture
|
|
def client(server):
|
|
return HTTPConnection("127.0.0.1", 18000)
|
|
|
|
|
|
def test_health(client):
|
|
"""验证健康检查。"""
|
|
client.request("GET", "/health")
|
|
resp = client.getresponse()
|
|
assert resp.status == 200
|
|
data = json.loads(resp.read())
|
|
assert data["status"] == "ok"
|
|
|
|
|
|
def test_create_rule(client):
|
|
"""验证创建规则。"""
|
|
body = json.dumps({
|
|
"name": "test_rule",
|
|
"description": "测试规则",
|
|
"condition_template": "如果用户是会员则打折"
|
|
})
|
|
client.request("POST", "/api/rules", body=body, headers={"Content-Type": "application/json"})
|
|
resp = client.getresponse()
|
|
assert resp.status == 201
|
|
data = json.loads(resp.read())
|
|
assert data["name"] == "test_rule"
|
|
assert "id" in data
|
|
|
|
|
|
def test_create_rule_missing_fields(client):
|
|
"""验证缺少必填字段返回错误。"""
|
|
body = json.dumps({"name": "only_name"})
|
|
client.request("POST", "/api/rules", body=body, headers={"Content-Type": "application/json"})
|
|
resp = client.getresponse()
|
|
assert resp.status == 400
|
|
|
|
|
|
def test_get_rule(client):
|
|
"""验证获取规则。"""
|
|
# 先创建
|
|
body = json.dumps({"name": "get_test", "condition_template": "test"})
|
|
client.request("POST", "/api/rules", body=body, headers={"Content-Type": "application/json"})
|
|
resp = client.getresponse()
|
|
data = json.loads(resp.read())
|
|
rule_id = data["id"]
|
|
|
|
# 再获取
|
|
client.request("GET", f"/api/rules/{rule_id}")
|
|
resp = client.getresponse()
|
|
data = json.loads(resp.read())
|
|
assert data["name"] == "get_test"
|
|
|
|
|
|
def test_get_rule_not_found(client):
|
|
"""验证获取不存在的规则。"""
|
|
client.request("GET", "/api/rules/nonexistent")
|
|
resp = client.getresponse()
|
|
assert resp.status == 404
|
|
|
|
|
|
def test_list_rules(client):
|
|
"""验证列出规则。"""
|
|
# 创建两条规则
|
|
for name in ["list_r1", "list_r2"]:
|
|
body = json.dumps({"name": name, "condition_template": "test"})
|
|
client.request("POST", "/api/rules", body=body, headers={"Content-Type": "application/json"})
|
|
client.getresponse()
|
|
|
|
# 列出
|
|
client.request("GET", "/api/rules")
|
|
resp = client.getresponse()
|
|
assert resp.status == 200
|
|
data = json.loads(resp.read())
|
|
assert "rules" in data
|
|
assert len(data["rules"]) >= 2
|
|
|
|
|
|
def test_delete_rule(client):
|
|
"""验证删除规则。"""
|
|
# 先创建
|
|
body = json.dumps({"name": "delete_test", "condition_template": "test"})
|
|
client.request("POST", "/api/rules", body=body, headers={"Content-Type": "application/json"})
|
|
resp = client.getresponse()
|
|
rule_id = json.loads(resp.read())["id"]
|
|
|
|
# 删除
|
|
client.request("DELETE", f"/api/rules/{rule_id}")
|
|
resp = client.getresponse()
|
|
assert resp.status == 200
|
|
|
|
# 确认已删除
|
|
client.request("GET", f"/api/rules/{rule_id}")
|
|
resp = client.getresponse()
|
|
assert resp.status == 404
|
|
|
|
|
|
def test_evaluate_not_implemented(client):
|
|
"""验证 evaluate 接口返回 placeholder。"""
|
|
body = json.dumps({"facts": {"user_id": "u123"}})
|
|
client.request("POST", "/api/rules/evaluate", body=body, headers={"Content-Type": "application/json"})
|
|
resp = client.getresponse()
|
|
assert resp.status == 200
|
|
data = json.loads(resp.read())
|
|
assert data["matched"] is False
|
|
assert "Executor not implemented" in data["message"]
|