Files
2026-07-13 13:12:33 +08:00

173 lines
5.5 KiB
Python

from __future__ import annotations
import ast
from pathlib import Path
import pytest
import opensquilla.gateway.rpc_wizard as rpc_wizard # noqa: F401 ensures registration
from opensquilla.application.wizard import reset_wizard_registry
from opensquilla.gateway.auth import Principal
from opensquilla.gateway.rpc import RpcContext, get_dispatcher
def _admin_ctx() -> RpcContext:
return RpcContext(
conn_id="t",
principal=Principal(
role="operator",
scopes=frozenset({"operator.admin"}),
is_owner=True,
authenticated=True,
),
)
@pytest.fixture(autouse=True)
def _clean_wizard_registry() -> None:
reset_wizard_registry()
@pytest.mark.asyncio
async def test_wizard_rpc_flow_uses_application_state_machine() -> None:
dispatcher = get_dispatcher()
ctx = _admin_ctx()
started = await dispatcher.dispatch(
"r1",
"wizard.start",
{"wizardType": "onboard_agent"},
ctx,
)
assert started.error is None, started.error
wizard_id = started.payload["wizardId"]
assert started.payload["step"]["stepId"] == "agent_identity"
advanced = await dispatcher.dispatch(
"r2",
"wizard.next",
{"wizardId": wizard_id, "answers": {"agent_name": "cora"}},
ctx,
)
assert advanced.error is None, advanced.error
assert advanced.payload == {
"step": {
"stepId": "system_prompt",
"title": "System Prompt & Persona",
"description": "Define how the agent behaves.",
"fields": [
{
"name": "system_prompt",
"label": "System Prompt",
"fieldType": "text",
"required": True,
"choices": None,
"default": None,
"description": "Long-form instructions the LLM sees at the top of every turn.",
},
{
"name": "persona_tone",
"label": "Persona Tone",
"fieldType": "select",
"required": False,
"choices": ["professional", "casual", "friendly"],
"default": "professional",
"description": "Conversational register used when no tone override applies.",
},
],
"nextStepId": "defaults",
},
"completed": False,
"result": None,
}
status = await dispatcher.dispatch("r3", "wizard.status", {"wizardId": wizard_id}, ctx)
assert status.error is None, status.error
assert status.payload["wizardId"] == wizard_id
assert status.payload["wizardType"] == "onboard_agent"
assert status.payload["currentStepId"] == "system_prompt"
assert status.payload["totalSteps"] == 3
assert status.payload["completed"] is False
@pytest.mark.asyncio
async def test_wizard_cancel_removes_session() -> None:
dispatcher = get_dispatcher()
ctx = _admin_ctx()
started = await dispatcher.dispatch(
"r1",
"wizard.start",
{"wizardType": "onboard_agent"},
ctx,
)
wizard_id = started.payload["wizardId"]
cancelled = await dispatcher.dispatch("r2", "wizard.cancel", {"wizardId": wizard_id}, ctx)
assert cancelled.error is None, cancelled.error
assert cancelled.payload == {"wizardId": wizard_id, "cancelled": True}
status = await dispatcher.dispatch("r3", "wizard.status", {"wizardId": wizard_id}, ctx)
assert status.error is not None
def test_gateway_rpc_wizard_depends_on_application_boundary() -> None:
source = Path(rpc_wizard.__file__).read_text(encoding="utf-8")
tree = ast.parse(source)
imports = [
node
for node in ast.walk(tree)
if isinstance(node, ast.ImportFrom) and node.module is not None
]
helper_names = {
"wizard_cancel_rpc_payload",
"wizard_next_rpc_payload",
"wizard_start_rpc_payload",
"wizard_status_rpc_payload",
}
imported_helpers = {
alias.name
for node in imports
if node.module == "opensquilla.application.wizard_rpc"
for alias in node.names
}
handlers = {
node.name: node
for node in tree.body
if isinstance(node, ast.AsyncFunctionDef)
and node.name
in {
"_handle_wizard_start",
"_handle_wizard_next",
"_handle_wizard_cancel",
"_handle_wizard_status",
}
}
handler_names = {
node.id
for handler in handlers.values()
for node in ast.walk(handler)
if isinstance(node, ast.Name)
}
direct_key_sets = {
tuple(key.value for key in node.keys if isinstance(key, ast.Constant))
for handler in handlers.values()
for node in ast.walk(handler)
if isinstance(node, ast.Dict)
}
handler_attributes = {
node.attr
for handler in handlers.values()
for node in ast.walk(handler)
if isinstance(node, ast.Attribute)
}
assert any(node.module == "opensquilla.application.wizard" for node in imports)
assert all(node.module != "opensquilla.gateway.wizard" for node in imports)
assert helper_names.issubset(imported_helpers)
assert helper_names.issubset(handler_names)
assert ("wizardId", "step") not in direct_key_sets
assert ("step", "completed", "result") not in direct_key_sets
assert ("wizardId", "cancelled") not in direct_key_sets
assert "to_dict" not in handler_attributes