Skip to content

Commit a947936

Browse files
[SELC-7991] feat: added API to upload signed contract to COMPLETED onboarding (#940)
1 parent 94c9ef0 commit a947936

File tree

9 files changed

+368
-1
lines changed

9 files changed

+368
-1
lines changed

apps/onboarding-ms/src/main/docs/openapi.json

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,6 +1788,55 @@
17881788
} ]
17891789
}
17901790
},
1791+
"/v1/onboarding/{onboardingId}/upload-contract-signed" : {
1792+
"put" : {
1793+
"tags" : [ "internal-v1" ],
1794+
"summary" : "Update COMPELTED onboarding uploading signed contract",
1795+
"description" : "Uploading signed contract given onboardingId, updating related token",
1796+
"operationId" : "uploadContractSigned",
1797+
"parameters" : [ {
1798+
"name" : "onboardingId",
1799+
"in" : "path",
1800+
"required" : true,
1801+
"schema" : {
1802+
"type" : "string"
1803+
}
1804+
} ],
1805+
"requestBody" : {
1806+
"content" : {
1807+
"multipart/form-data" : {
1808+
"schema" : {
1809+
"required" : [ "contract" ],
1810+
"type" : "object",
1811+
"properties" : {
1812+
"contract" : {
1813+
"format" : "binary",
1814+
"type" : "string"
1815+
}
1816+
}
1817+
}
1818+
}
1819+
}
1820+
},
1821+
"responses" : {
1822+
"200" : {
1823+
"description" : "OK",
1824+
"content" : {
1825+
"application/json" : { }
1826+
}
1827+
},
1828+
"401" : {
1829+
"description" : "Not Authorized"
1830+
},
1831+
"403" : {
1832+
"description" : "Not Allowed"
1833+
}
1834+
},
1835+
"security" : [ {
1836+
"SecurityScheme" : [ ]
1837+
} ]
1838+
}
1839+
},
17911840
"/v1/onboarding/{onboardingId}/withUserInfo" : {
17921841
"get" : {
17931842
"tags" : [ "Onboarding Controller" ],

apps/onboarding-ms/src/main/docs/openapi.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,42 @@ paths:
13121312
description: Not Allowed
13131313
security:
13141314
- SecurityScheme: []
1315+
/v1/onboarding/{onboardingId}/upload-contract-signed:
1316+
put:
1317+
tags:
1318+
- internal-v1
1319+
summary: Update COMPELTED onboarding uploading signed contract
1320+
description: "Uploading signed contract given onboardingId, updating related\
1321+
\ token"
1322+
operationId: uploadContractSigned
1323+
parameters:
1324+
- name: onboardingId
1325+
in: path
1326+
required: true
1327+
schema:
1328+
type: string
1329+
requestBody:
1330+
content:
1331+
multipart/form-data:
1332+
schema:
1333+
required:
1334+
- contract
1335+
type: object
1336+
properties:
1337+
contract:
1338+
format: binary
1339+
type: string
1340+
responses:
1341+
"200":
1342+
description: OK
1343+
content:
1344+
application/json: {}
1345+
"401":
1346+
description: Not Authorized
1347+
"403":
1348+
description: Not Allowed
1349+
security:
1350+
- SecurityScheme: []
13151351
/v1/onboarding/{onboardingId}/withUserInfo:
13161352
get:
13171353
tags:

apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/OnboardingController.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,4 +662,19 @@ public Uni<OnboardingGet> getOnboardingProduct(@PathParam(value = "institutionId
662662
return onboardingService.retrieveOnboardingByInstitutionId(institutionId, productId);
663663
}
664664

665+
@Operation(
666+
summary = "Update COMPELTED onboarding uploading signed contract",
667+
description = "Uploading signed contract given onboardingId, updating related token",
668+
operationId = "uploadContractSigned"
669+
)
670+
@PUT
671+
@Path("/{onboardingId}/upload-contract-signed")
672+
@Tag(name = "internal-v1")
673+
@Consumes(MediaType.MULTIPART_FORM_DATA)
674+
public Uni<Response> uploadContractSigned(@PathParam(value = "onboardingId") String onboardingId, @NotNull @RestForm("contract") File file, @Context ResteasyReactiveRequestContext ctx) {
675+
return onboardingService.uploadContractSigned(onboardingId, retrieveContractFromFormData(ctx.getFormData(), file))
676+
.map(ignore -> Response
677+
.status(HttpStatus.SC_NO_CONTENT)
678+
.build());
679+
}
665680
}

apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/controller/TokenController.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.util.List;
2222

2323
import lombok.AllArgsConstructor;
24-
import org.eclipse.microprofile.jwt.JsonWebToken;
2524
import org.eclipse.microprofile.openapi.annotations.Operation;
2625
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
2726
import org.jboss.resteasy.reactive.RestResponse;

apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/OnboardingService.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ Uni<OnboardingResponse> onboardingAggregationImport(
5858

5959
Uni<Onboarding> completeWithoutSignatureVerification(String tokenId, FormItem formItem);
6060

61+
Uni<Onboarding> uploadContractSigned(String tokenId, FormItem formItem);
62+
6163
Uni<OnboardingGetResponse> onboardingGet(OnboardingGetFilters filters);
6264

6365
Uni<Long> rejectOnboarding(String onboardingId, String reasonForReject);

apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/service/impl/OnboardingServiceDefault.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,28 @@ public Uni<Onboarding> completeWithoutSignatureVerification(
13701370
return complete(onboardingId, formItem, verification);
13711371
}
13721372

1373+
@Override
1374+
public Uni<Onboarding> uploadContractSigned(
1375+
String onboardingId, FormItem formItem) {
1376+
1377+
return retrieveOnboarding(onboardingId)
1378+
.onItem()
1379+
.transformToUni(this::checkIfCompleted)
1380+
.onItem()
1381+
.transformToUni(onboarding ->
1382+
uploadSignedContractAndUpdateToken(onboarding, formItem)
1383+
.onItem()
1384+
.transform(ignore -> {
1385+
onboarding.setUpdatedAt(LocalDateTime.now());
1386+
return onboarding;
1387+
}))
1388+
.onItem()
1389+
.transformToUni(onboarding -> updateOnboarding(onboardingId, onboarding)
1390+
.onItem()
1391+
.transformToUni(ignore -> updateTokenUpdatedAt(onboardingId))
1392+
.replaceWith(onboarding));
1393+
}
1394+
13731395
private Uni<Onboarding> complete(
13741396
String onboardingId,
13751397
FormItem formItem,
@@ -1482,6 +1504,34 @@ private Uni<String> updateTokenWithFilePath(String filepath, Token token) {
14821504
.replaceWith(filepath);
14831505
}
14841506

1507+
private Uni<Void> updateTokenUpdatedAt(String onboardingId) {
1508+
return Token.update("updatedAt", LocalDateTime.now())
1509+
.where("onboardingId", onboardingId)
1510+
.onItem()
1511+
.transform(ignore -> null);
1512+
}
1513+
1514+
private Uni<Onboarding> retrieveOnboarding(String onboardingId) {
1515+
// Retrieve Onboarding if exists
1516+
return Onboarding.findByIdOptional(onboardingId)
1517+
.onItem()
1518+
.transformToUni(
1519+
opt ->
1520+
opt
1521+
// I must cast to Onboarding because findByIdOptional return a generic
1522+
// ReactiveEntity
1523+
.map(Onboarding.class::cast)
1524+
.map(onboarding -> Uni.createFrom().item(onboarding))
1525+
.orElse(
1526+
Uni.createFrom()
1527+
.failure(
1528+
new InvalidRequestException(
1529+
String.format(
1530+
"Onboarding with id '%s' not found",
1531+
onboardingId)))));
1532+
}
1533+
1534+
14851535
private Uni<Onboarding> retrieveOnboardingAndCheckIfExpired(String onboardingId) {
14861536
// Retrieve Onboarding if exists
14871537
return Onboarding.findByIdOptional(onboardingId)
@@ -1517,6 +1567,19 @@ private Uni<Onboarding> checkIfToBeValidated(Onboarding onboarding) {
15171567
ONBOARDING_NOT_TO_BE_VALIDATED.getCode())));
15181568
}
15191569

1570+
private Uni<Onboarding> checkIfCompleted(Onboarding onboarding) {
1571+
return COMPLETED.equals(onboarding.getStatus())
1572+
? Uni.createFrom().item(onboarding)
1573+
: Uni.createFrom()
1574+
.failure(
1575+
new InvalidRequestException(
1576+
String.format(
1577+
ONBOARDING_NOT_COMPLETED.getMessage(),
1578+
onboarding.getId(),
1579+
onboarding.getStatus(),
1580+
ONBOARDING_NOT_COMPLETED.getCode())));
1581+
}
1582+
15201583
public static boolean isOnboardingExpired(LocalDateTime dateTime) {
15211584
LocalDateTime now = LocalDateTime.now();
15221585
return Objects.nonNull(dateTime) && (now.isEqual(dateTime) || now.isAfter(dateTime));

apps/onboarding-ms/src/main/java/it/pagopa/selfcare/onboarding/util/ErrorMessage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public enum ErrorMessage {
6464

6565
ONBOARDING_EXPIRED("0040", "Onboarding with id %s not found or it is expired!"),
6666
ONBOARDING_NOT_TO_BE_VALIDATED("0043", "Onboarding with id %s has not TO_BE_VALIDATED status!"),
67+
ONBOARDING_NOT_COMPLETED("0044", "Onboarding with id %s must be in COMPLETED status but it is in %s status!"),
6768
GET_INSTITUTION_BY_GEOTAXONOMY_ERROR("0053", "Error while searching institutions related to given geoTaxonomies"),
6869
GET_INSTITUTION_BY_PRODUCTID_ERROR("0053", "Error while searching institutions related to given productId"),
6970
GET_INSTITUTIONS_REQUEST_ERROR("0054", "Invalid request parameters sent. Allowed filters combinations taxCode and subunit or origin and originId"),

apps/onboarding-ms/src/test/java/it/pagopa/selfcare/onboarding/controller/OnboardingControllerTest.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import it.pagopa.selfcare.onboarding.entity.*;
3737
import it.pagopa.selfcare.onboarding.exception.InvalidRequestException;
3838
import it.pagopa.selfcare.onboarding.exception.ResourceNotFoundException;
39+
import it.pagopa.selfcare.onboarding.model.FormItem;
3940
import it.pagopa.selfcare.onboarding.model.OnboardingGetFilters;
4041
import it.pagopa.selfcare.onboarding.model.RecipientCodeStatus;
4142
import it.pagopa.selfcare.onboarding.service.OnboardingService;
@@ -1595,4 +1596,98 @@ void getOnboardingProduct_whenNotFound_shouldReturnNotFound() {
15951596
.statusCode(404);
15961597
}
15971598

1599+
@Test
1600+
void uploadContractSigned_unauthorized() {
1601+
File testFile = new File("src/test/resources/application.properties");
1602+
String onboardingId = "actual-onboarding-id";
1603+
1604+
given()
1605+
.when()
1606+
.pathParam("onboardingId", onboardingId)
1607+
.contentType(ContentType.MULTIPART)
1608+
.multiPart("contract", testFile)
1609+
.put("/{onboardingId}/upload-contract-signed")
1610+
.then()
1611+
.statusCode(401);
1612+
}
1613+
1614+
@Test
1615+
@TestSecurity(user = "userJwt")
1616+
void uploadContractSigned_whenSuccess_shouldReturnNoContent() {
1617+
File testFile = new File("src/test/resources/application.properties");
1618+
String onboardingId = "actual-onboarding-id";
1619+
1620+
when(onboardingService.uploadContractSigned(anyString(), any()))
1621+
.thenReturn(Uni.createFrom().nullItem());
1622+
1623+
given()
1624+
.when()
1625+
.pathParam("onboardingId", onboardingId)
1626+
.contentType(ContentType.MULTIPART)
1627+
.multiPart("contract", testFile)
1628+
.put("/{onboardingId}/upload-contract-signed")
1629+
.then()
1630+
.statusCode(204);
1631+
}
1632+
1633+
@Test
1634+
@TestSecurity(user = "userJwt")
1635+
void uploadContractSigned_withoutFile_shouldReturnBadRequest() {
1636+
String onboardingId = "actual-onboarding-id";
1637+
1638+
given()
1639+
.when()
1640+
.pathParam("onboardingId", onboardingId)
1641+
.contentType(ContentType.MULTIPART)
1642+
.put("/{onboardingId}/upload-contract-signed")
1643+
.then()
1644+
.statusCode(400);
1645+
}
1646+
1647+
@Test
1648+
@TestSecurity(user = "userJwt")
1649+
void uploadContractSigned_whenServiceThrowsException_shouldReturnError() {
1650+
File testFile = new File("src/test/resources/application.properties");
1651+
String onboardingId = "actual-onboarding-id";
1652+
String errorMessage = "Upload failed";
1653+
1654+
when(onboardingService.uploadContractSigned(anyString(), any()))
1655+
.thenReturn(Uni.createFrom().failure(new RuntimeException(errorMessage)));
1656+
1657+
given()
1658+
.when()
1659+
.pathParam("onboardingId", onboardingId)
1660+
.contentType(ContentType.MULTIPART)
1661+
.multiPart("contract", testFile)
1662+
.put("/{onboardingId}/upload-contract-signed")
1663+
.then()
1664+
.statusCode(500);
1665+
}
1666+
1667+
@Test
1668+
@TestSecurity(user = "userJwt")
1669+
void uploadContractSigned_shouldPassCorrectParametersToService() {
1670+
File testFile = new File("src/test/resources/application.properties");
1671+
String onboardingId = "actual-onboarding-id";
1672+
1673+
when(onboardingService.uploadContractSigned(anyString(), any()))
1674+
.thenReturn(Uni.createFrom().nullItem());
1675+
1676+
given()
1677+
.when()
1678+
.pathParam("onboardingId", onboardingId)
1679+
.contentType(ContentType.MULTIPART)
1680+
.multiPart("contract", testFile)
1681+
.put("/{onboardingId}/upload-contract-signed")
1682+
.then()
1683+
.statusCode(204);
1684+
1685+
ArgumentCaptor<String> idCaptor = ArgumentCaptor.forClass(String.class);
1686+
ArgumentCaptor<FormItem> formItemCaptor = ArgumentCaptor.forClass(FormItem.class);
1687+
verify(onboardingService, times(1))
1688+
.uploadContractSigned(idCaptor.capture(), formItemCaptor.capture());
1689+
assertEquals(idCaptor.getValue(), onboardingId);
1690+
assertNotNull(formItemCaptor.getValue());
1691+
}
1692+
15981693
}

0 commit comments

Comments
 (0)