//+------------------------------------------------------------------+
//|                                         TestRetryExecutor.mq5    |
//+------------------------------------------------------------------+

#include <Circuit_Breaker\RetryExecutor.mqh>

#define ASSERT(condition, msg) \
   if(!(condition)) { Print("FAIL: ", msg); } else { Print("PASS: ", msg); }

//--- Expose ComputeDelay for testing via a subclass
class CRetryExecutorTest : public CRetryExecutor
  {
public:
                     CRetryExecutorTest(const SRetryConfig &cfg) : CRetryExecutor(cfg) {}
                    ~CRetryExecutorTest(void) {}
   uint              TestComputeDelay(int attempt) const { return(ComputeDelay(attempt)); }
   bool              TestIsRetryable(uint code)    const { return(IsRetryable(code));     }
  };

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart(void)
  {
   SRetryConfig cfg = DefaultRetryConfig();
   cfg.m_base_delay_ms = 200;
   cfg.m_max_delay_ms  = 5000;

   CRetryExecutorTest t(cfg);

//--- Test 1: Exponential backoff sequence
   ASSERT(t.TestComputeDelay(1) == 200,  "Delay attempt 1 == 200ms");
   ASSERT(t.TestComputeDelay(2) == 400,  "Delay attempt 2 == 400ms");
   ASSERT(t.TestComputeDelay(3) == 800,  "Delay attempt 3 == 800ms");
   ASSERT(t.TestComputeDelay(4) == 1600, "Delay attempt 4 == 1600ms");
   ASSERT(t.TestComputeDelay(5) == 3200, "Delay attempt 5 == 3200ms");

//--- Test 2: Ceiling cap applied
   ASSERT(t.TestComputeDelay(6) == 5000, "Delay attempt 6 capped at 5000ms");
   ASSERT(t.TestComputeDelay(7) == 5000, "Delay attempt 7 capped at 5000ms");

//--- Test 3: Retryable codes correctly classified
   ASSERT(t.TestIsRetryable(10004), "10004 REQUOTE is retryable");
   ASSERT(t.TestIsRetryable(10006), "10006 CONNECTION is retryable");
   ASSERT(t.TestIsRetryable(10010), "10010 TIMEOUT is retryable");
   ASSERT(t.TestIsRetryable(10020), "10020 PRICE_CHANGED is retryable");
   ASSERT(t.TestIsRetryable(10021), "10021 PRICE_OFF is retryable");
   ASSERT(t.TestIsRetryable(10030), "10030 TOO_MANY_REQUESTS is retryable");

//--- Test 4: Non-retryable codes correctly rejected
   ASSERT(!t.TestIsRetryable(10009), "10009 DONE is not retryable");
   ASSERT(!t.TestIsRetryable(10016), "10016 INVALID_STOPS is not retryable");
   ASSERT(!t.TestIsRetryable(10019), "10019 NO_MONEY is not retryable");
   ASSERT(!t.TestIsRetryable(10018), "10018 MARKET_CLOSED is not retryable");
   ASSERT(!t.TestIsRetryable(0),     "0 (no error) is not retryable");

   Print("=== Retry executor unit tests complete ===");
  }
//+------------------------------------------------------------------+