Sending an object with files using the MockMvc test - java

I want to send an object to the controller that has several lists with files and several fields with plain text.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I want to create a test to send an object, but I do not know how to finish it.
#Test
public void testCreatePhotoContribution() throws Exception {
ContributionNew<Photo> contribution = new ContributionNew<>();
MockMultipartFile multipartFile = new MockMultipartFile("photo", "C:\\Users\\Jonatan\\Pictures\\2.png",
"image/png", "Spring Framework".getBytes());
Photo.Builder photoBuilder = new Photo.Builder(
multipartFile
);
contribution.getElementsToAdd().add(photoBuilder.build());
mockMvc
.perform(post("/api/v1.0/movies/{id}/contributions/photos", 1)
.contentType(...)
.content(...))
.andExpect(status().isCreated());
}
I do not know how to set the correct type for the transmitted data, set the content. Only tutorials about sending only files (not in objects) are available. But there are no guides where the file is one of the fields in the object. How to do it?

Related

Unable to make graphQL query mutation for File Upload when using #GrpahQLScalar File file

Not able to create graph QL query mutation for file upload using #GraphQLScalar File when trying with this query getting 501 internal server error This is my graph QL query for file upload.
Please help here with your suggestions and whomsoever knows can reply with their answer as soon as possible.
{"query" : "mutation ($jbpTemplate:JbpTemplateInput,$jbpFile:FileScalar){uploadJbpPlan(jbpTemplate:{jbpId:"123345",jbpYear:2022,fileName:"ayush.xlsx",jbpFile:$jbpFile}) {jbplist{name,notes,type,startDate,dueDate,owners {emailId,firstName},phase,rowError},majorErrors}}","variables":{"jbpTemplate":{jbpId:"123345",jbpYear:2022}}}
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString
public class JbpTemplateData {
private List<JbpPlan> jbplist;
private boolean hasError;
private List<String> majorErrors;
private JbpTemplate template;
}
#GraphQLApi
#Component
public class JbpTemplateResolver {
#Autowired
private JbpTemplateService templateService;
#LogExecutionTime
#GraphQLMutation(name = "uploadJBPFile")
public JbpTemplateData uploadJbpFile(#GraphQLArgumen`(name = "jbpTemplate") JbpTemplate jbpTemplate) throws IOException {
return templateService.uploadJbpPlanFile(jbpTemplate);
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class JbpTemplate {
private String jbpId;
private String fileName;
#GraphQLScalar
private File jbpFile;
private int jbpYear;
private String accountId;
private String subAccountId;
private List<JBPActivity> actvitiyList;
public JbpTemplate(JbpTemplate temp) {
this.jbpId = temp.getJbpId();
this.jbpFile = temp.getJbpFile();
this.fileName = temp.getFileName();
this.actvitiyList = temp.getActvitiyList();
}
}
#RestController
#CrossOrigin
public class Retail360GraphQLController extends GraphQLController<NativeWebRequest> {
#Autowired
public Retail360GraphQLController(GraphQL graphQL, GraphQLMvcExecutor executor) {
super(graphQL, executor);
}
/**
* The Request contains the following parts: operations: JSON String with the
* GQL Query map: Maps the multipart files to the variables of the GQL Query
*/
#PostMapping(value = "/retail360", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE })
#ResponseBody
public Object executeMultipartPost(#RequestParam("operations") String operations,
#RequestParam("map") String map,
MultipartHttpServletRequest multiPartRequest,
NativeWebRequest webRequest) throws IOException, ServletException {
GraphQLRequest graphQLRequest = new ObjectMapper().readerFor(GraphQLRequest.class).readValue(operations);
Map<String, ArrayList<String>> fileMap = new ObjectMapper().readerFor(Map.class).readValue(map);
mapRequestFilesToVariables(multiPartRequest, graphQLRequest, fileMap);
return this.executeJsonPost(graphQLRequest, new GraphQLRequest(null, null, null, null), webRequest);
}
private void mapRequestFilesToVariables(MultipartHttpServletRequest multiPartRequest,
GraphQLRequest graphQLRequest,
Map<String, ArrayList<String>> fileMap) throws IOException, ServletException {
for (var pair : fileMap.entrySet()) {
String targetVariable = "jbpTemplate";
if (graphQLRequest.getVariables().containsKey(targetVariable)) {
Part correspondingFile = multiPartRequest.getPart(pair.getKey());
String filename = correspondingFile.getSubmittedFileName();
File file = Files.write(Files.createTempFile(filename.substring(0, filename.lastIndexOf(".")), filename.substring(filename.lastIndexOf("."))), correspondingFile.getInputStream().readAllBytes()).toFile();
Map map = (HashMap) graphQLRequest.getVariables().get("jbpTemplate");
map.put("jbpFile", file);
}
}
}
}

Validating multipart/form-data in Spring REST api

I recently came up to an issue related to validation. Typically, I am building a REST api that allow users to create their account including avatars. All of the information should be submitted when user clicks to Register button. So, my server will then receive a request that includes some fields like name (string), birthday (datetime), ... and avatar (multipart file). So, the question is how to validate the received file is a truly image and has an allowed size and simultaneously validate that the others (email, password) are also valid.
For the case that all fields is text, we can easily validate them using the combination of annotations like this
Controller
#PostMapping(path = "")
public ResponseEntity<?> createNewAccount(#RequestBody #Valid RegisterRequest registerRequest) {
Long resourceId = service.createNewCoderAccount(registerRequest);
return ResponseEntity.created(location(resourceId)).build();
}
Request DTO
#ConfirmedPassword
public class RegisterRequest extends BaseRequest implements ShouldConfirmPassword {
#NotBlank(message = "Field 'email' is required but not be given")
#Email
#Unique(message = "Email has been already in use", service = UserValidatorService.class, column = "email")
private String email;
#NotBlank(message = "Field 'password' is required but not be given")
#Size(min = 6, message = "Password should contain at least 6 characters")
private String password;
#NotBlank(message = "Field 'confirmPassword' is required but not be given")
private String confirmPassword;
#NotBlank(message = "Field 'firstName' is required but not be given")
private String firstName;
#NotBlank(message = "Field 'lastName' is required but not be given")
private String lastName;
}
Or in case that the request containing only file(s), we can absolutely do like this
Controller
#PostMapping(path = "/{id}")
public ResponseEntity<?> editChallengeMetadata(
#ModelAttribute ChallengeMetadataRequest request,
BindingResult bindingResult,
#PathVariable("id") Long id,
#CurrentUser User user
) throws BindException {
challengeMetadataRequestValidator.validate(request, bindingResult);
if (bindingResult.hasErrors()) {
throw new BindException(bindingResult);
}
Long challengeId = service.updateChallengeMetadata(id, request, user);
return ResponseEntity.ok(RestResponse.build(challengeId, HttpStatus.OK));
}
Validator
public class ChallengeMetadataRequestValidator implements Validator {
#Override
public boolean supports(#NonNull Class<?> aClass) {
return ChallengeMetadataRequest.class.isAssignableFrom(aClass);
}
#Override
public void validate(#NonNull Object o, #NonNull Errors errors) {
ChallengeMetadataRequest request = (ChallengeMetadataRequest) o;
if (request.getBanner() != null && !request.getBanner().isEmpty()) {
if (!List.of("image/jpeg", "image/png").contains(request.getBanner().getContentType())) {
errors.rejectValue("banner", "challenge.mime-type.not-supported", new String[]{request.getBanner().getContentType()}, "Mime-type is not supported");
}
}
}
}
As you seen above, if I wrap all data (including avatar) in a DTO class, I definitely write its own validator. But what will happen if then I have to write manually hundreds validators like that.
So, do anyone have any idea about it, typically, make the multipart/form-data request becomes simalar with application/json request ?
Thanks and regards,

Spring Boot multiple files upload with multiple objects

I need help I last time create the controller with the single file upload with an object and it's work for me like
My POJO class
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "file", "data" })
public class FileWithObject<T> {
#JsonProperty("file")
private MultipartFile file;
#JsonRawValue
#JsonProperty("data")
private T data;
}
MY REST CONTOLLER
#RequestMapping(value="/filestore/{bucket-uuid}/appsport.com/singleFileUploadWithObject/{folder}",
method = RequestMethod.POST)
#ResponseBody
// api work
public String singleFileUploadWithObject(
#PathVariable(name="bucket-uuid", required = true) String bucketUUId,
#PathVariable(name="folder", required = false) String folder,
FileWithObject rawData) {
return pingResponse;
}
My Postman result
That's all work for me. How to send the list of the objects through the postman or is possible to handle that way request like below rest controller
#RequestMapping(value="/filestore/{bucket-uuid}/appsport.com/listOfObjectsWithSingleFile/{folder}", method = RequestMethod.POST)
#ResponseBody
public String listOfObjectsWithSingleFile(
#PathVariable(name="bucket-uuid", required = true) String bucketUUId,
#PathVariable(name="folder", required = false) String folder,
Set<FileWithObject> rawData) {
return pingResponse;
}
How to handle list of objects
[{
"file": fileobject,
"data": "zyz"
},{
"file": fileobject,
"data": "zyz"
}]
I'm trying to creating the api for this blow task
I have done this by use of Meta-Data concept there are few changes I did in controller and bean
Controller
#RequestMapping(value="/filestore/{bucket-uuid}/appsport.com/listOfObjectsWithSingleFile/{folder}",
method = RequestMethod.POST)
#ResponseBody
public String listOfObjectsWithSingleFile(
#PathVariable(name="bucket-uuid") String bucketUUId, #PathVariable(name="folder") String folder,
FileWithObject objects) { // change this Set<FileWithObject> rawData to FileWithObject objects
return pingResponse;
}
Bean
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "file", "files", "data" })
public class FileWithObject<T> {
#JsonProperty("file")
private MultipartFile file;
#JsonProperty("files")
private List<MultipartFile> files;
#JsonRawValue
#JsonProperty("data")
private T data;
// work like (meta-data)
List<FileWithObject> rawData;
// getter setter
}
Image's For Request
Note:- I'm Still looking for this issue handle this in a blow way
#RequestMapping(value="/filestore/{bucketuuid}/appsport.com/listOfObjectsWithSingleFile/{folder}", method = RequestMethod.POST)
#ResponseBody
public String listOfObjectsWithSingleFile(#PathVariable(name="bucket-uuid", required = true) String bucketUUId,
#PathVariable(name="folder", required = false) String folder,Set<FileWithObject> rawData) {
return pingResponse;
}
hope it helps some one

