Phase 5.5.4 (SPA library picker for ContextWidget) plus QA-driven polish. * New ModulePickerModal with search, kind tabs, single + multi-pick, and compact filter UX (favorites/hide-added inline; category + tag chips behind popover with active-count badge). * ModuleEditModal kind-aware body — wildcard option editor with enable/disable, weight overrides, mode picker (random/subset/pinned), sub-category multi-select. * Lock + internal per-instance overrides ported and adapted: locked_seed derives a per-wildcard rng via sha256(seed:binding); internal hides bindings from the public PIPELINE_CONTEXT payload. * Card indicators: modified dot (option-pool overrides), missing-from- library dot (polled hash check), in-card lock + internal toggles. * RNG-faithful assembler preview via new /wp/api/preview/resolve; header shows "PREVIEW · 42". UNRESOLVED chips clickable to strip $var. * Bulk collapse/expand toggle, focus toast w/ pulsing border overlay that tracks node bounds, friendly @name substitution in modal. * Backend: new /wp/api/modules/import-from-workflow endpoint preserves uuid; engine emits per-module effective seeds via WP_Context UI payload for authoritative lock-toggle defaults.
93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
"""End-to-end Test Runner tests. Spec §6 / 5.5.1.
|
|
|
|
These run the real aiohttp handler against a real in-memory DB. After
|
|
this task, posting a wildcard whose option references @{B_uuid} resolves
|
|
B and produces the expanded value, instead of leaking raw @{B} text."""
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
|
|
import pytest
|
|
from aiohttp import web
|
|
|
|
from engine.db.migrations import migrate
|
|
from engine.db.repositories import ModuleRepository
|
|
from wp_api import test_runner as test_runner_mod
|
|
|
|
|
|
@pytest.fixture
|
|
def app_with_db(tmp_path):
|
|
"""Build an aiohttp app with a freshly-migrated in-memory DB and the
|
|
test_runner endpoint wired up."""
|
|
conn = sqlite3.connect(":memory:")
|
|
conn.row_factory = sqlite3.Row
|
|
migrate(conn)
|
|
app = web.Application()
|
|
app["wp_db"] = conn
|
|
app.router.add_post("/wp/api/test", test_runner_mod.run_test)
|
|
return app, conn
|
|
|
|
|
|
async def test_nested_wildcard_ref_resolves_via_catalog(aiohttp_client, app_with_db):
|
|
"""Library has wildcards 'color' (UUID c0000001) and 'outfit' (UUID
|
|
o0000001) where outfit.options[0].value = "@{c0000001} dress". Test
|
|
Runner must load color into the catalog and resolve the ref so the
|
|
sample is "red dress" or "blue dress", never raw "@{c0000001} dress"."""
|
|
app, conn = app_with_db
|
|
repo = ModuleRepository(conn)
|
|
color = repo.create(
|
|
type="wildcard", name="color", description="",
|
|
category_id=None, tags=[],
|
|
payload={"options": [
|
|
{"value": "red", "weight": 1},
|
|
{"value": "blue", "weight": 1},
|
|
]},
|
|
)
|
|
outfit_payload = {
|
|
"options": [
|
|
{"value": f"@{{{color['id']}}} dress", "weight": 1},
|
|
],
|
|
}
|
|
repo.create(
|
|
type="wildcard", name="outfit", description="",
|
|
category_id=None, tags=[], payload=outfit_payload,
|
|
)
|
|
|
|
client = await aiohttp_client(app)
|
|
resp = await client.post("/wp/api/test", json={
|
|
"type": "wildcard",
|
|
"payload": outfit_payload,
|
|
"instance": {"variable_binding": "outfit"},
|
|
"samples": 5,
|
|
})
|
|
assert resp.status == 200
|
|
body = await resp.json()
|
|
samples = body["samples"]
|
|
assert len(samples) == 5
|
|
for s in samples:
|
|
assert s in ("red dress", "blue dress"), (
|
|
f"@{{{color['id']}}} did not resolve — got raw text {s!r}"
|
|
)
|
|
|
|
|
|
async def test_unknown_ref_emits_warning_does_not_crash(aiohttp_client, app_with_db):
|
|
"""A missing target produces a graceful warning + raw token in the
|
|
output (lenient mode) — Test Runner never strict-raises."""
|
|
app, conn = app_with_db
|
|
payload = {
|
|
"options": [{"value": "@{deadbeef} thing", "weight": 1}],
|
|
}
|
|
client = await aiohttp_client(app)
|
|
resp = await client.post("/wp/api/test", json={
|
|
"type": "wildcard",
|
|
"payload": payload,
|
|
"instance": {"variable_binding": "x"},
|
|
"samples": 1,
|
|
})
|
|
assert resp.status == 200
|
|
body = await resp.json()
|
|
warnings = body.get("warnings", [])
|
|
assert any("deadbeef" in str(w) for w in warnings), (
|
|
f"expected 'Unknown wildcard ref' warning for deadbeef, got: {warnings}"
|
|
)
|