Shemot 28:2 — V'asita vigdei kodesh l'Aharon achicha, l'khavod ul'tifaret. The builder makes the garments. The kohen wears them. Four access tiers (Operations spec §1.1): - Pe'ah (GET/POST /peah/ask) — public, no auth, no tracking. The stranger is safe to approach. - Courtyard (POST /courtyard/offering) — Bearer token auth, offering types: shelamim, olah, chatat, minchah - Chatzer (POST /chatzer/sanhedrin) — Sanhedrin deliberation, returns majority/minority with preserved dissent - Kodesh (GET /kodesh/health, /kodesh/status) — operator interface, health checks for K8s liveness/readiness probes Error format follows chatat protocol (Operations spec §1.2): KIYOR_* (422), SHABBAT (503), MANNA_DEPLETED (503), CLOUD_MOVING (503), HALT_* (503), UNAUTHORIZED (401) Implementation: - Plug.Router with Bandit HTTP server - Added to supervision tree via maybe_add_http_server - Checks system availability (Shabbat, halt) before processing - Health check reads L1 integrity, cloud, manna, halt monitor - Status shows all component states, signal count, refuge list - 16 API tests covering all four tiers + error format All 235 Elixir + 19 L3 + 195 Prolog + 33 Haskell = 463+ tests, 0 failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
252 lines
8.1 KiB
Elixir
252 lines
8.1 KiB
Elixir
defmodule VavPipeline.BigdeiKodeshTest do
|
|
@moduledoc """
|
|
Tests for the Bigdei Kodesh — the HTTP API layer.
|
|
|
|
Shemot 28:2 — V'asita vigdei kodesh l'Aharon achicha,
|
|
l'khavod ul'tifaret.
|
|
|
|
Four garments for four access tiers:
|
|
/peah/* — public, no auth
|
|
/courtyard/* — authenticated
|
|
/chatzer/* — full access
|
|
/kodesh/* — operator
|
|
"""
|
|
|
|
use ExUnit.Case, async: false
|
|
|
|
import Plug.Test
|
|
import Plug.Conn
|
|
|
|
alias VavPipeline.BigdeiKodesh.Router
|
|
|
|
@opts Router.init([])
|
|
|
|
# ============================================================
|
|
# Pe'ah — Public Endpoint (Machaneh Yisrael)
|
|
# ============================================================
|
|
describe "Pe'ah endpoint" do
|
|
test "GET /peah/ask without question returns usage" do
|
|
conn =
|
|
conn(:get, "/peah/ask")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 200
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["message"] =~ "Shorashim"
|
|
assert body["usage"] =~ "/peah/ask"
|
|
end
|
|
|
|
test "GET /peah/ask with question parameter processes it" do
|
|
conn =
|
|
conn(:get, "/peah/ask?question=What+is+bereishit")
|
|
|> Router.call(@opts)
|
|
|
|
# Should return 200 or 503 (if waiting for provision)
|
|
assert conn.status in [200, 503]
|
|
end
|
|
|
|
test "POST /peah/ask with valid question" do
|
|
conn =
|
|
conn(:post, "/peah/ask", Jason.encode!(%{question: "What is bereishit?"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status in [200, 503]
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
|
|
if conn.status == 200 do
|
|
assert Map.has_key?(body, "answer") or Map.has_key?(body, "processing")
|
|
end
|
|
end
|
|
|
|
test "POST /peah/ask without question returns 422" do
|
|
conn =
|
|
conn(:post, "/peah/ask", Jason.encode!(%{not_question: "wrong field"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 422
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["error"]["code"] == "KIYOR_VALIDATION_FAILED"
|
|
assert body["error"]["type"] == "purification_needed"
|
|
end
|
|
|
|
test "POST /peah/ask with empty question returns 422" do
|
|
conn =
|
|
conn(:post, "/peah/ask", Jason.encode!(%{question: ""}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 422
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Courtyard — Authenticated Endpoint
|
|
# ============================================================
|
|
describe "Courtyard endpoint" do
|
|
test "POST /courtyard/offering without auth returns 401" do
|
|
conn =
|
|
conn(:post, "/courtyard/offering", Jason.encode!(%{content: "test"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 401
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["error"]["code"] == "UNAUTHORIZED"
|
|
end
|
|
|
|
test "POST /courtyard/offering with auth but no content returns 422" do
|
|
conn =
|
|
conn(:post, "/courtyard/offering", Jason.encode!(%{offering_type: "shelamim"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("authorization", "Bearer test-token")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 422
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["error"]["code"] == "KIYOR_VALIDATION_FAILED"
|
|
end
|
|
|
|
test "POST /courtyard/offering with auth and content processes" do
|
|
conn =
|
|
conn(:post, "/courtyard/offering", Jason.encode!(%{
|
|
content: "bereishit bara elohim",
|
|
offering_type: "shelamim"
|
|
}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("authorization", "Bearer test-token")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status in [200, 503]
|
|
|
|
if conn.status == 200 do
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert Map.has_key?(body, "offering_id")
|
|
assert Map.has_key?(body, "offering_receipt")
|
|
assert body["offering_receipt"]["type"] == "shelamim"
|
|
end
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Chatzer — Sanhedrin Endpoint
|
|
# ============================================================
|
|
describe "Chatzer endpoint" do
|
|
test "POST /chatzer/sanhedrin without auth returns 401" do
|
|
conn =
|
|
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{case: "test case"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 401
|
|
end
|
|
|
|
test "POST /chatzer/sanhedrin without case returns 422" do
|
|
conn =
|
|
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{not_case: "wrong field"}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("authorization", "Bearer test-token")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 422
|
|
end
|
|
|
|
test "POST /chatzer/sanhedrin with valid case deliberates" do
|
|
conn =
|
|
conn(:post, "/chatzer/sanhedrin", Jason.encode!(%{
|
|
case: "Is cooking on Shabbat permitted for pikuach nefesh?"
|
|
}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("authorization", "Bearer test-token")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 200
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert Map.has_key?(body, "case_ref")
|
|
assert Map.has_key?(body, "deliberation")
|
|
assert body["deliberation"]["outcome"] in ["accepted", "rejected", "teiku"]
|
|
assert body["note"] =~ "not a binding ruling"
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Kodesh — Operator Endpoint
|
|
# ============================================================
|
|
describe "Kodesh endpoint" do
|
|
test "GET /kodesh/health returns health check" do
|
|
conn =
|
|
conn(:get, "/kodesh/health")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status in [200, 503]
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert Map.has_key?(body, "overall")
|
|
assert Map.has_key?(body, "checks")
|
|
assert body["overall"] in ["healthy", "degraded", "critical", "shabbat"]
|
|
|
|
# Required health checks
|
|
assert Map.has_key?(body["checks"], "l1_hash")
|
|
assert Map.has_key?(body["checks"], "halt_monitor_alive")
|
|
assert Map.has_key?(body["checks"], "esh_tamid")
|
|
end
|
|
|
|
test "GET /kodesh/status returns system status" do
|
|
conn =
|
|
conn(:get, "/kodesh/status")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 200
|
|
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert Map.has_key?(body, "cloud_state")
|
|
assert Map.has_key?(body, "components")
|
|
assert Map.has_key?(body, "halt_status")
|
|
assert Map.has_key?(body, "signal_count")
|
|
end
|
|
end
|
|
|
|
# ============================================================
|
|
# Error handling — Chatat protocol
|
|
# ============================================================
|
|
describe "error handling" do
|
|
test "unknown endpoint returns 404" do
|
|
conn =
|
|
conn(:get, "/unknown/path")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 404
|
|
body = Jason.decode!(conn.resp_body)
|
|
assert body["error"]["code"] == "NOT_FOUND"
|
|
end
|
|
|
|
test "all errors follow chatat format" do
|
|
conn =
|
|
conn(:post, "/peah/ask", Jason.encode!(%{}))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> Router.call(@opts)
|
|
|
|
assert conn.status == 422
|
|
body = Jason.decode!(conn.resp_body)
|
|
|
|
# Chatat format: error.code, error.type, error.message, error.guidance
|
|
assert Map.has_key?(body["error"], "code")
|
|
assert Map.has_key?(body["error"], "type")
|
|
assert Map.has_key?(body["error"], "message")
|
|
assert Map.has_key?(body["error"], "guidance")
|
|
end
|
|
|
|
test "all responses are JSON" do
|
|
conn =
|
|
conn(:get, "/kodesh/health")
|
|
|> Router.call(@opts)
|
|
|
|
content_type = get_resp_header(conn, "content-type")
|
|
assert Enum.any?(content_type, &String.contains?(&1, "application/json"))
|
|
end
|
|
end
|
|
end
|