How to pass an object to a ModelAttrbiute in MockMVC post?

User model:
#Entity
#Table(name="user")
public class User {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
#NotBlank
#Column(name="username")
private String username;
#NotEmpty
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="user_role", joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="role_id")})
private Set<Role> roles;
}
Controller:
#RequestMapping(value = {"/users/edit/{id}"}, method = RequestMethod.POST)
public String editUser(ModelMap model, #Valid #ModelAttribute("user") User user, BindingResult result) {
if(result.hasErrors()) {
return "AddUserView";
}
return "redirect:/users";
}
Test with MockMVC:
#Test
public void performUpdateUserTest() throws Throwable {
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("username", "User"));
}
Well, fine, I can pass a param username as always using param(). But what should I do with ROLES? This field is a separate object. I can't pass it using param(). Then how is it possible to pass it in the test?
The only way out I found is to create an entity and pass it using .flashAttr():
#Test
public void performUpdateUserTest() throws Throwable {
User user = new User("User", new HashSet<Role>(Arrays.asList(new Role("USER"))));
mockMvc.perform(post("/users/edit/{id}", user.getId())
.flashAttr("user", user));
}
But then, what if I need to test that user can't be updated because of binding error in the ROLES field(ROLES can't be null, and suppose, it was set as null)? Thus, I'm not able to create user(and use it with .flashAttr) already with a binding error as the exception will be thrown. And I still have to pass it separately.
Well, after a long time of searching, I found out that I should add a converter to the MockMVC. What converter is you can read HERE, for instance.
I had it already in my project but didn't realize that it didn't work with MockMVC.
So, you can add the converter to MockMVC like that:
#Autowired
private StringToRoleConverter stringToRoleConverter;
#Before
public void init() {
FormattingConversionService cs = new FormattingConversionService();
cs.addConverter(stringToRoleConverter);
mockMvc = MockMvcBuilders.standaloneSetup(userController)
.setConversionService(cs)
.build();
}
Converter itself:
#Component
public class StringToRoleConverter implements Converter<String, Role> {
#Autowired
private RoleService roleService;
#Override
public Role convert(String id) {
Role role = roleService.findById(Integer.valueOf(id));
return role;
}
}
And then I can add param like that:
mockMvc.perform(post("/users/edit/{id}", user.getId())
.param("roles", "2"))
though I'm passing a string there, it will be converter to Role with the help of Spring converter.

