@Test(groups = "fast")
  public void testSubscriptionEvent() throws Exception {

    final EffectiveSubscriptionInternalEvent e =
        new DefaultEffectiveSubscriptionEvent(
            UUID.randomUUID(),
            UUID.randomUUID(),
            UUID.randomUUID(),
            new DateTime(),
            EntitlementState.ACTIVE,
            "pro",
            "TRIAL",
            "DEFAULT",
            EntitlementState.CANCELLED,
            null,
            null,
            null,
            3L,
            SubscriptionBaseTransitionType.CANCEL,
            0,
            new DateTime(),
            1L,
            2L,
            null);

    final String json = mapper.writeValueAsString(e);

    final Class<?> claz = Class.forName(DefaultEffectiveSubscriptionEvent.class.getName());
    final Object obj = mapper.readValue(json, claz);
    Assert.assertTrue(obj.equals(e));
  }
  public void jsonSerializeTree(final ObjectMapper mapper, final OutputStream output)
      throws IOException {

    final JsonGenerator generator = mapper.getFactory().createJsonGenerator(output);
    generator.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);

    walkTree(
        new WalkCallback() {

          private int curDepth = 0;

          @Override
          public void onCurrentNode(
              final int depth, final NodeInterval curNode, final NodeInterval parent) {
            final ItemsNodeInterval node = (ItemsNodeInterval) curNode;
            if (node.isRoot()) {
              return;
            }

            try {
              if (curDepth < depth) {
                generator.writeStartArray();
                curDepth = depth;
              } else if (curDepth > depth) {
                generator.writeEndArray();
                curDepth = depth;
              }
              generator.writeObject(node);
            } catch (IOException e) {
              throw new RuntimeException("Failed to deserialize tree", e);
            }
          }
        });
    generator.close();
  }
  @Test(groups = "fast")
  public void testSerialization() throws Exception {
    final ObjectMapper objectMapper = new ObjectMapper();

    final UUID tagDefinitionId = UUID.randomUUID();
    final String tagDefinitionName = UUID.randomUUID().toString();
    final String tagDefinitionDescription = UUID.randomUUID().toString();
    final boolean controlTag = true;
    final TagDefinition tagDefinition =
        new DefaultTagDefinition(
            tagDefinitionId, tagDefinitionName, tagDefinitionDescription, controlTag);
    final UUID userToken = UUID.randomUUID();

    final DefaultControlTagDefinitionCreationEvent event =
        new DefaultControlTagDefinitionCreationEvent(
            tagDefinitionId, tagDefinition, 1L, 2L, UUID.randomUUID());

    final String json = objectMapper.writeValueAsString(event);
    final DefaultControlTagDefinitionCreationEvent fromJson =
        objectMapper.readValue(json, DefaultControlTagDefinitionCreationEvent.class);
    Assert.assertEquals(fromJson, event);
  }
  @Test(groups = "slow")
  public void testFailedPaymentWithPerTenantRetryConfig() throws Exception {
    // Create the tenant
    final String apiKeyTenant1 = "tenantSuperTuned";
    final String apiSecretTenant1 = "2367$$ffr79";
    loginTenant(apiKeyTenant1, apiSecretTenant1);
    final Tenant tenant1 = new Tenant();
    tenant1.setApiKey(apiKeyTenant1);
    tenant1.setApiSecret(apiSecretTenant1);
    killBillClient.createTenant(tenant1, createdBy, reason, comment);

    // Configure our plugin to fail
    mockPaymentProviderPlugin.makeAllInvoicesFailWithError(true);

    // Upload the config
    final ObjectMapper mapper = new ObjectMapper();
    final HashMap<String, String> perTenantProperties = new HashMap<String, String>();
    perTenantProperties.put("org.killbill.payment.retry.days", "1,1,1");
    final String perTenantConfig = mapper.writeValueAsString(perTenantProperties);

    final TenantKey tenantKey =
        killBillClient.postConfigurationPropertiesForTenant(perTenantConfig, basicRequestOptions());

    final Account accountJson = createAccountWithPMBundleAndSubscriptionAndWaitForFirstInvoice();

    final Payments payments = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments.size(), 1);
    Assert.assertEquals(payments.get(0).getTransactions().size(), 1);

    //
    // Because we have specified a retry interval of one day we should see the new attempt after
    // moving clock 1 day (and not 8 days which is default)
    //

    //
    // Now unregister special per tenant config and we the first retry occurs one day after (and
    // still fails), it now sets a retry date of 8 days
    //
    killBillClient.unregisterConfigurationForTenant(basicRequestOptions());
    // org.killbill.tenant.broadcast.rate has been set to 1s
    crappyWaitForLackOfProperSynchonization(2000);

    clock.addDays(1);

    Awaitility.await()
        .atMost(4, TimeUnit.SECONDS)
        .pollInterval(Duration.ONE_SECOND)
        .until(
            new Callable<Boolean>() {
              @Override
              public Boolean call() throws Exception {

                return killBillClient
                        .getPaymentsForAccount(accountJson.getAccountId())
                        .get(0)
                        .getTransactions()
                        .size()
                    == 2;
              }
            });
    final Payments payments2 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments2.size(), 1);
    Assert.assertEquals(payments2.get(0).getTransactions().size(), 2);
    Assert.assertEquals(
        payments2.get(0).getTransactions().get(0).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());
    Assert.assertEquals(
        payments2.get(0).getTransactions().get(1).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());

    clock.addDays(1);
    crappyWaitForLackOfProperSynchonization(3000);

    // No retry with default config
    final Payments payments3 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments3.size(), 1);
    Assert.assertEquals(payments3.get(0).getTransactions().size(), 2);

    mockPaymentProviderPlugin.makeAllInvoicesFailWithError(false);
    clock.addDays(7);

    Awaitility.await()
        .atMost(4, TimeUnit.SECONDS)
        .pollInterval(Duration.ONE_SECOND)
        .until(
            new Callable<Boolean>() {
              @Override
              public Boolean call() throws Exception {
                return killBillClient
                        .getPaymentsForAccount(accountJson.getAccountId())
                        .get(0)
                        .getTransactions()
                        .size()
                    == 3;
              }
            });
    final Payments payments4 = killBillClient.getPaymentsForAccount(accountJson.getAccountId());
    Assert.assertEquals(payments4.size(), 1);
    Assert.assertEquals(payments4.get(0).getTransactions().size(), 3);
    Assert.assertEquals(
        payments4.get(0).getTransactions().get(0).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());
    Assert.assertEquals(
        payments4.get(0).getTransactions().get(1).getStatus(),
        TransactionStatus.PAYMENT_FAILURE.name());
    Assert.assertEquals(
        payments4.get(0).getTransactions().get(2).getStatus(), TransactionStatus.SUCCESS.name());
  }