Skip to content

Commit 1ab6a9f

Browse files
feat: wire constraint generation on form save and validate on subscription
1 parent bad1347 commit 1ab6a9f

File tree

8 files changed

+243
-10
lines changed

8 files changed

+243
-10
lines changed

gravitee-apim-rest-api/gravitee-apim-rest-api-portal/gravitee-apim-rest-api-portal-rest/src/test/java/io/gravitee/rest/api/portal/rest/resource/AbstractResourceTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import static org.mockito.Mockito.reset;
1919

2020
import io.gravitee.apim.core.subscription.use_case.CreateSubscriptionUseCase;
21+
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
2122
import io.gravitee.rest.api.portal.rest.JerseySpringTest;
2223
import io.gravitee.rest.api.portal.rest.mapper.AnalyticsMapper;
2324
import io.gravitee.rest.api.portal.rest.mapper.ApiMapper;
@@ -319,6 +320,9 @@ public abstract class AbstractResourceTest extends JerseySpringTest {
319320
@Autowired
320321
protected EndpointConnectorPluginService endpointConnectorPluginService;
321322

323+
@Autowired
324+
protected SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator;
325+
322326
public AbstractResourceTest() {
323327
super(
324328
new AuthenticationProviderManager() {

gravitee-apim-rest-api/gravitee-apim-rest-api-portal/gravitee-apim-rest-api-portal-rest/src/test/java/io/gravitee/rest/api/portal/rest/resource/SubscriptionsResourceTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
import io.gravitee.apim.core.subscription.model.SubscriptionEntity;
3737
import io.gravitee.apim.core.subscription.use_case.CreateSubscriptionUseCase;
38+
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormValidationException;
3839
import io.gravitee.common.data.domain.Page;
3940
import io.gravitee.common.http.HttpStatusCode;
4041
import io.gravitee.rest.api.model.ApiKeyEntity;
@@ -58,6 +59,7 @@
5859
import jakarta.ws.rs.client.Entity;
5960
import jakarta.ws.rs.core.Response;
6061
import java.util.Collections;
62+
import java.util.List;
6163
import java.util.Map;
6264
import lombok.AllArgsConstructor;
6365
import lombok.Getter;
@@ -235,6 +237,19 @@ void shouldReturnBadRequestWhenMetadataKeyIsInvalid() {
235237
verify(createSubscriptionUseCase, times(1)).execute(any());
236238
}
237239

240+
@Test
241+
void shouldReturnBadRequestWhenSubscriptionFormMetadataIsInvalid() {
242+
doThrow(new SubscriptionFormValidationException(List.of("Field 'email' is required")))
243+
.when(createSubscriptionUseCase)
244+
.execute(any());
245+
246+
SubscriptionInput subscriptionInput = new SubscriptionInput().application(APPLICATION).plan(PLAN).metadata(Map.of());
247+
248+
final Response response = target().request().post(Entity.json(subscriptionInput));
249+
Assertions.assertEquals(HttpStatusCode.BAD_REQUEST_400, response.getStatus());
250+
verify(createSubscriptionUseCase, times(1)).execute(any());
251+
}
252+
238253
@Test
239254
public void shouldHaveBadRequestWhileCreatingSubscription() {
240255
final Response response = target().request().post(Entity.json(null));

gravitee-apim-rest-api/gravitee-apim-rest-api-portal/gravitee-apim-rest-api-portal-rest/src/test/java/io/gravitee/rest/api/portal/rest/spring/ResourceContextConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
import io.gravitee.apim.core.subscription.use_case.GetSubscriptionsUseCase;
145145
import io.gravitee.apim.core.subscription.use_case.ImportSubscriptionSpecUseCase;
146146
import io.gravitee.apim.core.subscription.use_case.UpdateSubscriptionUseCase;
147+
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
147148
import io.gravitee.apim.core.user.domain_service.UserContextLoader;
148149
import io.gravitee.apim.infra.adapter.SubscriptionAdapter;
149150
import io.gravitee.apim.infra.adapter.SubscriptionAdapterImpl;
@@ -1221,4 +1222,9 @@ public JsonSchemaChecker jsonSchemaChecker() {
12211222
public ClusterConfigurationSchemaService clusterConfigurationSchemaService() {
12221223
return mock(ClusterConfigurationSchemaService.class);
12231224
}
1225+
1226+
@Bean
1227+
SubscriptionFormSchemaGenerator subscriptionFormSchemaGenerator() {
1228+
return mock(SubscriptionFormSchemaGenerator.class);
1229+
}
12241230
}

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/subscription_form/model/SubscriptionForm.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,17 @@ public class SubscriptionForm {
4545
private boolean enabled;
4646
/**
4747
* Validation rules per field key, derived from GMD and persisted as JSON at the repository boundary.
48-
* {@code null} when nothing is stored for this form (including when there are no rules to persist).
48+
* On {@link #update}, this is always set to the constraints computed from the new GMD (possibly empty).
4949
*/
5050
private SubscriptionFormFieldConstraints validationConstraints;
5151

5252
/**
53-
* Updates this form (mutates in place). When {@code constraints} is {@code null},
54-
* existing validation constraints are left unchanged — use case can defer populating this until the resource layer.
53+
* Updates this form (mutates in place). Always replaces validation constraints with the given value
54+
* (typically {@link io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormConstraintsFactory#fromSchema}).
5555
*/
5656
public void update(GraviteeMarkdown gmdContent, SubscriptionFormFieldConstraints constraints) {
5757
this.gmdContent = gmdContent;
58-
if (constraints != null) {
59-
this.validationConstraints = constraints;
60-
}
58+
this.validationConstraints = constraints;
6159
}
6260

6361
/**

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/subscription_form/use_case/UpdateSubscriptionFormUseCase.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdown;
2020
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdownValidator;
2121
import io.gravitee.apim.core.subscription_form.crud_service.SubscriptionFormCrudService;
22+
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormConstraintsFactory;
23+
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
2224
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormNotFoundException;
2325
import io.gravitee.apim.core.subscription_form.model.SubscriptionForm;
2426
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormId;
@@ -41,6 +43,7 @@ public class UpdateSubscriptionFormUseCase {
4143
private final SubscriptionFormCrudService subscriptionFormCrudService;
4244
private final SubscriptionFormQueryService subscriptionFormQueryService;
4345
private final GraviteeMarkdownValidator graviteeMarkdownValidator;
46+
private final SubscriptionFormSchemaGenerator schemaGenerator;
4447

4548
public Output execute(Input input) {
4649
graviteeMarkdownValidator.validateNotEmpty(GraviteeMarkdown.of(input.gmdContent()));
@@ -54,7 +57,10 @@ public Output execute(Input input) {
5457
)
5558
);
5659

57-
existingForm.update(GraviteeMarkdown.of(input.gmdContent()), null);
60+
var gmd = GraviteeMarkdown.of(input.gmdContent());
61+
var schema = schemaGenerator.generate(gmd);
62+
var constraints = SubscriptionFormConstraintsFactory.fromSchema(schema);
63+
existingForm.update(gmd, constraints);
5864
var savedForm = subscriptionFormCrudService.update(existingForm);
5965

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

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/v4/impl/validation/SubscriptionValidationServiceImpl.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
import io.gravitee.apim.core.application_certificate.crud_service.ClientCertificateCrudService;
1919
import io.gravitee.apim.core.application_certificate.model.ClientCertificate;
2020
import io.gravitee.apim.core.application_certificate.model.ClientCertificateStatus;
21+
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSubmissionValidator;
22+
import io.gravitee.apim.core.subscription_form.model.SubscriptionForm;
23+
import io.gravitee.apim.core.subscription_form.query_service.SubscriptionFormQueryService;
2124
import io.gravitee.definition.model.v4.plan.PlanMode;
2225
import io.gravitee.rest.api.model.NewSubscriptionEntity;
2326
import io.gravitee.rest.api.model.PlanSecurityType;
@@ -32,6 +35,7 @@
3235
import io.gravitee.rest.api.service.v4.validation.SubscriptionMetadataSanitizer;
3336
import io.gravitee.rest.api.service.v4.validation.SubscriptionValidationService;
3437
import java.util.List;
38+
import java.util.Map;
3539
import java.util.Objects;
3640
import lombok.CustomLog;
3741
import lombok.RequiredArgsConstructor;
@@ -48,6 +52,7 @@ public class SubscriptionValidationServiceImpl extends TransactionalService impl
4852

4953
private final EntrypointConnectorPluginService entrypointService;
5054
private final SubscriptionMetadataSanitizer subscriptionMetadataSanitizer;
55+
private final SubscriptionFormQueryService subscriptionFormQueryService;
5156

5257
private final ClientCertificateCrudService clientCertificateCrudService;
5358

@@ -57,6 +62,22 @@ public void validateAndSanitize(final GenericPlanEntity genericPlanEntity, final
5762
if (subscription.getMetadata() != null) {
5863
subscription.setMetadata(subscriptionMetadataSanitizer.sanitizeAndValidate(subscription.getMetadata()));
5964
}
65+
validateSubscriptionFormMetadataIfApplicable(genericPlanEntity, subscription);
66+
}
67+
68+
private void validateSubscriptionFormMetadataIfApplicable(
69+
final GenericPlanEntity genericPlanEntity,
70+
final NewSubscriptionEntity subscription
71+
) {
72+
subscriptionFormQueryService
73+
.findDefaultForEnvironmentId(genericPlanEntity.getEnvironmentId())
74+
.filter(SubscriptionForm::isEnabled)
75+
.map(SubscriptionForm::getValidationConstraints)
76+
.filter(c -> c != null && !c.isEmpty())
77+
.ifPresent(constraints -> {
78+
var submitted = subscription.getMetadata() != null ? subscription.getMetadata() : Map.<String, String>of();
79+
new SubscriptionFormSubmissionValidator(constraints).validate(submitted);
80+
});
6081
}
6182

6283
@Override

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/apim/core/subscription_form/use_case/UpdateSubscriptionFormUseCaseTest.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,44 @@
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
import static org.mockito.ArgumentMatchers.any;
21+
import static org.mockito.Mockito.when;
2022

2123
import fixtures.core.model.SubscriptionFormFixtures;
2224
import inmemory.SubscriptionFormCrudServiceInMemory;
2325
import inmemory.SubscriptionFormQueryServiceInMemory;
2426
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdown;
2527
import io.gravitee.apim.core.gravitee_markdown.GraviteeMarkdownValidator;
2628
import io.gravitee.apim.core.gravitee_markdown.exception.GraviteeMarkdownContentEmptyException;
29+
import io.gravitee.apim.core.subscription_form.domain_service.SubscriptionFormSchemaGenerator;
2730
import io.gravitee.apim.core.subscription_form.exception.SubscriptionFormNotFoundException;
2831
import io.gravitee.apim.core.subscription_form.model.SubscriptionForm;
2932
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormId;
33+
import io.gravitee.apim.core.subscription_form.model.SubscriptionFormSchema;
3034
import java.util.List;
3135
import org.junit.jupiter.api.BeforeEach;
3236
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.extension.ExtendWith;
38+
import org.mockito.Mock;
39+
import org.mockito.junit.jupiter.MockitoExtension;
3340

41+
@ExtendWith(MockitoExtension.class)
3442
class UpdateSubscriptionFormUseCaseTest {
3543

3644
private final SubscriptionFormCrudServiceInMemory crudService = new SubscriptionFormCrudServiceInMemory();
3745
private final SubscriptionFormQueryServiceInMemory queryService = new SubscriptionFormQueryServiceInMemory();
3846
private final GraviteeMarkdownValidator gmdValidator = new GraviteeMarkdownValidator();
47+
48+
@Mock
49+
private SubscriptionFormSchemaGenerator schemaGenerator;
50+
3951
private UpdateSubscriptionFormUseCase useCase;
4052

4153
@BeforeEach
4254
void setUp() {
4355
crudService.reset();
4456
queryService.reset();
45-
useCase = new UpdateSubscriptionFormUseCase(crudService, queryService, gmdValidator);
57+
useCase = new UpdateSubscriptionFormUseCase(crudService, queryService, gmdValidator, schemaGenerator);
4658
}
4759

4860
@Test
@@ -52,6 +64,10 @@ void should_update_existing_form() {
5264
crudService.initWith(List.of(existingForm));
5365
queryService.initWith(List.of(existingForm));
5466

67+
when(schemaGenerator.generate(any(GraviteeMarkdown.class))).thenReturn(
68+
new SubscriptionFormSchema(List.of(new SubscriptionFormSchema.InputField("updated", false, null, null, null, null)))
69+
);
70+
5571
// When
5672
var result = useCase.execute(
5773
new UpdateSubscriptionFormUseCase.Input(
@@ -66,7 +82,24 @@ void should_update_existing_form() {
6682
GraviteeMarkdown.of("<gmd-input name=\"updated\" fieldKey=\"updated\"/>")
6783
);
6884
assertThat(result.subscriptionForm().getId()).isEqualTo(existingForm.getId());
69-
assertThat(result.subscriptionForm().getValidationConstraints()).isNull();
85+
assertThat(result.subscriptionForm().getValidationConstraints()).isNotNull();
86+
assertThat(result.subscriptionForm().getValidationConstraints().byFieldKey()).containsKey("updated");
87+
}
88+
89+
@Test
90+
void should_persist_empty_constraints_when_gmd_has_no_form_fields() {
91+
SubscriptionForm existingForm = SubscriptionFormFixtures.aSubscriptionForm();
92+
crudService.initWith(List.of(existingForm));
93+
queryService.initWith(List.of(existingForm));
94+
95+
when(schemaGenerator.generate(any(GraviteeMarkdown.class))).thenReturn(new SubscriptionFormSchema(List.of()));
96+
97+
var result = useCase.execute(
98+
new UpdateSubscriptionFormUseCase.Input(existingForm.getEnvironmentId(), existingForm.getId(), "<p>Only static content</p>")
99+
);
100+
101+
assertThat(result.subscriptionForm().getValidationConstraints()).isNotNull();
102+
assertThat(result.subscriptionForm().getValidationConstraints().isEmpty()).isTrue();
70103
}
71104

72105
@Test

0 commit comments

Comments
 (0)