Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
import io.gravitee.apim.core.subscription.use_case.ImportSubscriptionSpecUseCase;
import io.gravitee.apim.core.subscription.use_case.RejectSubscriptionUseCase;
import io.gravitee.apim.core.subscription.use_case.UpdateSubscriptionUseCase;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.apim.core.user.domain_service.UserContextLoader;
import io.gravitee.apim.infra.adapter.SubscriptionAdapter;
import io.gravitee.apim.infra.adapter.SubscriptionAdapterImpl;
Expand Down Expand Up @@ -1084,4 +1085,9 @@ public JsonSchemaChecker jsonSchemaChecker() {
public ClusterConfigurationSchemaService clusterConfigurationSchemaService() {
return mock(ClusterConfigurationSchemaService.class);
}

@Bean
public SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator() {
return mock(SubscriptionFormSchemaGenerator.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
import io.gravitee.apim.core.subscription.use_case.ImportSubscriptionSpecUseCase;
import io.gravitee.apim.core.subscription.use_case.RejectSubscriptionUseCase;
import io.gravitee.apim.core.subscription.use_case.UpdateSubscriptionUseCase;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.apim.core.user.domain_service.UserContextLoader;
import io.gravitee.apim.core.user.domain_service.UserDomainService;
import io.gravitee.apim.core.user.use_case.GetUserApisUseCase;
Expand Down Expand Up @@ -1267,4 +1268,9 @@ public GetUserApplicationsUseCase getUserApplicationsUseCase() {
public GetUserGroupsUseCase getUserGroupsUseCase() {
return mock(GetUserGroupsUseCase.class);
}

@Bean
public SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator() {
return mock(SubscriptionFormSchemaGenerator.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import inmemory.PortalNavigationItemsQueryServiceInMemory;
import inmemory.PortalPageContentQueryServiceInMemory;
import inmemory.SharedPolicyGroupCrudServiceInMemory;
import inmemory.SubscriptionFormQueryServiceInMemory;
import inmemory.SubscriptionSearchQueryServiceInMemory;
import inmemory.spring.InMemoryConfiguration;
import io.gravitee.apim.core.access_point.query_service.AccessPointQueryService;
Expand Down Expand Up @@ -148,6 +149,8 @@
import io.gravitee.apim.core.subscription.use_case.GetSubscriptionsUseCase;
import io.gravitee.apim.core.subscription.use_case.ImportSubscriptionSpecUseCase;
import io.gravitee.apim.core.subscription.use_case.UpdateSubscriptionUseCase;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.apim.core.subscription_form.query_service.SubscriptionFormQueryService;
import io.gravitee.apim.core.user.domain_service.UserContextLoader;
import io.gravitee.apim.infra.adapter.SubscriptionAdapter;
import io.gravitee.apim.infra.adapter.SubscriptionAdapterImpl;
Expand Down Expand Up @@ -1260,6 +1263,16 @@ public ApplicationCertificatesUpdateDomainService applicationCertificatesUpdateD
return mock(ApplicationCertificatesUpdateDomainService.class);
}

@Bean
public SubscriptionFormQueryService subscriptionFormQueryService() {
return new SubscriptionFormQueryServiceInMemory();
}

@Bean
public SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator() {
return mock(SubscriptionFormSchemaGenerator.class);
}

@Bean
public ClientCertificateValidationDomainService clientCertificateValidationDomainService() {
return mock(ClientCertificateValidationDomainService.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.mockito.Mockito.reset;

import io.gravitee.apim.core.subscription.use_case.CreateSubscriptionUseCase;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.rest.api.portal.rest.JerseySpringTest;
import io.gravitee.rest.api.portal.rest.mapper.AnalyticsMapper;
import io.gravitee.rest.api.portal.rest.mapper.ApiMapper;
Expand Down Expand Up @@ -319,6 +320,9 @@ public abstract class AbstractResourceTest extends JerseySpringTest {
@Autowired
protected EndpointConnectorPluginService endpointConnectorPluginService;

@Autowired
protected SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator;

public AbstractResourceTest() {
super(
new AuthenticationProviderManager() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import io.gravitee.apim.core.subscription.model.SubscriptionEntity;
import io.gravitee.apim.core.subscription.use_case.CreateSubscriptionUseCase;
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormValidationException;
import io.gravitee.common.data.domain.Page;
import io.gravitee.common.http.HttpStatusCode;
import io.gravitee.rest.api.model.ApiKeyEntity;
Expand All @@ -58,6 +59,7 @@
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Response;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down Expand Up @@ -235,6 +237,19 @@ void shouldReturnBadRequestWhenMetadataKeyIsInvalid() {
verify(createSubscriptionUseCase, times(1)).execute(any());
}

@Test
void shouldReturnBadRequestWhenSubscriptionFormMetadataIsInvalid() {
doThrow(new SubscriptionFormValidationException(List.of("Field 'email' is required")))
.when(createSubscriptionUseCase)
.execute(any());

SubscriptionInput subscriptionInput = new SubscriptionInput().application(APPLICATION).plan(PLAN).metadata(Map.of());

final Response response = target().request().post(Entity.json(subscriptionInput));
Assertions.assertEquals(HttpStatusCode.BAD_REQUEST_400, response.getStatus());
verify(createSubscriptionUseCase, times(1)).execute(any());
}

@Test
public void shouldHaveBadRequestWhileCreatingSubscription() {
final Response response = target().request().post(Entity.json(null));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
import io.gravitee.apim.core.subscription.use_case.GetSubscriptionsUseCase;
import io.gravitee.apim.core.subscription.use_case.ImportSubscriptionSpecUseCase;
import io.gravitee.apim.core.subscription.use_case.UpdateSubscriptionUseCase;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.apim.core.user.domain_service.UserContextLoader;
import io.gravitee.apim.infra.adapter.SubscriptionAdapter;
import io.gravitee.apim.infra.adapter.SubscriptionAdapterImpl;
Expand Down Expand Up @@ -1227,4 +1228,9 @@ public JsonSchemaChecker jsonSchemaChecker() {
public ClusterConfigurationSchemaService clusterConfigurationSchemaService() {
return mock(ClusterConfigurationSchemaService.class);
}

@Bean
SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator() {
return mock(SubscriptionFormSchemaGenerator.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,16 @@
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdown;
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdownValidator;
import io.gravitee.apim.core.subscription_form.crud_service.SubscriptionFormCrudService;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormConstraintsFactory;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSubmissionValidator;
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormNotFoundException;
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormValidationException;
import io.gravitee.apim.core.subscription_form.model.SubscriptionForm;
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormFieldConstraints;
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormId;
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormSchema;
import io.gravitee.apim.core.subscription_form.query_service.SubscriptionFormQueryService;
import java.util.List;
import lombok.CustomLog;
import lombok.RequiredArgsConstructor;

Expand All @@ -42,6 +47,7 @@ public class UpdateSubscriptionFormUseCase {
private final SubscriptionFormCrudService subscriptionFormCrudService;
private final SubscriptionFormQueryService subscriptionFormQueryService;
private final GraviteeMarkdownValidator graviteeMarkdownValidator;
private final SubscriptionFormSchemaGenerator schemaGenerator;

public Output execute(Input input) {
graviteeMarkdownValidator.validateNotEmpty(GraviteeMarkdown.of(input.gmdContent()));
Expand All @@ -55,14 +61,26 @@ public Output execute(Input input) {
)
);

existingForm.update(GraviteeMarkdown.of(input.gmdContent()), SubscriptionFormFieldConstraints.empty());
var gmd = GraviteeMarkdown.of(input.gmdContent());
var schema = schemaGenerator.generate(gmd);
validateFieldCount(schema);
var constraints = SubscriptionFormConstraintsFactory.fromSchema(schema);
existingForm.update(gmd, constraints);
var savedForm = subscriptionFormCrudService.update(existingForm);

log.info("Updated subscription form [{}] for environment [{}]", input.subscriptionFormId(), input.environmentId());

return new Output(savedForm);
}

private void validateFieldCount(SubscriptionFormSchema schema) {
if (schema != null && schema.fields().size() > SubscriptionFormSubmissionValidator.MAX_METADATA_COUNT) {
throw new SubscriptionFormValidationException(
List.of("Subscription form must not exceed " + SubscriptionFormSubmissionValidator.MAX_METADATA_COUNT + " fields")
);
}
}

public record Input(String environmentId, SubscriptionFormId subscriptionFormId, String gmdContent) {}

public record Output(SubscriptionForm subscriptionForm) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import io.gravitee.apim.core.application_certificate.crud_service.ClientCertificateCrudService;
import io.gravitee.apim.core.application_certificate.model.ClientCertificate;
import io.gravitee.apim.core.application_certificate.model.ClientCertificateStatus;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSubmissionValidator;
import io.gravitee.apim.core.subscription_form.model.SubscriptionForm;
import io.gravitee.apim.core.subscription_form.query_service.SubscriptionFormQueryService;
import io.gravitee.definition.model.v4.plan.PlanMode;
import io.gravitee.rest.api.model.NewSubscriptionEntity;
import io.gravitee.rest.api.model.PlanSecurityType;
Expand All @@ -32,6 +35,7 @@
import io.gravitee.rest.api.service.v4.validation.SubscriptionMetadataSanitizer;
import io.gravitee.rest.api.service.v4.validation.SubscriptionValidationService;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.CustomLog;
import lombok.RequiredArgsConstructor;
Expand All @@ -48,6 +52,7 @@ public class SubscriptionValidationServiceImpl extends TransactionalService impl

private final EntrypointConnectorPluginService entrypointService;
private final SubscriptionMetadataSanitizer subscriptionMetadataSanitizer;
private final SubscriptionFormQueryService subscriptionFormQueryService;

private final ClientCertificateCrudService clientCertificateCrudService;

Expand All @@ -57,6 +62,22 @@ public void validateAndSanitize(final GenericPlanEntity genericPlanEntity, final
if (subscription.getMetadata() != null) {
subscription.setMetadata(subscriptionMetadataSanitizer.sanitizeAndValidate(subscription.getMetadata()));
}
validateSubscriptionFormMetadataIfApplicable(genericPlanEntity, subscription.getMetadata());
}

private void validateSubscriptionFormMetadataIfApplicable(
final GenericPlanEntity genericPlanEntity,
final Map<String, String> metadata
) {
subscriptionFormQueryService
.findDefaultForEnvironmentId(genericPlanEntity.getEnvironmentId())
.filter(SubscriptionForm::isEnabled)
.map(SubscriptionForm::getValidationConstraints)
.filter(constraints -> !constraints.isEmpty())
.ifPresent(constraints -> {
var submitted = metadata != null ? metadata : Map.<String, String>of();
new SubscriptionFormSubmissionValidator(constraints).validate(submitted);
});
}

@Override
Expand All @@ -70,6 +91,7 @@ public void validateAndSanitize(
if (subscription.getMetadata() != null) {
subscription.setMetadata(subscriptionMetadataSanitizer.sanitizeAndValidate(subscription.getMetadata()));
}
validateSubscriptionFormMetadataIfApplicable(genericPlanEntity, subscription.getMetadata());
}

private void validateTls(final GenericPlanEntity genericPlanEntity, final UpdateSubscriptionEntity subscription, String applicationId) {
Expand Down Expand Up @@ -109,6 +131,7 @@ public void validateAndSanitize(
subscriptionMetadataSanitizer.sanitizeAndValidate(subscriptionConfiguration.getMetadata())
);
}
validateSubscriptionFormMetadataIfApplicable(genericPlanEntity, subscriptionConfiguration.getMetadata());
}

private SubscriptionConfigurationEntity validateAndSanitizeSubscriptionConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdown;
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdownValidator;
import io.gravitee.apim.core.gravitee_markdown.exception.GraviteeMarkdownContentEmptyException;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSubmissionValidator;
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormNotFoundException;
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormValidationException;
import io.gravitee.apim.core.subscription_form.model.SubscriptionForm;
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormFieldConstraints;
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormId;
import io.gravitee.apim.infra.domain_service.subscription_form.SubscriptionFormSchemaGeneratorImpl;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -37,13 +40,15 @@ class UpdateSubscriptionFormUseCaseTest {
private final SubscriptionFormCrudServiceInMemory crudService = new SubscriptionFormCrudServiceInMemory();
private final SubscriptionFormQueryServiceInMemory queryService = new SubscriptionFormQueryServiceInMemory();
private final GraviteeMarkdownValidator gmdValidator = new GraviteeMarkdownValidator();
private final SubscriptionFormSchemaGenerator schemaGenerator = new SubscriptionFormSchemaGeneratorImpl();

private UpdateSubscriptionFormUseCase useCase;

@BeforeEach
void setUp() {
crudService.reset();
queryService.reset();
useCase = new UpdateSubscriptionFormUseCase(crudService, queryService, gmdValidator);
useCase = new UpdateSubscriptionFormUseCase(crudService, queryService, gmdValidator, schemaGenerator);
}

@Test
Expand All @@ -67,7 +72,22 @@ void should_update_existing_form() {
GraviteeMarkdown.of("<gmd-input name=\"updated\" fieldKey=\"updated\"/>")
);
assertThat(result.subscriptionForm().getId()).isEqualTo(existingForm.getId());
assertThat(result.subscriptionForm().getValidationConstraints()).isEqualTo(SubscriptionFormFieldConstraints.empty());
assertThat(result.subscriptionForm().getValidationConstraints()).isNotNull();
assertThat(result.subscriptionForm().getValidationConstraints().byFieldKey()).containsKey("updated");
}

@Test
void should_persist_empty_constraints_when_gmd_has_no_form_fields() {
SubscriptionForm existingForm = SubscriptionFormFixtures.aSubscriptionForm();
crudService.initWith(List.of(existingForm));
queryService.initWith(List.of(existingForm));

var result = useCase.execute(
new UpdateSubscriptionFormUseCase.Input(existingForm.getEnvironmentId(), existingForm.getId(), "<p>Only static content</p>")
);

assertThat(result.subscriptionForm().getValidationConstraints()).isNotNull();
assertThat(result.subscriptionForm().getValidationConstraints().isEmpty()).isTrue();
}

@Test
Expand All @@ -80,6 +100,30 @@ void should_throw_exception_when_form_not_exists() {
assertThatThrownBy(() -> useCase.execute(input)).isInstanceOf(SubscriptionFormNotFoundException.class);
}

@Test
void should_throw_when_form_exceeds_max_field_count() {
SubscriptionForm existingForm = SubscriptionFormFixtures.aSubscriptionForm();
crudService.initWith(List.of(existingForm));
queryService.initWith(List.of(existingForm));

int tooMany = SubscriptionFormSubmissionValidator.MAX_METADATA_COUNT + 1;
StringBuilder gmd = new StringBuilder();
for (int i = 0; i < tooMany; i++) {
gmd.append("<gmd-input fieldKey=\"field").append(i).append("\"/>");
}

var input = new UpdateSubscriptionFormUseCase.Input(existingForm.getEnvironmentId(), existingForm.getId(), gmd.toString());

assertThatThrownBy(() -> useCase.execute(input))
.isInstanceOf(SubscriptionFormValidationException.class)
.extracting(e -> ((SubscriptionFormValidationException) e).getErrors())
.satisfies(errors ->
assertThat(errors).containsExactly(
"Subscription form must not exceed " + SubscriptionFormSubmissionValidator.MAX_METADATA_COUNT + " fields"
)
);
}

@Test
void should_throw_when_content_is_empty() {
// Given
Expand Down
Loading