killbill-aplcache

Add Payment retry tests for PluginException and refactor test

6/3/2012 10:16:45 PM

Details

diff --git a/api/src/main/java/com/ning/billing/config/PaymentConfig.java b/api/src/main/java/com/ning/billing/config/PaymentConfig.java
index f934906..7e8d5cc 100644
--- a/api/src/main/java/com/ning/billing/config/PaymentConfig.java
+++ b/api/src/main/java/com/ning/billing/config/PaymentConfig.java
@@ -29,22 +29,21 @@ public interface PaymentConfig extends NotificationConfig, KillbillConfig  {
     @Default("noop")
     public String getDefaultPaymentProvider();
 
-
     @Config("killbill.payment.retry.days")
     @Default("8,8,8")
     public List<Integer> getPaymentRetryDays();
 
     @Config("killbill.payment.failure.retry.start.sec")
     @Default("300")
-    public int getPaymentFailureRetryStart();
+    public int getPluginFailureRetryStart();
 
     @Config("killbill.payment.failure.retry.multiplier")
     @Default("2")
-    public int getPaymentFailureRetryMultiplier();
+    public int getPluginFailureRetryMultiplier();
 
     @Config("killbill.payment.failure.retry.max.attempts")
     @Default("8")
-    public int getPaymentFailureRetryMaxAttempts();
+    public int getPluginFailureRetryMaxAttempts();
 
 	@Override
     @Config("killbill.payment.engine.notifications.sleep")
diff --git a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
index 229850f..73151f8 100644
--- a/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
+++ b/payment/src/main/java/com/ning/billing/payment/core/PaymentProcessor.java
@@ -37,7 +37,6 @@ import com.ning.billing.ErrorCode;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.config.PaymentConfig;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoicePaymentApi;
 import com.ning.billing.payment.api.DefaultPayment;
@@ -70,7 +69,6 @@ public class PaymentProcessor extends ProcessorBase {
     private final InvoicePaymentApi invoicePaymentApi;
     private final FailedPaymentRetryServiceScheduler failedPaymentRetryService;
     private final PluginFailureRetryServiceScheduler pluginFailureRetryService;
-    private final PaymentConfig config;
     private final PaymentDao paymentDao;
     private final CallContextFactory factory;
     private final Clock clock;
@@ -87,7 +85,6 @@ public class PaymentProcessor extends ProcessorBase {
             final FailedPaymentRetryServiceScheduler failedPaymentRetryService,
             final PluginFailureRetryServiceScheduler pluginFailureRetryService,
             final PaymentDao paymentDao,
-            final PaymentConfig config,
             final Bus eventBus,
             final Clock clock,
             final GlobalLocker locker,
@@ -99,7 +96,6 @@ public class PaymentProcessor extends ProcessorBase {
         this.pluginFailureRetryService = pluginFailureRetryService;
         this.paymentDao = paymentDao;
         this.clock = clock;
-        this.config = config;
         this.factory = factory;
         this.paymentPluginDispatcher = new PluginDispatcher<Payment>(executor);
         this.voidPluginDispatcher = new PluginDispatcher<Void>(executor);
@@ -340,6 +336,7 @@ public class PaymentProcessor extends ProcessorBase {
             //
             paymentStatus = isInstantPayment ? PaymentStatus.PAYMENT_FAILURE_ABORTED : scheduleRetryOnPluginFailure(paymentInput.getId());  
             // STEPH message might need truncation to fit??
+            
             paymentDao.updateStatusForPaymentWithAttempt(paymentInput.getId(), paymentStatus, e.getMessage(), attemptInput.getId(), context);
 
             throw new PaymentApiException(ErrorCode.PAYMENT_CREATE_PAYMENT, account.getId(), e.getMessage());
@@ -354,8 +351,7 @@ public class PaymentProcessor extends ProcessorBase {
     
     private PaymentStatus scheduleRetryOnPluginFailure(UUID paymentId) {
         List<PaymentAttemptModelDao> allAttempts = paymentDao.getAttemptsForPayment(paymentId);
-        // STEPH unknown only?
-        final int retryAttempt = getNumberAttemptsInState(paymentId, allAttempts, PaymentStatus.UNKNOWN);
+        final int retryAttempt = getNumberAttemptsInState(paymentId, allAttempts, PaymentStatus.UNKNOWN, PaymentStatus.PLUGIN_FAILURE);
         final boolean isScheduledForRetry = pluginFailureRetryService.scheduleRetry(paymentId, retryAttempt);
         return isScheduledForRetry ? PaymentStatus.PLUGIN_FAILURE : PaymentStatus.PLUGIN_FAILURE_ABORTED; 
     }
diff --git a/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java b/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
index eee9302..c559720 100644
--- a/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
+++ b/payment/src/main/java/com/ning/billing/payment/glue/PaymentModule.java
@@ -51,7 +51,7 @@ public class PaymentModule extends AbstractModule {
     public final static String PLUGIN_EXECUTOR_NAMED = "PluginExecutor";
     
 
-    private final Properties props;
+    protected final Properties props;
 
     public PaymentModule() {
         this.props = System.getProperties();
diff --git a/payment/src/main/java/com/ning/billing/payment/retry/PluginFailureRetryService.java b/payment/src/main/java/com/ning/billing/payment/retry/PluginFailureRetryService.java
index f333170..d3e9c8a 100644
--- a/payment/src/main/java/com/ning/billing/payment/retry/PluginFailureRetryService.java
+++ b/payment/src/main/java/com/ning/billing/payment/retry/PluginFailureRetryService.java
@@ -92,13 +92,16 @@ public class PluginFailureRetryService extends BaseRetryService implements Retry
             return scheduleRetryFromTransaction(paymentId, nextRetryDate, transactionalDao);
         }
         
-        private DateTime getNextRetryDate(int retryAttempt) {
-            if (retryAttempt > config.getPaymentFailureRetryMaxAttempts()) {
+        private DateTime getNextRetryDate(final int retryAttempt) {
+            
+
+            if (retryAttempt > config.getPluginFailureRetryMaxAttempts()) {
                 return null;
             }
-            int nbSec = config.getPaymentFailureRetryStart();
-            while (--retryAttempt > 0) {
-                nbSec = nbSec * config.getPaymentFailureRetryMultiplier();
+            int nbSec = config.getPluginFailureRetryStart();
+            int remainingAttempts = retryAttempt;
+            while (--remainingAttempts > 0) {
+                nbSec = nbSec * config.getPluginFailureRetryMultiplier();
             }
             return clock.getUTCNow().plusSeconds(nbSec);
         }
diff --git a/payment/src/test/java/com/ning/billing/payment/glue/PaymentTestModuleWithMocks.java b/payment/src/test/java/com/ning/billing/payment/glue/PaymentTestModuleWithMocks.java
index c2fdc8c..15d0cf6 100644
--- a/payment/src/test/java/com/ning/billing/payment/glue/PaymentTestModuleWithMocks.java
+++ b/payment/src/test/java/com/ning/billing/payment/glue/PaymentTestModuleWithMocks.java
@@ -16,13 +16,16 @@
 
 package com.ning.billing.payment.glue;
 
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Properties;
+
 import org.apache.commons.collections.MapUtils;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.inject.Provider;
 import com.ning.billing.config.PaymentConfig;
-import com.ning.billing.junction.api.BillingApi;
-import com.ning.billing.mock.BrainDeadProxyFactory;
 import com.ning.billing.mock.glue.MockInvoiceModule;
 import com.ning.billing.mock.glue.MockNotificationQueueModule;
 import com.ning.billing.mock.glue.TestDbiModule;
@@ -34,7 +37,7 @@ import com.ning.billing.util.globallocker.GlobalLocker;
 import com.ning.billing.util.globallocker.MockGlobalLocker;
 import com.ning.billing.util.glue.BusModule;
 import com.ning.billing.util.glue.BusModule.BusType;
-import com.ning.billing.util.glue.GlobalLockerModule;
+
 import com.ning.billing.util.glue.TagStoreModule;
 
 public class PaymentTestModuleWithMocks extends PaymentModule {
@@ -48,7 +51,17 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
 	}
 	*/
 
+    private void loadSystemPropertiesFromClasspath(final String resource) {
+        final URL url = PaymentTestModuleWithMocks.class.getResource(resource);
+        assertNotNull(url);
 
+        try {
+            props.load(url.openStream());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
     public PaymentTestModuleWithMocks() {
         super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
                 "killbill.payment.engine.events.off", "false")));
@@ -66,6 +79,7 @@ public class PaymentTestModuleWithMocks extends PaymentModule {
 
     @Override
     protected void configure() {
+        loadSystemPropertiesFromClasspath("/payment.properties");
         super.configure();
         install(new BusModule(BusType.MEMORY));
         install(new MockNotificationQueueModule());
diff --git a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
index d083c9c..a32330d 100644
--- a/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
+++ b/payment/src/test/java/com/ning/billing/payment/TestRetryService.java
@@ -63,6 +63,7 @@ import com.ning.billing.payment.glue.PaymentTestModuleWithMocks;
 import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
 import com.ning.billing.payment.provider.PaymentProviderPluginRegistry;
 import com.ning.billing.payment.retry.FailedPaymentRetryService;
+import com.ning.billing.payment.retry.PluginFailureRetryService;
 import com.ning.billing.util.bus.Bus;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.glue.CallContextModule;
@@ -83,6 +84,8 @@ public class TestRetryService {
     private PaymentProviderPluginRegistry registry;
     @Inject
     private FailedPaymentRetryService retryService;
+    @Inject
+    private PluginFailureRetryService pluginRetryService;
 
     @Inject
     private ClockMock clock;
@@ -98,6 +101,9 @@ public class TestRetryService {
     @BeforeMethod(groups = "fast")
     public void setUp() throws Exception {
 
+        pluginRetryService.initialize(DefaultPaymentService.SERVICE_NAME);
+        pluginRetryService.start();
+        
         retryService.initialize(DefaultPaymentService.SERVICE_NAME);
         retryService.start();
         eventBus.start();
@@ -113,6 +119,7 @@ public class TestRetryService {
     @AfterMethod(groups = "fast")
     public void tearDown() throws Exception {
         retryService.stop();
+        pluginRetryService.stop();
         eventBus.stop();
     }
 
@@ -127,22 +134,38 @@ public class TestRetryService {
 
 
     @Test(groups = "fast")
+    public void testFailedPluginWithOneSuccessfulRetry() throws Exception {
+        testSchedulesRetryInternal(1, FailureType.PLUGIN_EXCEPTION);
+    }
+
+    @Test(groups = "fast")
+    public void testFailedPpluginWithLastRetrySuccess() throws Exception {
+        testSchedulesRetryInternal(paymentConfig.getPluginFailureRetryMaxAttempts(), FailureType.PLUGIN_EXCEPTION);
+    }
+
+    @Test(groups = "fast")
+    public void testAbortedPlugin() throws Exception {
+        testSchedulesRetryInternal(paymentConfig.getPluginFailureRetryMaxAttempts() + 1, FailureType.PLUGIN_EXCEPTION);
+    }
+
+
+    @Test(groups = "fast")
     public void testFailedPaymentWithOneSuccessfulRetry() throws Exception {
-        testSchedulesRetryInternal(1);
+        testSchedulesRetryInternal(1, FailureType.PAYMENT_FAILURE);
     }
 
     @Test(groups = "fast")
     public void testFailedPaymentWithLastRetrySuccess() throws Exception {
-        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size());
+        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size(), FailureType.PAYMENT_FAILURE);
     }
 
     @Test(groups = "fast")
     public void testAbortedPayment() throws Exception {
-        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size() + 1);
+        testSchedulesRetryInternal(paymentConfig.getPaymentRetryDays().size() + 1, FailureType.PAYMENT_FAILURE);
     }
 
 
-    private void testSchedulesRetryInternal(int maxTries) throws Exception {
+    private void testSchedulesRetryInternal(int maxTries, final FailureType failureType) throws Exception {
         
         final Account account = testHelper.createTestCreditCardAccount();
         final Invoice invoice = testHelper.createTestInvoice(account, clock.getUTCNow(), Currency.USD);
@@ -162,8 +185,7 @@ public class TestRetryService {
                                                        amount,
                                                        new BigDecimal("1.0"),
                                                        Currency.USD));
-
-        mockPaymentProviderPlugin.makeNextPaymentFailWithError();
+        setPaymentFailure(failureType);
         boolean failed = false;
         try {
             paymentProcessor.createPayment(account.getExternalKey(), invoice.getId(), amount, context, false);
@@ -172,20 +194,15 @@ public class TestRetryService {
         }
         assertTrue(failed);
 
-
-        //int maxTries = paymentAborted ? paymentConfig.getPaymentRetryDays().size() + 1 : 1;
-        
         for (int curFailure = 0; curFailure < maxTries; curFailure++) {
 
             if (curFailure < maxTries - 1) {
-                mockPaymentProviderPlugin.makeNextPaymentFailWithError();
+                setPaymentFailure(failureType);                
             }
 
-            if (curFailure < paymentConfig.getPaymentRetryDays().size()) {
+            if (curFailure < getMaxRetrySizeForFailureType(failureType)) {
                 
-                int nbDays = paymentConfig.getPaymentRetryDays().get(curFailure);            
-                clock.addDays(nbDays + 1);
-
+                moveClockForFailureType(failureType, curFailure);
                 try {
                     await().atMost(3, SECONDS).until(new Callable<Boolean>() {
                         @Override
@@ -201,12 +218,11 @@ public class TestRetryService {
                 }
             }
         }
-
-
         Payment payment = getPaymentForInvoice(invoice.getId());
         List<PaymentAttempt> attempts = payment.getAttempts();
         
-        int expectedAttempts = maxTries < paymentConfig.getPaymentRetryDays().size() ? maxTries + 1 : paymentConfig.getPaymentRetryDays().size() + 1;
+        int expectedAttempts = maxTries < getMaxRetrySizeForFailureType(failureType) ?
+                maxTries + 1 : getMaxRetrySizeForFailureType(failureType) + 1;
         assertEquals(attempts.size(), expectedAttempts);
         Collections.sort(attempts, new Comparator<PaymentAttempt>() {
             @Override
@@ -218,14 +234,54 @@ public class TestRetryService {
         for (int i = 0; i < attempts.size(); i++) {
             PaymentAttempt cur = attempts.get(i);
             if (i < attempts.size() - 1) {
-                assertEquals(cur.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE);
-            } else if (maxTries <= paymentConfig.getPaymentRetryDays().size()) {
+                if (failureType == FailureType.PAYMENT_FAILURE) {
+                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE);
+                } else {
+                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PLUGIN_FAILURE);                    
+                }
+            } else if (maxTries <= getMaxRetrySizeForFailureType(failureType)) {
                 assertEquals(cur.getPaymentStatus(), PaymentStatus.SUCCESS);
                 assertEquals(payment.getPaymentStatus(), PaymentStatus.SUCCESS);
             } else {
-                assertEquals(cur.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);      
-                assertEquals(payment.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);                
+                if (failureType == FailureType.PAYMENT_FAILURE) {
+                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);      
+                    assertEquals(payment.getPaymentStatus(), PaymentStatus.PAYMENT_FAILURE_ABORTED);
+                } else {
+                    assertEquals(cur.getPaymentStatus(), PaymentStatus.PLUGIN_FAILURE_ABORTED);      
+                    assertEquals(payment.getPaymentStatus(), PaymentStatus.PLUGIN_FAILURE_ABORTED);
+                }
             }
         }
     }
+    
+    private enum FailureType {
+        PLUGIN_EXCEPTION,
+        PAYMENT_FAILURE
+    }
+    
+    private void setPaymentFailure(FailureType failureType) {
+        if (failureType == FailureType.PAYMENT_FAILURE) {
+            mockPaymentProviderPlugin.makeNextPaymentFailWithError();
+        } else if (failureType == FailureType.PLUGIN_EXCEPTION) {
+            mockPaymentProviderPlugin.makeNextPaymentFailWithException();            
+        }
+    }
+    
+    private void moveClockForFailureType(FailureType failureType, int curFailure) {
+        if (failureType == FailureType.PAYMENT_FAILURE) {
+            int nbDays = paymentConfig.getPaymentRetryDays().get(curFailure);            
+            clock.addDays(nbDays + 1);
+        } else {
+            clock.addDays(1);
+        }
+    }
+    
+    private int getMaxRetrySizeForFailureType(FailureType failureType) {
+        if (failureType == FailureType.PAYMENT_FAILURE) {        
+            return paymentConfig.getPaymentRetryDays().size();
+        } else {
+            return paymentConfig.getPluginFailureRetryMaxAttempts();
+        }
+    }
+     
 }
diff --git a/payment/src/test/resources/payment.properties b/payment/src/test/resources/payment.properties
new file mode 100644
index 0000000..8a11d90
--- /dev/null
+++ b/payment/src/test/resources/payment.properties
@@ -0,0 +1,4 @@
+killbill.payment.failure.retry.start.sec=3600
+killbill.payment.failure.retry.multiplier=1
+killbill.payment.failure.retry.max.attempts=3
+