/*
* 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.analytics.api;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import org.joda.time.DateTime;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import com.ning.billing.account.api.Account;
import com.ning.billing.account.api.AccountCreationEvent;
import com.ning.billing.account.api.AccountUserApi;
import com.ning.billing.account.api.user.DefaultAccountCreationEvent;
import com.ning.billing.analytics.AnalyticsTestModule;
import com.ning.billing.analytics.AnalyticsTestSuiteWithEmbeddedDB;
import com.ning.billing.analytics.MockDuration;
import com.ning.billing.analytics.MockPhase;
import com.ning.billing.analytics.MockProduct;
import com.ning.billing.analytics.dao.BusinessAccountSqlDao;
import com.ning.billing.analytics.dao.BusinessSubscriptionTransitionSqlDao;
import com.ning.billing.analytics.model.BusinessSubscription;
import com.ning.billing.analytics.model.BusinessSubscriptionEvent;
import com.ning.billing.analytics.model.BusinessSubscriptionTransition;
import com.ning.billing.catalog.MockPriceList;
import com.ning.billing.catalog.api.Catalog;
import com.ning.billing.catalog.api.CatalogApiException;
import com.ning.billing.catalog.api.CatalogService;
import com.ning.billing.catalog.api.Currency;
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.PriceList;
import com.ning.billing.catalog.api.Product;
import com.ning.billing.catalog.api.ProductCategory;
import com.ning.billing.entitlement.api.user.DefaultEffectiveSubscriptionEvent;
import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
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.SubscriptionBundle;
import com.ning.billing.entitlement.api.user.SubscriptionTransitionData;
import com.ning.billing.entitlement.events.EntitlementEvent;
import com.ning.billing.entitlement.events.user.ApiEventType;
import com.ning.billing.invoice.api.Invoice;
import com.ning.billing.invoice.api.InvoiceCreationEvent;
import com.ning.billing.invoice.api.user.DefaultInvoiceCreationEvent;
import com.ning.billing.invoice.dao.InvoiceDao;
import com.ning.billing.invoice.model.DefaultInvoice;
import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.mock.MockAccountBuilder;
import com.ning.billing.mock.MockPlan;
import com.ning.billing.payment.api.DefaultPaymentInfoEvent;
import com.ning.billing.payment.api.PaymentInfoEvent;
import com.ning.billing.payment.api.PaymentStatus;
import com.ning.billing.payment.dao.PaymentDao;
import com.ning.billing.util.bus.Bus;
import com.ning.billing.util.callcontext.CallContext;
import com.ning.billing.util.callcontext.CallOrigin;
import com.ning.billing.util.callcontext.DefaultCallContextFactory;
import com.ning.billing.util.callcontext.UserType;
import com.ning.billing.util.clock.Clock;
import com.ning.billing.util.clock.DefaultClock;
import com.google.inject.Inject;
import static org.testng.Assert.fail;
@Guice(modules = {AnalyticsTestModule.class})
public class TestAnalyticsService extends AnalyticsTestSuiteWithEmbeddedDB {
final Product product = new MockProduct("platinum", "subscription", ProductCategory.BASE);
final Plan plan = new MockPlan("platinum-monthly", product);
final PlanPhase phase = new MockPhase(PhaseType.EVERGREEN, plan, MockDuration.UNLIMITED(), 25.95);
private static final Long TOTAL_ORDERING = 11L;
private static final String EXTERNAL_KEY = "12345";
private static final UUID ACCOUNT_ID = UUID.randomUUID();
private static final String ACCOUNT_KEY = "pierre-12345";
private static final Currency ACCOUNT_CURRENCY = Currency.EUR;
private static final BigDecimal INVOICE_AMOUNT = BigDecimal.valueOf(1243.11);
private final Clock clock = new DefaultClock();
private final CallContext context = new DefaultCallContextFactory(clock).createCallContext("Analytics Test", CallOrigin.TEST, UserType.TEST);
@Inject
private AccountUserApi accountApi;
@Inject
private EntitlementUserApi entitlementApi;
@Inject
private InvoiceDao invoiceDao;
@Inject
private PaymentDao paymentDao;
@Inject
private DefaultAnalyticsService service;
@Inject
private Bus bus;
@Inject
private BusinessSubscriptionTransitionSqlDao subscriptionSqlDao;
@Inject
private BusinessAccountSqlDao accountSqlDao;
private EffectiveSubscriptionEvent transition;
private BusinessSubscriptionTransition expectedTransition;
private AccountCreationEvent accountCreationNotification;
private InvoiceCreationEvent invoiceCreationNotification;
private PaymentInfoEvent paymentInfoNotification;
@Inject
private CatalogService catalogService;
private Catalog catalog;
@BeforeClass(groups = "slow")
public void setUp() throws IOException, ClassNotFoundException, SQLException, EntitlementUserApiException, CatalogApiException {
catalog = catalogService.getFullCatalog();
Mockito.when(catalog.findPlan(Mockito.anyString(), Mockito.<DateTime>any())).thenReturn(plan);
Mockito.when(catalog.findPhase(Mockito.anyString(), Mockito.<DateTime>any(), Mockito.<DateTime>any())).thenReturn(phase);
// Killbill generic setup
bus.start();
}
@BeforeMethod(groups = "slow")
public void createMocks() {
final Account account = new MockAccountBuilder(UUID.randomUUID())
.externalKey(ACCOUNT_KEY)
.currency(ACCOUNT_CURRENCY)
.build();
try {
final Account storedAccount = accountApi.createAccount(account, context);
// Create events for the bus and expected results
createSubscriptionTransitionEvent(storedAccount);
createAccountCreationEvent(storedAccount);
createInvoiceAndPaymentCreationEvents(storedAccount);
} catch (Throwable t) {
fail("Initializing accounts failed.", t);
}
}
private void createSubscriptionTransitionEvent(final Account account) throws EntitlementUserApiException {
final SubscriptionBundle bundle = entitlementApi.createBundleForAccount(account.getId(), EXTERNAL_KEY, context);
// Verify we correctly initialized the account subsystem
Assert.assertNotNull(bundle);
Assert.assertEquals(bundle.getKey(), EXTERNAL_KEY);
// Create a subscription transition event
final UUID subscriptionId = UUID.randomUUID();
final DateTime effectiveTransitionTime = clock.getUTCNow();
final DateTime requestedTransitionTime = clock.getUTCNow();
final PriceList priceList = new MockPriceList().setName("something");
transition = new DefaultEffectiveSubscriptionEvent(new SubscriptionTransitionData(
UUID.randomUUID(),
subscriptionId,
bundle.getId(),
EntitlementEvent.EventType.API_USER,
ApiEventType.CREATE,
requestedTransitionTime,
effectiveTransitionTime,
null,
null,
null,
null,
Subscription.SubscriptionState.ACTIVE,
plan,
phase,
priceList,
TOTAL_ORDERING,
null,
true), null);
expectedTransition = new BusinessSubscriptionTransition(
TOTAL_ORDERING,
transition.getBundleId(),
EXTERNAL_KEY,
ACCOUNT_ID,
ACCOUNT_KEY,
transition.getSubscriptionId(),
requestedTransitionTime,
BusinessSubscriptionEvent.subscriptionCreated(plan.getName(), catalog, clock.getUTCNow(), clock.getUTCNow()),
null,
new BusinessSubscription(priceList.getName(), plan.getName(), phase.getName(), ACCOUNT_CURRENCY, effectiveTransitionTime, Subscription.SubscriptionState.ACTIVE, catalog)
);
}
private void createAccountCreationEvent(final Account account) {
accountCreationNotification = new DefaultAccountCreationEvent(account, null);
}
private void createInvoiceAndPaymentCreationEvents(final Account account) {
final DefaultInvoice invoice = new DefaultInvoice(account.getId(), clock.getUTCToday(), clock.getUTCToday(), ACCOUNT_CURRENCY);
final FixedPriceInvoiceItem invoiceItem = new FixedPriceInvoiceItem(
UUID.randomUUID(), invoice.getId(), account.getId(), UUID.randomUUID(), UUID.randomUUID(), "somePlan", "somePhase", clock.getUTCToday(),
INVOICE_AMOUNT, ACCOUNT_CURRENCY);
invoice.addInvoiceItem(invoiceItem);
invoiceDao.create(invoice, invoice.getTargetDate().getDayOfMonth(), context);
final List<Invoice> invoices = invoiceDao.getInvoicesByAccount(account.getId());
Assert.assertEquals(invoices.size(), 1);
Assert.assertEquals(invoices.get(0).getInvoiceItems().size(), 1);
// It doesn't really matter what the events contain - the listener will go back to the db
invoiceCreationNotification = new DefaultInvoiceCreationEvent(invoice.getId(), account.getId(),
INVOICE_AMOUNT, ACCOUNT_CURRENCY, null);
paymentInfoNotification = new DefaultPaymentInfoEvent(account.getId(), invoices.get(0).getId(), null, invoices.get(0).getBalance(), -1, PaymentStatus.UNKNOWN, null, null, null, clock.getUTCNow());
//STEPH talk to Pierre
/*
paymentInfoNotification = new DefaultPaymentInfoEvent.Builder().setId(UUID.randomUUID()).setExternalPaymentId("12345abcdef").setPaymentMethod(PAYMENT_METHOD).setCardCountry(CARD_COUNTRY).build();
final PaymentAttempt2 paymentAttempt = new DefaultPaymentAttempt2(UUID.randomUUID(), invoice.getId(), account.getId(), BigDecimal.TEN,
ACCOUNT_CURRENCY, clock.getUTCNow(), clock.getUTCNow(), paymentInfoNotification.getId(), 1, new DateTime(), new DateTime(), PaymentAttemptStatus.COMPLETED_SUCCESS);
paymentDao.createPaymentAttempt(paymentAttempt, PaymentAttemptStatus.COMPLETED_SUCCESS, context);
paymentDao.insertPaymentInfoWithPaymentAttemptUpdate(paymentInfoNotification, paymentAttempt.getId(), context);
Assert.assertEquals(paymentDao.getPaymentInfoList(Arrays.asList(invoice.getId())).size(), 1);
*/
}
// STEPH talk to Pierre -- see previous remark hence disable test
@Test(groups = "slow", enabled = false)
public void testRegisterForNotifications() throws Exception {
// Make sure the service has been instantiated
Assert.assertEquals(service.getName(), "analytics-service");
// Test the bus and make sure we can register our service
try {
service.registerForNotifications();
} catch (Throwable t) {
Assert.fail("Unable to start the bus or service! " + t);
}
Assert.assertNull(accountSqlDao.getAccountByKey(ACCOUNT_KEY));
// Send events and wait for the async part...
bus.post(transition);
bus.post(accountCreationNotification);
Thread.sleep(5000);
Assert.assertEquals(subscriptionSqlDao.getTransitionsByKey(EXTERNAL_KEY).size(), 1);
Assert.assertEquals(subscriptionSqlDao.getTransitionsByKey(EXTERNAL_KEY).get(0), expectedTransition);
// Test invoice integration - the account creation notification has triggered a BAC update
Assert.assertTrue(accountSqlDao.getAccountByKey(ACCOUNT_KEY).getTotalInvoiceBalance().compareTo(INVOICE_AMOUNT) == 0);
// Post the same invoice event again - the invoice balance shouldn't change
bus.post(invoiceCreationNotification);
Thread.sleep(5000);
Assert.assertTrue(accountSqlDao.getAccountByKey(ACCOUNT_KEY).getTotalInvoiceBalance().compareTo(INVOICE_AMOUNT) == 0);
// Test payment integration - the fields have already been populated, just make sure the code is exercised
bus.post(paymentInfoNotification);
Thread.sleep(5000);
// STEPH talk to Pierre
//Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getPaymentMethod(), PAYMENT_METHOD);
//Assert.assertEquals(accountDao.getAccount(ACCOUNT_KEY).getBillingAddressCountry(), CARD_COUNTRY);
// Test the shutdown sequence
try {
bus.stop();
} catch (Throwable t) {
Assert.fail("Unable to stop the bus!");
}
}
}