I'm trying to use both #RequestBody and #RequestParam to send JSON and multiple files through Postman but it's not working. Is it possible to use both annotations in an API?
#RequestMapping(value = "/save/product/test", method = RequestMethod.POST)
public ResponseEntity<?> save(#Valid #RequestBody ProductVo productVo, #RequestParam("files") #NotNull #NotBlank MultipartFile[] uploadfiles) {
System.out.println("body " + productVo.toString());
for (MultipartFile file : uploadfiles) {
System.out.println(file.getOriginalFilename());
System.out.println(file.getContentType());
System.out.println(file.getName());
System.out.println(file.getSize());
}
return new ResponseEntity<APIResponse>(this.apiResponse, HttpStatus.NO_CONTENT);
}
#RequestParam takes parameter from uri, you are actually trying to achieve something else.
Here is an example controller takes json body and multipart file :
#RestController
#RequestMapping("/users")
public class UserController {
UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#PostMapping({"/", ""})
public ResponseEntity<User> post(#RequestPart("request") UserCreateRequest request, #RequestPart("file") MultipartFile file) throws IOException {
String photoPath = UUID.randomUUID() + file.getOriginalFilename().replaceAll(" ", "").trim();
// other logic
return ResponseEntity.ok(userService.create(request));
}
}
You can ease your life by wrapping the multipart alongside the other fields:
class UploadContext {
private MultipartFile file;
private UserCreateRequest request;
// other fields
}
and use this object in the controller:
#PostMapping(value = "/upload")
public void upload(UploadContext context) {
// controller logic here
}
Doc: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-multipart-forms
In your client code you have to clear the content-type from the headers:
headers: {'Content-Type': undefined}
Hope this helps.
Any number of files and a string param can be uploaded by having a MultipartHttpServletRequest and RequestParam.
One thing to be aware of: The MultipartHttpServletRequest will also hold all the request params, so technically you can even just have MultipartHttpServletRequest and parse it
Signature of Controller is:
public ResponseEntity<BulkUploadResponsePayload> filesAndJson(
#ApiParam(hidden = true) MultipartHttpServletRequest multipartRequest,
#RequestParam(value = "json-param",name = "json-param") String documentType) {
// multipartRequest will have all the files
// you can use json-param for any string
}
Related
I have a a service which is protected by oAuth. This is the structure for my RestController:
#CrossOrigin(origins = "*", allowedHeaders = "*")
#RestController
public class MainController {
#PostMapping("/v1/api")
#CustomAuthorizer(RequestURI = "api", ResourceType = ResourceType.API, GrantAccess = GrantAccess.EXECUTE)
public ResponseEntity<List<Details>> api(
#ApiParam(value = "Provide here request body", required = true) #Valid #RequestBody Input input,
#Valid #RequestParam("tId") String tenantId, #RequestHeader("Authorization") String auth) {
}
This is my Test for the same
#RunWith(SpringRunner.class)
#SpringBootTest
public class MainControllerMockTest {
#WithMockUser( username = "test")
#Test
public void testApi() throws Exception {
Input input = new Input();
//Set input here
MvcResult result = mockMvc.perform(post("/v1/api?tId=test")
.content(inputJson).contentType(MediaType.APPLICATION_JSON).header(HttpHeaders.AUTHORIZATION, "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJsd2lsbGlhbXMxNiIsInJvbGVzIjoidXNlciIsImlhdCI6MTUxNDQ0OTgzM30.WKMQ_oPPiDcc6sGtMJ1Y9hlrAAc6U3xQLuEHyAnM1FU")
.with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(status().isOk()).andReturn();
System.out.println(result.getResponse().getContentAsString());
assertEquals(200, result.getResponse().getStatus());
}
}
Authentication is done by our custom oAuth library, So when /api is called it checks for permissions, is there any way i can bypass that ? I tried mocking that Code as well but it didn't work. Any suggestions would be great.
I am facing below error while running junit for controller. I have already set content-type as Json, still error is same. Any suggestion what could be the issue ?
Error is
java.lang.AssertionError: expectation "expectNext({response=employee saved Successfully})" failed (expected: onNext({response=employee saved Successfully}); actual: onError(org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/octet-stream' not supported for bodyType=java.util.Map<java.lang.String, java.lang.String>))
Controller is
#Slf4j
#Controller
#RequiredArgsConstructor
public class EmployeeController {
private final EmployeeService employeeService;
#PostMapping(path ="/employees",produces = APPLICATION_JSON_VALUE)
public #ResponseBody Mono<Map<String, String>> saveEmployees(#RequestBody List<EmployeeDto> employeeDtos) {
log.info("Received request to save employees [{}]", employeeDtos);
return employeeService.saveEmployee(employeeDtos);
}
}
Service class as below:
#Service
#Slf4j
#RequiredArgsConstructor
public class EmployeeService {
private final WebClient webClient;
public Mono<Map<String, String>> saveEmployees(List<EmployeeDto> employeeDtos) {
return webClient
.post()
.uri("/create-employees")
.contentType(APPLICATION_JSON)
.bodyValue(employeeDtos)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, String>>() {
})
.doOnError(e -> log.error("Failed to save employees {}: {}", employeeDtos, e));
Junit is as below:
#Slf4j
#SpringBootTest
class EmployeeServiceTest {
private static final WireMockServer wireMockServer = new WireMockServer(wireMockConfig().dynamicPort());
#Autowired
private ObjectMapper objectMapper;
#Autowired
private EmployeeService employeeService;
#Test
void shouldMakeAPostApiCallToSaveEmployee() throws JsonProcessingException {
var actualemployeeDtos = "....";
var randomEmployeeDto = ...;
wireMockServer.stubFor(post("/create-employees")
.withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON_VALUE))
.withHeader(ACCEPT, containing(APPLICATION_JSON_VALUE))
.withRequestBody(equalToJson(actualemployeeDtos))
.willReturn(aResponse()
.withStatus(OK.value())
.withBody("{\"response\": \"employee saved Successfully\"}")));
StepVerifier
.create(employeeService.saveEmployee(List.of(randomEmployeeDto)))
.expectNext(singletonMap("response", "employee saved Successfully"))
.verifyComplete();
}
}
After debugging , i found that even response header need to be set with content-type as readWithMessageReaders () method of BodyExtractors class check for content-type.
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
set in below code fixed the failing testcase
#Test
void shouldMakeAPostApiCallToSaveEmployee() throws JsonProcessingException {
var actualemployeeDtos = "....";
var randomEmployeeDto = ...;
wireMockServer.stubFor(post("/create-employees")
.withHeader(CONTENT_TYPE, equalTo(APPLICATION_JSON_VALUE))
.withHeader(ACCEPT, containing(APPLICATION_JSON_VALUE))
.withRequestBody(equalToJson(actualemployeeDtos))
.willReturn(aResponse()
.withHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.withStatus(OK.value())
.withBody("{\"response\": \"Employees saved Successfully\"}")));
StepVerifier
.create(employeeService.saveEmployee(List.of(randomEmployeeDto)))
.expectNext(singletonMap("response", "Employees saved Successfully"))
.verifyComplete();
}
In my case I fixed the issue parsing manually one of the parts that contains a json object
This code did not work
#PostMapping(value = "salvaDomanda")
#ResponseBody
#ResponseStatus(HttpStatus.CREATED)
Domanda salvaDomanda(#RequestPart("data") Domanda domanda,
#RequestPart(name = "files", required = false) MultipartFile[] files)
This one
#PostMapping(value = "salvaDomanda")
#ResponseBody
#ResponseStatus(HttpStatus.CREATED)
Domanda salvaDomanda(#RequestPart("data") String jsonDomanda,
#RequestPart(name = "files", required = false) MultipartFile[] files)
throws ApplicationException, IOException {
ObjectMapper mapper = new ObjectMapper();
Domanda domanda = mapper.readValue(jsonDomanda, Domanda.class);
does.
My Spring boot version is 2.6.3 with java 17
I am not able to get how to call the controller method(API) in this case. How to send the MultipartFile as request parameter and how to pass the HttpServletRequest so that mockMvc can call the actual method for testing.
#RunWith(SpringRunner.class)
public class PartnerSiteLoadControllerTest {
private MockMvc mockMvc;
#Mock
private PartnerSiteUploadService partnerSiteUploadService;
#InjectMocks
private PartnerSiteLoadController partnerSiteLoadController;
#Before
public void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(partnerSiteLoadController)
.setControllerAdvice(new PartnerExceptionHandlerMapper()).build();
}
#Test
public void uploadSitesInBulk()throws Exception {
String userId = "userId";
HttpServletRequest request = mock(HttpServletRequest.class);
UserPrincipal userPrincipal = new UserPrincipal();
userPrincipal.setId("id");
BulkUploadResponseDTO bulkUploadResponseDTO = new BulkUploadResponseDTO();
FileInputStream inputFile = new FileInputStream( "src/test/resources/PartnerSites_2019-09-04_v1.xlsx");
MockMultipartFile file = new MockMultipartFile("PartnerSites_2019-09-04_v1.xlsx", "PartnerSites_2019-09-04_v1.xlsx", "multipart/form-data", inputFile);
when(file.getOriginalFilename()).thenReturn("PartnerSites_2019-09-04_v1.xlsx");
when(request.getAttribute("userPrincipal")).thenReturn(userPrincipal);
when(partnerSiteUploadService.uploadSitesInBulk(userId,file)).thenReturn(bulkUploadResponseDTO);
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/v4/slm/partners/sites/import")
.file(file).contentType(MediaType.MULTIPART_FORM_DATA).requestAttr("userPrincipal",userPrincipal))
.andExpect(status().isOk());
verify(partnerSiteUploadService,times(1)).uploadSitesInBulk(userId,file);
verifyNoMoreInteractions(partnerSiteUploadService);
}
}
Controller class method
#RestController
#RequestMapping("/v4/slm/partners/sites/import")
#Api(value = "Site Bulk Upload Service")
#Slf4j
#Validated
public class PartnerSiteLoadController {
private PartnerSiteUploadService partnerSiteUploadService;
#Autowired
public PartnerSiteLoadController(PartnerSiteUploadService partnerSiteUploadService) {
this.partnerSiteUploadService = partnerSiteUploadService;
}
#PostMapping(value = "", headers = ("content-type=multipart/*"))
#ApiOperation(value = "Import sites in bulk")
public ResponseEntity<BulkUploadResponseDTO> uploadSitesInBulk(#RequestParam("file") MultipartFile excelFile, HttpServletRequest request){
UserPrincipal userPrincipal = (UserPrincipal) request.getAttribute("userPrincipal");
String userId = userPrincipal.getId();
log.info("Received excel file with name {}......",excelFile.getOriginalFilename());
if(!excelFile.isEmpty()){
return ResponseEntity.status(HttpStatus.CREATED).body(partnerSiteUploadService.uploadSitesInBulk(userId,excelFile));
}
else{
throw new BadRequestException("Received empty excel file");
}
}
}
while executing the test I am getting the 400 error code. the mockmvc is not calling the original API.
I am using my spring boot REST API with the controller as follows:-
`
#Autowired
HazelcastInstance hazelcastinstance;
String username="VAKSDNDDODM#DLDM#DMOD#DI##*#EK";
#RestController
#RequestMapping(value = "/Acode/availabile/multiple/{Code}/")
public class MultiController {
#Resource(name = "AService")
protected AImpl AService;
#RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> readMulti(
#RequestHeader(value="Auth-Token") String token,
#RequestBody XRequest Request,
#PathVariable String Code) throws Exception {
if(token.equalsIgnoreCase(username))
{
Response response = AService.readMultiX(Request, Code);
}
else
{
response.setMessage("Unauthorized access!");
response.setStatus(340);
}
return new ResponseEntity<Response>(response, HttpStatus.OK);
}
}
`
I wish to do the header comparision before API control enters the controller such that declaring #RequestHeader is not required in controller.Is it possible to do this or do we have an implementation for that?
I have a really simple controller defined in this way:
#RequestMapping(value = "/api/test", method = RequestMethod.GET, produces = "application/json")
public #ResponseBody Object getObject(HttpServletRequest req, HttpServletResponse res) {
Object userId = req.getAttribute("userId");
if (userId == null){
res.setStatus(HttpStatus.BAD_REQUEST.value());
}
[....]
}
I tried to call using MockMvc in many different way but, I'm not able to provide the attribute "userId".
For instance, with this it doesn't work:
MockHttpSession mockHttpSession = new MockHttpSession();
mockHttpSession.setAttribute("userId", "TESTUSER");
mockMvc.perform(get("/api/test").session(mockHttpSession)).andExpect(status().is(200)).andReturn();
I also tried this, but without success:
MvcResult result = mockMvc.perform(get("/api/test").with(new RequestPostProcessor() {
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setParameter("userId", "testUserId");
request.setRemoteUser("TESTUSER");
return request;
}
})).andExpect(status().is(200)).andReturn();
In this case, I can set the RemoteUser but never the Attributes map on HttpServletRequest.
Any clue?
You add a request attribute by calling requestAttr ^^
mockMvc.perform(get("/api/test").requestAttr("userId", "testUserId")...
You could use
mvc.perform(post("/api/v1/...")
.with(request -> {
request.addHeader(HEADER_USERNAME_KEY, approver);
request.setAttribute("attrName", "attrValue");
return request;
})
.contentType(MediaType.APPLICATION_JSON)...
#ResponseStatus(HttpStatus.OK)
#GetMapping(Routes.VALIDATE_EMAIL_TOKEN + "/validate")
public String validateEmailToken(#RequestParam(value = "token") String token,
HttpServletRequest httpServletRequest) throws RestServiceException {
return credentionChangeService.getUserByToken(token, httpServletRequest);
}
//test method
#Mock
private HttpServletRequest httpServletRequest
#Mock
private MerchantCredentialsChangeService mockCredentionChangeService;
#Test
public void testValidateEmailToken() throws Exception {
final String token = "akfkldakkadjfiafkakflkd";
final String expectedUsername = "9841414141";
Mockito.when(mockCredentionChangeService.getUserByToken(Matchers.eq(token), Matchers.any(HttpServletRequest.class)))
.thenReturn(expectedUsername);
mockMvc.perform(get(Routes.VALIDATE_EMAIL_TOKEN + "/validate")
.param("token", token))
.andExpect(status().isOk())
.andExpect(MockMvcResultMatchers.content().string(expectedUsername));
}