#!/usr/bin/env elixir # Test script to verify the WithClauseError fix in TransactionProcessor # This simulates the error scenario and confirms the fix Mix.install([ {:decimal, "~> 2.0"}, {:jason, "~> 1.4"} ]) defmodule WithClauseFixTest do @moduledoc """ Test module to verify the WithClauseError fix in TransactionProcessor. """ # Mock ISOMsg to simulate the actual behavior defmodule MockISOMsg do defstruct mti: nil, fields: %{} def get(%MockISOMsg{fields: fields}, field_number) do Map.get(fields, field_number) # Returns value directly or nil (no {:ok, value}) end def new(mti) do %MockISOMsg{mti: mti, fields: %{}} end def set(%MockISOMsg{} = msg, field, value) do %{msg | fields: Map.put(msg.fields, field, value)} end end # Helper function that fixes the WithClauseError issue defp get_required_field(%MockISOMsg{} = message, field_number) do case MockISOMsg.get(message, field_number) do nil -> {:error, {:missing_field, field_number}} value -> {:ok, value} end end # Old broken version (would cause WithClauseError) def test_broken_version do IO.puts("šŸ”“ Testing BROKEN version (direct ISOMsg.get in with)...") message = MockISOMsg.new("0400") |> MockISOMsg.set(4, "100000") |> MockISOMsg.set(11, "12345671") # This is the value from the error |> MockISOMsg.set(41, "TERM001") |> MockISOMsg.set(42, "MERCHANT001") try do # This simulates the old broken code result = with {:ok, amount} <- MockISOMsg.get(message, 4), # Returns "100000" directly {:ok, s_tid_stan} <- MockISOMsg.get(message, 11), # Returns "12345671" directly {:ok, s_tid} <- MockISOMsg.get(message, 41), # Returns "TERM001" directly {:ok, s_mid} <- MockISOMsg.get(message, 42) do # Returns "MERCHANT001" directly {:ok, %{amount: amount, stan: s_tid_stan, tid: s_tid, mid: s_mid}} else {:error, reason} -> {:error, reason} end IO.puts(" āŒ UNEXPECTED: No error occurred - #{inspect(result)}") rescue error in WithClauseError -> IO.puts(" āœ… EXPECTED: WithClauseError occurred!") IO.puts(" Term that didn't match: #{inspect(error.term)}") IO.puts(" This is exactly the error from the logs!") end end # New fixed version (uses helper function) def test_fixed_version do IO.puts("\n🟢 Testing FIXED version (using get_required_field helper)...") message = MockISOMsg.new("0400") |> MockISOMsg.set(4, "100000") |> MockISOMsg.set(11, "12345671") # This is the value from the error |> MockISOMsg.set(41, "TERM001") |> MockISOMsg.set(42, "MERCHANT001") try do # This simulates the new fixed code result = with {:ok, amount} <- get_required_field(message, 4), {:ok, s_tid_stan} <- get_required_field(message, 11), {:ok, s_tid} <- get_required_field(message, 41), {:ok, s_mid} <- get_required_field(message, 42) do {:ok, %{amount: amount, stan: s_tid_stan, tid: s_tid, mid: s_mid}} else {:error, reason} -> {:error, reason} end case result do {:ok, data} -> IO.puts(" āœ… SUCCESS: Transaction data extracted successfully!") IO.puts(" Amount: #{data.amount}") IO.puts(" STAN: #{data.stan}") IO.puts(" Terminal ID: #{data.tid}") IO.puts(" Merchant ID: #{data.mid}") {:error, reason} -> IO.puts(" āŒ ERROR: #{inspect(reason)}") end rescue exception -> IO.puts(" āŒ EXCEPTION: #{inspect(exception)}") end end # Test missing field scenario def test_missing_field do IO.puts("\n🟔 Testing missing field scenario...") message = MockISOMsg.new("0400") |> MockISOMsg.set(4, "100000") # Missing field 11 (STAN) |> MockISOMsg.set(41, "TERM001") |> MockISOMsg.set(42, "MERCHANT001") result = with {:ok, amount} <- get_required_field(message, 4), {:ok, s_tid_stan} <- get_required_field(message, 11), # This will fail {:ok, s_tid} <- get_required_field(message, 41), {:ok, s_mid} <- get_required_field(message, 42) do {:ok, %{amount: amount, stan: s_tid_stan, tid: s_tid, mid: s_mid}} else {:error, reason} -> {:error, reason} end case result do {:ok, _data} -> IO.puts(" āŒ UNEXPECTED: Should have failed due to missing field") {:error, {:missing_field, field_number}} -> IO.puts(" āœ… SUCCESS: Correctly detected missing field #{field_number}") {:error, reason} -> IO.puts(" ā“ OTHER ERROR: #{inspect(reason)}") end end def run_tests do IO.puts("=" <> String.duplicate("=", 60)) IO.puts("Testing WithClauseError Fix in TransactionProcessor") IO.puts("=" <> String.duplicate("=", 60)) test_broken_version() test_fixed_version() test_missing_field() IO.puts("\n" <> "=" <> String.duplicate("=", 60)) IO.puts("EXPLANATION OF THE FIX") IO.puts("=" <> String.duplicate("=", 60)) IO.puts("THE PROBLEM:") IO.puts(" • ISOMsg.get/2 returns values directly: get(msg, 11) -> \"12345671\"") IO.puts(" • with statements expect {:ok, value} tuples") IO.puts(" • When with gets \"12345671\" instead of {:ok, \"12345671\"}") IO.puts(" • Result: WithClauseError{term: \"12345671\"}") IO.puts("") IO.puts("THE SOLUTION:") IO.puts(" • Added get_required_field/2 helper function") IO.puts(" • Converts ISOMsg.get/2 results to proper tuple format") IO.puts(" • Returns {:ok, value} for existing fields") IO.puts(" • Returns {:error, {:missing_field, field_number}} for missing fields") IO.puts("") IO.puts("NOW THE FLOW WORKS:") IO.puts(" 1. YSP Reversal (0400) message arrives") IO.puts(" 2. TransactionProcessor.process_reversal/2 called") IO.puts(" 3. get_required_field(msg, 11) -> {:ok, \"12345671\"}") IO.puts(" 4. with statement matches {:ok, value} pattern āœ…") IO.puts(" 5. Transaction processing continues successfully āœ…") end end # Run the tests WithClauseFixTest.run_tests()