defmodule TestEngine do @moduledoc """ Core test execution engine for running ISO8583 transaction test cases. """ alias ISO8583.PacketBuilder alias ISO8583.Parser alias Validator alias Reporter @doc """ Executes a single test case. ## Parameters - test_case: Map containing test case data - config: Configuration map with host, port, timeout ## Returns Test result map with status, actual values, and validation details """ def run_test(test_case, config) do test_id = Map.get(test_case, :test_id, "UNKNOWN") IO.puts("\n▶ Running test: #{test_id} - #{test_case.scenario}") result = %{ test_id: test_id, scenario: test_case.scenario, description: Map.get(test_case, :description, ""), status: :running, start_time: DateTime.utc_now(), error: nil } try do # Step 1: Build packet packet = PacketBuilder.build(test_case) IO.puts(" ✓ Packet built (#{byte_size(packet)} bytes)") # Step 2: Send to jPOS server case send_transaction(packet, config) do {:ok, response} -> IO.puts(" ✓ Response received (#{byte_size(response)} bytes)") # Step 3: Parse response case Parser.parse(response) do {:ok, parsed} -> IO.puts(" ✓ Response parsed - MTI: #{parsed.mti}") # Step 4: Validate response validation = Validator.validate(test_case, parsed) # Step 5: Determine test result result = result |> Map.put(:end_time, DateTime.utc_now()) |> Map.put(:status, validation.overall_status) |> Map.put(:actual_mti, parsed.mti) |> Map.put(:actual_response_code, Parser.get_response_code(parsed)) |> Map.put(:expected_mti, test_case.expected_mti_response) |> Map.put(:expected_response_code, test_case.expected_response_code) |> Map.put(:validation, validation) |> Map.put(:parsed_response, parsed) |> Map.put(:packet_hex, Base.encode16(packet)) |> Map.put(:response_hex, Base.encode16(response)) print_test_result(result) result {:error, parse_error} -> IO.puts(" ✗ Parse failed: #{inspect(parse_error)}") result |> Map.put(:status, :error) |> Map.put(:error, "Parse error: #{inspect(parse_error)}") |> Map.put(:end_time, DateTime.utc_now()) end {:error, send_error} -> IO.puts(" ✗ Send failed: #{inspect(send_error)}") result |> Map.put(:status, :error) |> Map.put(:error, "Send error: #{inspect(send_error)}") |> Map.put(:end_time, DateTime.utc_now()) end rescue e -> IO.puts(" ✗ Exception: #{Exception.message(e)}") result |> Map.put(:status, :error) |> Map.put(:error, "Exception: #{Exception.message(e)}") |> Map.put(:end_time, DateTime.utc_now()) end end @doc """ Sends a transaction packet to jPOS server and receives response. """ defp send_transaction(packet, config) do host = String.to_charlist(config.host) port = config.port timeout = Map.get(config, :timeout, 15_000) case :gen_tcp.connect(host, port, [:binary, active: false], 5000) do {:ok, socket} -> case :gen_tcp.send(socket, packet) do :ok -> result = :gen_tcp.recv(socket, 0, timeout) :gen_tcp.close(socket) result {:error, reason} -> :gen_tcp.close(socket) {:error, {:send_failed, reason}} end {:error, reason} -> {:error, {:connection_failed, reason}} end end @doc """ Prints a formatted test result to console. """ defp print_test_result(result) do case result.status do :pass -> IO.puts(" ✓ PASS - Response: #{result.actual_response_code}") :fail -> IO.puts(" ✗ FAIL") IO.puts(" Expected: MTI=#{result.expected_mti}, RC=#{result.expected_response_code}") IO.puts(" Actual: MTI=#{result.actual_mti}, RC=#{result.actual_response_code}") if result.validation.failures != [] do IO.puts(" Failures:") Enum.each(result.validation.failures, fn failure -> IO.puts(" - #{failure}") end) end :error -> IO.puts(" ✗ ERROR: #{result.error}") end end @doc """ Runs multiple test cases in sequence. """ def run_test_suite(test_cases, config) do IO.puts("\n" <> String.duplicate("=", 70)) IO.puts("Starting Test Suite: #{length(test_cases)} test cases") IO.puts(String.duplicate("=", 70)) start_time = DateTime.utc_now() results = Enum.map(test_cases, fn test_case -> run_test(test_case, config) end) end_time = DateTime.utc_now() duration = DateTime.diff(end_time, start_time, :millisecond) # Generate summary summary = %{ total: length(results), passed: Enum.count(results, &(&1.status == :pass)), failed: Enum.count(results, &(&1.status == :fail)), errors: Enum.count(results, &(&1.status == :error)), duration_ms: duration, start_time: start_time, end_time: end_time } IO.puts("\n" <> String.duplicate("=", 70)) IO.puts("Test Suite Complete") IO.puts(String.duplicate("=", 70)) IO.puts("Total: #{summary.total}") IO.puts("Passed: #{summary.passed} ✓") IO.puts("Failed: #{summary.failed} ✗") IO.puts("Errors: #{summary.errors} ⚠") IO.puts("Duration: #{duration}ms") IO.puts(String.duplicate("=", 70)) {results, summary} end end