killbill-uncached

Clock refactoring complete

5/9/2012 2:27:26 AM

Changes

entitlement/src/test/java/com/ning/billing/entitlement/api/ApiTestListener.java 233(+0 -233)

entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserToken.java 46(+0 -46)

jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleTimelineResource.java 50(+0 -50)

Details

diff --git a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvent.java b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvent.java
index 8dc3312..2745c33 100644
--- a/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvent.java
+++ b/api/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionEvent.java
@@ -65,4 +65,5 @@ public interface SubscriptionEvent extends BusEvent {
     Integer getRemainingEventsForUserOperation();
     
     Long getTotalOrdering();
+    
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
new file mode 100644
index 0000000..2dfe2ee
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+//package com.ning.billing.beatrix.integration.overdue;
+//
+//import static org.testng.Assert.assertNotNull;
+//
+//import java.io.ByteArrayInputStream;
+//import java.io.InputStream;
+//
+//import org.joda.time.DateTime;
+//import org.joda.time.Interval;
+//
+//import com.google.inject.Inject;
+//import com.ning.billing.account.api.Account;
+//import com.ning.billing.beatrix.integration.TestIntegrationBase;
+//import com.ning.billing.catalog.api.BillingPeriod;
+//import com.ning.billing.catalog.api.Duration;
+//import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+//import com.ning.billing.catalog.api.PriceListSet;
+//import com.ning.billing.catalog.api.ProductCategory;
+//import com.ning.billing.entitlement.api.user.SubscriptionBundle;
+//import com.ning.billing.entitlement.api.user.SubscriptionData;
+//import com.ning.billing.junction.api.BlockingApi;
+//import com.ning.billing.overdue.config.OverdueConfig;
+//import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
+//import com.ning.billing.util.clock.ClockMock;
+//import com.ning.billing.util.config.XMLLoader;
+//
+//public class TestOverdueIntegration extends TestIntegrationBase {
+//    private final String configXml =  
+//            "<overdueConfig>" +
+//                    "   <bundleOverdueStates>" +
+//                    "       <state name=\"OD1\">" +
+//                    "           <condition>" +
+//                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+//                    "                   <unit>MONTHS</unit><number>1</number>" +
+//                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+//                    "           </condition>" +
+//                    "           <externalMessage>Reached OD1</externalMessage>" +
+//                    "           <blockChanges>true</blockChanges>" +
+//                    "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+//                    "       </state>" +
+//                    "       <state name=\"OD2\">" +
+//                    "           <condition>" +
+//                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+//                    "                   <unit>MONTHS</unit><number>2</number>" +
+//                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+//                    "           </condition>" +
+//                    "           <externalMessage>Reached OD1</externalMessage>" +
+//                    "           <blockChanges>true</blockChanges>" +
+//                    "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+//                    "       </state>" +
+//                    "   </bundleOverdueStates>" +
+//                    "</overdueConfig>";
+//    private OverdueConfig config; 
+//    
+//    @Inject
+//    private ClockMock clock;
+//    
+//    @Inject
+//    private MockPaymentProviderPlugin paymentPlugin;
+//    
+//    @Inject
+//    private BlockingApi blockingApi;
+//    
+//    private Account account;
+//    private SubscriptionBundle bundle;
+//    private String productName;
+//    private BillingPeriod term;
+//    private String planSetName;
+//
+//    long twoWeeks = new Interval(clock.getUTCNow(), clock.getUTCNow().plusWeeks(2)).toDurationMillis();
+//    long fourWeeks = new Interval(clock.getUTCNow(), clock.getUTCNow().plusWeeks(4)).toDurationMillis();
+//    
+//    //@BeforeMethod
+//    public void setup() throws Exception {
+//        InputStream is = new ByteArrayInputStream(configXml.getBytes());
+//        config = XMLLoader.getObjectFromStreamNoValidation(is,  OverdueConfig.class);
+//        Account account = accountUserApi.createAccount(getAccountData(25), null, null, context);
+//        assertNotNull(account);
+//
+//        bundle = entitlementUserApi.createBundleForAccount(account.getId(), "whatever", context);
+//
+//        productName = "Shotgun";
+//        term = BillingPeriod.MONTHLY;
+//        planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
+//        
+//        // create account
+//        // set mock payments to fail
+//        // reset clock
+//        // configure basic OD state rules for 2 states OD1 1-2month, OD2 2-3 month
+//    }
+//    
+//    //@AfterMethod
+//    public void cleanup(){
+//        // Clear databases
+//    }
+//    
+//    public void testBasicOverdueState() throws Exception {
+//        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+//        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+//        
+//        
+//        // set next invoice to fail and create network 
+//        paymentPlugin.makeNextInvoiceFail();
+//        SubscriptionData baseSubscription = subscriptionDataFromSubscription(entitlementUserApi.createSubscription(bundle.getId(),
+//                new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context));
+//        assertNotNull(baseSubscription);
+//
+//
+//       // advance time 2weeks
+//        clock.addDeltaFromReality(twoWeeks);
+//        
+//       // should still be in clear state
+//       blockingApi.getBlockingStateFor(bundle);
+//        
+//       // set next invoice to fail and advance time 1 month
+//       clock.addDeltaFromReality(fourWeeks);
+//       
+//       // should now be in OD1 state
+//       // set next invoice to fail and advance time 1 month
+//       // should now be in OD2 state
+//
+//        
+//    }
+//}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
index f7db5d1..5f442c6 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegration.java
@@ -32,7 +32,7 @@ import org.testng.annotations.Test;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountApiException;
 import com.ning.billing.account.api.AccountData;
-import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.PhaseType;
@@ -49,24 +49,28 @@ import com.ning.billing.invoice.api.Invoice;
 public class TestIntegration extends TestIntegrationBase {
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInPast() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayInPast");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 31, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayPresent() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayPresent");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 1, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayAlignedWithTrial() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayAlignedWithTrial");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 2, false);
     }
 
     @Test(groups = "slow", enabled = true)
     public void testBasePlanCompleteWithBillingDayInFuture() throws Exception {
+        log.info("Starting testBasePlanCompleteWithBillingDayInFuture");
         DateTime startDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         testBasePlanComplete(startDate, 3, true);
     }
@@ -91,9 +95,12 @@ public class TestIntegration extends TestIntegrationBase {
         }
     }
 