MockMvc - calling a query with a complicated object

I want to send to the controller a complex object consisting of files and simple types.
public class ContributionNew<T extends MovieInfoDTO> {
private List<T> elementsToAdd;
private Map<Long, T> elementsToUpdate;
private Set<Long> idsToDelete;
private Set<String> sources;
private String comment;
}
public class Photo extends MovieInfoDTO {
private MultipartFile photo;
}
#PostMapping(value = "/{id}/contributions/photos")
#ResponseStatus(HttpStatus.CREATED)
public
ResponseEntity<Void> createPhotoContribution(
#ApiParam(value = "The movie ID", required = true)
#PathVariable("id") final Long id,
#ApiParam(value = "The contribution", required = true)
#RequestBody #Valid final ContributionNew<Photo> contribution
) {
I want to create a test to send an object, but I do not know how to finish it.
#Test
public void testCreatePhotoContribution() throws Exception {
ContributionNew<Photo> contribution = new ContributionNew<>();
MockMultipartFile multipartFile = new MockMultipartFile("photo", "C:\\Users\\Jonatan\\Pictures\\2.png",
"image/png", "Spring Framework".getBytes());
Photo.Builder photoBuilder = new Photo.Builder(
multipartFile
);
contribution.getElementsToAdd().add(photoBuilder.build());
mockMvc
.perform(post("/api/v1.0/movies/{id}/contributions/photos", 1)
.contentType(...)
.content(...))
.andExpect(status().isCreated());
}
I do not know how to send such an object as #ResuestBody? I do not know how to finish this test.
You can do something like this.
ObjectMapper = new ObjectMapper(); // You can also Autowire this
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mockMvc
.perform(post("/api/v1.0/movies/{id}/contributions/photos", 1)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(contribution)))
.andExpect(status().isCreated());

Categories

Resources