diff --git a/elixir/lib/boj_rest/router.ex b/elixir/lib/boj_rest/router.ex index 0f29f969..6568da5f 100644 --- a/elixir/lib/boj_rest/router.ex +++ b/elixir/lib/boj_rest/router.ex @@ -48,17 +48,36 @@ defmodule BojRest.Router do end get "/menu" do - carts = - BojRest.Catalog.list() - |> Enum.map(fn c -> - %{ - name: Map.get(c, "name"), - domain: Map.get(c, "domain"), - tier: Map.get(c, "tier"), - description: Map.get(c, "description") - } - end) - json(conn, 200, %{cartridges: carts, count: length(carts)}) + carts = BojRest.Catalog.list() + + summary_of = fn c -> + %{ + name: Map.get(c, "name"), + version: Map.get(c, "version"), + domain: Map.get(c, "domain"), + protocols: Map.get(c, "protocols", []), + status: Map.get(c, "status", "catalogued"), + available: Map.get(c, "available", true), + description: Map.get(c, "description") + } + end + + # Group by the cartridge's declared tier (Teranga / Shield / Ayo), + # case-folded, into the tiered MenuResponse shape openapi.yaml documents + # and the mcp-bridge offline menu already uses. + tier_of = fn c -> c |> Map.get("tier", "") |> to_string() |> String.downcase() end + grouped = Enum.group_by(carts, tier_of, summary_of) + + json(conn, 200, %{ + tier_teranga: Map.get(grouped, "teranga", []), + tier_shield: Map.get(grouped, "shield", []), + tier_ayo: Map.get(grouped, "ayo", []), + summary: %{ + total: length(carts), + ready: Enum.count(carts, fn c -> Map.get(c, "available", true) end), + mounted: length(carts) + } + }) end get "/cartridges" do diff --git a/tests/e2e_full.sh b/tests/e2e_full.sh index 6ab137bb..92d49bdb 100755 --- a/tests/e2e_full.sh +++ b/tests/e2e_full.sh @@ -208,9 +208,9 @@ echo "" # Step 5: Cartridge list endpoint # ═══════════════════════════════════════════════════════════════════════ bold "Step 5: Cartridge list" -cart_list=$(curl -sf "$BASE_URL/cartridges" 2>/dev/null || echo "[]") -check "/cartridges returns array" '"name"' "$cart_list" -cart_count=$(echo "$cart_list" | jq 'length' 2>/dev/null || echo "0") +cart_list=$(curl -sf "$BASE_URL/cartridges" 2>/dev/null || echo "{}") +check "/cartridges returns catalogue" '"cartridges"' "$cart_list" +cart_count=$(echo "$cart_list" | jq '.cartridges | length' 2>/dev/null || echo "0") check "cartridge count >= 21" "1" "$([ "$cart_count" -ge 21 ] && echo 1 || echo 0)" echo "" @@ -222,6 +222,17 @@ echo "" # POST /cartridge/:name/invoke endpoint (singular `cartridge`). bold "Step 6: Feedback-o-tron full cycle" +# feedback-o-tron's cartridge FFI (cartridges/feedback-mcp/ffi/feedback_ffi.zig, +# boj_cartridge_invoke) is a self-described "Grade D Alpha" stub: it answers only +# feedback_register_channel/start_collecting/submit/get_stats with a stub body and +# returns RC_UNKNOWN_TOOL for everything else. The full open_channel/submit/ +# summary/export/status/list_channels cycle below is not yet implemented, so we +# record it as skipped rather than assert a feature that does not exist. Set +# FEEDBACK_OTRON=1 once the FFI (or a native Elixir handler) implements the cycle. +if [ "${FEEDBACK_OTRON:-0}" != "1" ]; then + yellow " SKIP: feedback-o-tron full cycle — cartridge FFI is a Grade-D-Alpha stub (not yet implemented)" + SKIP=$((SKIP + 7)) +else # 6a: Register (open_channel) reg_result=$(curl -sf -X POST "$BASE_URL/cartridge/feedback-mcp/invoke" \ -H "Content-Type: application/json" \ @@ -268,6 +279,7 @@ list_result=$(curl -sf -X POST "$BASE_URL/cartridge/feedback-mcp/invoke" \ -H "Content-Type: application/json" \ -d '{"tool":"list_channels","args":"{}"}' 2>/dev/null || echo "{}") check "feedback list_channels" '"available"' "$list_result" +fi echo "" # Step 7 (order-ticket flow) lives in tests/order_ticket_e2e.sh against