package co.smartreceipts.android.purchases; import android.app.Application; import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.support.annotation.NonNull; import com.android.vending.billing.IInAppBillingService; import com.google.gson.Gson; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows; import org.robolectric.shadows.ShadowApplication; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import co.smartreceipts.android.analytics.Analytics; import co.smartreceipts.android.purchases.model.AvailablePurchase; import co.smartreceipts.android.purchases.model.ConsumablePurchase; import co.smartreceipts.android.purchases.model.InAppPurchase; import co.smartreceipts.android.purchases.model.ManagedProduct; import co.smartreceipts.android.purchases.model.Subscription; import co.smartreceipts.android.purchases.wallet.PurchaseWallet; import io.reactivex.schedulers.Schedulers; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anySetOf; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) public class PurchaseManagerTest { // Service response codes private static final int RESULT_OK = 0; private static final int RESULT_USER_CANCELED = 1; private static final int RESULT_BILLING_UNAVAILABLE = 3; private static final int RESULT_ITEM_UNAVAILABLE = 4; private static final int RESULT_DEVELOPER_ERROR = 5; private static final int RESULT_ERROR = 6; private static final int RESULT_ITEM_ALREADY_OWNED = 7; private static final int RESULT_ITEM_NOT_OWNED = 8; // Purchase state codes private static final int PURCHASE_STATE_PURCHASED = 0; private static final int PURCHASE_STATE_CANCELLED = 1; private static final int PURCHASE_STATE_REFUNDED = 2; // Metadata private static final String PURCHASE_TOKEN = "012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689"; private static final String IN_APP_DATA_SIGNATURE = "012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689012345689ABCDEF012345689ABCDEF012345689ABCDEF012345689=="; // Class under test PurchaseManager purchaseManager; Application application = RuntimeEnvironment.application; ShadowApplication shadowApplication = Shadows.shadowOf(application); String packageName = RuntimeEnvironment.application.getPackageName(); @Mock PurchaseWallet purchaseWallet; @Mock Analytics analytics; @Mock IBinder binder; @Mock IInAppBillingService inAppBillingService; @Mock ConsumablePurchase consumablePurchase; @Mock PurchaseEventsListener listener1, listener2, listener3; @Captor ArgumentCaptor<Bundle> bundleCaptor; @Captor ArgumentCaptor<Set<ManagedProduct>> updateManagedProductsCaptor; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(binder.queryLocalInterface("com.android.vending.billing.IInAppBillingService")).thenReturn(inAppBillingService); shadowApplication.setComponentNameAndServiceForBindService(new ComponentName("com.android.vending.billing", "InAppBillingService"), binder); purchaseManager = new PurchaseManager(application, purchaseWallet, analytics, Schedulers.trampoline(), Schedulers.trampoline()); purchaseManager.addEventListener(listener1); purchaseManager.addEventListener(listener2); purchaseManager.addEventListener(listener3); purchaseManager.removeEventListener(listener3); } @Test public void onCreate() { purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); } @Test public void initializeSubscriptionsThrowsRemoteException() throws Exception { // Configure when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenThrow(new RemoteException()); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); // Verify verifyInAppBillingServiceConnected(); verifyZeroInteractions(purchaseWallet); } @Test public void initializeConsumablePurchasesThrowsRemoteException() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenThrow(new RemoteException()); // Test purchaseManager.initialize(application); // Verify verifyInAppBillingServiceConnected(); verifyZeroInteractions(purchaseWallet); } @Test public void initializeSubscriptionsHasResponseError() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_ERROR); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); // Verify verifyInAppBillingServiceConnected(); verifyZeroInteractions(purchaseWallet); } @Test public void initializeConsumablePurchasesHasResponseError() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_ERROR); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); // Verify verifyInAppBillingServiceConnected(); verifyZeroInteractions(purchaseWallet); } @Test public void initializeWithNoneOwned() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.<ManagedProduct>emptySet(), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithCancelledConsumablePurchase() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.OcrScans50)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.OcrScans50, PURCHASE_STATE_CANCELLED)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.<ManagedProduct>emptySet(), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithCancelledSubscription() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.SmartReceiptsPlus)))); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.SmartReceiptsPlus, PURCHASE_STATE_CANCELLED)))); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.<ManagedProduct>emptySet(), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithRefundedConsumablePurchase() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.OcrScans50)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.OcrScans50, PURCHASE_STATE_REFUNDED)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.<ManagedProduct>emptySet(), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithRefundedSubscription() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.SmartReceiptsPlus)))); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.SmartReceiptsPlus, PURCHASE_STATE_REFUNDED)))); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.<ManagedProduct>emptySet(), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithConsumablePurchasesOwned() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.OcrScans50)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.OcrScans50, PURCHASE_STATE_PURCHASED)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); final ManagedProduct consumablePurchase = new ConsumablePurchase(InAppPurchase.OcrScans50, getInAppPurchaseData(InAppPurchase.OcrScans50, PURCHASE_STATE_PURCHASED), PURCHASE_TOKEN, IN_APP_DATA_SIGNATURE); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.singleton(consumablePurchase), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithSubscriptionsOwned() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.SmartReceiptsPlus)))); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.SmartReceiptsPlus, PURCHASE_STATE_PURCHASED)))); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>()); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>()); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); final ManagedProduct subscription = new Subscription(InAppPurchase.SmartReceiptsPlus, getInAppPurchaseData(InAppPurchase.SmartReceiptsPlus, PURCHASE_STATE_PURCHASED), PURCHASE_TOKEN, IN_APP_DATA_SIGNATURE); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(Collections.singleton(subscription), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void initializeWithSubscriptionsAndConsumablePurchasesOwned() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.SmartReceiptsPlus)))); getSubscriptionsResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.SmartReceiptsPlus, PURCHASE_STATE_PURCHASED)))); getSubscriptionsResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "subs", null)).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_ITEM_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseItem(InAppPurchase.OcrScans50)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_PURCHASE_DATA_LIST", new ArrayList<>(Collections.singletonList(getInAppPurchaseData(InAppPurchase.OcrScans50, PURCHASE_STATE_PURCHASED)))); getConsumablePurchasesResponse.putStringArrayList("INAPP_DATA_SIGNATURE_LIST", new ArrayList<>(Collections.singletonList(getInAppDataSignature()))); when(inAppBillingService.getPurchases(3, packageName, "inapp", null)).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.initialize(application); verifyInAppBillingServiceConnected(); final ManagedProduct subscription = new Subscription(InAppPurchase.SmartReceiptsPlus, getInAppPurchaseData(InAppPurchase.SmartReceiptsPlus, PURCHASE_STATE_PURCHASED), PURCHASE_TOKEN, IN_APP_DATA_SIGNATURE); final ManagedProduct consumablePurchase = new ConsumablePurchase(InAppPurchase.OcrScans50, getInAppPurchaseData(InAppPurchase.OcrScans50, PURCHASE_STATE_PURCHASED), PURCHASE_TOKEN, IN_APP_DATA_SIGNATURE); verify(purchaseWallet).updatePurchasesInWallet(updateManagedProductsCaptor.capture()); when(purchaseWallet.getActivePurchases()).thenReturn(updateManagedProductsCaptor.getValue()); assertEquals(new HashSet<>(Arrays.asList(subscription, consumablePurchase)), updateManagedProductsCaptor.getValue()); verify(purchaseWallet).getActivePurchases(); verifyNoMoreInteractions(purchaseWallet); } @Test public void getAvailableSubscriptionsThrowsRemoteException() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_ERROR); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("subs"), bundleCaptor.capture())).thenThrow(new RemoteException()); // Test purchaseManager.getAvailableSubscriptions().test() // Verify .assertNoValues() .assertNotComplete() .assertError(RemoteException.class); verifyZeroInteractions(purchaseWallet); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getSubscriptionSkus()); } @Test public void getAvailableSubscriptionsResponseError() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_ERROR); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("subs"), bundleCaptor.capture())).thenReturn(getSkuDetailsResponse); // Test purchaseManager.getAvailableSubscriptions().test() // Verify .assertNoValues() .assertNotComplete() .assertError(Exception.class); verifyZeroInteractions(purchaseWallet); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getSubscriptionSkus()); } @Test public void getAvailableSubscriptionsWithNoneAvailable() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSkuDetailsResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>()); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("subs"), bundleCaptor.capture())).thenReturn(getSkuDetailsResponse); // Test purchaseManager.getAvailableSubscriptions().test() // Verify .assertValue(Collections.<AvailablePurchase>emptySet()) .assertComplete() .assertNoErrors(); verify(purchaseWallet, never()).addPurchaseToWallet(any(ManagedProduct.class)); verify(purchaseWallet, never()).updatePurchasesInWallet(anySetOf(ManagedProduct.class)); verify(purchaseWallet, never()).removePurchaseFromWallet(any(InAppPurchase.class)); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getSubscriptionSkus()); } @Test public void getAvailableSubscriptionsWithSomeAvailable() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSkuDetailsResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>(Collections.singletonList(getSkuDetails(InAppPurchase.SmartReceiptsPlus)))); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("subs"), bundleCaptor.capture())).thenReturn(getSkuDetailsResponse); // Test purchaseManager.getAvailableSubscriptions().test() // Verify .assertValue(Collections.singleton(getSkuDetailsAsAvailablePurchase(InAppPurchase.SmartReceiptsPlus))) .assertComplete() .assertNoErrors(); verify(purchaseWallet, never()).addPurchaseToWallet(any(ManagedProduct.class)); verify(purchaseWallet, never()).updatePurchasesInWallet(anySetOf(ManagedProduct.class)); verify(purchaseWallet, never()).removePurchaseFromWallet(any(InAppPurchase.class)); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getSubscriptionSkus()); } @Test public void getAvailableConsumablePurchasesThrowsRemoteException() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_ERROR); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("inapp"), bundleCaptor.capture())).thenThrow(new RemoteException()); // Test purchaseManager.getAvailableConsumablePurchases().test() // Verify .assertNoValues() .assertNotComplete() .assertError(RemoteException.class); verifyZeroInteractions(purchaseWallet); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getConsumablePurchaseSkus()); } @Test public void getAvailableConsumablePurchasesResponseError() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_ERROR); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("inapp"), bundleCaptor.capture())).thenReturn(getSkuDetailsResponse); // Test purchaseManager.getAvailableConsumablePurchases().test() // Verify .assertNoValues() .assertNotComplete() .assertError(Exception.class); verifyZeroInteractions(purchaseWallet); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getConsumablePurchaseSkus()); } @Test public void getAvailableConsumablePurchasesWithNoneAvailable() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSkuDetailsResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>()); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("inapp"), bundleCaptor.capture())).thenReturn(getSkuDetailsResponse); // Test purchaseManager.getAvailableConsumablePurchases().test() // Verify .assertValue(Collections.<AvailablePurchase>emptySet()) .assertComplete() .assertNoErrors(); verify(purchaseWallet, never()).addPurchaseToWallet(any(ManagedProduct.class)); verify(purchaseWallet, never()).updatePurchasesInWallet(anySetOf(ManagedProduct.class)); verify(purchaseWallet, never()).removePurchaseFromWallet(any(InAppPurchase.class)); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getConsumablePurchaseSkus()); } @Test public void getAvailableConsumablePurchasesWithSomeAvailable() throws Exception { // Configure final Bundle getSkuDetailsResponse = new Bundle(); getSkuDetailsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSkuDetailsResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>(Collections.singletonList(getSkuDetails(InAppPurchase.OcrScans50)))); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("inapp"), bundleCaptor.capture())).thenReturn(getSkuDetailsResponse); // Test purchaseManager.getAvailableConsumablePurchases().test() // Verify .assertValue(Collections.singleton(getSkuDetailsAsAvailablePurchase(InAppPurchase.OcrScans50))) .assertComplete() .assertNoErrors(); verify(purchaseWallet, never()).addPurchaseToWallet(any(ManagedProduct.class)); verify(purchaseWallet, never()).updatePurchasesInWallet(anySetOf(ManagedProduct.class)); verify(purchaseWallet, never()).removePurchaseFromWallet(any(InAppPurchase.class)); assertEquals(bundleCaptor.getValue().getStringArrayList("ITEM_ID_LIST"), InAppPurchase.getConsumablePurchaseSkus()); } @Test public void getAllAvailablePurchases() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>(Collections.singletonList(getSkuDetails(InAppPurchase.SmartReceiptsPlus)))); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("subs"), bundleCaptor.capture())).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>(Collections.singletonList(getSkuDetails(InAppPurchase.OcrScans50)))); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("inapp"), bundleCaptor.capture())).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.getAllAvailablePurchases().test() .assertValue(new HashSet<>(Arrays.asList(getSkuDetailsAsAvailablePurchase(InAppPurchase.SmartReceiptsPlus), getSkuDetailsAsAvailablePurchase(InAppPurchase.OcrScans50)))) .assertComplete() .assertNoErrors(); verify(purchaseWallet, never()).addPurchaseToWallet(any(ManagedProduct.class)); verify(purchaseWallet, never()).updatePurchasesInWallet(anySetOf(ManagedProduct.class)); verify(purchaseWallet, never()).removePurchaseFromWallet(any(InAppPurchase.class)); } @Test public void getAllAvailablePurchaseSkus() throws Exception { // Configure final Bundle getSubscriptionsResponse = new Bundle(); getSubscriptionsResponse.putInt("RESPONSE_CODE", RESULT_OK); getSubscriptionsResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>(Collections.singletonList(getSkuDetails(InAppPurchase.SmartReceiptsPlus)))); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("subs"), bundleCaptor.capture())).thenReturn(getSubscriptionsResponse); final Bundle getConsumablePurchasesResponse = new Bundle(); getConsumablePurchasesResponse.putInt("RESPONSE_CODE", RESULT_OK); getConsumablePurchasesResponse.putStringArrayList("DETAILS_LIST", new ArrayList<>(Collections.singletonList(getSkuDetails(InAppPurchase.OcrScans50)))); when(inAppBillingService.getSkuDetails(eq(3), eq(packageName), eq("inapp"), bundleCaptor.capture())).thenReturn(getConsumablePurchasesResponse); // Test purchaseManager.getAllAvailablePurchaseSkus().test() .assertValue(new HashSet<>(Arrays.asList(InAppPurchase.SmartReceiptsPlus, InAppPurchase.OcrScans50))) .assertComplete() .assertNoErrors(); verify(purchaseWallet, never()).addPurchaseToWallet(any(ManagedProduct.class)); verify(purchaseWallet, never()).updatePurchasesInWallet(anySetOf(ManagedProduct.class)); verify(purchaseWallet, never()).removePurchaseFromWallet(any(InAppPurchase.class)); } @Test public void consumePurchaseThrowsRemoteException() throws Exception { // Configure when(consumablePurchase.getPurchaseToken()).thenReturn(PURCHASE_TOKEN); when(inAppBillingService.consumePurchase(3, packageName, PURCHASE_TOKEN)).thenThrow(new RemoteException()); // Test purchaseManager.consumePurchase(consumablePurchase).test() // Verify .assertNoValues() .assertNotComplete() .assertError(RemoteException.class); } @Test public void consumePurchaseFails() throws Exception { // Configure when(consumablePurchase.getPurchaseToken()).thenReturn(PURCHASE_TOKEN); when(inAppBillingService.consumePurchase(3, packageName, PURCHASE_TOKEN)).thenReturn(RESULT_ERROR); // Test purchaseManager.consumePurchase(consumablePurchase).test() // Verify .assertNoValues() .assertNotComplete() .assertError(Exception.class); } @Test public void consumePurchaseSucceeds() throws Exception { // Configure when(consumablePurchase.getPurchaseToken()).thenReturn(PURCHASE_TOKEN); when(inAppBillingService.consumePurchase(3, packageName, PURCHASE_TOKEN)).thenReturn(RESULT_OK); // Test purchaseManager.consumePurchase(consumablePurchase).test() // Verify .assertNoValues() .assertComplete() .assertNoErrors(); } private void verifyInAppBillingServiceConnected() { final Intent intent = shadowApplication.getNextStartedService(); assertNotNull(intent); assertEquals(intent.getAction(), "com.android.vending.billing.InAppBillingService.BIND"); assertFalse(shadowApplication.getBoundServiceConnections().isEmpty()); // TODO: Verify activity life-cycle callbacks are working with custom shadow } @NonNull private String getInAppPurchaseItem(@NonNull InAppPurchase inAppPurchase) throws Exception { return inAppPurchase.getSku(); } @NonNull private String getInAppPurchaseData(@NonNull InAppPurchase inAppPurchase, int purchaseState) throws Exception { // https://developer.android.com/google/play/billing/billing_reference.html final JSONObject json = new JSONObject(); if (Subscription.class.equals(inAppPurchase.getType())) { json.put("autoRenewing", true); } json.put("orderId", "orderId"); json.put("packageName", packageName); json.put("productId", inAppPurchase.getSku()); json.put("purchaseTime", 1234567890123L); json.put("purchaseState", purchaseState); json.put("developerPayload", "1234567890"); json.put("purchaseToken", PURCHASE_TOKEN); return json.toString(); } @NonNull private String getInAppDataSignature() throws Exception { return IN_APP_DATA_SIGNATURE; } @NonNull private String getSkuDetails(@NonNull InAppPurchase inAppPurchase) throws Exception { // https://developer.android.com/google/play/billing/billing_reference.html final JSONObject json = new JSONObject(); json.put("productId", inAppPurchase.getSku()); json.put("type", "subs"); //TODO json.put("price", "$4.99"); json.put("price_amount_micros", 4990000); json.put("price_currency_code", "USD"); json.put("title", "title"); json.put("description", "description"); return json.toString(); } @NonNull private AvailablePurchase getSkuDetailsAsAvailablePurchase(@NonNull InAppPurchase inAppPurchase) throws Exception { return new Gson().fromJson(getSkuDetails(inAppPurchase), AvailablePurchase.class); } }