mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-22 23:18:06 +01:00
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:
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user