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());
Related
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);
}
}
}
}
i try to write #WebMvcTest:
#ExtendWith(SpringExtension.class)
#WebMvcTest(AudienceController.class)
class LectureControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private LectureService lectureService;
#MockBean
private GroupService groupService;
#MockBean
private TeacherService teacherService;
#MockBean
private StudentService studentService;
#BeforeEach
public void init() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new LectureController(lectureService, groupService, teacherService, studentService)).build();
}
#Test
void whenGetRequest_thenReturnTTForTeacherPage() throws Exception {
Teacher teacher = new Teacher(1, "teacher", "first");
Subject subject = new Subject(1, "first");
LocalDateTime date = LocalDateTime.now();
Audience audience = new Audience(1, 100);
Group group = new Group(1, "group");
Lecture lecture = new Lecture(1, teacher, subject, Arrays.asList(group), date);
lecture.setAudience(audience);
when(teacherService.findOne(1)).thenReturn(teacher);
when(lectureService.findLecturesByTeacher(teacher, date, date)).thenReturn(Arrays.asList(lecture));
mockMvc.perform(get("/lecture/TTForTeacher/{id}", 1)
.param("startDay", date.toString())
.param("endDay", date.toString()))
.andDo(print())
.andExpect(status().isOk())
.andExpect(view().name("lecture/TTForTeacher"))
.andExpect(forwardedUrl("lecture/TTForTeacher"))
.andExpect(model().attribute("lectures", hasSize(1)))
.andExpect(model().attribute("lectures", hasItem(
allOf(
hasProperty("id", is(1)),
hasProperty("teacher", is(teacher)),
hasProperty("subject", is(subject)),
hasProperty("date", is(date)),
hasProperty("audience", is(audience)),
hasProperty("groups", is(Arrays.asList(group)))
)
)));
verify(lectureService, times(1)).findLecturesByTeacher(teacher, date, date);
}
for my controller:
#Controller
#RequestMapping("/lecture")
public class LectureController {
private LectureService lectureService;
private GroupService groupService;
private TeacherService teacherService;
private StudentService studentService;
public LectureController(LectureService lectureService, GroupService groupService, TeacherService teacherService,
StudentService studentService) {
this.lectureService = lectureService;
this.groupService = groupService;
this.teacherService = teacherService;
this.studentService = studentService;
}
#GetMapping("/TTForTeacher/{id}")
public String getTTForTeacher(#PathVariable("id") int id, #RequestParam(value = "startDay") LocalDateTime startDay,
#RequestParam(value = "endDay") LocalDateTime endDay, Model model) {
model.addAttribute("lectures",
lectureService.findLecturesByTeacher(teacherService.findOne(id), startDay, endDay));
return "lecture/TTForTeacher";
}
But it has failures: "Status expected:<200> but was:<400>"
and this in console:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /lecture/TTForTeacher/1
Parameters = {startDay=[10.05.2020, 10:00], endDay=[31.05.2020, 20:00]}
Headers = []
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.foxminded.university.controller.LectureController
Method = com.foxminded.university.controller.LectureController#getTTForTeacher(int, LocalDateTime, LocalDateTime, Model)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
I tried not use .param("day", date.toString), to set string of several formats to this param, to use not .param, but i have this exception, can u help me with it. All other test of other methods are successful, but there are no #RequestParam in those methods. So how can i test methods like this
The controller is missing logic to parse data. Spring by default does not know how to convert the string to datetime object. There are 2 ways of solving this, one solution could be read date as String instead of LocalDate and then parse String to get the startDay and endDay, but there is a better way using #DateTimeFormat https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/format/annotation/DateTimeFormat.html
Controller method becomes like this
public String getTTForTeacher(#PathVariable("id") int id, #RequestParam(value = "startDay") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDay,
#RequestParam(value = "endDay") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDay) {
Test mockMvc can be left as is since you are doing LocalDateTime.now().toString()
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.
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?
I need to expose two different set of values from my model, so i implemented 2 views
public class Views {
public static class Small{ }
public static class Large extends Small { }
}
Then, in my model i put (all other fields are annotated with JSONIgnore
#JsonView(Views.Small.class)
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id_posto", unique = true, nullable = false)
public int getIdPosto() {
return this.idPosto;
}
public void setIdPosto(int idPosto) {
this.idPosto = idPosto;
}
#JsonView(Views.Large.class)
#NotNull
#Column(name = "nome_posto_park")
public String getNomePosto() {
return this.nomePosto;
}
public void setNomePosto(String nomePosto) {
this.nomePosto = nomePosto;
}
On my Controllers I have 2 methods:
#RequestMapping(value = "/spots", method = RequestMethod.GET)
public ResponseEntity<Posto> getSpotStatus(#RequestParam(value = "idPosto") int idPosto,
#RequestParam(value = "occupied") boolean occupied) {
Posto posto = postoService.findByIdPosto(idPosto);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
mapper.setConfig(mapper.getSerializationConfig()
.withView(Views.Small.class));
mapper.convertValue(posto, JsonNode.class);
return new ResponseEntity<Posto>(posto, HttpStatus.OK);
and
#RequestMapping(value="/spot", method = RequestMethod.GET)
public ResponseEntity<List<Posto>> getSpotList(#RequestParam (value = "idPiano") int idPiano){
Piano piano = pianoService.findById(idPiano);
List<Posto> posti = postoService.showSpotsByFloor(-1, piano);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.DEFAULT_VIEW_INCLUSION);
mapper.setConfig(mapper.getSerializationConfig()
.withView(Views.Large.class));
mapper.convertValue(posti, JsonNode.class);
return new ResponseEntity<List<Posto>>(posti, HttpStatus.OK);
}
Che result is the same... (obviously the first is a single Posto and the second a List but all the fields from the model are serialized....
What I'm doing wrong when using views?
You need define produces and consume with agree view and annotation with #ResponseBody
Example: Change your needed
#Produces(value = { MediaType.APPLICATION_JSON_VALUE })
#Consumes(value = { MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody public ResponseEntity<List<Posto>> getSpotList(...
//when request, put your client agree request view
protected HttpEntity<T> headers()
{
final HttpHeaders headers = new HttpHeaders();
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
headers.set("application", MediaType.APPLICATION_JSON_VALUE);
// T define your type here
return new HttpEntity<T>(headers);
}
Your problem is that Spring instantiates its own Jackson ObjectMapper when the ApplicationContext starts.
You'll have to autowire the Spring managed ObjectMapper and configure that instance instead of creating your own with new.
private final ObjectMapper mapper;
public MyController(ObjectMapper mapper) {
this.mapper = mapper;
}