diff --git a/pom.xml b/pom.xml index a87740f..e39201a 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,25 @@ 1.17.3 test - + + + org.apache.httpcomponents + httpcore + 4.4.15 + + + + org.apache.httpcomponents + httpmime + 4.5.13 + + + org.apache.httpcomponents + httpclient + 4.5.13 + + + shared-folder-server diff --git a/src/main/java/com/project/sharedfolderserver/v1/file/FileDto.java b/src/main/java/com/project/sharedfolderserver/v1/file/FileDto.java index 518389a..53a55cb 100644 --- a/src/main/java/com/project/sharedfolderserver/v1/file/FileDto.java +++ b/src/main/java/com/project/sharedfolderserver/v1/file/FileDto.java @@ -6,13 +6,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.time.Instant; import java.util.UUID; @Data @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class FileDto { +public class FileDto implements Serializable { @JsonProperty("id") private UUID id; @JsonProperty("name") diff --git a/src/main/java/com/project/sharedfolderserver/v1/file/FileHttpController.java b/src/main/java/com/project/sharedfolderserver/v1/file/FileHttpController.java index 8c452c0..5a84f8e 100644 --- a/src/main/java/com/project/sharedfolderserver/v1/file/FileHttpController.java +++ b/src/main/java/com/project/sharedfolderserver/v1/file/FileHttpController.java @@ -6,10 +6,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; +import java.io.IOException; import java.util.List; import java.util.UUID; @@ -28,20 +32,22 @@ public ResponseEntity> list() { body(files); } - @PostMapping - public ResponseEntity create(@Validate(JsonSchema.FILE_CREATE) FileDto fileToAdd) { - log.info("in create, request body: " + fileToAdd); - FileDto addedFile = fileService.create(fileToAdd); + @PostMapping(consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }) + public ResponseEntity create (@RequestParam("file") MultipartFile file) throws IOException { + FileDto uploadedFile = new FileDto(); + uploadedFile.setName(file.getOriginalFilename()); + uploadedFile.setContent(file.getBytes()); + log.info("in create, request body: " + uploadedFile); + FileDto addedFile = fileService.create(uploadedFile); return ResponseEntity.status(HttpStatus.CREATED) .body(addedFile); } @GetMapping("{id}") - public ResponseEntity download(@PathVariable UUID id) { + public StreamingResponseBody download(@PathVariable UUID id) { FileDto file = fileService.findById(id) .orElseThrow(() -> new FileNotFoundError(id)); - return ResponseEntity.status(HttpStatus.OK) - .body(file); + return outputStream -> outputStream.write(file.getContent()); } @DeleteMapping("{id}") diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 27bff83..d8986cf 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -4,6 +4,10 @@ migration: baselineOnMigration: ${DB_MIGRATION_BASELINE_ENABLED:true} spring: + servlet: + multipart: + max-file-size: 200MB + max-request-size: 200MB flyway: # should be disabled as we use our migration enabled: false diff --git a/src/test/java/com/project/sharedfolderserver/file/FileHttpControllerIT.java b/src/test/java/com/project/sharedfolderserver/file/FileHttpControllerIT.java index 5e911fa..b949b6a 100644 --- a/src/test/java/com/project/sharedfolderserver/file/FileHttpControllerIT.java +++ b/src/test/java/com/project/sharedfolderserver/file/FileHttpControllerIT.java @@ -3,28 +3,42 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; import com.project.sharedfolderserver.BaseIT; import com.project.sharedfolderserver.TestUtils; import com.project.sharedfolderserver.v1.file.FileDto; import com.project.sharedfolderserver.v1.utils.error.Error; import com.project.sharedfolderserver.v1.utils.http.Response; import com.project.sharedfolderserver.v1.utils.json.JSON; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.*; import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.*; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -76,25 +90,28 @@ void successGetFileList() throws IOException { @Test @Sql(scripts = {SQL_SCRIPTS_PATH + "cleanTables.sql", SQL_SCRIPTS_PATH + "downloadFile.sql"}) void successDownload() throws IOException { + //preparing initializeCaseTest("file/success-download-file"); - FileDto expectedData = JSON.objectMapper.convertValue(expectedResult.get("data"), new TypeReference<>() { - }); - - ResponseEntity> response = - restTemplate.exchange( - getUrl(preRequest.get("path").asText()) - , HttpMethod.valueOf(preRequest.get("method").asText()) - , null - , new ParameterizedTypeReference<>() { - }); - - assertNotNull(response, "expect to have a response"); - Response actualBody = response.getBody(); - assertNotNull(actualBody, "expect to have a body in the response"); - assertTrue(CollectionUtils.isEmpty(actualBody.getErrors()), "expect no errors"); - FileDto actualData = actualBody.getData(); - assertNotNull(actualData, "expect to have a file in the response"); - assertEquals(expectedData, actualData, "expect the get a file with content"); + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpGet getRequest = new HttpGet(getUrl(preRequest.get("path").asText())); + + // executing + try (CloseableHttpResponse response = client.execute(getRequest)) { + + // validating + assertNotNull(response, "expect to have a response"); + assertNotNull(response.getStatusLine(), "expect to have a status"); + assertEquals(HttpStatus.OK.value(), response.getStatusLine().getStatusCode()," expect ok status code"); + byte[] fileContent = response.getEntity().getContent().readAllBytes(); + assertNotNull(fileContent, "expect to have a body in the response"); + JsonNode expectedData = expectedResult.get("data"); + assertEquals(Long.valueOf(expectedData.get("size").asText().split(" ")[0]), fileContent.length, "expect the same length"); + assertEquals(expectedData.get("content").asText().getBytes().length, fileContent.length, "expect the same length"); + for (int i=0; i() { }); + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpPost postRequest = new HttpPost(getUrl(preRequest.get("path").asText())); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + builder.addBinaryBody("file", preRequest.get("body").get("content").asText().getBytes(), ContentType.DEFAULT_BINARY, expectedData.getName()); + org.apache.http.HttpEntity entity = builder.build(); + postRequest.setEntity(entity); + + // executing + try (CloseableHttpResponse response = client.execute(postRequest)) { + + // validating + assertNotNull(response, "expect to have a response"); + assertNotNull(response.getStatusLine(), "expect to have a status"); + assertEquals(HttpStatus.CREATED.value(), response.getStatusLine().getStatusCode()," expect created status code"); + Response actualBody = JSON.objectMapper.readValue(response.getEntity().getContent(), new TypeReference<>() { + }); + assertNotNull(actualBody, "expect to have a body in the response"); + assertTrue(CollectionUtils.isEmpty(actualBody.getErrors()), "expect no errors"); + FileDto actualData = actualBody.getData(); + assertNotNull(actualData, "expect to have a file in the response"); + TestUtils.assertEqualsExcludedFields(expectedData, actualData, "id", "dateAdded", "dateModified"); + } + } - ResponseEntity> response = - restTemplate.exchange(getUrl(preRequest.get("path").asText()) - , HttpMethod.valueOf(preRequest.get("method").asText()) - , new HttpEntity<>(preRequest.get("body")) - , new ParameterizedTypeReference<>() { - }); - - assertNotNull(response, "expect to have a response"); - Response actualBody = response.getBody(); - assertNotNull(actualBody, "expect to have a body in the response"); - assertTrue(CollectionUtils.isEmpty(actualBody.getErrors()), "expect no errors"); - FileDto actualData = actualBody.getData(); - assertNotNull(actualData, "expect to have fileDto in the response"); - TestUtils.assertEqualsExcludedFields(expectedData, actualData, "id", "dateAdded", "dateModified"); } @DisplayName("Failed: upload file with existing name") @Test @Sql(scripts = {SQL_SCRIPTS_PATH + "cleanTables.sql", SQL_SCRIPTS_PATH + "uploadFileExistingName.sql"}) void failedUploadExistingName() throws IOException { + // preparing initializeCaseTest("file/failed-upload-file-existing-name"); List expectedErrors = JSON.objectMapper.convertValue(expectedResult.get("errors"), new TypeReference<>() { }); - - ResponseEntity> response = - restTemplate.exchange(getUrl(preRequest.get("path").asText()) - , HttpMethod.valueOf(preRequest.get("method").asText()) - , new HttpEntity<>(preRequest.get("body")) - , new ParameterizedTypeReference<>() { - }); - - assertNotNull(response, "expect to have a response"); - Response actualBody = response.getBody(); - assertNotNull(actualBody, "expect to have a body in the response"); - assertFalse(CollectionUtils.isEmpty(actualBody.getErrors()), "expected to get errors"); - FileDto actualData = actualBody.getData(); - assertNull(actualData, "expect data to be null"); - assertEquals(expectedErrors, actualBody.getErrors(), "expected the same errors"); + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpPost postRequest = new HttpPost(getUrl(preRequest.get("path").asText())); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + builder.addBinaryBody("file", preRequest.get("body").get("content").asText().getBytes(), ContentType.DEFAULT_BINARY, preRequest.get("body").get("name").asText()); + org.apache.http.HttpEntity entity = builder.build(); + postRequest.setEntity(entity); + + // executing + try (CloseableHttpResponse response = client.execute(postRequest)) { + + // validating + assertNotNull(response, "expect to have a response"); + assertNotNull(response.getStatusLine(), "expect to have a status"); + assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode()," expect bad request status code"); + Response actualBody = JSON.objectMapper.readValue(response.getEntity().getContent(), new TypeReference<>() { + }); + assertNotNull(actualBody, "expect to have a body in the response"); + assertFalse(CollectionUtils.isEmpty(actualBody.getErrors()), "expected to get errors"); + FileDto actualData = actualBody.getData(); + assertNull(actualData, "expect data to be null"); + assertEquals(expectedErrors, actualBody.getErrors(), "expected the same errors"); + } + } } @DisplayName("Failed: upload file with illegal name") @@ -189,69 +227,90 @@ void failedUploadExistingName() throws IOException { @NullAndEmptySource @ValueSource(strings = {"badname", "usik^*.ld", "=-0", " ggg.file.s"}) void failedUploadIllegalName(String badname) throws IOException { + // preparing initializeCaseTest("file/failed-upload-file-illegal-name"); List expectedErrors = JSON.objectMapper.convertValue(expectedResult.get("errors"), new TypeReference<>() { }); String errorMessage; - if (badname == null || badname.isEmpty()) { + if (badname != null && badname.isEmpty()) { errorMessage = "file could not be created. file name can not be empty"; } else { errorMessage = String.format("file could not be created. Illegal file name %s, file name must be in the form of NAME.KIND, using letters, numbers. some special characters are illegal", badname); } expectedErrors.stream().findFirst().ifPresent(error -> error.setMessage(errorMessage)); ((ObjectNode) (preRequest.get("body"))).put("name", badname); - - ResponseEntity> response = - restTemplate.exchange(getUrl(preRequest.get("path").asText()) - , HttpMethod.valueOf(preRequest.get("method").asText()) - , new HttpEntity<>(preRequest.get("body")) - , new ParameterizedTypeReference<>() { - }); - - assertNotNull(response, "expect to have a response"); - Response actualBody = response.getBody(); - assertNotNull(actualBody, "expect to have a body in the response"); - assertFalse(CollectionUtils.isEmpty(actualBody.getErrors()), "expected to get errors"); - FileDto actualData = actualBody.getData(); - assertNull(actualData, "expect data to be null"); - assertEquals(expectedErrors, actualBody.getErrors(), "expected the same errors"); + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpPost postRequest = new HttpPost(getUrl(preRequest.get("path").asText())); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + builder.addBinaryBody("file", preRequest.get("body").get("content").asText().getBytes(), ContentType.DEFAULT_BINARY, preRequest.get("body").get("name").asText()); + org.apache.http.HttpEntity entity = builder.build(); + postRequest.setEntity(entity); + + // executing + try (CloseableHttpResponse response = client.execute(postRequest)) { + + // validating + assertNotNull(response, "expect to have a response"); + assertNotNull(response.getStatusLine(), "expect to have a status"); + assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode(), " expect bad request status code"); + Response actualBody = JSON.objectMapper.readValue(response.getEntity().getContent(), new TypeReference<>() { + }); + assertNotNull(actualBody, "expect to have a body in the response"); + assertFalse(CollectionUtils.isEmpty(actualBody.getErrors()), "expected to have errors"); + FileDto actualData = actualBody.getData(); + assertNull(actualData, "expect data to be null"); + assertEquals(expectedErrors, actualBody.getErrors(), "expected the same errors"); + + } + } } @DisplayName("Failed: upload file with illegal content") @ParameterizedTest @MethodSource("generateIllegalUploadRequestParameters") - void failedUploadIllegalRequestParameters(String fieldName, JsonNode illegalFieldContent, String errorMessage) throws IOException { + void failedUploadIllegalRequestParameters( JsonNode illegalFieldContent, String errorMessage) throws IOException { + // preparing initializeCaseTest("file/failed-upload-file-illegal-content"); List expectedErrors = JSON.objectMapper.convertValue(expectedResult.get("errors"), new TypeReference<>() { }); Error expectedError = expectedErrors.stream().findFirst().orElseThrow(); - ((ObjectNode) (preRequest.get("body"))).set(fieldName, illegalFieldContent); - expectedError.setMessage(expectedError.getMessage().replace(FIELD_NAME_REPLACE_SIGN, fieldName).replace(TYPE_NAME_REPLACE_SIGN, errorMessage)); - - ResponseEntity> response = - restTemplate.exchange(getUrl(preRequest.get("path").asText()) - , HttpMethod.valueOf(preRequest.get("method").asText()) - , new HttpEntity<>(preRequest.get("body")) - , new ParameterizedTypeReference<>() { - }); - - assertNotNull(response, "expect to have a response"); - Response actualBody = response.getBody(); - assertNotNull(actualBody, "expect to have a body in the response"); - assertFalse(CollectionUtils.isEmpty(actualBody.getErrors()), "expected to get errors"); - FileDto actualData = actualBody.getData(); - assertNull(actualData, "expect data to be null"); - assertEquals(expectedErrors, actualBody.getErrors(), "expected the same errors"); + ((ObjectNode) (preRequest.get("body"))).put("name", illegalFieldContent.toString()); + expectedError.setMessage(expectedError.getMessage().replace(FIELD_NAME_REPLACE_SIGN, errorMessage)); + + try (CloseableHttpClient client = HttpClients.createDefault()) { + HttpPost postRequest = new HttpPost(getUrl(preRequest.get("path").asText())); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + builder.addBinaryBody("file", preRequest.get("body").get("content").asText().getBytes(), ContentType.DEFAULT_BINARY, illegalFieldContent.toString()); + org.apache.http.HttpEntity entity = builder.build(); + postRequest.setEntity(entity); + + // executing + try (CloseableHttpResponse response = client.execute(postRequest)) { + + // validating + assertNotNull(response, "expect to have a response"); + assertNotNull(response.getStatusLine(), "expect to have a status"); + assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusLine().getStatusCode(), " expect bad request status code"); + Response actualBody = JSON.objectMapper.readValue(response.getEntity().getContent(), new TypeReference<>() { + }); + assertNotNull(actualBody, "expect to have a body in the response"); + assertFalse(CollectionUtils.isEmpty(actualBody.getErrors()), "expected to have errors"); + FileDto actualData = actualBody.getData(); + assertNull(actualData, "expect data to be null"); + assertEquals(expectedErrors, actualBody.getErrors(), "expected the same errors"); + } + } } private Stream generateIllegalUploadRequestParameters() { return Stream.of( - Arguments.of("name", null, "null"), - Arguments.of("content", null, "null"), - Arguments.of("content", JSON.objectMapper.createArrayNode(), "array"), - Arguments.of("name", JSON.objectMapper.createArrayNode(), "array"), - Arguments.of("content", new IntNode(8), "integer"), - Arguments.of("name", new IntNode(8), "integer") + Arguments.of(JSON.objectMapper.createArrayNode(), "[]"), + Arguments.of( new IntNode(8), "8"), + Arguments.of( new LongNode(8L), "8"), + Arguments.of( JSON.object(),"{}") + ); } diff --git a/src/test/resources/cases/file/failed-upload-file-illegal-content.json b/src/test/resources/cases/file/failed-upload-file-illegal-content.json index fa11dcd..e7e1877 100644 --- a/src/test/resources/cases/file/failed-upload-file-illegal-content.json +++ b/src/test/resources/cases/file/failed-upload-file-illegal-content.json @@ -11,8 +11,8 @@ "data": null, "errors": [ { - "name": "ValidationError", - "message": "validation error [$.%fieldName%: %typeName% found, string expected]" + "name": "FileCannotBeCreatedError", + "message": "file could not be created. Illegal file name %fieldName%, file name must be in the form of NAME.KIND, using letters, numbers. some special characters are illegal" } ] } diff --git a/src/test/resources/cases/file/success-download-file.json b/src/test/resources/cases/file/success-download-file.json index 509005e..20cce83 100644 --- a/src/test/resources/cases/file/success-download-file.json +++ b/src/test/resources/cases/file/success-download-file.json @@ -6,12 +6,12 @@ "expectedResult": { "data": { "kind": "zip", - "content": "QVJsRUFBPT0=", + "content": "ARlEAA==", "id": "28cbd8e2-4b93-11ed-bdc3-0242ac120002", "name": "testFile.zip", "dateModified": 1436224954.000000000, "dateAdded": 1436224954.000000000, - "size": "4 bytes" + "size": "8 bytes" } } } \ No newline at end of file diff --git a/src/test/resources/cases/file/success-upload-file.json b/src/test/resources/cases/file/success-upload-file.json index ec64770..b66d785 100644 --- a/src/test/resources/cases/file/success-upload-file.json +++ b/src/test/resources/cases/file/success-upload-file.json @@ -14,7 +14,7 @@ "name": "testFile.zip", "dateModified": "2022-10-17T08:29:13.096594Z", "dateAdded": "2022-10-17T08:29:13.096553Z", - "size": "4 bytes" + "size": "8 bytes" } } } \ No newline at end of file