Update SubscriptionManager to store processor+customerId in a single attribute and a map

- add `type` query parameter to `/v1/subscription/{subscriberId}/create_payment_method`
This commit is contained in:
Chris Eager
2022-10-07 14:26:17 -05:00
committed by GitHub
parent 308437ec93
commit 6341770768
11 changed files with 641 additions and 102 deletions

View File

@@ -11,20 +11,27 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
import static org.whispersystems.textsecuregcm.util.AttributeValues.b;
import static org.whispersystems.textsecuregcm.util.AttributeValues.n;
import io.dropwizard.auth.PolymorphicAuthValueFactoryProvider;
import io.dropwizard.testing.junit5.DropwizardExtensionsSupport;
import io.dropwizard.testing.junit5.ResourceExtension;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.server.ServerProperties;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.signal.libsignal.zkgroup.receipts.ServerZkReceiptOperations;
@@ -42,9 +49,12 @@ import org.whispersystems.textsecuregcm.entities.Badge;
import org.whispersystems.textsecuregcm.entities.BadgeSvg;
import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
import org.whispersystems.textsecuregcm.stripe.StripeManager;
import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer;
import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
@ExtendWith(DropwizardExtensionsSupport.class)
class SubscriptionControllerTest {
@@ -72,6 +82,11 @@ class SubscriptionControllerTest {
.addResource(SUBSCRIPTION_CONTROLLER)
.build();
@BeforeEach
void setUp() {
when(STRIPE_MANAGER.getProcessor()).thenReturn(SubscriptionProcessor.STRIPE);
}
@AfterEach
void tearDown() {
reset(CLOCK, SUBSCRIPTION_CONFIG, SUBSCRIPTION_MANAGER, STRIPE_MANAGER, ZK_OPS, ISSUED_RECEIPTS_MANAGER,
@@ -95,19 +110,161 @@ class SubscriptionControllerTest {
assertThat(response.getStatus()).isEqualTo(422);
}
@Test
void createSubscriber() {
when(CLOCK.instant()).thenReturn(Instant.now());
// basic create
final byte[] subscriberUserAndKey = new byte[32];
Arrays.fill(subscriberUserAndKey, (byte) 1);
final String subscriberId = Base64.getEncoder().encodeToString(subscriberUserAndKey);
when(SUBSCRIPTION_MANAGER.get(any(), any())).thenReturn(CompletableFuture.completedFuture(
SubscriptionManager.GetResult.NOT_STORED));
final Map<String, AttributeValue> dynamoItem = Map.of(SubscriptionManager.KEY_PASSWORD, b(new byte[16]),
SubscriptionManager.KEY_CREATED_AT, n(Instant.now().getEpochSecond()),
SubscriptionManager.KEY_ACCESSED_AT, n(Instant.now().getEpochSecond())
);
final SubscriptionManager.Record record = SubscriptionManager.Record.from(
Arrays.copyOfRange(subscriberUserAndKey, 0, 16), dynamoItem);
when(SUBSCRIPTION_MANAGER.create(any(), any(), any())).thenReturn(CompletableFuture.completedFuture(record));
final Response createResponse = RESOURCE_EXTENSION.target(String.format("/v1/subscription/%s", subscriberId))
.request()
.put(Entity.json(""));
assertThat(createResponse.getStatus()).isEqualTo(200);
// creating should be idempotent
when(SUBSCRIPTION_MANAGER.get(any(), any())).thenReturn(CompletableFuture.completedFuture(
SubscriptionManager.GetResult.found(record)));
when(SUBSCRIPTION_MANAGER.accessedAt(any(), any())).thenReturn(CompletableFuture.completedFuture(null));
final Response idempotentCreateResponse = RESOURCE_EXTENSION.target(
String.format("/v1/subscription/%s", subscriberId))
.request()
.put(Entity.json(""));
assertThat(idempotentCreateResponse.getStatus()).isEqualTo(200);
// when the manager returns `null`, it means there was a password mismatch from the storage layer `create`.
// this could happen if there is a race between two concurrent `create` requests for the same user ID
when(SUBSCRIPTION_MANAGER.get(any(), any())).thenReturn(CompletableFuture.completedFuture(
SubscriptionManager.GetResult.NOT_STORED));
when(SUBSCRIPTION_MANAGER.create(any(), any(), any())).thenReturn(CompletableFuture.completedFuture(null));
final Response managerCreateNullResponse = RESOURCE_EXTENSION.target(
String.format("/v1/subscription/%s", subscriberId))
.request()
.put(Entity.json(""));
assertThat(managerCreateNullResponse.getStatus()).isEqualTo(403);
final byte[] subscriberUserAndMismatchedKey = new byte[32];
Arrays.fill(subscriberUserAndMismatchedKey, 0, 16, (byte) 1);
Arrays.fill(subscriberUserAndMismatchedKey, 16, 32, (byte) 2);
final String mismatchedSubscriberId = Base64.getEncoder().encodeToString(subscriberUserAndMismatchedKey);
// a password mismatch for an existing record
when(SUBSCRIPTION_MANAGER.get(any(), any())).thenReturn(CompletableFuture.completedFuture(
SubscriptionManager.GetResult.PASSWORD_MISMATCH));
final Response passwordMismatchResponse = RESOURCE_EXTENSION.target(
String.format("/v1/subscription/%s", mismatchedSubscriberId))
.request()
.put(Entity.json(""));
assertThat(passwordMismatchResponse.getStatus()).isEqualTo(403);
// invalid request data is a 404
final byte[] malformedUserAndKey = new byte[16];
Arrays.fill(malformedUserAndKey, (byte) 1);
final String malformedUserId = Base64.getEncoder().encodeToString(malformedUserAndKey);
final Response malformedUserAndKeyResponse = RESOURCE_EXTENSION.target(
String.format("/v1/subscription/%s", malformedUserId))
.request()
.put(Entity.json(""));
assertThat(malformedUserAndKeyResponse.getStatus()).isEqualTo(404);
}
@Test
void createPaymentMethod() {
final byte[] subscriberUserAndKey = new byte[32];
Arrays.fill(subscriberUserAndKey, (byte) 1);
final String subscriberId = Base64.getEncoder().encodeToString(subscriberUserAndKey);
when(CLOCK.instant()).thenReturn(Instant.now());
when(SUBSCRIPTION_MANAGER.get(any(), any())).thenReturn(CompletableFuture.completedFuture(
SubscriptionManager.GetResult.NOT_STORED));
final Map<String, AttributeValue> dynamoItem = Map.of(SubscriptionManager.KEY_PASSWORD, b(new byte[16]),
SubscriptionManager.KEY_CREATED_AT, n(Instant.now().getEpochSecond()),
SubscriptionManager.KEY_ACCESSED_AT, n(Instant.now().getEpochSecond())
);
final SubscriptionManager.Record record = SubscriptionManager.Record.from(
Arrays.copyOfRange(subscriberUserAndKey, 0, 16), dynamoItem);
when(SUBSCRIPTION_MANAGER.create(any(), any(), any(Instant.class)))
.thenReturn(CompletableFuture.completedFuture(
record));
final Response createSubscriberResponse = RESOURCE_EXTENSION
.target(String.format("/v1/subscription/%s", subscriberId))
.request()
.put(Entity.json(""));
assertThat(createSubscriberResponse.getStatus()).isEqualTo(200);
when(SUBSCRIPTION_MANAGER.get(any(), any()))
.thenReturn(CompletableFuture.completedFuture(SubscriptionManager.GetResult.found(record)));
final String customerId = "some-customer-id";
final ProcessorCustomer customer = new ProcessorCustomer(
customerId, SubscriptionProcessor.STRIPE);
when(STRIPE_MANAGER.createCustomer(any()))
.thenReturn(CompletableFuture.completedFuture(customer));
final SubscriptionManager.Record recordWithCustomerId = SubscriptionManager.Record.from(record.user, dynamoItem);
recordWithCustomerId.customerId = customerId;
recordWithCustomerId.processorsToCustomerIds.put(SubscriptionProcessor.STRIPE, customerId);
when(SUBSCRIPTION_MANAGER.updateProcessorAndCustomerId(any(SubscriptionManager.Record.class), any(),
any(Instant.class)))
.thenReturn(CompletableFuture.completedFuture(recordWithCustomerId));
final String clientSecret = "some-client-secret";
when(STRIPE_MANAGER.createPaymentMethodSetupToken(customerId))
.thenReturn(CompletableFuture.completedFuture(clientSecret));
final SubscriptionController.CreatePaymentMethodResponse createPaymentMethodResponse = RESOURCE_EXTENSION
.target(String.format("/v1/subscription/%s/create_payment_method", subscriberId))
.request()
.post(Entity.json(""))
.readEntity(SubscriptionController.CreatePaymentMethodResponse.class);
assertThat(createPaymentMethodResponse.processor()).isEqualTo(SubscriptionProcessor.STRIPE);
assertThat(createPaymentMethodResponse.clientSecret()).isEqualTo(clientSecret);
}
@Test
void getLevels() {
when(SUBSCRIPTION_CONFIG.getLevels()).thenReturn(Map.of(
1L, new SubscriptionLevelConfiguration("B1", "P1", Map.of("USD", new SubscriptionPriceConfiguration("R1", BigDecimal.valueOf(100)))),
2L, new SubscriptionLevelConfiguration("B2", "P2", Map.of("USD", new SubscriptionPriceConfiguration("R2", BigDecimal.valueOf(200)))),
3L, new SubscriptionLevelConfiguration("B3", "P3", Map.of("USD", new SubscriptionPriceConfiguration("R3", BigDecimal.valueOf(300))))
1L, new SubscriptionLevelConfiguration("B1", "P1",
Map.of("USD", new SubscriptionPriceConfiguration("R1", BigDecimal.valueOf(100)))),
2L, new SubscriptionLevelConfiguration("B2", "P2",
Map.of("USD", new SubscriptionPriceConfiguration("R2", BigDecimal.valueOf(200)))),
3L, new SubscriptionLevelConfiguration("B3", "P3",
Map.of("USD", new SubscriptionPriceConfiguration("R3", BigDecimal.valueOf(300))))
));
when(BADGE_TRANSLATOR.translate(any(), eq("B1"))).thenReturn(new Badge("B1", "cat1", "name1", "desc1",
List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
List.of("l", "m", "h", "x", "xx", "xxx"), "SVG",
List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
when(BADGE_TRANSLATOR.translate(any(), eq("B2"))).thenReturn(new Badge("B2", "cat2", "name2", "desc2",
List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
List.of("l", "m", "h", "x", "xx", "xxx"), "SVG",
List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
when(BADGE_TRANSLATOR.translate(any(), eq("B3"))).thenReturn(new Badge("B3", "cat3", "name3", "desc3",
List.of("l", "m", "h", "x", "xx", "xxx"), "SVG", List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
List.of("l", "m", "h", "x", "xx", "xxx"), "SVG",
List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
when(LEVEL_TRANSLATOR.translate(any(), eq("B1"))).thenReturn("Z1");
when(LEVEL_TRANSLATOR.translate(any(), eq("B2"))).thenReturn("Z2");
when(LEVEL_TRANSLATOR.translate(any(), eq("B3"))).thenReturn("Z3");

View File

@@ -9,11 +9,16 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult.Type.FOUND;
import static org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult.Type.NOT_STORED;
import static org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult.Type.PASSWORD_MISMATCH;
import static org.whispersystems.textsecuregcm.util.AttributeValues.b;
import static org.whispersystems.textsecuregcm.util.AttributeValues.n;
import static org.whispersystems.textsecuregcm.util.AttributeValues.s;
import java.security.SecureRandom;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
@@ -22,6 +27,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult;
import org.whispersystems.textsecuregcm.storage.SubscriptionManager.Record;
import org.whispersystems.textsecuregcm.subscriptions.ProcessorCustomer;
import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
@@ -85,8 +92,6 @@ class SubscriptionManagerTest {
void testCreateOnlyOnce() {
byte[] password1 = getRandomBytes(16);
byte[] password2 = getRandomBytes(16);
String customer1 = Base64.getEncoder().encodeToString(getRandomBytes(16));
String customer2 = Base64.getEncoder().encodeToString(getRandomBytes(16));
Instant created1 = Instant.ofEpochSecond(NOW_EPOCH_SECONDS);
Instant created2 = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
@@ -103,16 +108,16 @@ class SubscriptionManagerTest {
});
CompletableFuture<SubscriptionManager.Record> createFuture =
subscriptionManager.create(user, password1, customer1, created1);
Consumer<Record> recordRequirements = checkFreshlyCreatedRecord(user, password1, customer1, created1);
subscriptionManager.create(user, password1, created1);
Consumer<Record> recordRequirements = checkFreshlyCreatedRecord(user, password1, created1);
assertThat(createFuture).succeedsWithin(Duration.ofSeconds(3)).satisfies(recordRequirements);
// password check fails so this should return null
createFuture = subscriptionManager.create(user, password2, customer2, created2);
createFuture = subscriptionManager.create(user, password2, created2);
assertThat(createFuture).succeedsWithin(Duration.ofSeconds(3)).isNull();
// password check matches, but the record already exists so nothing should get updated
createFuture = subscriptionManager.create(user, password1, customer2, created2);
createFuture = subscriptionManager.create(user, password1, created2);
assertThat(createFuture).succeedsWithin(Duration.ofSeconds(3)).satisfies(recordRequirements);
}
@@ -120,27 +125,67 @@ class SubscriptionManagerTest {
void testGet() {
byte[] wrongUser = getRandomBytes(16);
byte[] wrongPassword = getRandomBytes(16);
assertThat(subscriptionManager.create(user, password, customer, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
assertThat(getResult.type).isEqualTo(FOUND);
assertThat(getResult.record).isNotNull().satisfies(checkFreshlyCreatedRecord(user, password, customer, created));
assertThat(getResult.record).isNotNull().satisfies(checkFreshlyCreatedRecord(user, password, created));
});
assertThat(subscriptionManager.get(user, wrongPassword)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
assertThat(getResult.type).isEqualTo(PASSWORD_MISMATCH);
assertThat(getResult.record).isNull();
});
assertThat(subscriptionManager.get(user, wrongPassword)).succeedsWithin(Duration.ofSeconds(3))
.satisfies(getResult -> {
assertThat(getResult.type).isEqualTo(PASSWORD_MISMATCH);
assertThat(getResult.record).isNull();
});
assertThat(subscriptionManager.get(wrongUser, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
assertThat(getResult.type).isEqualTo(NOT_STORED);
assertThat(getResult.record).isNull();
});
assertThat(subscriptionManager.get(wrongUser, password)).succeedsWithin(Duration.ofSeconds(3))
.satisfies(getResult -> {
assertThat(getResult.type).isEqualTo(NOT_STORED);
assertThat(getResult.record).isNull();
});
}
@Test
void testLookupByCustomerId() {
assertThat(subscriptionManager.create(user, password, customer, created)).succeedsWithin(Duration.ofSeconds(3));
void testUpdateCustomerIdAndProcessor() throws Exception {
Instant subscriptionUpdated = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
final CompletableFuture<GetResult> getUser = subscriptionManager.get(user, password);
assertThat(getUser).succeedsWithin(Duration.ofSeconds(3));
final Record userRecord = getUser.get().record;
assertThat(subscriptionManager.updateProcessorAndCustomerId(userRecord,
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE),
subscriptionUpdated)).succeedsWithin(Duration.ofSeconds(3))
.hasFieldOrPropertyWithValue("customerId", customer)
.hasFieldOrPropertyWithValue("processorsToCustomerIds", Map.of(SubscriptionProcessor.STRIPE, customer));
assertThat(
subscriptionManager.updateProcessorAndCustomerId(userRecord,
new ProcessorCustomer(customer + "1", SubscriptionProcessor.STRIPE),
subscriptionUpdated)).succeedsWithin(Duration.ofSeconds(3))
.hasFieldOrPropertyWithValue("customerId", customer)
.hasFieldOrPropertyWithValue("processorsToCustomerIds", Map.of(SubscriptionProcessor.STRIPE, customer));
// TODO test new customer ID with new processor does change the customer ID, once there is another processor
assertThat(subscriptionManager.getSubscriberUserByStripeCustomerId(customer))
.succeedsWithin(Duration.ofSeconds(3)).
isEqualTo(user);
}
@Test
void testLookupByCustomerId() throws Exception {
Instant subscriptionUpdated = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
final CompletableFuture<GetResult> getUser = subscriptionManager.get(user, password);
assertThat(getUser).succeedsWithin(Duration.ofSeconds(3));
final Record userRecord = getUser.get().record;
assertThat(subscriptionManager.updateProcessorAndCustomerId(userRecord,
new ProcessorCustomer(customer, SubscriptionProcessor.STRIPE),
subscriptionUpdated)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.getSubscriberUserByStripeCustomerId(customer)).
succeedsWithin(Duration.ofSeconds(3)).
isEqualTo(user);
@@ -149,7 +194,7 @@ class SubscriptionManagerTest {
@Test
void testCanceledAt() {
Instant canceled = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 42);
assertThat(subscriptionManager.create(user, password, customer, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.canceledAt(user, canceled)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
assertThat(getResult).isNotNull();
@@ -167,7 +212,7 @@ class SubscriptionManagerTest {
String subscriptionId = Base64.getEncoder().encodeToString(getRandomBytes(16));
Instant subscriptionCreated = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 1);
long level = 42;
assertThat(subscriptionManager.create(user, password, customer, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.subscriptionCreated(user, subscriptionId, subscriptionCreated, level)).
succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
@@ -187,7 +232,7 @@ class SubscriptionManagerTest {
void testSubscriptionLevelChanged() {
Instant at = Instant.ofEpochSecond(NOW_EPOCH_SECONDS + 500);
long level = 1776;
assertThat(subscriptionManager.create(user, password, customer, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.create(user, password, created)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.subscriptionLevelChanged(user, at, level)).succeedsWithin(Duration.ofSeconds(3));
assertThat(subscriptionManager.get(user, password)).succeedsWithin(Duration.ofSeconds(3)).satisfies(getResult -> {
assertThat(getResult).isNotNull();
@@ -200,6 +245,74 @@ class SubscriptionManagerTest {
});
}
@Test
void testSubscriptionAddProcessorAttribute() throws Exception {
final byte[] user = new byte[16];
Arrays.fill(user, (byte) 1);
final byte[] hmac = new byte[16];
Arrays.fill(hmac, (byte) 2);
final String customerId = "abcdef";
// manually create an existing record, with only KEY_CUSTOMER_ID
dynamoDbExtension.getDynamoDbClient().putItem(p ->
p.tableName(dynamoDbExtension.getTableName())
.item(Map.of(
SubscriptionManager.KEY_USER, b(user),
SubscriptionManager.KEY_PASSWORD, b(hmac),
SubscriptionManager.KEY_CREATED_AT, n(Instant.now().getEpochSecond()),
SubscriptionManager.KEY_CUSTOMER_ID, s(customerId),
SubscriptionManager.KEY_ACCESSED_AT, n(Instant.now().getEpochSecond())
))
);
final CompletableFuture<GetResult> firstGetResult = subscriptionManager.get(user, hmac);
assertThat(firstGetResult).succeedsWithin(Duration.ofSeconds(1));
final Record firstRecord = firstGetResult.get().record;
assertThat(firstRecord.customerId).isEqualTo(customerId);
assertThat(firstRecord.processor).isNull();
assertThat(firstRecord.processorsToCustomerIds).isEmpty();
// Try to update the user to have a different customer ID. This should quietly fail,
// and just return the existing customer ID.
final CompletableFuture<Record> firstUpdate = subscriptionManager.updateProcessorAndCustomerId(firstRecord,
new ProcessorCustomer(customerId + "something else", SubscriptionProcessor.STRIPE),
Instant.now());
assertThat(firstUpdate).succeedsWithin(Duration.ofSeconds(1));
final String firstUpdateCustomerId = firstUpdate.get().customerId;
assertThat(firstUpdateCustomerId).isEqualTo(customerId);
// Now update with the existing customer ID. All fields should now be populated.
final CompletableFuture<Record> secondUpdate = subscriptionManager.updateProcessorAndCustomerId(firstRecord,
new ProcessorCustomer(customerId, SubscriptionProcessor.STRIPE), Instant.now());
assertThat(secondUpdate).succeedsWithin(Duration.ofSeconds(1));
final String secondUpdateCustomerId = secondUpdate.get().customerId;
assertThat(secondUpdateCustomerId).isEqualTo(customerId);
final CompletableFuture<GetResult> secondGetResult = subscriptionManager.get(user, hmac);
assertThat(secondGetResult).succeedsWithin(Duration.ofSeconds(1));
final Record secondRecord = secondGetResult.get().record;
assertThat(secondRecord.customerId).isEqualTo(customerId);
assertThat(secondRecord.processor).isEqualTo(SubscriptionProcessor.STRIPE);
assertThat(secondRecord.processorsToCustomerIds).isEqualTo(Map.of(SubscriptionProcessor.STRIPE, customerId));
}
@Test
void testProcessorAndCustomerId() {
final ProcessorCustomer processorCustomer =
new ProcessorCustomer("abc", SubscriptionProcessor.STRIPE);
assertThat(processorCustomer.toDynamoBytes()).isEqualTo(new byte[]{1, 97, 98, 99});
}
private static byte[] getRandomBytes(int length) {
byte[] result = new byte[length];
SECURE_RANDOM.nextBytes(result);
@@ -208,12 +321,12 @@ class SubscriptionManagerTest {
@Nonnull
private static Consumer<Record> checkFreshlyCreatedRecord(
byte[] user, byte[] password, String customer, Instant created) {
byte[] user, byte[] password, Instant created) {
return record -> {
assertThat(record).isNotNull();
assertThat(record.user).isEqualTo(user);
assertThat(record.password).isEqualTo(password);
assertThat(record.customerId).isEqualTo(customer);
assertThat(record.customerId).isNull();
assertThat(record.createdAt).isEqualTo(created);
assertThat(record.subscriptionId).isNull();
assertThat(record.subscriptionCreatedAt).isNull();