Skip to content

Commit

Permalink
PI-870: validate overall code coverage (#59)
Browse files Browse the repository at this point in the history
* add validations to request bodies

* extend tests

* adapt fh catalog client tests

* increase dao coverage

* remove validators for id fields as they are set by backend

* strip error message

* move validator to its own package, add comments

* replace email annotation with same regex that is used in frontend

* fix log

* remove default port from package.json

* add some module tests for registration API

* cleanup

* simplify registrationNumber validator

* add missing quotes

* cleanup

* reset wizard api on tests

* cleanup
  • Loading branch information
M-Busk authored Feb 12, 2025
1 parent 7d0d8f2 commit e9c95db
Show file tree
Hide file tree
Showing 50 changed files with 1,777 additions and 662 deletions.
1 change: 1 addition & 0 deletions backend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
implementation(libs.springBootStarterWebflux)
implementation(libs.springBootStarterDataJpa)
implementation(libs.springBootStarterSecurity)
implementation(libs.springBootStarterValidation)
implementation(libs.hibernateValidator)
implementation(libs.openApi)
implementation(libs.titaniumJsonLd)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import eu.possiblex.portal.application.entity.CreateRegistrationRequestTO;
import eu.possiblex.portal.application.entity.RegistrationRequestEntryTO;
import jakarta.validation.Valid;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
Expand All @@ -16,7 +17,7 @@ public interface ParticipantRegistrationRestApi {
* @param request participant registration request
*/
@PostMapping(value = "/request", produces = MediaType.APPLICATION_JSON_VALUE)
void registerParticipant(@RequestBody CreateRegistrationRequestTO request);
void registerParticipant(@Valid @RequestBody CreateRegistrationRequestTO request);

/**
* GET request for retrieving registration requests for the given pagination request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
import eu.possiblex.portal.business.entity.exception.RegistrationRequestConflictException;
import eu.possiblex.portal.business.entity.exception.RegistrationRequestProcessingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.lang.NonNull;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.util.HashMap;
import java.util.Map;

import static org.springframework.http.HttpStatus.*;

/**
Expand Down Expand Up @@ -54,6 +63,28 @@ public ResponseEntity<ErrorResponseTO> handleException(ParticipantComplianceExce
UNPROCESSABLE_ENTITY);
}

/**
* Handle Spring validation exceptions.
*/
@Override
public ResponseEntity<Object> handleMethodArgumentNotValid(@NonNull MethodArgumentNotValidException ex,
@NonNull HttpHeaders headers, @NonNull HttpStatusCode status, @NonNull WebRequest request) {

logError(ex);

Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
StringBuilder message = new StringBuilder();
errors.forEach((key, value) -> message.append(key).append(": ").append(value).append("; "));

return new ResponseEntity<>(new ErrorResponseTO("Request contained errors.", message.toString().strip()),
BAD_REQUEST);
}

/**
* Handle all other exceptions.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@ public interface ParticipantRegistrationRestApiMapper {
@Mapping(target = "id", ignore = true)
PxExtendedLegalParticipantCredentialSubject credentialSubjectsToExtendedLegalParticipantCs(
CreateRegistrationRequestTO request);

GxNestedLegalRegistrationNumberCredentialSubject registrationNumberCsToNestedLegalRegistrationNumberCs(
GxLegalRegistrationNumberCredentialSubject request);
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import eu.possiblex.portal.application.entity.credentials.gx.participants.GxLegalParticipantCredentialSubject;
import eu.possiblex.portal.application.entity.credentials.gx.participants.GxLegalRegistrationNumberCredentialSubject;
import eu.possiblex.portal.application.entity.credentials.px.participants.PxParticipantExtensionCredentialSubject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
Expand All @@ -14,9 +16,15 @@
@AllArgsConstructor
public class CreateRegistrationRequestTO {

@Valid
@NotNull(message = "Participant credential subject is required")
private GxLegalParticipantCredentialSubject participantCs;

@Valid
@NotNull(message = "Registration number credential subject is required")
private GxLegalRegistrationNumberCredentialSubject registrationNumberCs;

@Valid
@NotNull(message = "Participant extension credential subject is required")
private PxParticipantExtensionCredentialSubject participantExtensionCs;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
@AllArgsConstructor
public abstract class PojoCredentialSubject {
// base fields
// no input validations as this will be set by the backend
private String id;
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import eu.possiblex.portal.application.entity.credentials.serialization.StringDeserializer;
import eu.possiblex.portal.application.entity.credentials.serialization.StringSerializer;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
Expand All @@ -38,13 +39,15 @@ public class GxVcard {
@JsonProperty("gx:countryCode")
@JsonSerialize(using = StringSerializer.class)
@JsonDeserialize(using = StringDeserializer.class)
@NotNull
@NotBlank(message = "Country code is needed")
@Pattern(regexp = "^([A-Z]{2}|[A-Z]{3}|\\d{3})$", message = "An ISO 3166-1 alpha2, alpha-3 or numeric format value is expected.")
private String countryCode;

@JsonProperty("gx:countrySubdivisionCode")
@JsonSerialize(using = StringSerializer.class)
@JsonDeserialize(using = StringDeserializer.class)
@NotNull
@NotBlank(message = "Country subdivision code is needed")
@Pattern(regexp = "^[A-Z]{2}-[A-Z0-9]{1,3}$", message = "An ISO 3166-2 format value is expected.")
private String countrySubdivisionCode;

@JsonProperty("vcard:street-address")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
@NoArgsConstructor
public class NodeKindIRITypeId {

@NotNull
// no input validations as this will be set by the backend
@JsonAlias("@id")
private String id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import eu.possiblex.portal.application.entity.credentials.gx.datatypes.NodeKindIRITypeId;
import eu.possiblex.portal.application.entity.credentials.serialization.StringDeserializer;
import eu.possiblex.portal.application.entity.credentials.serialization.StringSerializer;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;

Expand Down Expand Up @@ -58,22 +60,25 @@ public class GxLegalParticipantCredentialSubject extends PojoCredentialSubject {
"https://schema.org/");

// Tagus
@NotNull
// no input validations as this will be set by the backend
@JsonProperty("gx:legalRegistrationNumber")
@JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<NodeKindIRITypeId> legalRegistrationNumber; // will be gx:registrationNumber in Loire

// Loire
@NotNull
@Valid
@NotNull(message = "Legal address is needed")
@JsonProperty("gx:legalAddress")
private GxVcard legalAddress; // contains Tagus gx:countrySubdivisionCode

// Loire
@NotNull
@Valid
@NotNull(message = "Headquarter address is needed")
@JsonProperty("gx:headquarterAddress")
private GxVcard headquarterAddress; // contains Tagus gx:countrySubdivisionCode

// Loire
@NotBlank(message = "Name is needed")
@JsonProperty("schema:name")
@JsonSerialize(using = StringSerializer.class)
@JsonDeserialize(using = StringDeserializer.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import eu.possiblex.portal.application.entity.credentials.PojoCredentialSubject;
import eu.possiblex.portal.application.entity.credentials.serialization.StringDeserializer;
import eu.possiblex.portal.application.entity.credentials.serialization.StringSerializer;
import eu.possiblex.portal.application.entity.credentials.validation.AtLeastOneRegistrationNumberNotEmpty;
import lombok.*;

import java.util.Map;
Expand All @@ -35,6 +36,7 @@
@JsonIgnoreProperties(ignoreUnknown = true, value = { "type", "@context" }, allowGetters = true)
@NoArgsConstructor
@AllArgsConstructor
@AtLeastOneRegistrationNumberNotEmpty(message = "At least one registration number type must be provided")
public class GxLegalRegistrationNumberCredentialSubject extends PojoCredentialSubject {

@Getter(AccessLevel.NONE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import eu.possiblex.portal.application.entity.credentials.PojoCredentialSubject;
import eu.possiblex.portal.application.entity.credentials.serialization.StringDeserializer;
import eu.possiblex.portal.application.entity.credentials.serialization.StringSerializer;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.*;

import java.util.Map;
Expand All @@ -33,9 +35,11 @@ public class PxParticipantExtensionCredentialSubject extends PojoCredentialSubje
public static final Map<String, String> CONTEXT = Map.of(TYPE_NAMESPACE, "http://w3id.org/gaia-x/possible-x#",
"vcard", "http://www.w3.org/2006/vcard/ns#", "xsd", "http://www.w3.org/2001/XMLSchema#");

@NotBlank(message = "Mail address is needed")
@JsonProperty("px:mailAddress")
@JsonSerialize(using = StringSerializer.class)
@JsonDeserialize(using = StringDeserializer.class)
@Pattern(regexp = "^((?!\\.)[\\w\\-_.]*[^.])(@\\w+)(\\.\\w+(\\.\\w+)?[^.\\W])$", message = "Mail address must be a valid email address")
private String mailAddress;

@JsonProperty("type")
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit e9c95db

Please sign in to comment.