defmodule VavPipeline.MannaLedgerTest do @moduledoc """ Tests for the MannaLedger — manna consumption and anti-hoarding enforcement. Shemot 16:4 — D'var yom b'yomo — the day's matter in its day. Shemot 16:19 — Al yoter mimenu ad boker — do not leave surplus. Shemot 16:20 — Vayotiru... vayarum tola'im vayiv'ash — hoarded manna rots. """ use ExUnit.Case, async: false alias VavPipeline.MannaLedger setup do MannaLedger.reset() :ok end # Helper to load provision from a test fixture file defp write_test_provision(opts \\ []) do today = Date.utc_today() |> Date.to_iso8601() compute = Keyword.get(opts, :compute_units, 100) api_calls = Keyword.get(opts, :api_calls_external, 50) panels = Keyword.get(opts, :sanhedrin_panels_max, 3) type = Keyword.get(opts, :type, "regular") expires = DateTime.utc_now() |> DateTime.add(6 * 3600) |> DateTime.to_iso8601() provision = %{ "date" => today, "provision" => %{ "compute_units" => compute, "memory_mb" => 1024, "api_calls_external" => api_calls, "storage_delta_mb" => 10, "sanhedrin_panels_max" => panels }, "type" => type, "expires_at" => expires, "written_by" => "test", "written_at" => DateTime.utc_now() |> DateTime.to_iso8601() } path = VavPipeline.Manna.provision_file() File.mkdir_p!(Path.dirname(path)) File.write!(path, Jason.encode!(provision)) on_exit(fn -> File.rm(path) end) :ok end # ============================================================ # Loading provision — d'var yom b'yomo # ============================================================ describe "load_provision" do test "loads provision from manna file" do write_test_provision(compute_units: 200, api_calls_external: 100) assert {:ok, :loaded} = MannaLedger.load_provision() ledger = MannaLedger.ledger() assert ledger.allocated.compute_units == 200 assert ledger.allocated.api_calls_external == 100 assert ledger.consumed.compute_units == 0 end test "returns :already_loaded if same day" do write_test_provision() assert {:ok, :loaded} = MannaLedger.load_provision() assert {:ok, :already_loaded} = MannaLedger.load_provision() end test "returns :no_provision when no manna file" do # Ensure provision file doesn't exist path = VavPipeline.Manna.provision_file() File.rm(path) assert {:ok, :no_provision} = MannaLedger.load_provision() end end # ============================================================ # Consumption — v'laktu d'var yom b'yomo # ============================================================ describe "consume" do setup do write_test_provision(compute_units: 10, api_calls_external: 5, sanhedrin_panels_max: 2) MannaLedger.load_provision() :ok end test "consumes resource and returns remaining" do assert {:ok, 7} = MannaLedger.consume(:compute_units, 3) assert {:ok, 4} = MannaLedger.consume(:compute_units, 3) end test "rejects when resource exhausted" do MannaLedger.consume(:compute_units, 10) assert {:error, :provision_exhausted} = MannaLedger.consume(:compute_units, 1) end test "rejects when amount exceeds remaining" do MannaLedger.consume(:compute_units, 8) assert {:error, :provision_exhausted} = MannaLedger.consume(:compute_units, 5) end test "different resources tracked independently" do MannaLedger.consume(:compute_units, 5) MannaLedger.consume(:api_calls_external, 3) assert MannaLedger.remaining(:compute_units) == 5 assert MannaLedger.remaining(:api_calls_external) == 2 end test "returns error when no provision loaded" do # Force a fresh cycle by enforcing boundary first MannaLedger.enforce_cycle_boundary() # Now no provision is loaded assert {:error, :no_provision} = MannaLedger.consume(:compute_units, 1) end end # ============================================================ # Remaining and available? # ============================================================ describe "remaining and available?" do setup do write_test_provision(compute_units: 20) MannaLedger.load_provision() :ok end test "remaining returns correct balance" do assert MannaLedger.remaining(:compute_units) == 20 MannaLedger.consume(:compute_units, 7) assert MannaLedger.remaining(:compute_units) == 13 end test "available? checks sufficiency" do assert MannaLedger.available?(:compute_units, 20) assert MannaLedger.available?(:compute_units, 1) refute MannaLedger.available?(:compute_units, 21) MannaLedger.consume(:compute_units, 15) assert MannaLedger.available?(:compute_units, 5) refute MannaLedger.available?(:compute_units, 6) end end # ============================================================ # Cycle boundary enforcement — al yoter mimenu ad boker # ============================================================ describe "enforce_cycle_boundary" do setup do write_test_provision(compute_units: 100, api_calls_external: 50) MannaLedger.load_provision() :ok end test "releases surplus and zeroes allocation" do MannaLedger.consume(:compute_units, 30) MannaLedger.consume(:api_calls_external, 10) {:ok, %{released: released}} = MannaLedger.enforce_cycle_boundary() # 100 allocated - 30 consumed = 70 released assert released.compute_units == 70 # 50 allocated - 10 consumed = 40 released assert released.api_calls_external == 40 end test "after enforcement, all resources are zero" do MannaLedger.consume(:compute_units, 50) MannaLedger.enforce_cycle_boundary() assert MannaLedger.remaining(:compute_units) == 0 assert MannaLedger.remaining(:api_calls_external) == 0 end test "after enforcement, consume returns :no_provision" do MannaLedger.enforce_cycle_boundary() assert {:error, :no_provision} = MannaLedger.consume(:compute_units, 1) end test "full consumption leaves nothing to release" do MannaLedger.consume(:compute_units, 100) MannaLedger.consume(:api_calls_external, 50) {:ok, %{released: released}} = MannaLedger.enforce_cycle_boundary() assert released.compute_units == 0 assert released.api_calls_external == 0 end test "enforcement records timestamp" do MannaLedger.enforce_cycle_boundary() ledger = MannaLedger.ledger() assert ledger.last_enforcement != nil end end # ============================================================ # Manna.enforce_anti_hoarding integration # ============================================================ describe "Manna.enforce_anti_hoarding integration" do setup do write_test_provision(compute_units: 50) MannaLedger.load_provision() :ok end test "Manna.enforce_anti_hoarding delegates to ledger" do MannaLedger.consume(:compute_units, 20) assert :ok = VavPipeline.Manna.enforce_anti_hoarding() # After enforcement, resources are zeroed assert MannaLedger.remaining(:compute_units) == 0 end end # ============================================================ # Ledger state # ============================================================ describe "ledger state" do test "tracks cycle count" do write_test_provision() initial = MannaLedger.ledger().cycle_count MannaLedger.load_provision() assert MannaLedger.ledger().cycle_count == initial + 1 # Force new cycle MannaLedger.enforce_cycle_boundary() # Can't easily test second load on same day since it returns :already_loaded # But cycle_count incremented on load end end end