-    @Test(groups = "slow", enabled = true)
+    // STEPH set to disabled until test written properly and fixed
+    @Test(groups = "slow", enabled = false)
     public void testRepairChangeBPWithAddonIncluded() throws Exception {
         
+        log.info("Starting testRepairChangeBPWithAddonIncluded");
+        
         DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
         clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
         
@@ -142,10 +149,13 @@ public class TestIntegration extends TestIntegrationBase {
         busHandler.pushExpectedEvent(NextEvent.PAYMENT);
         clock.addDeltaFromReality(it.toDurationMillis());
         assertTrue(busHandler.isCompleted(DELAY));
+        
+        assertListenerStatus();
     }
    
     @Test(groups = {"slow"})
     public void testRepairForInvoicing() throws AccountApiException, EntitlementUserApiException {
+
         log.info("Starting testRepairForInvoicing");
 
         Account account = accountUserApi.createAccount(getAccountData(1), null, null, context);
@@ -178,6 +188,8 @@ public class TestIntegration extends TestIntegrationBase {
     @Test(groups = "slow", enabled = false)
     public void testWithRecreatePlan() throws Exception {
 
+        log.info("Starting testWithRecreatePlan");
+
         DateTime initialDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         int billingDay = 2;
 
@@ -244,7 +256,7 @@ public class TestIntegration extends TestIntegrationBase {
         subscription.recreate(new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), endDate, context);
         assertTrue(busHandler.isCompleted(DELAY));
 
-
+        assertListenerStatus();
     }
      
     private void testBasePlanComplete(DateTime initialCreationDate, int billingDay,
@@ -440,12 +452,18 @@ public class TestIntegration extends TestIntegrationBase {
 
         // The invoice system is still working to verify there is nothing to do
         Thread.sleep(DELAY);
+        
+        assertListenerStatus();
+        
         log.info("TEST PASSED !");
     }
 
 
     @Test(groups = "slow")
     public void testForMultipleRecurringPhases() throws AccountApiException, EntitlementUserApiException, InterruptedException {
+
+        log.info("Starting testForMultipleRecurringPhases");
+        
         DateTime initialCreationDate = new DateTime(2012, 2, 1, 0, 3, 42, 0);
         clock.setDeltaFromReality(initialCreationDate.getMillis() - clock.getUTCNow().getMillis());
 
@@ -505,5 +523,7 @@ public class TestIntegration extends TestIntegrationBase {
         invoices = invoiceUserApi.getInvoicesByAccount(accountId);
         assertNotNull(invoices);
         assertEquals(invoices.size(),14);
+
+        assertListenerStatus();
     }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index a70bce9..579f47c 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -36,6 +36,7 @@ import org.skife.jdbi.v2.TransactionCallback;
 import org.skife.jdbi.v2.TransactionStatus;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -45,7 +46,10 @@ import com.google.inject.Inject;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountService;
 import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestListenerStatus;
 import com.ning.billing.beatrix.lifecycle.Lifecycle;
+import com.ning.billing.catalog.DefaultCatalogService;
 import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.EntitlementService;
@@ -54,6 +58,7 @@ import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceService;
@@ -67,7 +72,7 @@ import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.ClockMock;
 
-public class TestIntegrationBase implements TestFailure {
+public class TestIntegrationBase implements TestListenerStatus {
 
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
     protected static final int ROUNDING_METHOD = InvoicingConfiguration.getRoundingMode();
@@ -80,7 +85,7 @@ public class TestIntegrationBase implements TestFailure {
     protected static final Logger log = LoggerFactory.getLogger(TestIntegration.class);
     protected static long AT_LEAST_ONE_MONTH_MS =  31L * 24L * 3600L * 1000L;
 
-    protected static final long DELAY = 5000;
+    protected static final long DELAY = 10000;
 
     @Inject
     protected IDBI dbi;
@@ -119,28 +124,29 @@ public class TestIntegrationBase implements TestFailure {
     @Inject
     protected AccountUserApi accountUserApi;
 
-    protected TestBusHandler busHandler;
+    protected TestApiListener busHandler;
 
     
-    private boolean currentTestStatusSuccess;
-    private String currentTestFailedMsg;
+    private boolean isListenerFailed;
+    private String listenerFailedMsg;
     
     @Override
     public void failed(String msg) {
-        currentTestStatusSuccess = false;
-        currentTestFailedMsg = msg;
+        isListenerFailed = true;
+        listenerFailedMsg = msg;
     }
 
     @Override
-    public void reset() {
-        currentTestStatusSuccess = true;
-        currentTestFailedMsg = null;
+    public void resetTestListenerStatus() {
+        isListenerFailed = false;
+        listenerFailedMsg = null;
     }
 
-    protected void assertFailureFromBusHandler() {
-        if (!currentTestStatusSuccess) {
-            log.error(currentTestFailedMsg);
-            fail();
+    
+    protected void assertListenerStatus() {
+        if (isListenerFailed) {
+            log.error(listenerFailedMsg);
+            Assert.fail(listenerFailedMsg);
         }
     }
 
@@ -163,73 +169,50 @@ public class TestIntegrationBase implements TestFailure {
         helper.initDb(junctionDb);
     }
 
+  
     @BeforeClass(groups = "slow")
     public void setup() throws Exception{
 
         setupMySQL();
         
-        cleanupData();
-        
         context = new DefaultCallContextFactory(clock).createCallContext("Integration Test", CallOrigin.TEST, UserType.TEST);
-
-        /**
-         * Initialize lifecyle for subset of services
-         */
-        busHandler = new TestBusHandler(this);
-        lifecycle.fireStartupSequencePriorEventRegistration();
-        busService.getBus().register(busHandler);
-        lifecycle.fireStartupSequencePostEventRegistration();
+        busHandler = new TestApiListener(this);
+        
     }
 
     @AfterClass(groups = "slow")
     public void tearDown() throws Exception {
-        lifecycle.fireShutdownSequencePriorEventUnRegistration();
-        busService.getBus().unregister(busHandler);
-        lifecycle.fireShutdownSequencePostEventUnRegistration();
         helper.stopMysql();
     }
 
 
     @BeforeMethod(groups = "slow")
-    public void setupTest() {
+    public void setupTest() throws Exception {
 
         log.warn("\n");
         log.warn("RESET TEST FRAMEWORK\n\n");
-        cleanupData();
-        busHandler.reset();
+        
+        // Pre test cleanup
+        helper.cleanupAllTables();
+
         clock.resetDeltaFromReality();
-        reset();
+        resetTestListenerStatus();
+        
+        // Start services
+        lifecycle.fireStartupSequencePriorEventRegistration();
+        busService.getBus().register(busHandler);
+        lifecycle.fireStartupSequencePostEventRegistration();
     }
 
     @AfterMethod(groups = "slow")
-    public void cleanupTest() {
+    public void cleanupTest() throws Exception {
+        lifecycle.fireShutdownSequencePriorEventUnRegistration();
+        busService.getBus().unregister(busHandler);
+        lifecycle.fireShutdownSequencePostEventUnRegistration();
+
         log.warn("DONE WITH TEST\n");
     }
     
-    protected void cleanupData() {
-        dbi.inTransaction(new TransactionCallback<Void>() {
-            @Override
-            public Void inTransaction(Handle h, TransactionStatus status)
-                    throws Exception {
-                h.execute("truncate table accounts");
-                h.execute("truncate table entitlement_events");
-                h.execute("truncate table subscriptions");
-                h.execute("truncate table bundles");
-                h.execute("truncate table notifications");
-                h.execute("truncate table claimed_notifications");
-                h.execute("truncate table invoices");
-                h.execute("truncate table fixed_invoice_items");
-                h.execute("truncate table recurring_invoice_items");
-                h.execute("truncate table tag_definitions");
-                h.execute("truncate table tags");
-                h.execute("truncate table custom_fields");
-                h.execute("truncate table invoice_payments");
-                h.execute("truncate table payment_attempts");
-                h.execute("truncate table payments");
-                return null;
-            }
-        });
-    }
 
     protected void verifyTestResult(UUID accountId, UUID subscriptionId,
                                   DateTime startDate, DateTime endDate,
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
index 6490640..c733097 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestRepairIntegration.java
@@ -32,7 +32,7 @@ import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
 import com.ning.billing.account.api.Account;
-import com.ning.billing.beatrix.integration.TestBusHandler.NextEvent;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
@@ -56,11 +56,13 @@ public class TestRepairIntegration extends TestIntegrationBase {
     
     @Test(groups={"slow"}, enabled=true)
     public void testRepairChangeBPWithAddonIncludedIntrial() throws Exception {
+        log.info("Starting testRepairChangeBPWithAddonIncludedIntrial");
         testRepairChangeBPWithAddonIncluded(true);
     }
     
     @Test(groups={"slow"}, enabled=true)
     public void testRepairChangeBPWithAddonIncludedOutOfTrial() throws Exception {
+        log.info("Starting testRepairChangeBPWithAddonIncludedOutOfTrial");
         testRepairChangeBPWithAddonIncluded(false);
     }
     
@@ -168,7 +170,7 @@ public class TestRepairIntegration extends TestIntegrationBase {
             assertEquals(newBaseSubscription.getAllTransitions().size(), 3);
             assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
 
-            assertFailureFromBusHandler();
+            assertListenerStatus();
         }
      }
     
@@ -327,6 +329,4 @@ public class TestRepairIntegration extends TestIntegrationBase {
             }
         });
     }
-    
-
 }
diff --git a/bin/clean-and-install b/bin/clean-and-install
new file mode 100755
index 0000000..c6c3ac4
--- /dev/null
+++ b/bin/clean-and-install
@@ -0,0 +1,22 @@
+#! /usr/bin/env bash
+
+###################################################################################
+#                                                                                 #
+#                   Copyright 2010-2011 Ning, Inc.                                #
+#                                                                                 #
+#      Ning licenses this file to you under the Apache License, version 2.0       #
+#      (the "License"); you may not use this file except in compliance with the   #
+#      License.  You may obtain a copy of the License at:                         #
+#                                                                                 #
+#          http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                                 #
+#      Unless required by applicable law or agreed to in writing, software        #
+#      distributed under the License is distributed on an "AS IS" BASIS, WITHOUT  #
+#      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the  #
+#      License for the specific language governing permissions and limitations    #
+#      under the License.                                                         #
+#                                                                                 #
+###################################################################################
+
+bin/db-helper -a clean -d killbill; 
+mvn -Dcom.ning.billing.dbi.test.useLocalDb=true clean install 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
index ea310f9..3cf74ec 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultSubscriptionEvent.java
@@ -348,5 +348,24 @@ public class DefaultSubscriptionEvent implements SubscriptionEvent {
             return false;
         return true;
     }
+
+    @Override
+    public String toString() {
+        return "DefaultSubscriptionEvent [transitionType=" + transitionType
+                + ", effectiveTransitionTime=" + effectiveTransitionTime        
+                + ", totalOrdering=" + totalOrdering
+                + ", subscriptionId=" + subscriptionId + ", bundleId="
+                + bundleId + ", eventId=" + eventId
+                + ", requestedTransitionTime=" + requestedTransitionTime
+                + ", previousState=" + previousState + ", previousPriceList="
+                + previousPriceList + ", previousPlan=" + previousPlan
+                + ", previousPhase=" + previousPhase + ", nextState="
+                + nextState + ", nextPriceList=" + nextPriceList
+                + ", nextPlan=" + nextPlan + ", nextPhase=" + nextPhase
+                + ", remainingEventsForUserOperation="
+                + remainingEventsForUserOperation + ", userToken=" + userToken
+                + ", startDate=" + startDate + "]";
+                
+    }
     
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
index 0353bb7..e1f6c28 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -27,15 +27,16 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementBundleMigration;
@@ -50,14 +51,17 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlan() {
 
         try {
+            
+            log.info("Starting testSingleBasePlan");
+
             final DateTime startDate = clock.getUTCNow().minusMonths(2);
-            DateTime beforeMigration = clock.getUTCNow();
+            DateTime beforeMigration =  clock.getUTCNow();
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlan(startDate);
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -73,6 +77,8 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
             assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
+
+            assertListenerStatus();
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -80,16 +86,17 @@ public abstract class TestMigration extends TestApiBase {
 
     public void testPlanWithAddOn() {
         try {
+            log.info("Starting testPlanWithAddOn");
             DateTime beforeMigration = clock.getUTCNow();
             final DateTime initalBPStart = clock.getUTCNow().minusMonths(3);
             final DateTime initalAddonStart = clock.getUTCNow().minusMonths(1).plusDays(7);
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanAndAddons(initalBPStart, initalAddonStart);
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -120,6 +127,7 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(aoSubscription.getCurrentPlan().getName(), "telescopic-scope-monthly");
             assertEquals(aoSubscription.getChargedThroughDate(), initalAddonStart.plusMonths(1));
 
+            assertListenerStatus();
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -128,15 +136,15 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanFutureCancelled() {
 
         try {
-
+            log.info("Starting testSingleBasePlanFutureCancelled");
             final DateTime startDate = clock.getUTCNow().minusMonths(1);
             DateTime beforeMigration = clock.getUTCNow();
             EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanFutreCancelled(startDate);
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -153,12 +161,13 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
             assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_BILLING);
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
- //           Duration oneYear = getDurationYear(1);
- //           clock.setDeltaFromReality(oneYear, 0);
-            clock.addYears(1);
-            assertTrue(testListener.isApiCompleted(5000));
+
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusYears(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertNotNull(subscription.getEndDate());
@@ -168,6 +177,8 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getState(), SubscriptionState.CANCELLED);
             assertNull(subscription.getCurrentPlan());
 
+            assertListenerStatus();
+            
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -176,12 +187,14 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanWithPendingPhase() {
 
         try {
+            
+            log.info("Starting testSingleBasePlanWithPendingPhase");
             final DateTime trialDate = clock.getUTCNow().minusDays(10);
             EntitlementAccountMigration toBeMigrated = createAccountFuturePendingPhase(trialDate);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -199,12 +212,12 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
             assertEquals(subscription.getChargedThroughDate(), trialDate.plusDays(30));
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_BILLING);
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            Duration thirtyDays = getDurationDay(30);
-           // clock.setDeltaFromReality(thirtyDays, 0);
-            clock.addDays(30);
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_BILLING);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             assertEquals(subscription.getStartDate(), trialDate);
             assertEquals(subscription.getEndDate(), null);
@@ -214,6 +227,8 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
             assertEquals(subscription.getCurrentPhase().getName(), "assault-rifle-monthly-evergreen");
 
+            assertListenerStatus();
+            
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
@@ -222,13 +237,14 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanWithPendingChange() {
 
         try {
+            log.info("Starting testSingleBasePlanWithPendingChange");
             DateTime beforeMigration = clock.getUTCNow();
             EntitlementAccountMigration toBeMigrated = createAccountFuturePendingChange();
             DateTime afterMigration = clock.getUTCNow();
 
-            testListener.pushNextApiExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
             assertEquals(bundles.size(), 1);
@@ -244,10 +260,11 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-monthly");
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
-            Duration oneMonth = getDurationMonth(1);
-            clock.setDeltaFromReality(oneMonth, 0);
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
             assertEquals(subscription.getEndDate(), null);
@@ -257,6 +274,8 @@ public abstract class TestMigration extends TestApiBase {
             assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
             assertEquals(subscription.getCurrentPlan().getName(), "shotgun-annual");
 
+            assertListenerStatus();
+            
         } catch (EntitlementMigrationApiException e) {
             Assert.fail("", e);
         }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
index 6ea53bc..0195d69 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigrationMemory.java
@@ -31,26 +31,26 @@ public class TestMigrationMemory extends TestMigration {
     }
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testSingleBasePlan() {
         super.testSingleBasePlan();
     }
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testSingleBasePlanFutureCancelled() {
         super.testSingleBasePlanFutureCancelled();
     }
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testPlanWithAddOn() {
         super.testPlanWithAddOn();
     }
 
 
     @Override
-    @Test(enabled=false, groups="fast")
+    @Test(enabled=true, groups="fast")
     public void testSingleBasePlanWithPendingPhase() {
         super.testSingleBasePlanWithPendingPhase();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 62d905a..7d1389e 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -34,6 +34,7 @@ import org.joda.time.DateTimeZone;
 import org.joda.time.Period;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
@@ -42,6 +43,9 @@ import org.testng.annotations.BeforeMethod;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.ning.billing.account.api.AccountData;
+import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestApiListener.NextEvent;
+import com.ning.billing.api.TestListenerStatus;
 import com.ning.billing.catalog.DefaultCatalogService;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Catalog;
@@ -54,7 +58,6 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
 import com.ning.billing.dbi.MysqlTestingHelper;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.billing.ChargeThruApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
 import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
@@ -80,7 +83,8 @@ import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.glue.RealImplementation;
 
 
-public abstract class TestApiBase {
+public abstract class TestApiBase implements TestListenerStatus {
+    
     protected static final Logger log = LoggerFactory.getLogger(TestApiBase.class);
 
     protected static final long DAY_IN_MS = (24 * 3600 * 1000);
@@ -100,12 +104,23 @@ public abstract class TestApiBase {
 
     protected AccountData accountData;
     protected Catalog catalog;
-    protected ApiTestListener testListener;
+    protected TestApiListener testListener;
     protected SubscriptionBundle bundle;
 
     private MysqlTestingHelper helper;
     protected CallContext context = new TestCallContext("Api Test");
 
+    private boolean isListenerFailed;
+    private String listenerFailedMsg;    
+
+    //
+    // The date on which we make our test start; just to ensure that running tests at different dates does not
+    // produce different results. nothing specific about that date; we could change it to anything.
+    //
+    protected DateTime testStartDate = new DateTime(2012, 5, 7, 0, 3, 42, 0);
+
+
+    
     public static void loadSystemPropertiesFromClasspath(final String resource) {
         final URL url = TestApiBase.class.getResource(resource);
         assertNotNull(url);
@@ -131,6 +146,19 @@ public abstract class TestApiBase {
         }
     }
 
+    
+    @Override
+    public void failed(final String msg) {
+        this.isListenerFailed = true;
+        this.listenerFailedMsg = msg;
+    }
+
+    @Override
+    public void resetTestListenerStatus() {
+        this.isListenerFailed = false;
+        this.listenerFailedMsg = null;
+    }
+    
     @BeforeClass(alwaysRun = true)
     public void setup() throws Exception {
 
@@ -149,64 +177,97 @@ public abstract class TestApiBase {
         dao = g.getInstance(EntitlementDao.class);
         clock = (ClockMock) g.getInstance(Clock.class);
         helper = (isSqlTest(dao)) ? g.getInstance(MysqlTestingHelper.class) : null;
+        init();
+    }
+
+    private void init() throws Exception {
+
+        setupDao();
 
         ((DefaultCatalogService) catalogService).loadCatalog();
         ((Engine) entitlementService).initialize();
-        init(g);
-        ((DefaultBusService) busService).startBus();
-    }
 
-    private static boolean isSqlTest(EntitlementDao theDao) {
-        return (! (theDao instanceof MockEntitlementDaoMemory));
+        accountData = getAccountData();
+        assertNotNull(accountData);
+        catalog = catalogService.getFullCatalog();
+        assertNotNull(catalog);
+        testListener = new TestApiListener(this);
     }
 
-    private void setupMySQL() throws IOException {
+    private void setupDao() throws IOException {
         if (helper != null) {
             final String entitlementDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/entitlement/ddl.sql"));
             final String utilDdl = IOUtils.toString(TestApiBase.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
             helper.startMysql();
-            helper.cleanupAllTables();
             helper.initDb(entitlementDdl);
             helper.initDb(utilDdl);
         }
     }
 
-    private void init(Injector g) throws Exception {
-
-        setupMySQL();
-        accountData = getAccountData();
-        assertNotNull(accountData);
-        catalog = catalogService.getFullCatalog();
-        assertNotNull(catalog);
-        testListener = new ApiTestListener(busService.getBus());
+    private static boolean isSqlTest(EntitlementDao theDao) {
+        return (! (theDao instanceof MockEntitlementDaoMemory));
     }
-
+   
     @BeforeMethod(alwaysRun = true)
     public void setupTest() throws Exception {
 
         log.warn("RESET TEST FRAMEWORK\n\n");
 
+        // CLEANUP ALL DB TABLES OR IN MEMORY STRUCTURES
+        cleanupDao();
+        
+        // RESET LIST OF EXPECTED EVENTS
         if (testListener != null) {
             testListener.reset();
+            resetTestListenerStatus();
         }
-
+        
+        // RESET CLOCK
         clock.resetDeltaFromReality();
-        ((MockEntitlementDao) dao).reset();
 
+        // START BUS AND REGISTER LISTENER
+        busService.getBus().start();
         busService.getBus().register(testListener);
+        
+        // START NOTIFICATION QUEUE FOR ENTITLEMENT
+        ((Engine)entitlementService).start();
+        
+        // SETUP START DATE
+        clock.setDeltaFromReality(testStartDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        // CREATE NEW BUNDLE FOR TEST
         UUID accountId = UUID.randomUUID();
         bundle = entitlementApi.createBundleForAccount(accountId, "myDefaultBundle", context);
         assertNotNull(bundle);
-
-        ((Engine)entitlementService).start();
     }
 
     @AfterMethod(alwaysRun = true)
     public void cleanupTest() throws Exception {
+        
+        // UNREGISTER TEST LISTENER AND STOP BUS
         busService.getBus().unregister(testListener);
+        busService.getBus().stop();
+        
+        // STOP NOTIFICATION QUEUE
         ((Engine)entitlementService).stop();
+
         log.warn("DONE WITH TEST\n");
     }
+    
+    protected void assertListenerStatus() {
+        if (isListenerFailed) {
+            log.error(listenerFailedMsg);
+            Assert.fail(listenerFailedMsg);
+        }
+    }
+    
+    private void cleanupDao() {
+        if (helper != null) {
+            helper.cleanupAllTables();
+        } else {
+            ((MockEntitlementDao) dao).reset();
+        }
+    }
 
     protected SubscriptionData createSubscription(final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
         throws EntitlementUserApiException {
@@ -219,13 +280,13 @@ public abstract class TestApiBase {
 
     protected SubscriptionData createSubscriptionWithBundle(final UUID bundleId, final String productName, final BillingPeriod term, final String planSet, final DateTime requestedDate)
         throws EntitlementUserApiException {
-        testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
-
+        
+        testListener.pushExpectedEvent(NextEvent.CREATE);
         SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundleId,
                 new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSet, null),
                 requestedDate == null ? clock.getUTCNow() : requestedDate, context);
         assertNotNull(subscription);
-        assertTrue(testListener.isApiCompleted(5000));
+        assertTrue(testListener.isCompleted(5000));
         return subscription;
     }
 
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
index e988805..fce7e8a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairBP.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -34,6 +35,7 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
@@ -43,7 +45,6 @@ import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.timeline.BundleTimeline;
 import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
 import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
@@ -67,6 +68,8 @@ public class TestRepairBP extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testFetchBundleRepair() throws Exception  {
 
+        log.info("Starting testFetchBundleRepair");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -125,11 +128,14 @@ public class TestRepairBP extends TestApiBaseRepair {
                 assertEquals(events.get(1).getPlanPhaseSpecifier().getBillingPeriod(), aoTerm);                    
             }
         }
+        assertListenerStatus();
     }
     
     @Test(groups={"slow"})
     public void testBPRepairWithCancellationOnstart() throws Exception {
 
+        log.info("Starting testBPRepairWithCancellationOnstart");
+        
         String baseProduct = "Shotgun";
         DateTime startDate = clock.getUTCNow();
         
@@ -137,8 +143,8 @@ public class TestRepairBP extends TestApiBaseRepair {
         Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
 
         // Stays in trial-- for instance
-        Duration durationShift = getDurationDay(10);
-        clock.setDeltaFromReality(durationShift, 0);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(10));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
         sortEventsOnBundle(bundleRepair);
@@ -190,9 +196,9 @@ public class TestRepairBP extends TestApiBaseRepair {
         
        // SECOND RE-ISSUE CALL-- NON DRY RUN
         dryRun = false;
-        testListener.expectRepairCompletion();
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
-        assertTrue(testListener.isRepairCompleted(5000));
+        assertTrue(testListener.isCompleted(5000));
         
         subscriptionRepair = realRunBundleRepair.getSubscriptions();
         assertEquals(subscriptionRepair.size(), 1);
@@ -211,10 +217,15 @@ public class TestRepairBP extends TestApiBaseRepair {
         assertEquals(realRunBaseSubscription.getStartDate(), startDate);
 
         assertEquals(realRunBaseSubscription.getState(), SubscriptionState.CANCELLED);
+        
+        assertListenerStatus();
     }
     
     @Test(groups={"slow"})
     public void testBPRepairReplaceCreateBeforeTrial() throws Exception {
+        
+        log.info("Starting testBPRepairReplaceCreateBeforeTrial");
+        
         String baseProduct = "Shotgun";
         String newBaseProduct = "Assault-Rifle";
         
@@ -229,10 +240,14 @@ public class TestRepairBP extends TestApiBaseRepair {
                     ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
 
         testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
+        assertListenerStatus();
     }
 
     @Test(groups={"slow"}, enabled=true)
     public void testBPRepairReplaceCreateInTrial() throws Exception {
+        
+        log.info("Starting testBPRepairReplaceCreateInTrial");
+        
         String baseProduct = "Shotgun";
         String newBaseProduct = "Assault-Rifle";
         
@@ -248,9 +263,10 @@ public class TestRepairBP extends TestApiBaseRepair {
 
         UUID baseSubscriptionId = testBPRepairCreate(true, startDate, clockShift, baseProduct, newBaseProduct, expected);
         
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        clock.addDeltaFromReality(getDurationDay(32));
-        assertTrue(testListener.isApiCompleted(5000));
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
         
         // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
         SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
@@ -269,11 +285,16 @@ public class TestRepairBP extends TestApiBaseRepair {
         PlanPhase currentPhase = subscription.getCurrentPhase();
         assertNotNull(currentPhase);
         assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        
+        assertListenerStatus();
     }
 
     
     @Test(groups={"slow"})
     public void testBPRepairReplaceCreateAfterTrial() throws Exception {
+        
+        log.info("Starting testBPRepairReplaceCreateAfterTrial");
+        
         String baseProduct = "Shotgun";
         String newBaseProduct = "Assault-Rifle";
         
@@ -288,25 +309,28 @@ public class TestRepairBP extends TestApiBaseRepair {
                     ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, restartDate.plusDays(30)));
 
         testBPRepairCreate(false, startDate, clockShift, baseProduct, newBaseProduct, expected);
-        
+        assertListenerStatus();
     }
     
     
     private UUID testBPRepairCreate(boolean inTrial, DateTime startDate, int clockShift, 
             String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents) throws Exception {
 
+        log.info("Starting testBPRepairCreate");
+        
         // CREATE BP
         Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
 
         // MOVE CLOCK
         if (clockShift > 0) {
             if (!inTrial) {
-                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+                testListener.pushExpectedEvent(NextEvent.PHASE);
             }               
-            Duration durationShift = getDurationDay(clockShift);
-            clock.setDeltaFromReality(durationShift, 0);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift));
+            clock.addDeltaFromReality(it.toDurationMillis());
             if (!inTrial) {
-                assertTrue(testListener.isApiCompleted(5000));
+                assertTrue(testListener.isCompleted(5000));
             }
         }
 
@@ -327,7 +351,7 @@ public class TestRepairBP extends TestApiBaseRepair {
         // FIRST ISSUE DRY RUN
         BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
         
-        boolean dryRun = true;
+        boolean dryRun = true;        
         BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
         List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
         assertEquals(subscriptionRepair.size(), 1);
@@ -362,8 +386,9 @@ public class TestRepairBP extends TestApiBaseRepair {
         
        // SECOND RE-ISSUE CALL-- NON DRY RUN
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
-
+        assertTrue(testListener.isCompleted(5000));
         subscriptionRepair = realRunBundleRepair.getSubscriptions();
         assertEquals(subscriptionRepair.size(), 1);
         cur = subscriptionRepair.get(0);
@@ -402,6 +427,8 @@ public class TestRepairBP extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testBPRepairAddChangeInTrial() throws Exception {
         
+        log.info("Starting testBPRepairAddChangeInTrial");
+        
         String baseProduct = "Shotgun";
         String newBaseProduct = "Assault-Rifle";
         
@@ -420,9 +447,10 @@ public class TestRepairBP extends TestApiBaseRepair {
         UUID baseSubscriptionId = testBPRepairAddChange(true, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
         
         // CHECK WHAT"S GOING ON AFTER WE MOVE CLOCK-- FUTURE MOTIFICATION SHOULD KICK IN
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        clock.addDeltaFromReality(getDurationDay(32));
-        assertTrue(testListener.isApiCompleted(5000));
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
         SubscriptionData subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscriptionId);
         
         assertEquals(subscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
@@ -439,11 +467,15 @@ public class TestRepairBP extends TestApiBaseRepair {
         PlanPhase currentPhase = subscription.getCurrentPhase();
         assertNotNull(currentPhase);
         assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        
+        assertListenerStatus();
     }
 
     @Test(groups={"slow"})
     public void testBPRepairAddChangeAfterTrial() throws Exception {
         
+        log.info("Starting testBPRepairAddChangeAfterTrial");
+        
         String baseProduct = "Shotgun";
         String newBaseProduct = "Assault-Rifle";
         
@@ -459,24 +491,27 @@ public class TestRepairBP extends TestApiBaseRepair {
         expected.add(createExistingEventForAssertion(SubscriptionTransitionType.CHANGE, newBaseProduct, PhaseType.EVERGREEN,
                 ProductCategory.BASE, PriceListSet.DEFAULT_PRICELIST_NAME, BillingPeriod.MONTHLY, changeDate));
         testBPRepairAddChange(false, startDate, clockShift, baseProduct, newBaseProduct, expected, 3);
+    
+        assertListenerStatus();
     }
     
 
     private UUID testBPRepairAddChange(boolean inTrial, DateTime startDate, int clockShift, 
             String baseProduct, String newBaseProduct, List<ExistingEvent> expectedEvents, int expectedTransitions) throws Exception {
 
+        
         // CREATE BP
         Subscription baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
 
         // MOVE CLOCK
         if (!inTrial) {
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
         }               
-
-        Duration durationShift = getDurationDay(clockShift);
-        clock.setDeltaFromReality(durationShift, 0);
+        
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(clockShift));
+        clock.addDeltaFromReality(it.toDurationMillis());
         if (!inTrial) {
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
         }
 
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
@@ -498,6 +533,7 @@ public class TestRepairBP extends TestApiBaseRepair {
         
         boolean dryRun = true;
         BundleTimeline dryRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        
         List<SubscriptionTimeline> subscriptionRepair = dryRunBundleRepair.getSubscriptions();
         assertEquals(subscriptionRepair.size(), 1);
         SubscriptionTimeline cur = subscriptionRepair.get(0);
@@ -532,7 +568,9 @@ public class TestRepairBP extends TestApiBaseRepair {
         
        // SECOND RE-ISSUE CALL-- NON DRY RUN
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
 
         subscriptionRepair = realRunBundleRepair.getSubscriptions();
         assertEquals(subscriptionRepair.size(), 1);
@@ -572,15 +610,19 @@ public class TestRepairBP extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testRepairWithFurureCancelEvent() throws Exception {
       
+        log.info("Starting testRepairWithFurureCancelEvent");
+        
         DateTime startDate = clock.getUTCNow();
         
         // CREATE BP
         Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
 
         // MOVE CLOCK -- OUT OF TRIAL
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);                
-        clock.setDeltaFromReality(getDurationDay(35), 0);
-        assertTrue(testListener.isApiCompleted(5000));
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(35));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
         
         // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
         DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
@@ -616,7 +658,9 @@ public class TestRepairBP extends TestApiBaseRepair {
         BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
         
         boolean dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
      
         baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
         
@@ -633,6 +677,8 @@ public class TestRepairBP extends TestApiBaseRepair {
         PlanPhase currentPhase = baseSubscription.getCurrentPhase();
         assertNotNull(currentPhase);
         assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
+        
+        assertListenerStatus();
     }
     
     
@@ -640,12 +686,11 @@ public class TestRepairBP extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testENT_REPAIR_VIEW_CHANGED_newEvent() throws Exception {
        
+        log.info("Starting testENT_REPAIR_VIEW_CHANGED_newEvent");
+        
         TestWithException test = new TestWithException();
         DateTime startDate = clock.getUTCNow();
         
-        testListener.reset();
-        clock.resetDeltaFromReality();
-
         final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
         
         test.withException(new TestWithExceptionCallback() {
@@ -663,12 +708,13 @@ public class TestRepairBP extends TestApiBaseRepair {
 
                 BundleTimeline bRepair =  createBundleRepair(bundle.getId(), bundleRepair.getViewId(), Collections.singletonList(sRepair));
 
-                testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+                testListener.pushExpectedEvent(NextEvent.CHANGE);
                 DateTime changeTime = clock.getUTCNow();
                 baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
-                assertTrue(testListener.isApiCompleted(5000));
-                                
+                assertTrue(testListener.isCompleted(5000));
+                
                 repairApi.repairBundle(bRepair, true, context);
+                assertListenerStatus();
             }
         }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
     }
@@ -676,12 +722,11 @@ public class TestRepairBP extends TestApiBaseRepair {
     @Test(groups={"slow"}, enabled=false)
     public void testENT_REPAIR_VIEW_CHANGED_ctd() throws Exception {
        
+        log.info("Starting testENT_REPAIR_VIEW_CHANGED_ctd");
+        
         TestWithException test = new TestWithException();
         DateTime startDate = clock.getUTCNow();
         
-        testListener.reset();
-        clock.resetDeltaFromReality();
-
         final Subscription baseSubscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
         
         test.withException(new TestWithExceptionCallback() {
@@ -704,6 +749,8 @@ public class TestRepairBP extends TestApiBaseRepair {
                 entitlementApi.getSubscriptionFromId(baseSubscription.getId());
 
                 repairApi.repairBundle(bRepair, true, context);
+                
+                assertListenerStatus();
             }
         }, ErrorCode.ENT_REPAIR_VIEW_CHANGED);
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
index 57a78e6..ec75a30 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithAO.java
@@ -24,11 +24,13 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.annotations.Test;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
@@ -38,7 +40,6 @@ import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.timeline.BundleTimeline;
 import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
 import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline.DeletedEvent;
@@ -59,6 +60,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testRepairChangeBPWithAddonIncluded() throws Exception {
         
+        log.info("Starting testRepairChangeBPWithAddonIncluded");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -67,15 +70,16 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
         // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-        Duration someTimeLater = getDurationDay(3);
-        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
         
         SubscriptionData aoSubscription2 = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
         // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
-        clock.addDeltaFromReality(someTimeLater);
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
         sortEventsOnBundle(bundleRepair);
@@ -165,8 +169,11 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         assertEquals(newBaseSubscription.getAllTransitions().size(), 2);
         assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
         
-        dryRun = false;
+        dryRun = false;        
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+
         
         
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
@@ -210,6 +217,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testRepairChangeBPWithAddonNonAvailable() throws Exception {
         
+        log.info("Starting testRepairChangeBPWithAddonNonAvailable");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -218,17 +227,18 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
         // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-        Duration someTimeLater = getDurationDay(3);
-        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
         
         // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        someTimeLater = getDurationDay(32);
-        clock.addDeltaFromReality(someTimeLater);
-        assertTrue(testListener.isApiCompleted(7000));        
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(7000));        
 
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
         sortEventsOnBundle(bundleRepair);
@@ -295,8 +305,10 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
         
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
-        
+        assertTrue(testListener.isCompleted(5000));
+
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
         assertEquals(aoRepair.getExistingEvents().size(), 3);
 
@@ -327,6 +339,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testRepairCancelBP_EOT_WithAddons() throws Exception {
         
+        log.info("Starting testRepairCancelBP_EOT_WithAddons");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -335,17 +349,19 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
         // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-        Duration someTimeLater = getDurationDay(3);
-        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
 
         SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
         
         // MOVE CLOCK A LITTLE BIT MORE -- AFTER TRIAL
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        someTimeLater = getDurationDay(40);
-        clock.addDeltaFromReality(someTimeLater);
-        assertTrue(testListener.isApiCompleted(7000));
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(7000));
         
         // SET CTD to BASE SUBSCRIPTION SP CANCEL OCCURS EOT
         DateTime newChargedThroughDate = baseSubscription.getStartDate().plusDays(30).plusMonths(1);
@@ -414,7 +430,9 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
         
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bundleRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
         
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
         assertEquals(aoRepair.getExistingEvents().size(), 3);
@@ -443,11 +461,12 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION + 1);
         
         // MOVE CLOCK AFTER CANCEL DATE
-        testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-        testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-        someTimeLater = getDurationDay(32);
-        clock.addDeltaFromReality(someTimeLater);
-        assertTrue(testListener.isApiCompleted(7000));
+        testListener.pushExpectedEvent(NextEvent.CANCEL);
+        testListener.pushExpectedEvent(NextEvent.CANCEL);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(32));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(7000));
 
         newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
         assertEquals(newAoSubscription.getState(), SubscriptionState.CANCELLED);
@@ -464,6 +483,9 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     
     @Test(groups={"slow"})
     public void testRepairCancelAO() throws Exception {
+        
+        log.info("Starting testRepairCancelAO");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -472,13 +494,14 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
         // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-        Duration someTimeLater = getDurationDay(3);
-        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
         // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
-        clock.addDeltaFromReality(someTimeLater);
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
         sortEventsOnBundle(bundleRepair);
@@ -530,7 +553,10 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         assertEquals(newBaseSubscription.getActiveVersion(), SubscriptionEvents.INITIAL_VERSION);
         
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
+
         
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
         assertEquals(aoRepair.getExistingEvents().size(), 2);
@@ -553,6 +579,9 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     
     @Test(groups={"slow"})
     public void testRepairRecreateAO() throws Exception {
+        
+        log.info("Starting testRepairRecreateAO");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -561,13 +590,14 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
         // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-        Duration someTimeLater = getDurationDay(3);
-        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
         // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
-        clock.addDeltaFromReality(someTimeLater);
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
         sortEventsOnBundle(bundleRepair);
@@ -616,7 +646,9 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         
         // NOW COMMIT
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
         
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
         assertEquals(aoRepair.getExistingEvents().size(), 2);
@@ -644,6 +676,8 @@ public class TestRepairWithAO extends TestApiBaseRepair {
     @Test(groups={"slow"})
     public void testRepairChangeAOOK() throws Exception {
         
+        log.info("Starting testRepairChangeAOOK");
+        
         String baseProduct = "Shotgun";
         BillingPeriod baseTerm = BillingPeriod.MONTHLY;
         String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
@@ -652,13 +686,14 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
         // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-        Duration someTimeLater = getDurationDay(3);
-        clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+        clock.addDeltaFromReality(it.toDurationMillis());
 
         SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
         // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
-        clock.addDeltaFromReality(someTimeLater);
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+        clock.addDeltaFromReality(it.toDurationMillis());
         
         BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
         sortEventsOnBundle(bundleRepair);
@@ -706,7 +741,9 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         
         // AND NOW COMMIT
         dryRun = false;
+        testListener.pushExpectedEvent(NextEvent.REPAIR_BUNDLE);
         BundleTimeline realRunBundleRepair = repairApi.repairBundle(bRepair, dryRun, context);
+        assertTrue(testListener.isCompleted(5000));
         
         aoRepair = getSubscriptionRepair(aoSubscription.getId(), realRunBundleRepair);
         assertEquals(aoRepair.getExistingEvents().size(), 3);
@@ -734,10 +771,11 @@ public class TestRepairWithAO extends TestApiBaseRepair {
         assertNotNull(currentPhase);
         assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
         
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        someTimeLater = getDurationDay(60);
-        clock.addDeltaFromReality(someTimeLater);
-        assertTrue(testListener.isApiCompleted(5000));
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        
+        it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(60));
+        clock.addDeltaFromReality(it.toDurationMillis());
+        assertTrue(testListener.isCompleted(5000));
         
         newAoSubscription = (SubscriptionData)  entitlementApi.getSubscriptionFromId(aoSubscription.getId());
         currentPhase = newAoSubscription.getCurrentPhase();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
index 285b17d..7421b48 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/timeline/TestRepairWithError.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -31,6 +32,7 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
@@ -38,7 +40,6 @@ import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.SubscriptionTransitionType;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.timeline.BundleTimeline;
 import com.ning.billing.entitlement.api.timeline.EntitlementRepairException;
 import com.ning.billing.entitlement.api.timeline.SubscriptionTimeline;
@@ -61,27 +62,30 @@ public class TestRepairWithError extends TestApiBaseRepair {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleMemory());
     }
 
-
-    @BeforeMethod(groups={"fast"})
-    public void beforeMethod() throws Exception {
+    @BeforeMethod(alwaysRun = true)
+    public void setupTest() throws Exception {
+        super.setupTest();
         test = new TestWithException();
         startDate = clock.getUTCNow();
         baseSubscription = createSubscription(baseProduct, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, startDate);
-        testListener.reset();
-        clock.resetDeltaFromReality();
     }
   
     @Test(groups={"fast"})
     public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_NEW_EVENT_BEFORE_LAST_BP_REMAINING");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException {
 
                 // MOVE AFTER TRIAL
-                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-                Duration durationShift = getDurationDay(40);
-                clock.setDeltaFromReality(durationShift, 0);
-                assertTrue(testListener.isApiCompleted(5000));
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+                
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
+                assertTrue(testListener.isCompleted(5000));
                 
                 BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                 sortEventsOnBundle(bundleRepair);
@@ -99,23 +103,26 @@ public class TestRepairWithError extends TestApiBaseRepair {
     
     @Test(groups={"fast"})
     public void testENT_REPAIR_INVALID_DELETE_SET() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_INVALID_DELETE_SET");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
 
-                Duration durationShift = getDurationDay(3);
-                clock.setDeltaFromReality(durationShift, 0);
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+                clock.addDeltaFromReality(it.toDurationMillis());
                 
-                testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+                testListener.pushExpectedEvent(NextEvent.CHANGE);
                 DateTime changeTime = clock.getUTCNow();
                 baseSubscription.changePlan("Assault-Rifle", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, changeTime, context);
-                assertTrue(testListener.isApiCompleted(5000));
+                assertTrue(testListener.isCompleted(5000));
                 
                 // MOVE AFTER TRIAL
-                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-                durationShift = getDurationDay(40);
-                clock.addDeltaFromReality(durationShift);
-                assertTrue(testListener.isApiCompleted(5000));
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+                it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                assertTrue(testListener.isCompleted(5000));
                 
                 BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                 sortEventsOnBundle(bundleRepair);
@@ -133,6 +140,9 @@ public class TestRepairWithError extends TestApiBaseRepair {
 
     @Test(groups={"fast"})
     public void testENT_REPAIR_NON_EXISTENT_DELETE_EVENT() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_NON_EXISTENT_DELETE_EVENT");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException {
@@ -153,15 +163,18 @@ public class TestRepairWithError extends TestApiBaseRepair {
     
     @Test(groups={"fast"})
     public void testENT_REPAIR_SUB_RECREATE_NOT_EMPTY() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_SUB_RECREATE_NOT_EMPTY");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException {
                 
                 // MOVE AFTER TRIAL
-                   testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-                   Duration durationShift = getDurationDay(40);
-                   clock.setDeltaFromReality(durationShift, 0);
-                   assertTrue(testListener.isApiCompleted(5000));
+                   testListener.pushExpectedEvent(NextEvent.PHASE);
+                   Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                   clock.addDeltaFromReality(it.toDurationMillis());
+                   assertTrue(testListener.isCompleted(5000));
                    
                    BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                    sortEventsOnBundle(bundleRepair);
@@ -181,15 +194,19 @@ public class TestRepairWithError extends TestApiBaseRepair {
 
     @Test(groups={"fast"})
     public void testENT_REPAIR_SUB_EMPTY() throws Exception {
+
+        log.info("Starting testENT_REPAIR_SUB_EMPTY");
+        
         test.withException(new TestWithExceptionCallback() {
+
             @Override
             public void doTest() throws EntitlementRepairException {
                 
              // MOVE AFTER TRIAL
-                testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-                Duration durationShift = getDurationDay(40);
-                clock.setDeltaFromReality(durationShift, 0);
-                assertTrue(testListener.isApiCompleted(5000));
+                testListener.pushExpectedEvent(NextEvent.PHASE);
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(40));
+                clock.addDeltaFromReality(it.toDurationMillis());
+                assertTrue(testListener.isCompleted(5000));
                 
                 BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                 sortEventsOnBundle(bundleRepair);
@@ -209,19 +226,22 @@ public class TestRepairWithError extends TestApiBaseRepair {
     
     @Test(groups={"fast"})
     public void testENT_REPAIR_AO_CREATE_BEFORE_BP_START() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_AO_CREATE_BEFORE_BP_START");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
                
 
                 // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-                Duration someTimeLater = getDurationDay(3);
-                clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
-
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
                 SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
                 // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
-                clock.addDeltaFromReality(someTimeLater);
+                it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
 
                 BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                 sortEventsOnBundle(bundleRepair);
@@ -254,19 +274,22 @@ public class TestRepairWithError extends TestApiBaseRepair {
     
     @Test(groups={"fast"})
     public void testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_NEW_EVENT_BEFORE_LAST_AO_REMAINING");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
                 
 
                 // MOVE CLOCK A LITTLE BIT-- STILL IN TRIAL
-                Duration someTimeLater = getDurationDay(3);
-                clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
-
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
                 SubscriptionData aoSubscription = createSubscription("Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
                 // MOVE CLOCK A LITTLE BIT MORE -- STILL IN TRIAL
-                clock.addDeltaFromReality(someTimeLater);
+                it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
 
                 BundleTimeline bundleRepair = repairApi.getBundleRepair(bundle.getId());
                 sortEventsOnBundle(bundleRepair);
@@ -298,13 +321,17 @@ public class TestRepairWithError extends TestApiBaseRepair {
 
     @Test(groups={"fast"})
     public void testENT_REPAIR_BP_RECREATE_MISSING_AO() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_BP_RECREATE_MISSING_AO");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
 
               //testListener.pushExpectedEvent(NextEvent.PHASE);
 
-                clock.setDeltaFromReality(getDurationDay(5), 0);
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
                 //assertTrue(testListener.isCompleted(5000));
 
                 SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -337,14 +364,18 @@ public class TestRepairWithError extends TestApiBaseRepair {
     //
     @Test(groups={"fast"}, enabled=false)
     public void testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_BP_RECREATE_MISSING_AO_CREATE");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
                 /*
               //testListener.pushExpectedEvent(NextEvent.PHASE);
 
-                clock.setDeltaFromReality(getDurationDay(5), 0);
-                //assertTrue(testListener.isCompleted(5000));
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(4));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
 
                 SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
                 
@@ -382,6 +413,9 @@ public class TestRepairWithError extends TestApiBaseRepair {
     
     @Test(groups={"fast"}, enabled=false)
     public void testENT_REPAIR_MISSING_AO_DELETE_EVENT() throws Exception {
+        
+        log.info("Starting testENT_REPAIR_MISSING_AO_DELETE_EVENT");
+        
         test.withException(new TestWithExceptionCallback() {
             @Override
             public void doTest() throws EntitlementRepairException, EntitlementUserApiException {
@@ -389,6 +423,10 @@ public class TestRepairWithError extends TestApiBaseRepair {
                 
                 /*
                 // MOVE CLOCK -- JUST BEFORE END OF TRIAL
+                 *                 
+                Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(29));
+                clock.addDeltaFromReality(it.toDurationMillis());
+
                 clock.setDeltaFromReality(getDurationDay(29), 0);
                 
                 SubscriptionData aoSubscription = createSubscription("Laser-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index 2780fe5..d412e59 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -21,15 +21,15 @@ import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
-import org.apache.commons.lang.NotImplementedException;
 import org.joda.time.DateTime;
-import org.joda.time.Period;
+import org.joda.time.Interval;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.CatalogApiException;
 import com.ning.billing.catalog.api.Duration;
@@ -40,8 +40,6 @@ import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PlanSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.catalog.api.TimeUnit;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
@@ -57,6 +55,8 @@ public class TestUserApiAddOn extends TestApiBase {
     @Test(enabled=true, groups={"slow"})
     public void testCreateCancelAddon() {
 
+        log.info("Starting testCreateCancelAddon");
+
         try {
             String baseProduct = "Shotgun";
             BillingPeriod baseTerm = BillingPeriod.MONTHLY;
@@ -75,11 +75,12 @@ public class TestUserApiAddOn extends TestApiBase {
             aoSubscription.cancel(now, false, context);
 
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            assertTrue(testListener.isCompleted(5000));
 
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -87,6 +88,9 @@ public class TestUserApiAddOn extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testCancelBPWithAddon() {
+
+        log.info("Starting testCancelBPWithAddon");
+
         try {
 
             String baseProduct = "Shotgun";
@@ -103,13 +107,13 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
 
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
-            Duration twoMonths = getDurationMonth(2);
-            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(5000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
@@ -129,15 +133,19 @@ public class TestUserApiAddOn extends TestApiBase {
 
             // MOVE AFTER CANCELLATION
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS IS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
+
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -146,6 +154,9 @@ public class TestUserApiAddOn extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testChangeBPWithAddonNonIncluded() {
+
+        log.info("Starting testChangeBPWithAddonNonIncluded");
+
         try {
 
             String baseProduct = "Shotgun";
@@ -162,13 +173,13 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
 
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
-            Duration twoMonths = getDurationMonth(2);
-            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(5000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD TO CHANGE IN FUTURE
             DateTime now = clock.getUTCNow();
@@ -183,15 +194,16 @@ public class TestUserApiAddOn extends TestApiBase {
             String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
             baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, now, context);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -199,6 +211,9 @@ public class TestUserApiAddOn extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testChangeBPWithAddonNonAvailable() {
+
+        log.info("Starting testChangeBPWithAddonNonAvailable");
+
         try {
 
             String baseProduct = "Shotgun";
@@ -215,13 +230,13 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
 
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
 
             // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
-            Duration twoMonths = getDurationMonth(2);
-            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(5000));
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD TO CANCEL IN FUTURE
             DateTime now = clock.getUTCNow();
@@ -245,16 +260,18 @@ public class TestUserApiAddOn extends TestApiBase {
 
             // MOVE AFTER CHANGE
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
 
             // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
+            assertListenerStatus();
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -263,6 +280,9 @@ public class TestUserApiAddOn extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testAddonCreateWithBundleAlign() {
+
+        log.info("Starting testAddonCreateWithBundleAlign");
+
         try {
             String aoProduct = "Telescopic-Scope";
             BillingPeriod aoTerm = BillingPeriod.MONTHLY;
@@ -278,6 +298,7 @@ public class TestUserApiAddOn extends TestApiBase {
 
             testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
 
+            assertListenerStatus();
         } catch (CatalogApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -287,6 +308,8 @@ public class TestUserApiAddOn extends TestApiBase {
     @Test(enabled=true, groups={"slow"})
     public void testAddonCreateWithSubscriptionAlign() {
 
+        log.info("Starting testAddonCreateWithSubscriptionAlign");
+
         try {
             String aoProduct = "Laser-Scope";
             BillingPeriod aoTerm = BillingPeriod.MONTHLY;
@@ -302,13 +325,15 @@ public class TestUserApiAddOn extends TestApiBase {
 
             testAddonCreateInternal(aoProduct, aoTerm, aoPriceList, alignement);
 
-            } catch (CatalogApiException e) {
-                Assert.fail(e.getMessage());
-            }
+            assertListenerStatus();
+        } catch (CatalogApiException e) {
+            Assert.fail(e.getMessage());
+        }
     }
 
 
     private void testAddonCreateInternal(String aoProduct, BillingPeriod aoTerm, String aoPriceList, PlanAlignmentCreate expAlignement) {
+
         try {
 
             String baseProduct = "Shotgun";
@@ -319,9 +344,9 @@ public class TestUserApiAddOn extends TestApiBase {
             SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
 
             // MOVE CLOCK 14 DAYS LATER
-            Duration someTimeLater = getDurationDay(13);
-            clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
-
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(14));
+            clock.addDeltaFromReality(it.toDurationMillis());
+  
             // CREATE ADDON
             DateTime beforeAOCreation = clock.getUTCNow();
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
@@ -338,73 +363,46 @@ public class TestUserApiAddOn extends TestApiBase {
             assertNotNull(aoCurrentPhase);
             assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
-           assertDateWithin(aoSubscription.getStartDate(), beforeAOCreation, afterAOCreation);
-           assertEquals(aoSubscription.getBundleStartDate(), baseSubscription.getBundleStartDate());
-
-           // CHECK next AO PHASE EVENT IS INDEED A MONTH AFTER BP STARTED => BUNDLE ALIGNMENT
-           SubscriptionEvent aoPendingTranstion = aoSubscription.getPendingTransition();
-
-           if (expAlignement == PlanAlignmentCreate.START_OF_BUNDLE) {
-               assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), baseSubscription.getStartDate().plusMonths(1));
-           } else {
-               assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), aoSubscription.getStartDate().plusMonths(1));
-           }
-
-           // ADD TWO PHASE EVENTS (BP + AO)
-           testListener.reset();
-           testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-           testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-
-           // MOVE THROUGH TIME TO GO INTO EVERGREEN
-           
-           // Talk with Stephane about this fix. It seemed that the add on phase change was not appearing in the queue
-           // hypothesis is that waiting a period that is exactly the duration of the phase might be an instant too short
-           // depending how the comparison works
-           
-           //someTimeLater = aoCurrentPhase.getDuration();
-           someTimeLater = new Duration() {
-                @Override
-                public TimeUnit getUnit() {
-                   return TimeUnit.DAYS;
-                }
-
-                @Override
-                public int getNumber() {
-                   return 32;
-                }
-
-                @Override
-                public DateTime addToDateTime(DateTime dateTime) {
-                   throw new NotImplementedException();
-                }
-                @Override
-                public Period toJodaPeriod() {
-                    throw new UnsupportedOperationException();
-                }
-           };
-           
-           clock.addDeltaFromReality(someTimeLater);
-           clock.addDeltaFromReality(getDurationDay(1));
-           assertTrue(testListener.isApiCompleted(5000));
-
-
-           // CHECK EVERYTHING AGAIN
-           aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
-
-           aoCurrentPlan = aoSubscription.getCurrentPlan();
-           assertNotNull(aoCurrentPlan);
-           assertEquals(aoCurrentPlan.getProduct().getName(),aoProduct);
-           assertEquals(aoCurrentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
-           assertEquals(aoCurrentPlan.getBillingPeriod(), aoTerm);
-
-           aoCurrentPhase = aoSubscription.getCurrentPhase();
-           assertNotNull(aoCurrentPhase);
-           assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.EVERGREEN);
-
-
-           aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
-           aoPendingTranstion = aoSubscription.getPendingTransition();
-           assertNull(aoPendingTranstion);
+            assertDateWithin(aoSubscription.getStartDate(), beforeAOCreation, afterAOCreation);
+            assertEquals(aoSubscription.getBundleStartDate(), baseSubscription.getBundleStartDate());
+
+            // CHECK next AO PHASE EVENT IS INDEED A MONTH AFTER BP STARTED => BUNDLE ALIGNMENT
+            SubscriptionEvent aoPendingTranstion = aoSubscription.getPendingTransition();
+
+            if (expAlignement == PlanAlignmentCreate.START_OF_BUNDLE) {
+                assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), baseSubscription.getStartDate().plusMonths(1));
+            } else {
+                assertEquals(aoPendingTranstion.getEffectiveTransitionTime(), aoSubscription.getStartDate().plusMonths(1));
+            }
+
+            // ADD TWO PHASE EVENTS (BP + AO)
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+
+            // MOVE THROUGH TIME TO GO INTO EVERGREEN
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(33));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
+
+
+            // CHECK EVERYTHING AGAIN
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+
+            aoCurrentPlan = aoSubscription.getCurrentPlan();
+            assertNotNull(aoCurrentPlan);
+            assertEquals(aoCurrentPlan.getProduct().getName(),aoProduct);
+            assertEquals(aoCurrentPlan.getProduct().getCategory(), ProductCategory.ADD_ON);
+            assertEquals(aoCurrentPlan.getBillingPeriod(), aoTerm);
+
+            aoCurrentPhase = aoSubscription.getCurrentPhase();
+            assertNotNull(aoCurrentPhase);
+            assertEquals(aoCurrentPhase.getPhaseType(), PhaseType.EVERGREEN);
+
+
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            aoPendingTranstion = aoSubscription.getPendingTransition();
+            assertNull(aoPendingTranstion);
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
index 09046f1..7fb06e0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancel.java
@@ -22,15 +22,16 @@ import static org.testng.Assert.assertNull;
 import static org.testng.Assert.assertTrue;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.util.clock.DefaultClock;
@@ -55,20 +56,21 @@ public abstract class TestUserApiCancel extends TestApiBase {
             assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
 
             // ADVANCE TIME still in trial
-            Duration moveALittleInTime = getDurationDay(3);
-            clock.setDeltaFromReality(moveALittleInTime, 0);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+            clock.addDeltaFromReality(it.toDurationMillis());
 
             DateTime future = clock.getUTCNow();
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
 
             // CANCEL in trial period to get IMM policy
             subscription.cancel(clock.getUTCNow(), false, context);
             currentPhase = subscription.getCurrentPhase();
-            testListener.isApiCompleted(1000);
+            testListener.isCompleted(3000);
 
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -94,9 +96,11 @@ public abstract class TestUserApiCancel extends TestApiBase {
             checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -106,21 +110,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-
             // CANCEL
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             // MOVE TO EOT + RECHECK
-            clock.addDeltaFromReality(ctd);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             DateTime future = clock.getUTCNow();
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -147,22 +155,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
             checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
 
             // CANCEL
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNull(currentPhase);
             checkNextPhaseChange(subscription, 0, null);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -191,9 +202,10 @@ public abstract class TestUserApiCancel extends TestApiBase {
             checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
@@ -203,27 +215,25 @@ public abstract class TestUserApiCancel extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
-
-            // CANCEL
+            // CANCEL EOT
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isApiCompleted(2000));
 
             subscription.uncancel(context);
-
+            
             // MOVE TO EOT + RECHECK
-            clock.addDeltaFromReality(ctd);
-            DateTime future = clock.getUTCNow();
-            assertFalse(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.UNCANCEL);            
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertEquals(currentPlan.getProduct().getName(), prod);
             currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
     }
-
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
index 7eccbdd..625002a 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCancelMemory.java
@@ -33,25 +33,25 @@ public class TestUserApiCancelMemory extends TestUserApiCancel {
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCancelSubscriptionIMM() {
         super.testCancelSubscriptionIMM();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCancelSubscriptionEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testCancelSubscriptionEOTWithChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCancelSubscriptionEOTWithNoChargeThroughDate() {
         super.testCancelSubscriptionEOTWithNoChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testUncancel() throws EntitlementBillingApiException {
         super.testUncancel();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
index b5b98e4..b1d2c0b 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlan.java
@@ -25,8 +25,10 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
@@ -34,7 +36,6 @@ import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.events.EntitlementEvent;
@@ -69,7 +70,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
     private void tChangePlanBundleAlignEOTWithNoChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
         String toProd, BillingPeriod toTerm, String toPlanSet) {
 
-        log.info("Starting testChangePlanBundleAlignEOTWithNoChargeThroughDateReal");
+        log.info("Starting testChangePlanBundleAlignEOTWithNoChargeThroughDate");
 
         try {
 
@@ -78,22 +79,26 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // MOVE TO NEXT PHASE
             PlanPhase currentPhase = subscription.getCurrentPhase();
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
             DateTime futureNow = clock.getUTCNow();
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
-            assertTrue(testListener.isApiCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
 
             // CHANGE PLAN
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             // CHECK CHANGE PLAN
             currentPhase = subscription.getCurrentPhase();
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.EVERGREEN);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -101,13 +106,14 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
     protected void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
+        log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
         testChangePlanBundleAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", "Pistol", BillingPeriod.ANNUAL, "gunclubDiscount");
     }
 
     private void testChangePlanBundleAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
             String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
-        log.info("Starting testChangeSubscriptionEOTWithChargeThroughDate");
+        
         try {
 
             // CREATE
@@ -118,9 +124,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
@@ -131,10 +138,11 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
 
             // RE READ SUBSCRIPTION + CHANGE PLAN
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             // CHECK CHANGE PLAN
@@ -151,14 +159,16 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
             // MOVE TO EOT
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             currentPhase = subscription.getCurrentPhase();
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.DISCOUNT);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -179,37 +189,31 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             SubscriptionData subscription = createSubscription(fromProd, fromTerm, fromPlanSet);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
 
-            Duration moveALittleInTime = getDurationDay(3);
-            clock.setDeltaFromReality(moveALittleInTime, 0);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(3));
+            clock.addDeltaFromReality(it.toDurationMillis());
 
             // CHANGE PLAN IMM
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
             checkChangePlan(subscription, toProd, ProductCategory.BASE, toTerm, PhaseType.TRIAL);
 
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             PlanPhase currentPhase = subscription.getCurrentPhase();
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange);
 
             // NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+            clock.addDeltaFromReality(it.toDurationMillis());
             DateTime futureNow = clock.getUTCNow();
 
-            /*
-            try {
-                Thread.sleep(1000 * 3000);
-            } catch (Exception e) {
-
-            }
-            */
-
             assertTrue(futureNow.isAfter(nextExpectedPhaseChange));
-            assertTrue(testListener.isApiCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -217,14 +221,13 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
     protected void testChangePlanChangePlanAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
+        log.info("Starting testChangePlanChangePlanAlignEOTWithChargeThroughDate");
         tChangePlanChangePlanAlignEOTWithChargeThroughDate("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, "Assault-Rifle", BillingPeriod.ANNUAL, "rescue");
     }
 
     private void tChangePlanChangePlanAlignEOTWithChargeThroughDate(String fromProd, BillingPeriod fromTerm, String fromPlanSet,
             String toProd, BillingPeriod toTerm, String toPlanSet) throws EntitlementBillingApiException {
 
-        log.info("Starting testChangePlanBundleAlignEOTWithChargeThroughDate");
-
         try {
 
             DateTime currentTime = clock.getUTCNow();
@@ -235,11 +238,12 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
             currentTime = clock.getUTCNow();
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             Duration ctd = getDurationMonth(1);
@@ -254,19 +258,22 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             // CHANGE PLAN
             currentTime = clock.getUTCNow();
-
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan(toProd, toTerm, toPlanSet, clock.getUTCNow(), context);
 
             checkChangePlan(subscription, fromProd, ProductCategory.BASE, fromTerm, PhaseType.EVERGREEN);
 
             // CHECK CHANGE DID NOT KICK IN YET
-            assertFalse(testListener.isApiCompleted(2000));
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             // MOVE TO AFTER CTD
-            clock.addDeltaFromReality(ctd);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             // CHECK CORRECT PRODUCT, PHASE, PLAN SET
             String currentProduct =  subscription.getCurrentPlan().getProduct().getName();
@@ -276,22 +283,27 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-
             // MOVE TIME ABOUT ONE MONTH BEFORE NEXT EXPECTED PHASE CHANGE
-            clock.addDeltaFromReality(getDurationMonth(11));
-
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(11));
+            clock.addDeltaFromReality(it.toDurationMillis());
             currentTime = clock.getUTCNow();
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(newChargedThroughDate, currentPhase.getDuration());
             checkNextPhaseChange(subscription, 1, nextExpectedPhaseChange);
 
             // MOVE TIME RIGHT AFTER NEXT EXPECTED PHASE CHANGE
-            clock.addDeltaFromReality(getDurationMonth(1));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
             currentTime = clock.getUTCNow();
-            assertTrue(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -299,15 +311,18 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
     protected void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
 
+        log.info("Starting testMultipleChangeLastIMM");
         try {
             SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.MONTHLY, "gunclubDiscount");
             PlanPhase trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             List<Duration> durationList = new ArrayList<Duration>();
@@ -320,14 +335,16 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CHANGE EOT
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
+            testListener.reset();
 
             // CHANGE
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertTrue(testListener.isCompleted(5000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -338,7 +355,8 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
-
+            
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -346,15 +364,17 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
     protected void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
 
+        log.info("Starting testMultipleChangeLastEOT");
         try {
 
             SubscriptionData subscription = createSubscription("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount");
             PlanPhase trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             List<Duration> durationList = new ArrayList<Duration>();
@@ -366,15 +386,17 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CHANGE EOT
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Shotgun", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             // CHANGE EOT
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             // CHECK NO CHANGE OCCURED YET
@@ -389,9 +411,11 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
 
             // ACTIVATE CHNAGE BY MOVING AFTER CTD
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isApiCompleted(3000));
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
 
             currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -406,9 +430,10 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isApiCompleted(3000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(6));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             currentPlan = subscription.getCurrentPlan();
@@ -421,7 +446,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
-
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -429,6 +454,9 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
 
     protected void testCorrectPhaseAlignmentOnChange() {
+        
+        log.info("Starting testCorrectPhaseAlignmentOnChange");
+        
         try {
 
             SubscriptionData subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -436,13 +464,15 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE 2 DAYS AHEAD
-            clock.setDeltaFromReality(getDurationDay(1), DAY_IN_MS);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(2));
+            clock.addDeltaFromReality(it.toDurationMillis());
+    
 
             // CHANGE IMMEDIATE TO A 3 PHASES PLAN
             testListener.reset();
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertTrue(testListener.isApiCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
             testListener.reset();
 
             // CHECK EVERYTHING LOOKS CORRECT
@@ -456,9 +486,11 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
             // MOVE AFTER TRIAL PERIOD -> DISCOUNT
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(trialPhase.getDuration());
-            assertTrue(testListener.isApiCompleted(3000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(30));
+            clock.addDeltaFromReality(it.toDurationMillis());
+
+            assertTrue(testListener.isCompleted(5000));
 
             trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.DISCOUNT);
@@ -471,6 +503,7 @@ public abstract class TestUserApiChangePlan extends TestApiBase {
 
             assertEquals(nextPhaseEffectiveDate, expectedNextPhaseDate);
 
+            assertListenerStatus();
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
index 8bbdd2d..0da1169 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanMemory.java
@@ -33,38 +33,38 @@ public class TestUserApiChangePlanMemory extends TestUserApiChangePlan {
 
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testChangePlanBundleAlignEOTWithNoChargeThroughDate() {
          super.testChangePlanBundleAlignEOTWithNoChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testChangePlanBundleAlignEOTWithChargeThroughDate() throws EntitlementBillingApiException {
         super.testChangePlanBundleAlignEOTWithChargeThroughDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testChangePlanBundleAlignIMM() {
         super.testChangePlanBundleAlignIMM();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testMultipleChangeLastIMM() throws EntitlementBillingApiException {
         super.testMultipleChangeLastIMM();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testMultipleChangeLastEOT() throws EntitlementBillingApiException {
         super.testMultipleChangeLastEOT();
     }
 
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCorrectPhaseAlignmentOnChange() {
         super.testCorrectPhaseAlignmentOnChange();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
index 509b056..1039b85 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiChangePlanSql.java
@@ -33,7 +33,7 @@ public class TestUserApiChangePlanSql extends TestUserApiChangePlan {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
-    @Test(enabled= true, groups={"stress"})
+    @Test(enabled= false, groups={"stress"})
     public void stressTest() throws Exception {
         for (int i = 0; i < MAX_STRESS_ITERATIONS; i++) {
             cleanupTest();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
index ef4533d..6517044 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreate.java
@@ -24,17 +24,18 @@ import static org.testng.Assert.assertTrue;
 import java.util.List;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
@@ -56,8 +57,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
 
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
 
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
@@ -69,8 +70,10 @@ public abstract class TestUserApiCreate extends TestApiBase {
             assertEquals(subscription.getBundleId(), bundle.getId());
             assertEquals(subscription.getStartDate(), requestedDate);
 
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
+            
         } catch (EntitlementUserApiException e) {
         	log.error("Unexpected exception",e);
             Assert.fail(e.getMessage());
@@ -89,7 +92,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, PhaseType.EVERGREEN), clock.getUTCNow(), context);
@@ -111,6 +114,8 @@ public abstract class TestUserApiCreate extends TestApiBase {
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
+            assertListenerStatus();
+            
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -127,7 +132,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null),
@@ -149,7 +154,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             List<EntitlementEvent> events = dao.getPendingEventsForSubscription(subscription.getId());
             assertNotNull(events);
@@ -160,15 +165,16 @@ public abstract class TestUserApiCreate extends TestApiBase {
             DateTime nextExpectedPhaseChange = DefaultClock.addDuration(subscription.getStartDate(), currentPhase.getDuration());
             assertEquals(nextPhaseChange, nextExpectedPhaseChange);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
 
             DateTime futureNow = clock.getUTCNow();
             assertTrue(futureNow.isAfter(nextPhaseChange));
 
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -184,7 +190,7 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.ANNUAL;
             String planSetName = "gunclubDiscount";
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
 
             // CREATE SUBSCRIPTION
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
@@ -194,27 +200,30 @@ public abstract class TestUserApiCreate extends TestApiBase {
             PlanPhase currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.TRIAL);
-            assertTrue(testListener.isApiCompleted(5000));
+            assertTrue(testListener.isCompleted(5000));
 
             // MOVE TO DISCOUNT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
-            assertTrue(testListener.isApiCompleted(2000));
+            
 
             // MOVE TO EVERGREEN PHASE + RE-READ SUBSCRIPTION
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusYears(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
             currentPhase = subscription.getCurrentPhase();
             assertNotNull(currentPhase);
             assertEquals(currentPhase.getPhaseType(), PhaseType.EVERGREEN);
 
-
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
@@ -229,17 +238,15 @@ public abstract class TestUserApiCreate extends TestApiBase {
             BillingPeriod term = BillingPeriod.ANNUAL;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
 
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null), clock.getUTCNow(), context);
             assertNotNull(subscription);
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
     }
-
-
-
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
index 7987d87..e4969f1 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiCreateMemory.java
@@ -32,31 +32,31 @@ public class TestUserApiCreateMemory extends TestUserApiCreate {
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCreateWithRequestedDate() {
         super.testCreateWithRequestedDate();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testCreateWithInitialPhase() {
         super.testSimpleSubscriptionThroughPhases();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     public void testSimpleCreateSubscription() {
         super.testSimpleCreateSubscription();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     protected void testSimpleSubscriptionThroughPhases() {
         super.testSimpleSubscriptionThroughPhases();
     }
 
     @Override
-    @Test(enabled=true, groups={"fast-disabled"})
+    @Test(enabled=true, groups={"fast"})
     protected void testSubscriptionWithAddOn() {
         super.testSubscriptionWithAddOn();
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
index fdd6e66..0973666 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiDemos.java
@@ -26,19 +26,20 @@ import java.util.List;
 import java.util.UUID;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.ProductCategory;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
@@ -69,6 +70,7 @@ public class TestUserApiDemos extends TestApiBase {
     @Test(enabled=true, groups="demos")
     public void testDemo1() throws EntitlementBillingApiException {
 
+        log.info("Starting testSubscriptionWithAddOn");
         try {
             System.out.println("DEMO 1 START");
 
@@ -80,16 +82,17 @@ public class TestUserApiDemos extends TestApiBase {
             displayState(subscription.getId(), "STEP 1. CREATED SUBSCRIPTION");
 
             /* STEP 2. CHANGE PLAN WHILE IN TRIAL */
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Assault-Rifle", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertTrue(testListener.isApiCompleted(3000));
+            assertTrue(testListener.isCompleted(5000));
 
             displayState(subscription.getId(), "STEP 2. CHANGED PLAN WHILE IN TRIAL");
 
             /* STEP 3. MOVE TO DISCOUNT PHASE */
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(3000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             displayState(subscription.getId(), "STEP 3. MOVE TO DISCOUNT PHASE");
 
@@ -103,25 +106,28 @@ public class TestUserApiDemos extends TestApiBase {
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, context);
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Shotgun", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             displayState(subscription.getId(), "STEP 4. SET CTD AND CHANGE PLAN EOT (Shotgun)");
 
             /* STEP 5. CHANGE AGAIN */
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(3000));
             testListener.reset();
 
             displayState(subscription.getId(), "STEP 5. CHANGE AGAIN EOT (Pistol)");
 
             /* STEP 6. MOVE TO EOT AND CHECK CHANGE OCCURED */
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             Plan currentPlan = subscription.getCurrentPlan();
             assertNotNull(currentPlan);
@@ -136,9 +142,10 @@ public class TestUserApiDemos extends TestApiBase {
             displayState(subscription.getId(), "STEP 6. MOVE TO EOT");
 
             /* STEP 7.  MOVE TO NEXT PHASE */
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.addDeltaFromReality(currentPhase.getDuration());
-            assertTrue(testListener.isApiCompleted(5000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(6));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             currentPlan = subscription.getCurrentPlan();
@@ -154,11 +161,12 @@ public class TestUserApiDemos extends TestApiBase {
             displayState(subscription.getId(), "STEP 7.  MOVE TO NEXT PHASE");
 
             /* STEP 8. CANCEL IMM (NO CTD) */
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
 
             displayState(subscription.getId(), "STEP 8.  CANCELLATION");
 
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
         }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index c3f6061..c56e0e0 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -25,6 +25,7 @@ import java.util.UUID;
 import javax.annotation.Nullable;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
@@ -32,11 +33,11 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
 import com.ning.billing.ErrorCode;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PlanPhase;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.glue.MockEngineModuleMemory;
 import com.ning.billing.util.clock.DefaultClock;
@@ -52,6 +53,9 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionBadCatalog() {
+        
+        log.info("Starting testCreateSubscriptionBadCatalog");
+        
         // WRONG PRODUCTS
         tCreateSubscriptionInternal(bundle.getId(), null, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NULL_PRODUCT_NAME);
         tCreateSubscriptionInternal(bundle.getId(), "Whatever", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.CAT_NO_SUCH_PRODUCT);
@@ -68,16 +72,19 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionNoBundle() {
+        log.info("Starting testCreateSubscriptionNoBundle");
         tCreateSubscriptionInternal(null, "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_NO_BUNDLE);
     }
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionNoBP() {
+        log.info("Starting testCreateSubscriptionNoBP");
         tCreateSubscriptionInternal(bundle.getId(), "Telescopic-Scope", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_NO_BP);
     }
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionBPExists() {
+        log.info("Starting testCreateSubscriptionBPExists");
         try {
             createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
             tCreateSubscriptionInternal(bundle.getId(), "Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, ErrorCode.ENT_CREATE_BP_EXISTS);
@@ -89,6 +96,7 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testRecreateSubscriptionBPNotCancelled() {
+        log.info("Starting testRecreateSubscriptionBPNotCancelled");
         try {
             SubscriptionData subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
             try {
@@ -105,6 +113,7 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionAddOnNotAvailable() {
+        log.info("Starting testCreateSubscriptionAddOnNotAvailable");
         try {
             UUID accountId = UUID.randomUUID();
             SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
@@ -118,6 +127,7 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testCreateSubscriptionAddOnIncluded() {
+        log.info("Starting testCreateSubscriptionAddOnIncluded");
         try {
             UUID accountId = UUID.randomUUID();
             SubscriptionBundle aoBundle = entitlementApi.createBundleForAccount(accountId, "myAOBundle", context);
@@ -150,10 +160,11 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testChangeSubscriptionNonActive() {
+        log.info("Starting testChangeSubscriptionNonActive");
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
             try {
                 subscription.changePlan("Pistol", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), context);
@@ -174,15 +185,17 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=true, groups={"fast"})
     public void testChangeSubscriptionFutureCancelled() {
+        log.info("Starting testChangeSubscriptionFutureCancelled");
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
             PlanPhase trialPhase = subscription.getCurrentPhase();
 
             // MOVE TO NEXT PHASE
             PlanPhase currentPhase = subscription.getCurrentPhase();
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(3000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
 
             // SET CTD TO CANCEL IN FUTURE
@@ -204,6 +217,8 @@ public class TestUserApiError extends TestApiBase {
                     assertFalse(true);
                 }
             }
+            
+            assertListenerStatus();
         } catch (Exception e) {
             e.printStackTrace();
             Assert.assertFalse(true);
@@ -213,10 +228,12 @@ public class TestUserApiError extends TestApiBase {
 
     @Test(enabled=false, groups={"fast"})
     public void testCancelBadState() {
+        log.info("Starting testCancelBadState");
     }
 
     @Test(enabled=true, groups={"fast"})
     public void testUncancelBadState() {
+        log.info("Starting testUncancelBadState");
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
 
@@ -230,7 +247,7 @@ public class TestUserApiError extends TestApiBase {
                     assertFalse(true);
                 }
             }
-
+            assertListenerStatus();
         } catch (Exception e) {
             e.printStackTrace();
             Assert.assertFalse(true);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
index b7506dd..389ac5f 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiRecreate.java
@@ -25,9 +25,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 
 public abstract class TestUserApiRecreate extends TestApiBase {
@@ -36,9 +36,10 @@ public abstract class TestUserApiRecreate extends TestApiBase {
 
 
     protected void testRecreateWithBPCanceledThroughSubscription() {
-        log.info("Starting testRecreateWithBPCanceled");
+        log.info("Starting testRecreateWithBPCanceledThroughSubscription");
         try {
             testCreateAndRecreate(false);
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             log.error("Unexpected exception",e);
             Assert.fail(e.getMessage());
@@ -46,9 +47,10 @@ public abstract class TestUserApiRecreate extends TestApiBase {
     }
 
     protected void testCreateWithBPCanceledFromUserApi() {
-        log.info("Starting testCreateWithBPCanceled");
+        log.info("Starting testCreateWithBPCanceledFromUserApi");
         try {
             testCreateAndRecreate(true);
+            assertListenerStatus();
         } catch (EntitlementUserApiException e) {
             log.error("Unexpected exception",e);
             Assert.fail(e.getMessage());
@@ -65,8 +67,8 @@ public abstract class TestUserApiRecreate extends TestApiBase {
         BillingPeriod term = BillingPeriod.MONTHLY;
         String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        testListener.pushExpectedEvent(NextEvent.CREATE);
         SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                 getProductSpecifier(productName, planSetName, term, null), requestedDate, context);
         assertNotNull(subscription);
@@ -75,7 +77,7 @@ public abstract class TestUserApiRecreate extends TestApiBase {
         assertEquals(subscription.getStartDate(), requestedDate);
         assertEquals(productName, subscription.getCurrentPlan().getProduct().getName());
 
-        assertTrue(testListener.isApiCompleted(5000));
+        assertTrue(testListener.isCompleted(5000));
 
         // CREATE (AGAIN) WITH NEW PRODUCT
         productName = "Pistol";
@@ -95,11 +97,11 @@ public abstract class TestUserApiRecreate extends TestApiBase {
         }
 
         // NOW CANCEL ADN THIS SHOULD WORK
-        testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+        testListener.pushExpectedEvent(NextEvent.CANCEL);
         subscription.cancel(null, false, context);
 
-        testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-        testListener.pushNextApiExpectedEvent(NextEvent.RE_CREATE);
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        testListener.pushExpectedEvent(NextEvent.RE_CREATE);
 
         // Avoid ordering issue for events at exact same date; this is actually a real good test, we
         // we test it at Beatrix level. At this level that would work for sql tests but not for in memory.
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
index 92ac1b0..7f874de 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiScenarios.java
@@ -21,17 +21,18 @@ import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
 import org.joda.time.DateTime;
+import org.joda.time.Interval;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhase;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.billing.EntitlementBillingApiException;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
@@ -44,7 +45,7 @@ public class TestUserApiScenarios extends TestApiBase {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
-    @Test(enabled=true)
+    @Test(groups={"slow"}, enabled=true)
     public void testChangeIMMCancelUncancelChangeEOT() throws EntitlementBillingApiException {
 
         log.info("Starting testChangeIMMCancelUncancelChangeEOT");
@@ -54,14 +55,15 @@ public class TestUserApiScenarios extends TestApiBase {
             PlanPhase trialPhase = subscription.getCurrentPhase();
             assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
 
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.ANNUAL, "gunclubDiscount", clock.getUTCNow(), context);
-            testListener.isApiCompleted(3000);
+            testListener.isCompleted(5000);
 
             // MOVE TO NEXT PHASE
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            clock.setDeltaFromReality(trialPhase.getDuration(), DAY_IN_MS);
-            assertTrue(testListener.isApiCompleted(2000));
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
             // SET CTD
             Duration ctd = getDurationMonth(1);
@@ -71,22 +73,28 @@ public class TestUserApiScenarios extends TestApiBase {
             subscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(subscription.getId());
 
             // CANCEL EOT
-            testListener.pushNextApiExpectedEvent(NextEvent.CANCEL);
+            testListener.setNonExpectedMode();
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
             subscription.cancel(clock.getUTCNow(), false, context);
-            assertFalse(testListener.isApiCompleted(2000));
+            assertFalse(testListener.isCompleted(5000));
             testListener.reset();
 
             // UNCANCEL
             subscription.uncancel(context);
 
             // CHANGE EOT
-            testListener.pushNextApiExpectedEvent(NextEvent.CHANGE);
+            testListener.setNonExpectedMode();            
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
             subscription.changePlan("Pistol", BillingPeriod.MONTHLY, "gunclubDiscount", clock.getUTCNow(), context);
-            assertFalse(testListener.isApiCompleted(2000));
-
-            clock.addDeltaFromReality(ctd);
-            assertTrue(testListener.isApiCompleted(3000));
+            assertFalse(testListener.isCompleted(5000));
+            testListener.reset();
+            
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+            clock.addDeltaFromReality(it.toDurationMillis());
+            assertTrue(testListener.isCompleted(5000));
 
+            assertListenerStatus();
 
         } catch (EntitlementUserApiException e) {
             Assert.fail(e.getMessage());
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
index 90966d8..c208521 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserCustomFieldsSql.java
@@ -28,9 +28,9 @@ import org.testng.annotations.Test;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Stage;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.PriceListSet;
-import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
 import com.ning.billing.util.callcontext.CallContext;
@@ -66,7 +66,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testOverwriteCustomFields() {
-        log.info("Starting testCreateWithRequestedDate");
+        log.info("Starting testOverwriteCustomFields");
         try {
 
             DateTime init = clock.getUTCNow();
@@ -76,8 +76,8 @@ public class TestUserCustomFieldsSql extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null), requestedDate, context);
             assertNotNull(subscription);
@@ -122,7 +122,7 @@ public class TestUserCustomFieldsSql extends TestApiBase {
 
     @Test(enabled=true, groups={"slow"})
     public void testBasicCustomFields() {
-        log.info("Starting testCreateWithRequestedDate");
+        log.info("Starting testBasicCustomFields");
         try {
 
             DateTime init = clock.getUTCNow();
@@ -132,8 +132,8 @@ public class TestUserCustomFieldsSql extends TestApiBase {
             BillingPeriod term = BillingPeriod.MONTHLY;
             String planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            testListener.pushNextApiExpectedEvent(NextEvent.PHASE);
-            testListener.pushNextApiExpectedEvent(NextEvent.CREATE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.CREATE);
             SubscriptionData subscription = (SubscriptionData) entitlementApi.createSubscription(bundle.getId(),
                     getProductSpecifier(productName, planSetName, term, null), requestedDate, context);
             assertNotNull(subscription);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
index 9b75099..5eb760c 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/dao/MockEntitlementDaoMemory.java
@@ -158,7 +158,6 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     @Override
     public void createSubscription(final SubscriptionData subscription, final List<EntitlementEvent> initialEvents,
                                    final CallContext context) {
-
         synchronized(events) {
             events.addAll(initialEvents);
             for (final EntitlementEvent cur : initialEvents) {
@@ -285,7 +284,7 @@ public class MockEntitlementDaoMemory implements EntitlementDao, MockEntitlement
     @Override
     public void cancelSubscription(final UUID subscriptionId, final EntitlementEvent cancelEvent,
                                    final CallContext context, final int seqId) {
-        synchronized (cancelEvent) {
+        synchronized(events) {
             cancelNextPhaseEvent(subscriptionId);
             insertEvent(cancelEvent);
         }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
index 1241b7d..4521257 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/AccountTimelineJson.java
@@ -15,16 +15,21 @@
  */
 package com.ning.billing.jaxrs.json;
 
+import java.math.BigDecimal;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.UUID;
 
 import org.codehaus.jackson.annotate.JsonCreator;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonView;
+import org.joda.time.DateTime;
 
 import com.ning.billing.account.api.Account;
+import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.entitlement.api.timeline.BundleTimeline;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentInfoEvent;
 
 public class AccountTimelineJson {
@@ -52,7 +57,7 @@ public class AccountTimelineJson {
         this.payments = payments;
     }
     
-    public AccountTimelineJson(Account account, List<Invoice> invoices, List<PaymentInfoEvent> payments, List<BundleTimeline> bundles) {
+    public AccountTimelineJson(Account account, List<Invoice> invoices, List<PaymentAttempt> payments, List<BundleTimeline> bundles) {
         this.account = new AccountJsonSimple(account.getId().toString(), account.getExternalKey());
         this.bundles = new LinkedList<BundleJsonWithSubscriptions>();
         for (BundleTimeline cur : bundles) {
@@ -63,10 +68,13 @@ public class AccountTimelineJson {
             this.invoices.add(new InvoiceJson(cur.getTotalAmount(), cur.getId().toString(), cur.getInvoiceDate(), Integer.toString(cur.getInvoiceNumber()), cur.getBalance()));
         }
         this.payments = new LinkedList<PaymentJson>();
-        for (PaymentInfoEvent cur : payments) {
-            // STEPH how to link that payment with the invoice ??
-            this.payments.add(new PaymentJson(cur.getAmount(), null , cur.getPaymentNumber(), null, cur.getEffectiveDate(), cur.getStatus()));
-        }
+        for (PaymentAttempt cur : payments) {
+            String status = cur.getPaymentId() != null ? "Success" : "Failed";
+            BigDecimal paidAmount = cur.getPaymentId() != null ? cur.getAmount() : BigDecimal.ZERO;
+            
+            this.payments.add(new PaymentJson(cur.getAmount(), paidAmount, cur.getInvoiceId().toString(), cur.getPaymentId(), cur.getCreatedDate(), cur.getUpdatedDate(),
+                    cur.getRetryCount(), cur.getCurrency().toString(), status));
+          }
     }
     
     public AccountTimelineJson() {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
new file mode 100644
index 0000000..cb9e36b
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/CustomFieldJson.java
@@ -0,0 +1,51 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+
+import com.ning.billing.util.customfield.CustomField;
+
+public class CustomFieldJson {
+
+    private final String name;
+    private final String value;
+    
+    public CustomFieldJson() {
+        this.name = null;
+        this.value = null;
+    }
+    
+    @JsonCreator
+    public CustomFieldJson(String name, String value) {
+        super();
+        this.name = name;
+        this.value = value;
+    }
+    
+    public CustomFieldJson(CustomField input) {
+        this.name = input.getName();
+        this.value = input.getValue();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java
index 30264d7..1a33a04 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/InvoiceJson.java
@@ -23,6 +23,8 @@ import org.codehaus.jackson.annotate.JsonProperty;
 import org.codehaus.jackson.map.annotate.JsonView;
 import org.joda.time.DateTime;
 
+import com.ning.billing.invoice.api.Invoice;
+
 public class InvoiceJson {
 
     @JsonView(BundleTimelineViews.Base.class)
@@ -40,6 +42,15 @@ public class InvoiceJson {
     @JsonView(BundleTimelineViews.Base.class)
     private final BigDecimal balance;
 
+    
+    public InvoiceJson() {
+        this.amount = null;
+        this.invoiceId = null;
+        this.invoiceDate = null;
+        this.invoiceNumber = null;
+        this.balance = null;
+    }
+    
     @JsonCreator
     public InvoiceJson(@JsonProperty("amount") BigDecimal amount,
             @JsonProperty("invoice_id") String invoiceId,
@@ -54,6 +65,14 @@ public class InvoiceJson {
         this.balance = balance;
     }
 
+    public InvoiceJson(Invoice input) {
+        this.amount = input.getTotalAmount();
+        this.invoiceId = input.getId().toString();
+        this.invoiceDate = input.getInvoiceDate();
+        this.invoiceNumber = String.valueOf(input.getInvoiceNumber());
+        this.balance = input.getBalance();
+    }
+    
     public BigDecimal getAmount() {
         return amount;
     }
@@ -73,4 +92,56 @@ public class InvoiceJson {
     public BigDecimal getBalance() {
         return balance;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((amount == null) ? 0 : amount.hashCode());
+        result = prime * result + ((balance == null) ? 0 : balance.hashCode());
+        result = prime * result
+                + ((invoiceDate == null) ? 0 : invoiceDate.hashCode());
+        result = prime * result
+                + ((invoiceId == null) ? 0 : invoiceId.hashCode());
+        result = prime * result
+                + ((invoiceNumber == null) ? 0 : invoiceNumber.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        InvoiceJson other = (InvoiceJson) obj;
+        if (amount == null) {
+            if (other.amount != null)
+                return false;
+        } else if (!amount.equals(other.amount))
+            return false;
+        if (balance == null) {
+            if (other.balance != null)
+                return false;
+        } else if (!balance.equals(other.balance))
+            return false;
+        if (invoiceDate == null) {
+            if (other.invoiceDate != null)
+                return false;
+        } else if (!invoiceDate.equals(other.invoiceDate))
+            return false;
+        if (invoiceId == null) {
+            if (other.invoiceId != null)
+                return false;
+        } else if (!invoiceId.equals(other.invoiceId))
+            return false;
+        if (invoiceNumber == null) {
+            if (other.invoiceNumber != null)
+                return false;
+        } else if (!invoiceNumber.equals(other.invoiceNumber))
+            return false;
+        return true;
+    }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java
index 3a9fd4d..50df2ef 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/PaymentJson.java
@@ -27,37 +27,55 @@ import com.ning.billing.util.clock.DefaultClock;
 
 public class PaymentJson {
 
-    @JsonView(BundleTimelineViews.Base.class)
     private final BigDecimal paidAmount;
-    
-    @JsonView(BundleTimelineViews.Base.class)
+
+    private final BigDecimal amount;
+
     private final String invoiceId;
     
-    @JsonView(BundleTimelineViews.Base.class)
     private final String paymentId;
     
-    @JsonView(BundleTimelineViews.Base.class)
     private final DateTime requestedDate;
     
-    @JsonView(BundleTimelineViews.Base.class)
     private final DateTime effectiveDate;
     
-    @JsonView(BundleTimelineViews.Base.class)
+    private final Integer retryCount;
+    
+    private final String currency;
+    
     private final String status;
+      
+    public PaymentJson() {
+        this.amount = null;
+        this.paidAmount = null;
+        this.invoiceId = null;
+        this.paymentId = null;
+        this.requestedDate = null;
+        this.effectiveDate = null;
+        this.currency = null;
+        this.retryCount = null;
+        this.status = null;
+    }
 
     @JsonCreator
-    public PaymentJson(@JsonProperty("paid_amount") BigDecimal paidAmount,
+    public PaymentJson(@JsonProperty("amount") BigDecimal amount,
+            @JsonProperty("paid_amount") BigDecimal paidAmount,
             @JsonProperty("invoice_id") String invoiceId,
             @JsonProperty("payment_id") String paymentId,
             @JsonProperty("requested_dt") DateTime requestedDate,
             @JsonProperty("effective_dt") DateTime effectiveDate,
+            @JsonProperty("retry_count") Integer retryCount,
+            @JsonProperty("currency") String currency,            
             @JsonProperty("status") String status) {
         super();
+        this.amount = amount;
         this.paidAmount = paidAmount;
         this.invoiceId = invoiceId;
         this.paymentId = paymentId;
         this.requestedDate = DefaultClock.toUTCDateTime(requestedDate);
         this.effectiveDate = DefaultClock.toUTCDateTime(effectiveDate);
+        this.currency = currency;
+        this.retryCount = retryCount;
         this.status = status;
     }
 
@@ -84,4 +102,16 @@ public class PaymentJson {
     public String getStatus() {
         return status;
     }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public Integer getRetryCount() {
+        return retryCount;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
index fea22e6..2f798df 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/SubscriptionJsonWithEvents.java
@@ -51,6 +51,12 @@ public class SubscriptionJsonWithEvents extends SubscriptionJsonSimple {
         @JsonView(BundleTimelineViews.Timeline.class)
         private final DateTime effectiveDate;
 
+        public SubscriptionReadEventJson() {
+            super();
+            this.eventId = null;
+            this.effectiveDate = null;
+        }
+ 
         @JsonCreator
         public SubscriptionReadEventJson(@JsonProperty("event_id") String eventId,
                 @JsonProperty("billing_period") String billingPeriod,
@@ -148,6 +154,15 @@ public class SubscriptionJsonWithEvents extends SubscriptionJsonSimple {
         @JsonView(BundleTimelineViews.Timeline.class)
         private final String phase;
 
+        public SubscriptionBaseEventJson() {
+            this.billingPeriod = null;
+            this.requestedDate = null;
+            this.product = null;
+            this.priceList = null;
+            this.eventType = null;
+            this.phase = null;
+        }
+        
         @JsonCreator
         public SubscriptionBaseEventJson(@JsonProperty("billing_period") String billingPeriod,
                 @JsonProperty("requested_dt") DateTime requestedDate,
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java
new file mode 100644
index 0000000..25ef04c
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/json/TagDefinitionJson.java
@@ -0,0 +1,77 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs.json;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonProperty;
+
+public class TagDefinitionJson {
+    
+    private final String name;
+    private final String description;
+
+    public TagDefinitionJson()  {
+        this.name = null;
+        this.description = null;
+    }
+    
+    @JsonCreator
+    public TagDefinitionJson(@JsonProperty("name") String name,
+            @JsonProperty("description") String description) {
+        super();
+        this.name = name;
+        this.description = description;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((description == null) ? 0 : description.hashCode());
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        TagDefinitionJson other = (TagDefinitionJson) obj;
+        if (description == null) {
+            if (other.description != null)
+                return false;
+        } else if (!description.equals(other.description))
+            return false;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        return true;
+    }
+}
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
index 29883f1..9061eac 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/AccountResource.java
@@ -18,7 +18,7 @@ package com.ning.billing.jaxrs.resources;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
-import java.net.URI;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -29,6 +29,7 @@ import java.util.UUID;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
@@ -37,12 +38,12 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriBuilder;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Collections2;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -61,10 +62,19 @@ import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.AccountTimelineJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
 import com.ning.billing.jaxrs.util.Context;
 import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.TagHelper;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
 import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.customfield.StringCustomField;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
 
 
 @Singleton
@@ -79,8 +89,10 @@ public class AccountResource implements BaseJaxrsResource {
     private final InvoiceUserApi invoiceApi;
     private final PaymentApi paymentApi;
     private final Context context;
+    private final TagUserApi tagUserApi;
     private final JaxrsUriBuilder uriBuilder;
-
+    private final TagHelper tagHelper;
+    
     @Inject
     public AccountResource(final JaxrsUriBuilder uriBuilder,
             final AccountUserApi accountApi,
@@ -88,14 +100,18 @@ public class AccountResource implements BaseJaxrsResource {
             final InvoiceUserApi invoiceApi,
             final PaymentApi paymentApi,
             final EntitlementTimelineApi timelineApi,
+            final TagUserApi tagUserApi,
+            final TagHelper tagHelper,
             final Context context) {
         this.uriBuilder = uriBuilder;
     	this.accountApi = accountApi;
+    	this.tagUserApi = tagUserApi;
         this.entitlementApi = entitlementApi;
         this.invoiceApi = invoiceApi;
         this.paymentApi = paymentApi;
         this.timelineApi = timelineApi;
         this.context = context;
+        this.tagHelper = tagHelper;
     }
 
     @GET
@@ -157,17 +173,22 @@ public class AccountResource implements BaseJaxrsResource {
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createAccount(AccountJson json) {
+    public Response createAccount(final AccountJson json,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
 
         try {
-        	
             AccountData data = json.toAccountData();
-            final Account account = accountApi.createAccount(data, null, null, context.createContext());
-            URI uri = UriBuilder.fromPath(account.getId().toString()).build();
-            return uriBuilder.buildResponse(AccountResource.class, "getAccount", account.getId());
+            final Account account = accountApi.createAccount(data, null, null, context.createContext(createdBy, reason, comment));
+            Response response = uriBuilder.buildResponse(AccountResource.class, "getAccount", account.getId());
+            return response;
         } catch (AccountApiException e) {
-            log.info(String.format("Failed to create account %s", json), e);
-            return Response.status(Status.BAD_REQUEST).build();
+            final String error = String.format("Failed to create account %s", json);
+            log.info(error, e);
+            return Response.status(Status.BAD_REQUEST).entity(error).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
         }
     }
 
@@ -175,11 +196,15 @@ public class AccountResource implements BaseJaxrsResource {
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     @Path("/{accountId:" + UUID_PATTERN + "}")
-    public Response updateAccount(AccountJson json, @PathParam("accountId") String accountId) {
+    public Response updateAccount(final AccountJson json,
+            @PathParam("accountId") final String accountId,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
         try {
             AccountData data = json.toAccountData();
             UUID uuid = UUID.fromString(accountId);
-            accountApi.updateAccount(uuid, data, context.createContext());
+            accountApi.updateAccount(uuid, data, context.createContext(createdBy, reason, comment));
             return getAccount(accountId);
         } catch (AccountApiException e) {
         	if (e.getCode() == ErrorCode.ACCOUNT_DOES_NOT_EXIST_FOR_ID.getCode()) {
@@ -188,6 +213,8 @@ public class AccountResource implements BaseJaxrsResource {
         		log.info(String.format("Failed to update account %s with %s", accountId, json), e);
         		return Response.status(Status.BAD_REQUEST).build();
         	}
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
         }
     }
 
@@ -216,7 +243,7 @@ public class AccountResource implements BaseJaxrsResource {
             Account account = accountApi.getAccountById(UUID.fromString(accountId));
            
             List<Invoice> invoices = invoiceApi.getInvoicesByAccount(account.getId());
-            List<PaymentInfoEvent> payments = Collections.emptyList();
+            List<PaymentAttempt> payments = new LinkedList<PaymentAttempt>();
 
             if (invoices.size() > 0) {
                 Collection<String> tmp = Collections2.transform(invoices, new Function<Invoice, String>() {
@@ -227,8 +254,9 @@ public class AccountResource implements BaseJaxrsResource {
                 });
                 List<String> invoicesId = new ArrayList<String>();
                 invoicesId.addAll(tmp);
-
-                payments = paymentApi.getPaymentInfo(invoicesId);
+                for (String curId : invoicesId) {
+                    payments.addAll(paymentApi.getPaymentAttemptsForInvoiceId(curId));
+                }
             }
 
             List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(account.getId());
@@ -245,4 +273,174 @@ public class AccountResource implements BaseJaxrsResource {
             return Response.status(Status.INTERNAL_SERVER_ERROR).build();
         }
     }
+    
+    
+    /****************************      TAGS     ******************************/
+    
+    @GET
+    @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getAccountTags(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            List<Tag> tags = account.getTagList();
+            Collection<String> tagNameList = (tags.size() == 0) ?
+                    Collections.<String>emptyList() :
+                Collections2.transform(tags, new Function<Tag, String>() {
+                @Override
+                public String apply(Tag input) {
+                    return input.getTagDefinitionName();
+                }
+            });
+            return Response.status(Status.OK).entity(tagNameList).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+    }
+
+    
+    @POST
+    @Path(BaseJaxrsResource.TAGS + "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createAccountTag(@PathParam("accountId") final String accountId,
+            @QueryParam(QUERY_TAGS) final String tagList,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            Preconditions.checkNotNull(tagList, "Query % list cannot be null", QUERY_TAGS);
+            
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+            List<TagDefinition> input = tagHelper.getTagDifinitionFromTagList(tagList);
+            account.addTagsFromDefinitions(input);
+            Response response = uriBuilder.buildResponse(AccountResource.class, "getAccountTags", account.getId());
+            return response;
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    @DELETE
+    @Path(BaseJaxrsResource.TAGS +  "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteAccountTag(@PathParam("accountId") final String accountId,
+            @QueryParam(QUERY_TAGS) final String tagList,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+
+            // Tag APIs needs tome rework...
+            String inputTagList = tagList;
+            if (inputTagList == null) {
+                List<Tag> existingTags = account.getTagList();
+                StringBuilder tmp = new StringBuilder();
+                for (Tag cur : existingTags) {
+                    tmp.append(cur.getTagDefinitionName());
+                    tmp.append(",");
+                }
+                inputTagList = tmp.toString();
+            }
+
+            List<TagDefinition> input = tagHelper.getTagDifinitionFromTagList(tagList);   
+            for (TagDefinition cur : input) {
+                account.removeTag(cur);
+            }
+
+            return Response.status(Status.OK).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    /************************   CUSTOM FIELDS   ******************************/
+    
+    @GET
+    @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getAccountCustomFields(@PathParam("accountId") String accountId) {
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            List<CustomField> fields = account.getFieldList();
+            List<CustomFieldJson> result = new LinkedList<CustomFieldJson>();
+            for (CustomField cur : fields) {
+                result.add(new CustomFieldJson(cur));
+            }
+            return Response.status(Status.OK).entity(result).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+    }
+    
+    
+    @POST
+    @Path(BaseJaxrsResource.CUSTOM_FIELDS + "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createCustomField(@PathParam("accountId") final String accountId,
+            List<CustomFieldJson> customFields,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            LinkedList<CustomField> input = new LinkedList<CustomField>();
+            for (CustomFieldJson cur : customFields) {
+                input.add(new StringCustomField(cur.getName(), cur.getValue()));
+            }
+            account.saveFields(input, context.createContext(createdBy, reason, comment));
+            Response response = uriBuilder.buildResponse(AccountResource.class, "getAccountCustomFields", account.getId());            
+            return response;
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    @DELETE
+    @Path(BaseJaxrsResource.CUSTOM_FIELDS +  "/{accountId:" + UUID_PATTERN + "}")    
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response deleteCustomFields(@PathParam("accountId") final String accountId,
+            @QueryParam(QUERY_CUSTOM_FIELDS) final String cutomFieldList,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            Account account = accountApi.getAccountById(UUID.fromString(accountId));
+            // STEPH missing API to delete custom fields
+            return Response.status(Status.OK).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
index 79f4b04..2a9e2ee 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BaseJaxrsResource.java
@@ -17,14 +17,25 @@ package com.ning.billing.jaxrs.resources;
 
 public interface BaseJaxrsResource {
 	
-	public static final String API_PREFIX = "";
-	public static final String API_VERSION = "/1.0";
+    public static final String API_PREFIX = "";
+    public static final String API_VERSION = "/1.0";
+    public static final String API_POSTFIX = "/kb";
+    
+    public static final String PREFIX = API_PREFIX + API_VERSION + API_POSTFIX;
 	
 	public static final String TIMELINE = "timeline";
 	
 	/*
+	 * Metadata Additional headers 
+	 */
+	public static String HDR_CREATED_BY = "X-Killbill-CreatedBy";
+	public static String HDR_REASON = "X-Killbill-Reason";  
+	public static String HDR_COMMENT = "X-Killbill-Comment";   	
+	
+	/*
 	 * Patterns
 	 */
+	public static String STRING_PATTERN = "\\w+";	
 	public static String UUID_PATTERN = "\\w+-\\w+-\\w+-\\w+-\\w+";
 	
 	/*
@@ -33,15 +44,30 @@ public interface BaseJaxrsResource {
 	public static final String QUERY_EXTERNAL_KEY = "external_key";
 	public static final String QUERY_REQUESTED_DT = "requested_date";
 	public static final String QUERY_CALL_COMPLETION = "call_completion";
-	public static final String QUERY_CALL_TIMEOUT = "call_timeout_sec";	
+	public static final String QUERY_CALL_TIMEOUT = "call_timeout_sec";    
+	public static final String QUERY_DRY_RUN = "dry_run";      
+	public static final String QUERY_TARGET_DATE = "target_date";          
+	public static final String QUERY_ACCOUNT_ID = "account_id";           	
 	
-	public static final String ACCOUNTS = "accounts";	
-	public static final String ACCOUNTS_PATH = API_PREFIX + API_VERSION + "/" + ACCOUNTS;
+	public static final String QUERY_TAGS = "tag_list";    
+	public static final String QUERY_CUSTOM_FIELDS = "custom_field_list";    	
+	
+	public static final String ACCOUNTS = "accounts";  
+    public static final String ACCOUNTS_PATH = PREFIX + "/" + ACCOUNTS;
 
 	public static final String BUNDLES = "bundles";		
-	public static final String BUNDLES_PATH = API_PREFIX + API_VERSION + "/" + BUNDLES;
+	public static final String BUNDLES_PATH = PREFIX + "/" + BUNDLES;
+
+    public static final String SUBSCRIPTIONS = "subscriptions";     
+    public static final String SUBSCRIPTIONS_PATH = PREFIX + "/" + SUBSCRIPTIONS;
+
+    public static final String TAG_DEFINITIONS = "tag_definitions";     
+    public static final String TAG_DEFINITIONS_PATH = PREFIX + "/" + TAG_DEFINITIONS;
 
-	public static final String SUBSCRIPTIONS = "subscriptions";		
-	public static final String SUBSCRIPTIONS_PATH = API_PREFIX + API_VERSION + "/" + SUBSCRIPTIONS;
+    public static final String INVOICES = "invoices";     
+    public static final String INVOICES_PATH = PREFIX + "/" + INVOICES;
 
+    
+    public static final String TAGS = "tags";
+    public static final String CUSTOM_FIELDS = "custom_fields";    
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
index d3bb1a2..e62d48e 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/BundleResource.java
@@ -24,6 +24,7 @@ import java.util.UUID;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -102,14 +103,20 @@ public class BundleResource implements BaseJaxrsResource {
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createBundle(final BundleJsonNoSubsciptions json) {
+    public Response createBundle(final BundleJsonNoSubsciptions json,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
         try {
             UUID accountId = UUID.fromString(json.getAccountId());
-            final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(accountId, json.getExternalKey(), context.createContext());
+            final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(accountId, json.getExternalKey(),
+                    context.createContext(createdBy, reason, comment));
             return uriBuilder.buildResponse(BundleResource.class, "getBundle", bundle.getId());
         } catch (EntitlementUserApiException e) {
             log.info(String.format("Failed to create bundle %s", json), e);
             return Response.status(Status.BAD_REQUEST).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
         }
     }
 
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index e5a1c97..5cc35bd 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -18,8 +18,14 @@ package com.ning.billing.jaxrs.resources;
 
 import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
+import java.util.LinkedList;
+import java.util.List;
+import java.util.UUID;
+
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -29,34 +35,117 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+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.entitlement.api.timeline.EntitlementTimelineApi;
+import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceApiException;
+import com.ning.billing.invoice.api.InvoiceUserApi;
+import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.InvoiceJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.jaxrs.util.TagHelper;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.util.api.TagUserApi;
+
 
 
+@Path(BaseJaxrsResource.INVOICES_PATH)
+public class InvoiceResource implements BaseJaxrsResource {
 
-@Path("/1.0/invoice")
-public class InvoiceResource {
 
+    private static final Logger log = LoggerFactory.getLogger(AccountResource.class);
 
+    private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+    
+    private final AccountUserApi accountApi;
+    private final InvoiceUserApi invoiceApi;
+    private final Context context;
+    private final JaxrsUriBuilder uriBuilder;
+    
+    @Inject
+    public InvoiceResource(final AccountUserApi accountApi,
+            final InvoiceUserApi invoiceApi,
+            final Context context,
+            final JaxrsUriBuilder uriBuilder) {
+        this.accountApi = accountApi;
+        this.invoiceApi = invoiceApi;
+        this.context = context;
+        this.uriBuilder = uriBuilder;
+    }
+    
     @GET
     @Produces(APPLICATION_JSON)
-    public Response getInvoices(@QueryParam("accountId") String accountId) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    public Response getInvoices(@QueryParam(QUERY_ACCOUNT_ID) final String accountId) {
+        try {
+            
+            Preconditions.checkNotNull(accountId, "% query parameter must be specified", QUERY_ACCOUNT_ID);
+            accountApi.getAccountById(UUID.fromString(accountId));
+            List<Invoice> invoices = invoiceApi.getInvoicesByAccount(UUID.fromString(accountId));
+            List<InvoiceJson> result = new LinkedList<InvoiceJson>();
+            for (Invoice cur : invoices) {
+                result.add(new InvoiceJson(cur));
+            }
+            return Response.status(Status.OK).entity(result).build();
+        } catch (AccountApiException e) {
+            return Response.status(Status.NO_CONTENT).build();            
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).build();            
+        }
     }
 
     @GET
     @Path("/{invoiceId:\\w+-\\w+-\\w+-\\w+-\\w+}")
     @Produces(APPLICATION_JSON)
-    public Response getInvoice(@PathParam("invoiceId") String accountId) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    public Response getInvoice(@PathParam("invoiceId") String invoiceId) {
+        Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId));
+        InvoiceJson json = new InvoiceJson(invoice);
+        return Response.status(Status.OK).entity(json).build();
     }
 
     @POST
-    @Path("/{accountId:\\w+-\\w+-\\w+-\\w+-\\w+}")
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
-    public Response createFutureInvoice(InvoiceJson invoice,
-            @PathParam("accountId") String accountId,
-            @QueryParam("targetDate") String targetDate) {
-        return Response.status(Status.INTERNAL_SERVER_ERROR).build();
+    public Response createFutureInvoice(final InvoiceJson invoice,
+            @QueryParam(QUERY_ACCOUNT_ID) final String accountId,
+            @QueryParam(QUERY_TARGET_DATE) final String targetDate,
+            @QueryParam(QUERY_DRY_RUN) @DefaultValue("false") final Boolean dryRun,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+
+        try {
+            
+            Preconditions.checkNotNull(accountId, "% needs to be specified", QUERY_ACCOUNT_ID);
+            Preconditions.checkNotNull(targetDate, "% needs to be specified", QUERY_TARGET_DATE);
+            
+            DateTime inputDate = (targetDate != null) ? DATE_TIME_FORMATTER.parseDateTime(targetDate) : null;        
+            
+            accountApi.getAccountById(UUID.fromString(accountId));
+            Invoice generatedInvoice = invoiceApi.triggerInvoiceGeneration(UUID.fromString(accountId), inputDate, dryRun.booleanValue(),
+                    context.createContext(createdBy, reason, comment));
+            if (dryRun) {
+                return Response.status(Status.OK).entity(new InvoiceJson(generatedInvoice)).build();
+            } else {
+               return uriBuilder.buildResponse(InvoiceResource.class, "getInvoice", generatedInvoice.getId());
+            }
+        } catch (AccountApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();  
+        } catch (InvoiceApiException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();  
+        } catch (NullPointerException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();            
+        }
     }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
index 259fe9f..aeb3f7a 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/SubscriptionResource.java
@@ -26,6 +26,7 @@ import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
@@ -103,47 +104,18 @@ public class SubscriptionResource implements BaseJaxrsResource {
 
         }
     }
-    
-    /*
-    @GET
-    @Path("/{subscriptionId:" + UUID_PATTERN + "}")
-    @Produces(APPLICATION_JSON)
-    public StreamingOutput getSubscription(@PathParam("subscriptionId") final String subscriptionId) {
-
-        UUID uuid = UUID.fromString(subscriptionId);
-        final Subscription subscription = entitlementApi.getSubscriptionFromId(uuid);
-        if (subscription == null) {
-            throw new WebApplicationException(Response.Status.NO_CONTENT);
-        }
-        final SubscriptionJson json = new SubscriptionJson(subscription, null, null, null);
-        return new StreamingOutput() {
-
-            final SubscriptionJson json = new SubscriptionJson(subscription, null, null, null);
-            
-            @Override
-            public void write(OutputStream output) throws IOException,
-                    WebApplicationException {
-                
-                final ObjectWriter objWriter = objectMapper.writerWithView(BundleTimelineViews.Base.class);
-                
-                Writer writer = new StringWriter();
-                objWriter.writeValue(writer, json);
-                String baseJson = writer.toString();
-                output.write(baseJson.getBytes());
-                output.flush();
-            }
-        };
-    }
-    */
-
+  
 
     @POST
     @Consumes(APPLICATION_JSON)
     @Produces(APPLICATION_JSON)
     public Response createSubscription(final SubscriptionJsonNoEvents subscription,
-            final @QueryParam(QUERY_REQUESTED_DT) String requestedDate,
-            final @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") Boolean callCompletion,
-            final @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") long timeoutSec) {
+            @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+            @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+            @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
 
 
         SubscriptionCallCompletionCallback<Subscription> callback = new SubscriptionCallCompletionCallback<Subscription>() {
@@ -168,7 +140,7 @@ public class SubscriptionResource implements BaseJaxrsResource {
             }
         };
         SubscriptionCallCompletion<Subscription> callCompletionCreation = new SubscriptionCallCompletion<Subscription>();
-        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion);
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
     }
 
     @PUT
@@ -176,10 +148,13 @@ public class SubscriptionResource implements BaseJaxrsResource {
     @Consumes(APPLICATION_JSON)
     @Path("/{subscriptionId:" + UUID_PATTERN + "}")
     public Response changeSubscriptionPlan(final SubscriptionJsonNoEvents subscription,
-            final @PathParam("subscriptionId") String subscriptionId,
-            final @QueryParam(QUERY_REQUESTED_DT) String requestedDate,
-            final @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") Boolean callCompletion,
-            final @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") long timeoutSec) {
+            @PathParam("subscriptionId") final String subscriptionId,
+            @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+            @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+            @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
 
         SubscriptionCallCompletionCallback<Response> callback = new SubscriptionCallCompletionCallback<Response>() {
 
@@ -222,18 +197,21 @@ public class SubscriptionResource implements BaseJaxrsResource {
             }
         };
         SubscriptionCallCompletion<Response> callCompletionCreation = new SubscriptionCallCompletion<Response>();
-        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion);
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
     }
 
     @PUT
     @Path("/{subscriptionId:" + UUID_PATTERN + "}/uncancel")
     @Produces(APPLICATION_JSON)
-    public Response uncancelSubscriptionPlan(@PathParam("subscriptionId") String subscriptionId) {
+    public Response uncancelSubscriptionPlan(@PathParam("subscriptionId") final String subscriptionId,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
         try {
             UUID uuid = UUID.fromString(subscriptionId);
             Subscription current = entitlementApi.getSubscriptionFromId(uuid);
         
-            current.uncancel(context.createContext());
+            current.uncancel(context.createContext(createdBy, reason, comment));
             return Response.status(Status.OK).build();
         } catch (EntitlementUserApiException e) {
             if(e.getCode() == ErrorCode.ENT_INVALID_SUBSCRIPTION_ID.getCode()) {
@@ -249,9 +227,12 @@ public class SubscriptionResource implements BaseJaxrsResource {
     @Path("/{subscriptionId:" + UUID_PATTERN + "}")
     @Produces(APPLICATION_JSON)
     public Response cancelSubscriptionPlan(final @PathParam("subscriptionId") String subscriptionId,
-            final @QueryParam(QUERY_REQUESTED_DT) String requestedDate,
-            final @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") Boolean callCompletion,
-            final @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") long timeoutSec) {
+            @QueryParam(QUERY_REQUESTED_DT) final String requestedDate,
+            @QueryParam(QUERY_CALL_COMPLETION) @DefaultValue("false") final Boolean callCompletion,
+            @QueryParam(QUERY_CALL_TIMEOUT) @DefaultValue("3") final long timeoutSec,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
 
         SubscriptionCallCompletionCallback<Response> callback = new SubscriptionCallCompletionCallback<Response>() {
 
@@ -287,7 +268,7 @@ public class SubscriptionResource implements BaseJaxrsResource {
             }
         };
         SubscriptionCallCompletion<Response> callCompletionCreation = new SubscriptionCallCompletion<Response>();
-        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion);
+        return callCompletionCreation.withSynchronization(callback, timeoutSec, callCompletion, createdBy, reason, comment);
     }
 
     private final static class CompletionUserRequestSubscription extends CompletionUserRequestBase {
@@ -332,9 +313,14 @@ public class SubscriptionResource implements BaseJaxrsResource {
 
     private class SubscriptionCallCompletion<T> {
 
-        public Response withSynchronization(final SubscriptionCallCompletionCallback<T> callback, final long timeoutSec, final boolean callCompletion) {
+        public Response withSynchronization(final SubscriptionCallCompletionCallback<T> callback,
+                final long timeoutSec,
+                final boolean callCompletion,
+                final String createdBy,
+                final String reason,
+                final String comment) {
 
-            CallContext ctx = context.createContext();
+            CallContext ctx = context.createContext(createdBy, reason, comment);
             CompletionUserRequestSubscription waiter = callCompletion ? new CompletionUserRequestSubscription(ctx.getUserToken()) : null; 
             try {
                 if (waiter != null) {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
index cc19095..3111240 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/TagResource.java
@@ -16,6 +16,104 @@
 
 package com.ning.billing.jaxrs.resources;
 
-public class TagResource {
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.HeaderParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.util.Context;
+import com.ning.billing.jaxrs.util.JaxrsUriBuilder;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+@Singleton
+@Path(BaseJaxrsResource.TAG_DEFINITIONS_PATH)
+public class TagResource implements BaseJaxrsResource {
+    
+    private final TagUserApi tagUserApi;
+    private final Context context;
+    private final JaxrsUriBuilder uriBuilder;
+    
+    @Inject
+    public TagResource(TagUserApi tagUserApi, final JaxrsUriBuilder uriBuilder, final Context context) {
+        this.tagUserApi = tagUserApi;
+        this.context = context;
+        this.uriBuilder = uriBuilder;
+    }
+    
+    @GET
+    @Produces(APPLICATION_JSON)
+    public Response getTagDefinitions() {
+        
+        List<TagDefinitionJson> result = new LinkedList<TagDefinitionJson>();
+        List<TagDefinition> tagDefinitions = tagUserApi.getTagDefinitions();
+        for (TagDefinition cur : tagDefinitions) {
+            result.add(new TagDefinitionJson(cur.getName(), cur.getDescription()));
+        }
+        return Response.status(Status.OK).entity(result).build();
+    }
+    
+    @GET
+    @Path("/{tagDefinitionName:" + STRING_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response getTagDefinition(@PathParam("tagDefinitionName") final String tagDefName) {
+        try {
+            TagDefinition tagDef = tagUserApi.getTagDefinition(tagDefName);
+            TagDefinitionJson json = new TagDefinitionJson(tagDef.getName(), tagDef.getDescription());
+            return Response.status(Status.OK).entity(json).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.NO_CONTENT).build(); 
+        }
+    }
+
+
+
+    @POST
+    @Consumes(APPLICATION_JSON)
+    @Produces(APPLICATION_JSON)
+    public Response createTagDefinition(final TagDefinitionJson json,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            TagDefinition createdTagDef =  tagUserApi.create(json.getName(), json.getDescription(), context.createContext(createdBy, reason, comment));
+            return uriBuilder.buildResponse(TagResource.class, "getTagDefinition", createdTagDef.getName());
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.NO_CONTENT).build(); 
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
+    
+    @DELETE
+    @Path("/{tagDefinitionName:" + STRING_PATTERN + "}")
+    @Produces(APPLICATION_JSON)
+    public Response deleteTagDefinition(@PathParam("tagDefinitionName") String tagDefName,
+            @HeaderParam(HDR_CREATED_BY) final String createdBy,
+            @HeaderParam(HDR_REASON) final String reason,
+            @HeaderParam(HDR_COMMENT) final String comment) {
+        try {
+            tagUserApi.deleteTagDefinition(tagDefName, context.createContext(createdBy, reason, comment));
+            return Response.status(Status.NO_CONTENT).build();
+        } catch (TagDefinitionApiException e) {
+            return Response.status(Status.NO_CONTENT).build(); 
+        } catch (IllegalArgumentException e) {
+            return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
+        }
+    }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
index 004a468..729f166 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/Context.java
@@ -17,7 +17,9 @@ package com.ning.billing.jaxrs.util;
 
 import java.util.UUID;
 
+import com.google.common.base.Preconditions;
 import com.google.inject.Inject;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextFactory;
 import com.ning.billing.util.callcontext.CallOrigin;
@@ -37,8 +39,13 @@ public class Context {
         this.contextFactory = factory;
     }
 
-    // Simplistic until we decide how to populate that
-    public CallContext createContext() {
-        return contextFactory.createCallContext("Unknown", origin, userType, UUID.randomUUID());
+    public CallContext createContext(final String createdBy, final String reason, final String comment)
+    throws IllegalArgumentException {
+        try {
+            Preconditions.checkNotNull(createdBy, String.format("Header %s needs to be set", BaseJaxrsResource.HDR_CREATED_BY));
+            return contextFactory.createCallContext(createdBy, origin, userType, UUID.randomUUID());
+        } catch (NullPointerException e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
     }
 }
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
index a38a9fe..b351b4c 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/JaxrsUriBuilder.java
@@ -26,7 +26,7 @@ import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 public class JaxrsUriBuilder {
 
 	
-	public Response buildResponse(final Class<? extends BaseJaxrsResource> theClass, final String getMethodName, final UUID objectId) {
+	public Response buildResponse(final Class<? extends BaseJaxrsResource> theClass, final String getMethodName, final Object objectId) {
 		URI uri = UriBuilder.fromPath(objectId.toString()).build();
 		Response.ResponseBuilder ri = Response.created(uri);
 		return ri.entity(new Object() {
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java
new file mode 100644
index 0000000..e965deb
--- /dev/null
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/util/TagHelper.java
@@ -0,0 +1,49 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs.util;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.util.api.TagDefinitionApiException;
+import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class TagHelper {
+
+    private final TagUserApi tagUserApi;
+    
+    @Inject
+    public TagHelper(final TagUserApi tagUserApi) {
+        this.tagUserApi = tagUserApi;
+    }
+    
+    public List<TagDefinition> getTagDifinitionFromTagList(final String tagList) throws TagDefinitionApiException {
+        List<TagDefinition> result = new LinkedList<TagDefinition>();
+        String [] tagParts = tagList.split(",\\s*");
+        for (String cur : tagParts) {
+            TagDefinition curDef = tagUserApi.getTagDefinition(cur);
+            // Yack should throw excption
+            if (curDef == null) {
+                throw new TagDefinitionApiException(ErrorCode.TAG_DEFINITION_DOES_NOT_EXIST, cur);
+            }
+            result.add(curDef);
+        }
+        return result;
+    }
+}
diff --git a/jaxrs/src/main/resources/.dont-let-git-remove-this-directory b/jaxrs/src/main/resources/.dont-let-git-remove-this-directory
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/jaxrs/src/main/resources/.dont-let-git-remove-this-directory
diff --git a/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
new file mode 100644
index 0000000..df4f4c3
--- /dev/null
+++ b/overdue/src/test/java/com/ning/billing/overdue/config/TestOverdueConfig.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.overdue.config;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.testng.annotations.Test;
+
+import com.ning.billing.util.config.XMLLoader;
+
+public class TestOverdueConfig {
+    private String xml = 
+            "<overdueConfig>" +
+                    "   <bundleOverdueStates>" +
+                    "       <state name=\"OD1\">" +
+                    "           <condition>" +
+                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "                   <unit>MONTHS</unit><number>1</number>" +
+                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "           </condition>" +
+                    "           <externalMessage>Reached OD1</externalMessage>" +
+                    "           <blockChanges>true</blockChanges>" +
+                    "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                    "       </state>" +
+                    "       <state name=\"OD2\">" +
+                    "           <condition>" +
+                    "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "                   <unit>MONTHS</unit><number>2</number>" +
+                    "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                    "           </condition>" +
+                    "           <externalMessage>Reached OD1</externalMessage>" +
+                    "           <blockChanges>true</blockChanges>" +
+                    "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                    "       </state>" +
+                    "   </bundleOverdueStates>" +
+                    "</overdueConfig>";
+
+    @Test
+    public void testParseConfig() throws Exception {
+        InputStream is = new ByteArrayInputStream(xml.getBytes());
+        OverdueConfig c = XMLLoader.getObjectFromStreamNoValidation(is,  OverdueConfig.class);
+
+    }
+
+}
diff --git a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
index 3ec9e22..227e94a 100644
--- a/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
+++ b/server/src/main/java/com/ning/billing/server/listeners/KillbillGuiceListener.java
@@ -26,11 +26,11 @@ import com.ning.billing.util.bus.BusService;
 import com.ning.jetty.base.modules.ServerModuleBuilder;
 import com.ning.jetty.core.listeners.SetupServer;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
 import com.google.inject.Injector;
 import com.google.inject.Module;
 
-import org.codehaus.jackson.map.ObjectMapper;
-import org.codehaus.jackson.map.SerializationConfig;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -72,6 +72,9 @@ public class KillbillGuiceListener extends SetupServer
         killbillBusService = theInjector.getInstance(BusService.class);
         killbilleventHandler = theInjector.getInstance(KillbillEventHandler.class); 
         
+        ObjectMapper mapper = theInjector.getInstance(ObjectMapper.class);
+        mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());
+
         //
         // Fire all Startup levels up to service start
         //
diff --git a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
index cb126da..d6c3691 100644
--- a/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
+++ b/server/src/main/java/com/ning/billing/server/modules/KillbillServerModule.java
@@ -31,11 +31,12 @@ import com.ning.billing.entitlement.glue.DefaultEntitlementModule;
 import com.ning.billing.invoice.glue.DefaultInvoiceModule;
 import com.ning.billing.jaxrs.resources.AccountResource;
 import com.ning.billing.jaxrs.resources.BundleResource;
-import com.ning.billing.jaxrs.resources.BundleTimelineResource;
 import com.ning.billing.jaxrs.resources.InvoiceResource;
 import com.ning.billing.jaxrs.resources.PaymentResource;
 import com.ning.billing.jaxrs.resources.SubscriptionResource;
+import com.ning.billing.jaxrs.resources.TagResource;
 import com.ning.billing.jaxrs.util.KillbillEventHandler;
+import com.ning.billing.jaxrs.util.TagHelper;
 import com.ning.billing.junction.glue.DefaultJunctionModule;
 import com.ning.billing.payment.setup.PaymentModule;
 import com.ning.billing.util.glue.BusModule;
@@ -61,12 +62,13 @@ public class KillbillServerModule extends AbstractModule
     }
 
     protected void configureResources() {
+        bind(TagHelper.class).asEagerSingleton();
         bind(AccountResource.class).asEagerSingleton();
         bind(BundleResource.class).asEagerSingleton();
         bind(SubscriptionResource.class).asEagerSingleton();
-        bind(BundleTimelineResource.class).asEagerSingleton();
         bind(InvoiceResource.class).asEagerSingleton();
         bind(PaymentResource.class).asEagerSingleton();
+        bind(TagResource.class).asEagerSingleton();
         bind(KillbillEventHandler.class).asEagerSingleton();
     }
 
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
index 8d971ec..7c19d35 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestAccount.java
@@ -16,15 +16,20 @@
 package com.ning.billing.jaxrs;
 
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
 import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 
 
 import javax.ws.rs.core.Response.Status;
 
 
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.PropertyNamingStrategy;
 import org.joda.time.DateTime;
 import org.joda.time.Interval;
 import org.slf4j.Logger;
@@ -38,7 +43,9 @@ import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.AccountTimelineJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.CustomFieldJson;
 import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
 import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
 import com.ning.http.client.Response;
 
@@ -48,6 +55,8 @@ public class TestAccount extends TestJaxrsBase {
 	private static final Logger log = LoggerFactory.getLogger(TestAccount.class);
 
 
+	
+	
 	@Test(groups="slow", enabled=true)
 	public void testAccountOk() throws Exception {
 		
@@ -138,6 +147,60 @@ public class TestAccount extends TestJaxrsBase {
         Assert.assertEquals(objFromJson.getBundles().size(), 1); 
         Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().size(), 1);
         Assert.assertEquals(objFromJson.getBundles().get(0).getSubscriptions().get(0).getEvents().size(), 2);        
- 
+ 	}
+
+	@Test(groups="slow", enabled=false)
+	public void testAccountWithTags() throws Exception {
+	    //Create Tag definition
+	    TagDefinitionJson input = new TagDefinitionJson("yoyo", "nothing more to say");
+	    String baseJson = mapper.writeValueAsString(input);
+	    Response response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+	    assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+	    
+	    AccountJson accountJson = createAccount("couroucoucou", "shdwdsqgfhwe", "couroucoucou@yahoo.com");
+	    assertNotNull(accountJson);
+	        
+	    Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(BaseJaxrsResource.QUERY_TAGS, input.getName());
+        String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + BaseJaxrsResource.TAGS + "/" + accountJson.getAcountId() ;
+	    response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+        
+        /*
+         * STEPH Some how Location returns the ID twice (confused) :
+         * Location: http://127.0.0.1:8080/1.0/kb/accounts/tags/ebb5f830-6f0a-4521-9553-521d173169be/ebb5f830-6f0a-4521-9553-521d173169be
+         * 
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        */
+
+	}
+	
+    @Test(groups="slow", enabled=false)
+	public void testAccountWithCustomFields() throws Exception {
+        
+        AccountJson accountJson = createAccount("carafe", "shdwhwgaz", "carafe@yahoo.com");
+        assertNotNull(accountJson);
+        
+        List<CustomFieldJson> customFields =  new LinkedList<CustomFieldJson>();
+        customFields.add(new CustomFieldJson("1", "value1"));
+        customFields.add(new CustomFieldJson("2", "value2"));
+        customFields.add(new CustomFieldJson("3", "value3"));  
+        String baseJson = mapper.writeValueAsString(customFields);
+
+        String uri = BaseJaxrsResource.ACCOUNTS_PATH + "/" + BaseJaxrsResource.CUSTOM_FIELDS + "/" + accountJson.getAcountId() ;
+        Response response = doPost(uri,baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        
 	}
 }
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
index 0d1f1cd..f0182bd 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestBundle.java
@@ -23,12 +23,12 @@ import java.util.Map;
 
 import javax.ws.rs.core.Response.Status;
 
-import org.codehaus.jackson.type.TypeReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.ning.billing.jaxrs.json.AccountJson;
 import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
 import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
new file mode 100644
index 0000000..9a039fd
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestInvoice.java
@@ -0,0 +1,126 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.joda.time.DateTime;
+import org.joda.time.Interval;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.catalog.api.BillingPeriod;
+import com.ning.billing.catalog.api.ProductCategory;
+import com.ning.billing.jaxrs.json.AccountJson;
+import com.ning.billing.jaxrs.json.BundleJsonNoSubsciptions;
+import com.ning.billing.jaxrs.json.InvoiceJson;
+import com.ning.billing.jaxrs.json.SubscriptionJsonNoEvents;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestInvoice extends TestJaxrsBase  {
+
+    private final DateTimeFormatter DATE_TIME_FORMATTER = ISODateTimeFormat.dateTime();
+    
+    private static final Logger log = LoggerFactory.getLogger(TestInvoice.class);
+
+
+    @Test(groups="slow", enabled=true)
+    public void testInvoiceOk() throws Exception {
+        
+        DateTime initialDate = new DateTime(2012, 4, 25, 0, 3, 42, 0);
+        clock.setDeltaFromReality(initialDate.getMillis() - clock.getUTCNow().getMillis());
+        
+        
+        AccountJson accountJson = createAccount("poupou", "qhddffrwe", "poupou@yahoo.com");
+        assertNotNull(accountJson);
+        
+        BundleJsonNoSubsciptions bundleJson = createBundle(accountJson.getAcountId(), "9967599");
+        assertNotNull(bundleJson);
+        
+        SubscriptionJsonNoEvents subscriptionJson = createSubscription(bundleJson.getBundleId(), "Shotgun", ProductCategory.BASE.toString(), BillingPeriod.MONTHLY.toString(), true);
+        assertNotNull(subscriptionJson);
+        
+        // MOVE AFTER TRIAL
+        Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(3).plusDays(1));
+        clock.addDeltaFromReality(it.toDurationMillis());
+
+        crappyWaitForLackOfProperSynchonization();
+        
+        String uri = BaseJaxrsResource.INVOICES_PATH;
+        Map<String, String> queryParams = new HashMap<String, String>();
+        queryParams.put(BaseJaxrsResource.QUERY_ACCOUNT_ID, accountJson.getAcountId());
+        
+        Response response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        List<InvoiceJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJson>>() {});
+        assertNotNull(objFromJson);
+        log.info(baseJson);
+        assertEquals(objFromJson.size(), 4);
+        
+        // Check we can retrieve an individual invoice
+        uri = BaseJaxrsResource.INVOICES_PATH + "/" + objFromJson.get(0).getInvoiceId();
+        response = doGet(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode());        
+        baseJson = response.getResponseBody();
+        InvoiceJson firstInvoiceJson = mapper.readValue(baseJson, InvoiceJson.class);
+        assertNotNull(objFromJson);    
+        assertEquals(firstInvoiceJson, objFromJson.get(0));
+        
+        // Then create a dryRun Invoice
+        DateTime futureDate = clock.getUTCNow().plusMonths(1).plusDays(3);
+        uri = BaseJaxrsResource.INVOICES_PATH;
+        queryParams.put(BaseJaxrsResource.QUERY_TARGET_DATE, futureDate.toString());
+        queryParams.put(BaseJaxrsResource.QUERY_DRY_RUN, "true");        
+        response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.OK.getStatusCode()); 
+        baseJson = response.getResponseBody();
+        InvoiceJson futureInvoice = mapper.readValue(baseJson, InvoiceJson.class);
+        assertNotNull(futureInvoice);    
+        log.info(baseJson);
+        
+        // The one more time with no DryRun
+        queryParams.remove(BaseJaxrsResource.QUERY_DRY_RUN);
+        response = doPost(uri, null, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        Assert.assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+        
+        String location = response.getHeader("Location");
+        Assert.assertNotNull(location);
+        
+        // Check again # invoices, should be 5 this time
+        uri = BaseJaxrsResource.INVOICES_PATH;
+        response = doGet(uri, queryParams, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        objFromJson = mapper.readValue(baseJson, new TypeReference<List<InvoiceJson>>() {});
+        assertNotNull(objFromJson);
+        log.info(baseJson);
+        assertEquals(objFromJson.size(), 5);
+    }
+}
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 477005e..ce664b3 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -34,7 +34,6 @@ import com.ning.billing.util.email.EmailModule;
 import com.ning.billing.util.email.templates.TemplateModule;
 import com.ning.billing.util.glue.GlobalLockerModule;
 import org.apache.commons.io.IOUtils;
-import org.codehaus.jackson.map.ObjectMapper;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.skife.config.ConfigurationObjectFactory;
 import org.skife.jdbi.v2.IDBI;
@@ -46,11 +45,15 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.joda.JodaModule;
 import com.google.inject.Module;
 import com.ning.billing.account.glue.AccountModule;
 import com.ning.billing.analytics.setup.AnalyticsModule;
+import com.ning.billing.api.TestApiListener;
 import com.ning.billing.beatrix.glue.BeatrixModule;
-import com.ning.billing.beatrix.integration.TestBusHandler;
 import com.ning.billing.beatrix.integration.TestIntegration;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.glue.CatalogModule;
@@ -88,7 +91,7 @@ public class TestJaxrsBase {
 
     private final static String PLUGIN_NAME = "noop";
 
-    protected static final int DEFAULT_HTTP_TIMEOUT_SEC =  5;
+    protected static final int DEFAULT_HTTP_TIMEOUT_SEC = 5;
 
     protected static final Map<String, String> DEFAULT_EMPTY_QUERY = new HashMap<String, String>();
 
@@ -107,8 +110,13 @@ public class TestJaxrsBase {
     protected AsyncHttpClient httpClient;	
     protected ObjectMapper mapper;
     protected ClockMock clock;
-    protected TestBusHandler busHandler;
+    protected TestApiListener busHandler;
 
+    // Context informtation to be passed around
+    private static final String createdBy = "Toto";
+    private static final String reason = "i am god";
+    private static final String comment = "no comment";    
+    
     public static void loadSystemPropertiesFromClasspath(final String resource) {
         final URL url = TestJaxrsBase.class.getResource(resource);
         assertNotNull(url);
@@ -224,7 +232,12 @@ public class TestJaxrsBase {
         loadConfig();
         httpClient = new AsyncHttpClient();
         mapper = new ObjectMapper();
-        busHandler = new TestBusHandler(null);
+        mapper.registerModule(new JodaModule());
+        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+
+        mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy());        
+
+        busHandler = new TestApiListener(null);
         this.helper = listener.getMysqlTestingHelper();
         this.clock =  (ClockMock) listener.getClock();
     }
@@ -374,8 +387,10 @@ public class TestJaxrsBase {
         BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("POST", getUrlFromUri(uri), queryParams);
         if (body != null) {
             builder.setBody(body);
+        } else {
+            builder.setBody("{}");
         }
-        return executeAndWait(builder, timeoutSec);
+        return executeAndWait(builder, timeoutSec, true);
     }
 
     protected Response doPut(final String uri, final String body, final Map<String, String> queryParams, final int timeoutSec) {
@@ -383,14 +398,16 @@ public class TestJaxrsBase {
         BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("PUT", url, queryParams);
         if (body != null) {
             builder.setBody(body);
+        } else {
+            builder.setBody("{}");
         }
-        return executeAndWait(builder, timeoutSec);
+        return executeAndWait(builder, timeoutSec, true);
     }
 
     protected Response doDelete(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
         final String url = String.format("http://%s:%d%s", config.getServerHost(), config.getServerPort(), uri);
         BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("DELETE", url, queryParams);
-        return executeAndWait(builder, timeoutSec);
+        return executeAndWait(builder, timeoutSec, true);
     }
 
     protected Response doGet(final String uri, final Map<String, String> queryParams, final int timeoutSec) {
@@ -400,10 +417,17 @@ public class TestJaxrsBase {
 
     protected Response doGetWithUrl(final String url, final Map<String, String> queryParams, final int timeoutSec) {
         BoundRequestBuilder builder = getBuilderWithHeaderAndQuery("GET", url, queryParams);
-        return executeAndWait(builder, timeoutSec);
+        return executeAndWait(builder, timeoutSec, false);
     }
 
-    private Response executeAndWait(final BoundRequestBuilder builder, final int timeoutSec) {
+    private Response executeAndWait(final BoundRequestBuilder builder, final int timeoutSec, final boolean addContextHeader) {
+        
+        if (addContextHeader) {
+            builder.addHeader(BaseJaxrsResource.HDR_CREATED_BY, createdBy);
+            builder.addHeader(BaseJaxrsResource.HDR_REASON, reason);
+            builder.addHeader(BaseJaxrsResource.HDR_COMMENT, comment);            
+        }
+        
         Response response = null;
         try {
             ListenableFuture<Response> futureStatus = 
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestTag.java b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
new file mode 100644
index 0000000..c1f460a
--- /dev/null
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestTag.java
@@ -0,0 +1,114 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.jaxrs;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+
+import javax.ws.rs.core.Response.Status;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.ning.billing.jaxrs.json.TagDefinitionJson;
+import com.ning.billing.jaxrs.resources.BaseJaxrsResource;
+import com.ning.http.client.Response;
+
+public class TestTag extends TestJaxrsBase {
+
+    private static final Logger log = LoggerFactory.getLogger(TestTag.class);
+
+    @Test(groups="slow", enabled=true)
+    public void testTagDefinitionOk() throws Exception {
+    
+        TagDefinitionJson input = new TagDefinitionJson("blue", "realxing color");
+        String baseJson = mapper.writeValueAsString(input);
+        Response response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        String location = response.getHeader("Location");
+        assertNotNull(location);
+
+        // Retrieves by Id based on Location returned
+        response = doGetWithUrl(location, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+
+        baseJson = response.getResponseBody();
+        TagDefinitionJson objFromJson = mapper.readValue(baseJson, TagDefinitionJson.class);
+        assertNotNull(objFromJson);
+        assertEquals(objFromJson, input);
+    }
+    
+    @Test(groups="slow", enabled=true)
+    public void testMultipleTagDefinitionOk() throws Exception {
+    
+        Response response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        String baseJson = response.getResponseBody();
+        
+        List<TagDefinitionJson> objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+        int sizeSystemTag = (objFromJson == null || objFromJson.size() == 0) ? 0 : objFromJson.size();
+        
+        TagDefinitionJson input = new TagDefinitionJson("blue", "realxing color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        input = new TagDefinitionJson("red", "hot color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        input = new TagDefinitionJson("yellow", "vibrant color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        input = new TagDefinitionJson("green", "super realxing color");
+        baseJson = mapper.writeValueAsString(input);
+        response = doPost(BaseJaxrsResource.TAG_DEFINITIONS_PATH, baseJson, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.CREATED.getStatusCode());
+
+        response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        
+        objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+        assertNotNull(objFromJson);
+        assertEquals(objFromJson.size(), 4 + sizeSystemTag);
+
+        // STEPH currently broken Tag API does not work as expected...
+        
+        /*
+        String uri = BaseJaxrsResource.TAG_DEFINITIONS_PATH + "/green"; 
+        response = doDelete(uri, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.NO_CONTENT.getStatusCode());
+    
+        response = doGet(BaseJaxrsResource.TAG_DEFINITIONS_PATH, DEFAULT_EMPTY_QUERY, DEFAULT_HTTP_TIMEOUT_SEC);
+        assertEquals(response.getStatusCode(), Status.OK.getStatusCode());
+        baseJson = response.getResponseBody();
+        
+        objFromJson = mapper.readValue(baseJson, new TypeReference<List<TagDefinitionJson>>() {});
+        assertNotNull(objFromJson);
+        assertEquals(objFromJson.size(), 3 + sizeSystemTag);
+        */
+    }
+    
+}
diff --git a/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
index 2b1b834..bfce245 100644
--- a/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
+++ b/util/src/main/java/com/ning/billing/util/bus/PersistentBus.java
@@ -19,11 +19,8 @@ package com.ning.billing.util.bus;
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
 
 import org.codehaus.jackson.map.ObjectMapper;
 import org.codehaus.jackson.map.SerializationConfig;
@@ -40,11 +37,12 @@ import com.ning.billing.util.Hostname;
 import com.ning.billing.util.bus.dao.BusEventEntry;
 import com.ning.billing.util.bus.dao.PersistentBusSqlDao;
 import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.queue.PersistentQueueBase;
 
 
-public class PersistentBus implements Bus  {
+public class PersistentBus extends PersistentQueueBase implements Bus {
 
-    private final static int NB_BUS_THREADS = 3;
+    private final static int NB_BUS_THREADS = 1;
     private final static long TIMEOUT_MSEC = 15L * 1000L; // 15 sec
     private final static long DELTA_IN_PROCESSING_TIME_MS = 1000L * 60L * 5L; // 5 minutes
     private final static long SLEEP_TIME_MS = 1000; // 1 sec
@@ -53,16 +51,13 @@ public class PersistentBus implements Bus  {
     private static final Logger log = LoggerFactory.getLogger(PersistentBus.class);
     
     private final PersistentBusSqlDao dao;
-    private final ExecutorService executor;
     
     private final ObjectMapper objectMapper;
     private final EventBusDelegate eventBusDelegate;
     private final Clock clock;
     private final String hostname;
     
-    protected boolean isProcessingEvents;
-    private int curActiveThreads;
-    
+
     
     private static final class EventBusDelegate extends EventBus {
         public EventBusDelegate(String busName) {
@@ -86,115 +81,34 @@ public class PersistentBus implements Bus  {
     
     @Inject
     public PersistentBus(final IDBI dbi, final Clock clock) {
+        super("Bus", Executors.newFixedThreadPool(NB_BUS_THREADS, new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(new ThreadGroup(DefaultBusService.EVENT_BUS_GROUP_NAME),
+                        r,
+                        DefaultBusService.EVENT_BUS_TH_NAME);
+            }
+        }), NB_BUS_THREADS, TIMEOUT_MSEC, SLEEP_TIME_MS);
         this.dao = dbi.onDemand(PersistentBusSqlDao.class);
         this.clock = clock;
         this.objectMapper = new ObjectMapper();
         this.objectMapper.disable(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS);
         this.eventBusDelegate = new EventBusDelegate("Killbill EventBus");
-        final ThreadGroup group = new ThreadGroup(DefaultBusService.EVENT_BUS_GROUP_NAME);
-        this.executor = Executors.newFixedThreadPool(NB_BUS_THREADS, new ThreadFactory() {
-            @Override
-            public Thread newThread(Runnable r) {
-                return new Thread(group, r, DefaultBusService.EVENT_BUS_TH_NAME);
-            }
-        });
         this.hostname = Hostname.get();
-        this.isProcessingEvents = false;
     }
-
     
     @Override
     public void start() {
-        
-        isProcessingEvents = true;
-        curActiveThreads = 0;
-        
-        final PersistentBus thePersistentBus = this;
-        final CountDownLatch doneInitialization = new CountDownLatch(NB_BUS_THREADS);
-
-        log.info("Starting Persistent BUS with {} threads, countDownLatch = {}", NB_BUS_THREADS, doneInitialization.getCount());
-        
-        for (int i = 0; i < NB_BUS_THREADS; i++) {
-            executor.execute(new Runnable() {
-                @Override
-                public void run() {
-
-                    log.info(String.format("PersistentBus thread %s [%d] started",
-                            Thread.currentThread().getName(),
-                            Thread.currentThread().getId()));
-                    
-                    synchronized(thePersistentBus) {
-                        curActiveThreads++;
-                    }
-
-                    doneInitialization.countDown();
-                    
-                    try {
-                        while (true) {
-                            
-                            synchronized(thePersistentBus) {
-                                if (!isProcessingEvents) {
-                                    thePersistentBus.notify();
-                                    break;
-                                }
-                            }
-
-                            try {
-                                doProcessEvents();
-                            } catch (Exception e) {
-                                log.error(String.format("PersistentBus thread  %s  [%d] got an exception..",
-                                        Thread.currentThread().getName(),
-                                        Thread.currentThread().getId()), e);
-                            }
-                            sleepALittle();
-                        }
-                    } catch (InterruptedException e) {
-                        log.info(Thread.currentThread().getName() + " got interrupted, exting...");
-                    } catch (Throwable e) {
-                        log.error(Thread.currentThread().getName() + " got an exception exiting...", e);
-                        // Just to make it really obvious in the log
-                        e.printStackTrace();
-                    } finally {
-                        
-                        log.info(String.format("PersistentBus thread %s [%d] exited",
-                                Thread.currentThread().getName(),
-                                Thread.currentThread().getId()));
-                    
-                        synchronized(thePersistentBus) {
-                            curActiveThreads--;
-                        }
-                    }
-                }
-                
-                private void sleepALittle() throws InterruptedException {
-                    Thread.sleep(SLEEP_TIME_MS);
-                }
-            });
-        }
-        try {
-            boolean success = doneInitialization.await(TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
-            if (!success) {
-                log.warn("Failed to wait for all threads to be started, got {}/{}", doneInitialization.getCount(), NB_BUS_THREADS);
-            } else {
-                log.info("Done waiting for all threads to be started, got {}/{}", doneInitialization.getCount(), NB_BUS_THREADS);
-            }
-        } catch (InterruptedException e) {
-            log.warn("PersistentBus start sequence got interrupted...");
-        }
+        startQueue();
     }
-    
-    
-    private BusEvent deserializeBusEvent(final String className, final String json) {
-        try {
-            Class<?> claz = Class.forName(className);
-            return (BusEvent) objectMapper.readValue(json, claz);
-        } catch (Exception e) {
-            log.error(String.format("Failed to deserialize json object %s for class %s", json, className), e);
-            return null;
-        }
+
+    @Override
+    public void stop() {
+        stopQueue();
     }
-    
-    private int doProcessEvents() {
+
+    @Override
+    public int doProcessEvents() {
 
         List<BusEventEntry> events = getNextBusEvent();
         if (events.size() == 0) {
@@ -212,6 +126,16 @@ public class PersistentBus implements Bus  {
         return result;
     }
 
+    private BusEvent deserializeBusEvent(final String className, final String json) {
+        try {
+            Class<?> claz = Class.forName(className);
+            return (BusEvent) objectMapper.readValue(json, claz);
+        } catch (Exception e) {
+            log.error(String.format("Failed to deserialize json object %s for class %s", json, className), e);
+            return null;
+        }
+    }
+
     
     private List<BusEventEntry> getNextBusEvent() {
 
@@ -231,34 +155,6 @@ public class PersistentBus implements Bus  {
         return Collections.emptyList();
     }
 
-
-    @Override
-    public void stop() {
-        int remaining = 0;
-        try {
-            synchronized(this) {
-                isProcessingEvents = false;
-                long ini = System.currentTimeMillis();
-                long remainingWaitTimeMs = TIMEOUT_MSEC;
-                while (curActiveThreads > 0 && remainingWaitTimeMs > 0) {
-                    wait(1000);
-                    remainingWaitTimeMs = TIMEOUT_MSEC - (System.currentTimeMillis() - ini);
-                }
-                remaining = curActiveThreads;
-            }
-            
-        } catch (InterruptedException ignore) {
-            log.info("PersistentBus has been interrupted during stop sequence");
-        } finally {
-            if (remaining > 0) {
-                log.error(String.format("PersistentBus stopped with %d active remaing threads", remaining));
-            } else {
-                log.info("PersistentBus completed sucesfully shutdown sequence");
-            }
-            curActiveThreads = 0;
-        }
-    }
-
     @Override
     public void register(Object handlerInstance) throws EventBusException {
         eventBusDelegate.register(handlerInstance);
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
index 29cd69c..f943d0b 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/dao/NotificationSqlDao.java
@@ -64,7 +64,7 @@ public interface NotificationSqlDao extends Transactional<NotificationSqlDao>, C
     public void insertNotification(@Bind(binder = NotificationSqlDaoBinder.class) Notification evt);
 
     @SqlUpdate
-    public void insertClaimedHistory(@Bind("sequence_id") int sequenceId, @Bind("owner") String owner, @Bind("claimed_dt") Date clainedDate, @Bind("notification_id") String notificationId);
+    public void insertClaimedHistory(@Bind("owner") String owner, @Bind("claimed_dt") Date clainedDate, @Bind("notification_id") String notificationId);
 
     public static class NotificationSqlDaoBinder extends BinderBase implements Binder<Bind, Notification> {
         @Override
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
index c6958c2..3db7ce1 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/DefaultNotificationQueue.java
@@ -41,10 +41,10 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
     }
 
     @Override
-    protected int doProcessEvents(final int sequenceId) {
+    public int doProcessEvents() {
 
         logDebug("ENTER doProcessEvents");
-        List<Notification> notifications = getReadyNotifications(sequenceId);
+        List<Notification> notifications = getReadyNotifications();
         if (notifications.size() == 0) {
             logDebug("EXIT doProcessEvents");
             return 0;
@@ -85,7 +85,7 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
         dao.clearNotification(cleared.getId(), hostname);
     }
 
-    private List<Notification> getReadyNotifications(final int seqId) {
+    private List<Notification> getReadyNotifications() {
 
         final Date now = clock.getUTCNow().toDate();
         final Date nextAvailable = clock.getUTCNow().plus(config.getDaoClaimTimeMs()).toDate();
@@ -101,7 +101,7 @@ public class DefaultNotificationQueue extends NotificationQueueBase {
                     cur.getUUID(), cur.getNotificationKey(), cur.getEffectiveDate(), Boolean.valueOf(claimed));
             if (claimed) {
                 claimedNotifications.add(cur);
-                dao.insertClaimedHistory(seqId, hostname, now, cur.getUUID().toString());
+                dao.insertClaimedHistory(hostname, now, cur.getUUID().toString());
             }
         }
 
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
index 14b68e0..d5c2425 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueue.java
@@ -22,8 +22,9 @@ import org.joda.time.DateTime;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.QueueLifecycle;
 
-public interface NotificationQueue {
+public interface NotificationQueue extends QueueLifecycle {
 
    /**
     *
@@ -61,25 +62,9 @@ public interface NotificationQueue {
    public int processReadyNotification();
 
    /**
-    * Stops the queue. Blocks until queue is completely stopped.
-    *
-    * @see NotificationQueueHandler.completedQueueStop to be notified when the notification thread exited
-    */
-   public void stopQueue();
-
-   /**
-    * Starts the queue. Blocks until queue has completely started.
-    *
-    * @see NotificationQueueHandler.completedQueueStart to be notified when the notification thread started
-    */
-   public void startQueue();
-
-   /**
     *
     * @return the name of that queue
     */
    public String getFullQName();
 
-   
-
 }
diff --git a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
index eb3f269..7c82db6 100644
--- a/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
+++ b/util/src/main/java/com/ning/billing/util/notificationq/NotificationQueueBase.java
@@ -17,10 +17,8 @@
 package com.ning.billing.util.notificationq;
 
 import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.slf4j.Logger;
@@ -30,48 +28,30 @@ import com.ning.billing.config.NotificationConfig;
 import com.ning.billing.util.Hostname;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.notificationq.NotificationQueueService.NotificationQueueHandler;
+import com.ning.billing.util.queue.PersistentQueueBase;
 
 
-public abstract class NotificationQueueBase implements NotificationQueue {
+public abstract class NotificationQueueBase extends PersistentQueueBase implements NotificationQueue {
 
     protected final static Logger log = LoggerFactory.getLogger(NotificationQueueBase.class);
 
-    private static final long MAX_NOTIFICATION_THREAD_WAIT_MS = 10000; // 10 secs
-    private static final long NOTIFICATION_THREAD_WAIT_INCREMENT_MS = 1000; // 1 sec
-    private static final long NANO_TO_MS = (1000 * 1000);
-
     protected static final String NOTIFICATION_THREAD_PREFIX = "Notification-";
-    protected final long STOP_WAIT_TIMEOUT_MS = 60000;
+    protected static final long STOP_WAIT_TIMEOUT_MS = 60000;
 
     protected final String svcName;
     protected final String queueName;
     protected final NotificationQueueHandler handler;
     protected final NotificationConfig config;
 
-    protected final Executor executor;
     protected final Clock clock;
     protected final String hostname;
 
-    protected static final AtomicInteger sequenceId = new AtomicInteger();
-
     protected AtomicLong nbProcessedEvents;
 
-    // Use this object's monitor for synchronization (no need for volatile)
-    protected boolean isProcessingEvents;
-
-    private boolean startedComplete = false;
-    private boolean stoppedComplete = false;
-
     // Package visibility on purpose
     NotificationQueueBase(final Clock clock,  final String svcName, final String queueName, final NotificationQueueHandler handler, final NotificationConfig config) {
-        this.clock = clock;
-        this.svcName = svcName;
-        this.queueName = queueName;
-        this.handler = handler;
-        this.config = config;
-        this.hostname = Hostname.get();
+        super(svcName, Executors.newFixedThreadPool(1, new ThreadFactory() {
 
-        this.executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
             @Override
             public Thread newThread(Runnable r) {
                 Thread th = new Thread(r);
@@ -84,103 +64,21 @@ public abstract class NotificationQueueBase implements NotificationQueue {
                 });
                 return th;
             }
-        });
-    }
-
+        }), 1, STOP_WAIT_TIMEOUT_MS, config.getNotificationSleepTimeMs());
 
-    @Override
-    public int processReadyNotification() {
-        return doProcessEvents(sequenceId.incrementAndGet());
+        this.clock = clock;
+        this.svcName = svcName;
+        this.queueName = queueName;
+        this.handler = handler;
+        this.config = config;
+        this.hostname = Hostname.get();
+        this.nbProcessedEvents = new AtomicLong();
     }
 
 
     @Override
-    public void stopQueue() {
-        if (config.isNotificationProcessingOff()) {
-            completedQueueStop();
-            return;
-        }
-
-        synchronized(this) {
-            isProcessingEvents = false;
-            try {
-                log.info("NotificationQueue requested to stop");
-                wait(STOP_WAIT_TIMEOUT_MS);
-                log.info("NotificationQueue requested should have exited");
-            } catch (InterruptedException e) {
-                log.warn("NotificationQueue got interrupted exception when stopping notifications", e);
-            }
-        }
-        waitForNotificationStopCompletion();
-    }
-
-    @Override
-    public void startQueue() {
-
-        this.isProcessingEvents = true;
-        this.nbProcessedEvents = new AtomicLong();
-
-
-        if (config.isNotificationProcessingOff()) {
-            log.warn(String.format("KILLBILL NOTIFICATION PROCESSING FOR SVC %s IS OFF !!!", getFullQName()));
-            completedQueueStart();
-            return;
-        }
-        final NotificationQueueBase notificationQueue = this;
-
-        executor.execute(new Runnable() {
-            @Override
-            public void run() {
-
-                log.info(String.format("NotificationQueue thread %s [%d] started",
-                        Thread.currentThread().getName(),
-                        Thread.currentThread().getId()));
-
-                // Thread is now started, notify the listener
-                completedQueueStart();
-
-                try {
-                    while (true) {
-
-                        synchronized (notificationQueue) {
-                            if (!isProcessingEvents) {
-                                log.info(String.format("NotificationQueue has been requested to stop, thread  %s  [%d] stopping...",
-                                        Thread.currentThread().getName(),
-                                        Thread.currentThread().getId()));
-                                notificationQueue.notify();
-                                break;
-                            }
-                        }
-
-                        // Callback may trigger exceptions in user code so catch anything here and live with it.
-                        try {
-                            doProcessEvents(sequenceId.getAndIncrement());
-                        } catch (Exception e) {
-                            log.error(String.format("NotificationQueue thread  %s  [%d] got an exception..",
-                                    Thread.currentThread().getName(),
-                                    Thread.currentThread().getId()), e);
-                        }
-                        sleepALittle();
-                    }
-                } catch (InterruptedException e) {
-                    log.warn(Thread.currentThread().getName() + " got interrupted ", e);
-                } catch (Throwable e) {
-                    log.error(Thread.currentThread().getName() + " got an exception exiting...", e);
-                    // Just to make it really obvious in the log
-                    e.printStackTrace();
-                } finally {
-                    completedQueueStop();
-                    log.info(String.format("NotificationQueue thread  %s  [%d] exited...",
-                            Thread.currentThread().getName(),
-                            Thread.currentThread().getId()));
-                }
-            }
-
-            private void sleepALittle() throws InterruptedException {
-                Thread.sleep(config.getNotificationSleepTimeMs());
-            }
-        });
-        waitForNotificationStartCompletion();
+    public int processReadyNotification() {
+        return doProcessEvents();
     }
 
     @Override
@@ -188,61 +86,12 @@ public abstract class NotificationQueueBase implements NotificationQueue {
         return getFullQName();
     }
 
-    private void completedQueueStop() {
-    	synchronized (this) {
-    		stoppedComplete = true;
-            this.notifyAll();
-        }
-    }
-
-    private void completedQueueStart() {
-        synchronized (this) {
-        	startedComplete = true;
-            this.notifyAll();
-        }
-    }
-
-    private void waitForNotificationStartCompletion() {
-        waitForNotificationEventCompletion(true);
-    }
-
-    private void waitForNotificationStopCompletion() {
-        waitForNotificationEventCompletion(false);
-    }
-
-    private void waitForNotificationEventCompletion(boolean startEvent) {
-
-        long ini = System.nanoTime();
-        synchronized(this) {
-            do {
-                if ((startEvent ? startedComplete : stoppedComplete)) {
-                    break;
-                }
-                try {
-                    this.wait(NOTIFICATION_THREAD_WAIT_INCREMENT_MS);
-                } catch (InterruptedException e ) {
-                    Thread.currentThread().interrupt();
-                    throw new NotificationError(e);
-                }
-            } while (!(startEvent ? startedComplete : stoppedComplete) &&
-                    (System.nanoTime() - ini) / NANO_TO_MS < MAX_NOTIFICATION_THREAD_WAIT_MS);
-
-            if (!(startEvent ? startedComplete : stoppedComplete)) {
-                log.error("Could not {} notification thread in {} msec !!!",
-                        (startEvent ? "start" : "stop"),
-                        MAX_NOTIFICATION_THREAD_WAIT_MS);
-                throw new NotificationError("Failed to start service!!");
-            }
-            log.info("Notification thread has been {} in {} ms",
-                    (startEvent ? "started" : "stopped"),
-                    (System.nanoTime() - ini) / NANO_TO_MS);
-        }
-    }
 
     @Override
     public String getFullQName() {
         return svcName + ":" +  queueName;
     }
 
-    protected abstract int doProcessEvents(int sequenceId);
+    @Override
+    public abstract int doProcessEvents();
 }
diff --git a/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java b/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java
new file mode 100644
index 0000000..a6844ad
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/queue/PersistentQueueBase.java
@@ -0,0 +1,159 @@
+/* 
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.ning.billing.util.queue;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public abstract class PersistentQueueBase implements QueueLifecycle {
+
+    private static final Logger log = LoggerFactory.getLogger(PersistentQueueBase.class);
+
+    private final int nbThreads;
+    private final Executor executor;
+    private final String svcName;
+    private final long sleepTimeMs;
+    private final long waitTimeoutMs;
+
+    private boolean isProcessingEvents;
+    private int curActiveThreads;
+    
+    public PersistentQueueBase(final String svcName, final Executor executor, final int nbThreads, final long waitTimeoutMs, final long sleepTimeMs) {
+        this.executor = executor;
+        this.nbThreads = nbThreads;
+        this.svcName = svcName;
+        this.waitTimeoutMs = waitTimeoutMs;
+        this.sleepTimeMs = sleepTimeMs;
+        this.isProcessingEvents = false;
+        this.curActiveThreads = 0;
+    }
+    
+    @Override
+    public void startQueue() {
+        
+        isProcessingEvents = true;
+        curActiveThreads = 0;
+        
+        final PersistentQueueBase thePersistentQ = this;
+        final CountDownLatch doneInitialization = new CountDownLatch(nbThreads);
+
+        log.info(String.format("%s: Starting with %d threads",
+                svcName, nbThreads));
+        
+        for (int i = 0; i < nbThreads; i++) {
+            executor.execute(new Runnable() {
+                @Override
+                public void run() {
+
+                    log.info(String.format("%s: Thread %s [%d] starting",
+                            svcName,
+                            Thread.currentThread().getName(),
+                            Thread.currentThread().getId()));
+                    
+                    synchronized(thePersistentQ) {
+                        curActiveThreads++;
+                    }
+
+                    doneInitialization.countDown();
+                    
+                    try {
+                        while (true) {
+                            
+                            synchronized(thePersistentQ) {
+                                if (!isProcessingEvents) {
+                                    thePersistentQ.notify();
+                                    break;
+                                }
+                            }
+
+                            try {
+                                doProcessEvents();
+                            } catch (Exception e) {
+                                log.warn(String.format("%s: Thread  %s  [%d] got an exception, catching and moving on...",
+                                        svcName,
+                                        Thread.currentThread().getName(),
+                                        Thread.currentThread().getId()), e);
+                            }
+                            sleepALittle();
+                        }
+                    } catch (InterruptedException e) {
+                        log.info(String.format("%s: Thread %s got interrupted, exting... ", svcName, Thread.currentThread().getName()));
+                    } catch (Throwable e) {
+                        log.error(String.format("%s: Thread %s got an exception, exting... ", svcName, Thread.currentThread().getName()), e);                        
+                    } finally {
+
+                        log.info(String.format("%s: Thread %s has exited", svcName, Thread.currentThread().getName()));                                                
+                        synchronized(thePersistentQ) {
+                            curActiveThreads--;
+                        }
+                    }
+                }
+                
+                private void sleepALittle() throws InterruptedException {
+                    Thread.sleep(sleepTimeMs);
+                }
+            });
+        }
+        try {
+            boolean success = doneInitialization.await(sleepTimeMs, TimeUnit.MILLISECONDS);
+            if (!success) {
+                
+                log.warn(String.format("%s: Failed to wait for all threads to be started, got %d/%d", svcName, (nbThreads - doneInitialization.getCount()), nbThreads));
+            } else {
+                log.info(String.format("%s: Done waiting for all threads to be started, got %d/%d", svcName, (nbThreads - doneInitialization.getCount()), nbThreads));                
+            }
+        } catch (InterruptedException e) {
+            log.warn(String.format("%s: Start sequence, got interrupted", svcName));
+        }
+    }
+    
+    
+    @Override
+    public void stopQueue() {
+        int remaining = 0;
+        try {
+            synchronized(this) {
+                isProcessingEvents = false;
+                long ini = System.currentTimeMillis();
+                long remainingWaitTimeMs = waitTimeoutMs;
+                while (curActiveThreads > 0 && remainingWaitTimeMs > 0) {
+                    wait(1000);
+                    remainingWaitTimeMs = waitTimeoutMs - (System.currentTimeMillis() - ini);
+                }
+                remaining = curActiveThreads;
+            }
+            
+        } catch (InterruptedException ignore) {
+            log.info(String.format("%s: Stop sequence has been interrupted, remaining active threads = %d", svcName, curActiveThreads));
+        } finally {
+            if (remaining > 0) {
+                log.error(String.format("%s: Stop sequence completed with %d active remaing threads", svcName, curActiveThreads));
+            } else {
+                log.info(String.format("%s: Stop sequence completed with %d active remaing threads", svcName, curActiveThreads));                
+            }
+            curActiveThreads = 0;
+        }
+    }
+    
+    
+    @Override
+    public abstract int doProcessEvents();
+}
diff --git a/util/src/main/resources/com/ning/billing/util/ddl.sql b/util/src/main/resources/com/ning/billing/util/ddl.sql
index cec35b1..e32e27b 100644
--- a/util/src/main/resources/com/ning/billing/util/ddl.sql
+++ b/util/src/main/resources/com/ning/billing/util/ddl.sql
@@ -100,7 +100,6 @@ CREATE INDEX  `idx_get_ready` ON notifications (`effective_dt`,`created_dt`,`id`
 DROP TABLE IF EXISTS claimed_notifications;
 CREATE TABLE claimed_notifications (
     id int(11) unsigned NOT NULL AUTO_INCREMENT,
-    sequence_id int(11) unsigned NOT NULL,
     owner_id varchar(64) NOT NULL,
     claimed_dt datetime NOT NULL,
     notification_id char(36) NOT NULL,
diff --git a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
index 6751f04..5797d5a 100644
--- a/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
+++ b/util/src/main/resources/com/ning/billing/util/notificationq/dao/NotificationSqlDao.sql.stg
@@ -88,13 +88,11 @@ insertNotification() ::= <<
 
 insertClaimedHistory(sequence_id, owner, hostname, claimed_dt, notification_id) ::= <<
     insert into claimed_notifications (
-        sequence_id
-        , owner_id
+          owner_id
         , claimed_dt
         , notification_id
       ) values (
-        :sequence_id
-        , :owner
+          :owner
         , :claimed_dt
         , :notification_id
       );
diff --git a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
index 9f4da5e..ab702cb 100644
--- a/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
+++ b/util/src/test/java/com/ning/billing/util/clock/ClockMock.java
@@ -18,6 +18,13 @@ package com.ning.billing.util.clock;
 
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
+import org.joda.time.Days;
+import org.joda.time.Months;
+import org.joda.time.MutablePeriod;
+import org.joda.time.Period;
+import org.joda.time.ReadablePeriod;
+import org.joda.time.Weeks;
+import org.joda.time.Years;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -25,8 +32,10 @@ import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.TimeUnit;
 
 public class ClockMock implements Clock {
+    
+    private MutablePeriod delta = new MutablePeriod();
     private static final Logger log = LoggerFactory.getLogger(ClockMock.class);
-    DateTime currentTime = new DateTime(DateTimeZone.UTC);
+
      
     @Override
     public synchronized DateTime getNow(DateTimeZone tz) {
@@ -35,35 +44,37 @@ public class ClockMock implements Clock {
 
     @Override
     public synchronized DateTime getUTCNow() {
-        try {
-        return currentTime.plus(epsilon).minus(currentTime.getMillisOfSecond());
-        } catch(RuntimeException e) {
-            throw e;
-        }
+        return truncate(adjust(now()));
     }
     
+    private DateTime adjust(DateTime now) {
+        return now.plus(delta);
+    }
+
     public synchronized void setTime(DateTime time) {
-        adjustTo(time.toDateTime(DateTimeZone.UTC));
-   }
+        DateTime prev = getUTCNow();
+        delta = new MutablePeriod(now(), time);
+        logChange(prev);
+    }
     
     public synchronized void addDays(int days) {
-        adjustTo(currentTime.plusDays(days));
+        adjustTo(Days.days(days));
     }
     
     public synchronized void addWeeks(int weeks) {
-        adjustTo(currentTime.plusWeeks(weeks));
+        adjustTo(Weeks.weeks(weeks));
     }
     
     public synchronized void addMonths(int months) {
-        adjustTo(currentTime.plusMonths(months));
+        adjustTo(Months.months(months));
     }
     
     public synchronized void addYears(int years) {
-        adjustTo(currentTime.plusMonths(years));
+        adjustTo(Years.years(years));
     }
     
     public synchronized void reset() {
-        adjustTo(new DateTime(DateTimeZone.UTC));
+        delta = new MutablePeriod();
     }
     
     @Override
@@ -71,59 +82,66 @@ public class ClockMock implements Clock {
         return getUTCNow().toString();
     }
     
-    private void adjustTo(DateTime newTime) {
-        if(newTime == null) {
-            log.error("Attempting to adjust clock to a null value");
-            newTime = new DateTime(DateTimeZone.UTC);
-        }
-        logClockAdjustment(currentTime, newTime);
-        currentTime = newTime;
+    private void adjustTo(ReadablePeriod period) {
+        DateTime prev = getUTCNow();
+        delta.add(period);
+        logChange(prev);
+    }
+    
+    private void logChange(DateTime prev) {     
+        DateTime now = getUTCNow();
+        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, now));
     }
     
-    private void logClockAdjustment(DateTime prev, DateTime next) {
-        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, next));
+    private DateTime now() {
+        return new DateTime(DateTimeZone.UTC);
     }
 
+    private DateTime truncate(DateTime time) {
+        return time.minus(time.getMillisOfSecond());
+    }
+   
     //
     //Backward compatibility stuff
     //
-    long epsilon = 0;
-    
-    public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
-        this.epsilon = epsilon;
-        addDeltaFromReality(delta);
+    public synchronized void setDeltaFromReality(Duration duration, long epsilon) {
+        DateTime prev = getUTCNow();
+        delta.addMillis((int)epsilon);
+        addDeltaFromReality(duration);
+        logChange(prev);
+        
     }
 
     public synchronized void addDeltaFromReality(Duration delta) {
-        adjustTo(addDurationToDateTime(delta,currentTime));
+        adjustTo(periodFromDuration(delta));
     }
 
     public synchronized void setDeltaFromReality(long delta) {
-        adjustTo(currentTime.plus(delta));
+        adjustTo(new Period(delta));
     }
 
     public synchronized void addDeltaFromReality(long delta) {
-        adjustTo(currentTime.plus(delta));
+        adjustTo(new Period(delta));
     }
 
     public synchronized void resetDeltaFromReality() {
         reset();
     }
     
-    public DateTime addDurationToDateTime(Duration duration, DateTime dateTime) {
-        if (duration.getUnit() != TimeUnit.UNLIMITED) {return dateTime;}
+    public ReadablePeriod periodFromDuration(Duration duration) {
+        if (duration.getUnit() != TimeUnit.UNLIMITED) {return new Period();}
 
         switch (duration.getUnit()) {
             case DAYS:
-                return dateTime.plusDays(duration.getNumber());
+                return Days.days(duration.getNumber());
             case MONTHS:
-                return dateTime.plusMonths(duration.getNumber());
+                return Months.months(duration.getNumber());
             case YEARS:
-                return dateTime.plusYears(duration.getNumber());
+                return Years.years(duration.getNumber());
             case UNLIMITED:
-                return dateTime.plusYears(100);
-            default:
-                return dateTime;
+                return Years.years(100);
+           default:
+                return new Period();
         }
     }
     
diff --git a/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java b/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java
new file mode 100644
index 0000000..b31db77
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/util/clock/OldClockMock.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.clock;
+
+import com.ning.billing.catalog.api.Duration;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// STEPH should really be in tests but not accessible from other sub modules
+public class OldClockMock extends DefaultClock {
+
+    private static final Logger log = LoggerFactory.getLogger(OldClockMock.class);
+
+    private enum DeltaType {
+        DELTA_NONE,
+        DELTA_DURATION,
+        DELTA_ABS
+    }
+
+    private long deltaFromRealityMs;
+    private List<Duration> deltaFromRealityDuration;
+    private long deltaFromRealityDurationEpsilon;
+    private DeltaType deltaType;
+
+    public OldClockMock() {
+        deltaType = DeltaType.DELTA_NONE;
+        deltaFromRealityMs = 0;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityDuration = null;
+    }
+
+    @Override
+    public synchronized DateTime getNow(DateTimeZone tz) {
+        return adjust(super.getNow(tz));
+    }
+
+    @Override
+    public synchronized DateTime getUTCNow() {
+        return getNow(DateTimeZone.UTC);
+    }
+
+    private void logClockAdjustment(DateTime prev, DateTime next) {
+        log.info(String.format("            ************      ADJUSTING CLOCK FROM %s to %s     ********************", prev, next));
+    }
+
+    public synchronized void setDeltaFromReality(Duration delta, long epsilon) {
+        DateTime prev = getUTCNow();
+        deltaType = DeltaType.DELTA_DURATION;
+        deltaFromRealityDuration = new ArrayList<Duration>();
+        deltaFromRealityDuration.add(delta);
+        deltaFromRealityDurationEpsilon = epsilon;
+        deltaFromRealityMs = 0;
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void addDeltaFromReality(Duration delta) {
+        DateTime prev = getUTCNow();
+        if (deltaType != DeltaType.DELTA_DURATION) {
+            throw new RuntimeException("ClockMock should be set with type DELTA_DURATION");
+        }
+        deltaFromRealityDuration.add(delta);
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void setDeltaFromReality(long delta) {
+        DateTime prev = getUTCNow();
+        deltaType = DeltaType.DELTA_ABS;
+        deltaFromRealityDuration = null;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityMs = delta;
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void addDeltaFromReality(long delta) {
+        DateTime prev = getUTCNow();
+        if (deltaType != DeltaType.DELTA_ABS) {
+            throw new RuntimeException("ClockMock should be set with type DELTA_ABS");
+        }
+        deltaFromRealityDuration = null;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityMs += delta;
+        logClockAdjustment(prev, getUTCNow());
+    }
+
+    public synchronized void resetDeltaFromReality() {
+        deltaType = DeltaType.DELTA_NONE;
+        deltaFromRealityDuration = null;
+        deltaFromRealityDurationEpsilon = 0;
+        deltaFromRealityMs = 0;
+    }
+
+    private DateTime adjust(DateTime realNow) {
+        switch(deltaType) {
+            case DELTA_NONE:
+                return realNow;
+            case DELTA_ABS:
+                return adjustFromAbsolute(realNow);
+            case DELTA_DURATION:
+                return adjustFromDuration(realNow);
+            default:
+                return null;
+        }
+    }
+
+    private DateTime adjustFromDuration(DateTime input) {
+
+        DateTime result = input;
+        for (Duration cur : deltaFromRealityDuration) {
+            switch (cur.getUnit()) {
+            case DAYS:
+                result = result.plusDays(cur.getNumber());
+                break;
+
+            case MONTHS:
+                result = result.plusMonths(cur.getNumber());
+                break;
+
+            case YEARS:
+                result = result.plusYears(cur.getNumber());
+                break;
+
+            case UNLIMITED:
+            default:
+                throw new RuntimeException("ClockMock is adjusting an unlimited time period");
+            }
+        }
+        if (deltaFromRealityDurationEpsilon != 0) {
+            result = result.plus(deltaFromRealityDurationEpsilon);
+        }
+        return result;
+    }
+
+    private DateTime adjustFromAbsolute(DateTime input) {
+        return truncateMs(input.plus(deltaFromRealityMs));
+    }
+
+    @Override
+    public String toString() {
+        return getUTCNow().toString();
+    }
+
+    
+}
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
index e52bf20..4702409 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/dao/TestNotificationSqlDao.java
@@ -49,7 +49,6 @@ import static org.testng.Assert.assertNotNull;
 @Guice(modules = TestNotificationSqlDao.TestNotificationSqlDaoModule.class)
 public class TestNotificationSqlDao {
 
-    private static AtomicInteger sequenceId = new AtomicInteger();
     private final static String hostname = "Yop";
     
     @Inject
@@ -124,7 +123,7 @@ public class TestNotificationSqlDao {
         DateTime nextAvailable = now.plusMinutes(5);
         int res = dao.claimNotification(ownerId, nextAvailable.toDate(), notification.getId(), now.toDate());
         assertEquals(res, 1);
-        dao.insertClaimedHistory(sequenceId.incrementAndGet(), ownerId, now.toDate(), notification.getUUID().toString());
+        dao.insertClaimedHistory(ownerId, now.toDate(), notification.getUUID().toString());
 
         notification = fetchNotification(notification.getUUID().toString());
         assertEquals(notification.getNotificationKey(), notificationKey);
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
index 658752a..86ec0ea 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/MockNotificationQueue.java
@@ -75,7 +75,7 @@ public class MockNotificationQueue extends NotificationQueueBase implements Noti
     }
 
     @Override
-    protected int doProcessEvents(int sequenceId) {
+    public int doProcessEvents() {
 
         int result = 0;