diff --git a/src/main/java/com/alkemy/ong/config/SecurityConfig.java b/src/main/java/com/alkemy/ong/config/SecurityConfig.java index 89bf58a9..22e4e83b 100644 --- a/src/main/java/com/alkemy/ong/config/SecurityConfig.java +++ b/src/main/java/com/alkemy/ong/config/SecurityConfig.java @@ -32,28 +32,38 @@ protected void configure(HttpSecurity http) throws Exception { .antMatchers("/api/docs/**", "/api/swagger-ui/**", "/v3/api-docs/**", "/auth/login", "/auth/register").permitAll() .antMatchers(HttpMethod.GET, ApiConstants.ORGANIZATIONS_URI + "/{id}").permitAll() .antMatchers(HttpMethod.POST, ApiConstants.CONTACTS_URI).permitAll() + .antMatchers(HttpMethod.POST, ApiConstants.MEMBERS_URI).authenticated() + .antMatchers(HttpMethod.GET, ApiConstants.MEMBERS_URI).authenticated() .antMatchers(HttpMethod.PUT, ApiConstants.MEMBERS_URI + "/{id}").authenticated() - .antMatchers(HttpMethod.GET, ApiConstants.USERS_URI, ApiConstants.CONTACTS_URI).hasRole("ADMIN") - .antMatchers(HttpMethod.PATCH, ApiConstants.USERS_URI + "/{id}").hasAnyRole("USER", "ADMIN") - .antMatchers(HttpMethod.DELETE, ApiConstants.USERS_URI + "/{id}").hasAnyRole("USER", "ADMIN") + .antMatchers(HttpMethod.PATCH, ApiConstants.USERS_URI + "/{id}").authenticated() + .antMatchers(HttpMethod.DELETE, ApiConstants.USERS_URI + "/{id}").authenticated() + .antMatchers(HttpMethod.DELETE, ApiConstants.COMMENTS_URI + "/{id}").authenticated() + .antMatchers(HttpMethod.PUT, ApiConstants.COMMENTS_URI + "/{id}").authenticated() + .antMatchers(HttpMethod.POST, ApiConstants.COMMENTS_URI).authenticated() .antMatchers(HttpMethod.GET, ApiConstants.SLIDES_URI).hasRole("ADMIN") .antMatchers(HttpMethod.GET, ApiConstants.SLIDES_URI + "/{id}").hasRole("ADMIN") - .antMatchers(HttpMethod.POST, ApiConstants.COMMENTS_URI).hasRole("USER") - .antMatchers(HttpMethod.DELETE, ApiConstants.COMMENTS_URI + "/{id}").hasAnyRole("USER", "ADMIN") - .antMatchers(HttpMethod.PUT, ApiConstants.COMMENTS_URI + "/{id}").hasAnyRole("USER", "ADMIN") + .antMatchers(HttpMethod.GET, ApiConstants.USERS_URI).hasRole("ADMIN") + .antMatchers(HttpMethod.GET, ApiConstants.CONTACTS_URI).hasRole("ADMIN") .antMatchers(HttpMethod.GET, ApiConstants.COMMENTS_URI).hasRole("ADMIN") .antMatchers(HttpMethod.GET, ApiConstants.CATEGORIES_URI + "/{id}").hasRole("ADMIN") + // Default access for each Method .antMatchers(HttpMethod.GET).authenticated() .antMatchers(HttpMethod.POST).hasRole("ADMIN") .antMatchers(HttpMethod.PATCH).hasRole("ADMIN") .antMatchers(HttpMethod.DELETE).hasRole("ADMIN") .antMatchers(HttpMethod.PUT).hasRole("ADMIN") - .and().exceptionHandling() + // Error handling + .and() + .exceptionHandling() .authenticationEntryPoint(new AuthenticationEntryPointHandler()) .accessDeniedHandler(new CustomAccessDeniedHandler()) - .and().sessionManagement() + // Session + .and() + .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + // filters + .and() + .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } @Bean diff --git a/src/main/java/com/alkemy/ong/ports/input/rs/response/MemberResponse.java b/src/main/java/com/alkemy/ong/ports/input/rs/response/MemberResponse.java index 979bff7a..b496b722 100644 --- a/src/main/java/com/alkemy/ong/ports/input/rs/response/MemberResponse.java +++ b/src/main/java/com/alkemy/ong/ports/input/rs/response/MemberResponse.java @@ -1,5 +1,6 @@ package com.alkemy.ong.ports.input.rs.response; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,16 +15,22 @@ public class MemberResponse { private Long id; + @JsonProperty("name") private String name; + @JsonProperty("facebook_url") private String facebookUrl; + @JsonProperty("instagram_url") private String instagramUrl; + @JsonProperty("linkedin_url") private String linkedinUrl; + @JsonProperty("image") private String image; + @JsonProperty("description") private String description; } diff --git a/src/test/java/com/alkemy/ong/ports/input/rs/controller/MemberControllerIT.java b/src/test/java/com/alkemy/ong/ports/input/rs/controller/MemberControllerIT.java new file mode 100644 index 00000000..742ce73f --- /dev/null +++ b/src/test/java/com/alkemy/ong/ports/input/rs/controller/MemberControllerIT.java @@ -0,0 +1,136 @@ +package com.alkemy.ong.ports.input.rs.controller; + +import com.alkemy.ong.H2Config; +import com.alkemy.ong.config.util.JsonUtils; +import com.alkemy.ong.core.model.Member; +import com.alkemy.ong.core.usecase.MemberService; +import com.alkemy.ong.ports.input.rs.api.ApiConstants; +import com.alkemy.ong.ports.input.rs.request.MemberRequest; +import com.alkemy.ong.ports.input.rs.response.MemberResponseList; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithUserDetails; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.web.servlet.MockMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles("test") +@SpringJUnitConfig(classes = H2Config.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class MemberControllerIT { + + @Autowired + MockMvc mockMvc; + + @Mock + MemberService memberService; + + + @Test + @Order(1) + @WithUserDetails("jdoe@somosmas.org") + void createMember_shouldReturn201() throws Exception { + + MemberRequest request = MemberRequest.builder() + .name("foo") + .facebookUrl("foo.faceboock") + .instagramUrl("foo.instagram") + .linkedinUrl("foo.linkedin") + .description("foo.description") + .image("foo.image") + .build(); + given(memberService.createEntity(any(Member.class))).willReturn(99L); + + final String actualLocation = mockMvc.perform(post(ApiConstants.MEMBERS_URI) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtils.objectToJson(request))) + .andExpect(status().isCreated()) + .andDo(print()) + .andReturn() + .getResponse() + .getHeader(HttpHeaders.LOCATION); + + assertThat(actualLocation).isEqualTo("http://localhost/v1/members/1"); + } + @Test + @Order(2) + @WithUserDetails("jdoe@somosmas.org") + void getMembers_shouldReturn200() throws Exception{ + String content = mockMvc.perform(get(ApiConstants.MEMBERS_URI)) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(content).isNotBlank(); + + MemberResponseList response = JsonUtils.jsonToObject(content, MemberResponseList.class); + + assertThat(response.getTotalElements()).isEqualTo(1); + assertThat(response.getTotalPages()).isEqualTo(1); + assertThat(response.getNextUri()).isEqualTo("http://localhost/v1/members?page=1"); + assertThat(response.getPreviousUri()).isEqualTo("http://localhost/v1/members?page=0"); + assertThat(response.getContent()).isNotEmpty(); + } + + @Test + @Order(3) + @WithUserDetails("jdoe@somosmas.org") + void upDateMember_shouldReturn204() throws Exception { + MemberRequest request = MemberRequest.builder() + .name("faa") + .facebookUrl("faa.faceboock") + .instagramUrl("faa.instagram") + .linkedinUrl("faa.linkedin") + .description("faa.description") + .image("faa.image") + .build(); + + mockMvc.perform(put(ApiConstants.MEMBERS_URI + "/1") + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtils.objectToJson(request))) + .andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + @Order(4) + @WithUserDetails("admin@somosmas.org") + void deleteMember_shouldReturn204() throws Exception { + mockMvc.perform(delete(ApiConstants.MEMBERS_URI + "/1")) + .andExpect(status().isNoContent()) + .andDo(print()); + } + + + @Test + @Order(5) + @WithUserDetails("jdoe@somosmas.org") + void deleteMember_shouldReturn403() throws Exception { + mockMvc.perform(delete(ApiConstants.MEMBERS_URI + "/1")) + .andExpect(status().isForbidden()) + .andDo(print()); + } + + + +} \ No newline at end of file diff --git a/src/test/java/com/alkemy/ong/ports/input/rs/controller/MemberControllerTest.java b/src/test/java/com/alkemy/ong/ports/input/rs/controller/MemberControllerTest.java new file mode 100644 index 00000000..f7787222 --- /dev/null +++ b/src/test/java/com/alkemy/ong/ports/input/rs/controller/MemberControllerTest.java @@ -0,0 +1,145 @@ +package com.alkemy.ong.ports.input.rs.controller; + +import com.alkemy.ong.config.exception.handler.GlobalExceptionHandler; +import com.alkemy.ong.config.util.JsonUtils; +import com.alkemy.ong.core.model.Member; +import com.alkemy.ong.core.model.MemberList; +import com.alkemy.ong.core.usecase.MemberService; +import com.alkemy.ong.ports.input.rs.api.ApiConstants; +import com.alkemy.ong.ports.input.rs.mapper.MemberControllerMapper; +import com.alkemy.ong.ports.input.rs.request.MemberRequest; +import com.alkemy.ong.ports.input.rs.response.MemberResponseList; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@ExtendWith(MockitoExtension.class) +class MemberControllerTest { + + + private MockMvc mockMvc; + + @InjectMocks + MemberController memberController; + + @Mock + MemberService memberService; + + @Spy + MemberControllerMapper mapper = Mappers.getMapper(MemberControllerMapper.class); + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(memberController) + .setControllerAdvice(GlobalExceptionHandler.class) + .build(); + } + + @Test + void createMember_shouldReturn201() throws Exception { + + MemberRequest request = MemberRequest.builder() + .name("foo") + .facebookUrl("foo.faceboock") + .instagramUrl("foo.instagram") + .linkedinUrl("foo.linkedin") + .description("foo.description") + .image("foo.image") + .build(); + + given(memberService.createEntity(any(Member.class))).willReturn(99L); + + final String actualLocation = mockMvc.perform(post(ApiConstants.MEMBERS_URI) + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtils.objectToJson(request))) + .andExpect(status().isCreated()) + .andDo(print()) + .andReturn() + .getResponse() + .getHeader(HttpHeaders.LOCATION); + + assertThat(actualLocation).isEqualTo("http://localhost/v1/members/99"); + + } + + @Test + void getMembers_shouldReturn200() throws Exception { + Member member = new Member(); + member.setId(99L); + member.setName("foo"); + member.setImage("foo.image"); + + MemberList list = new MemberList(List.of(member), Pageable.ofSize(1), 1); + + given(memberService.getList(any(PageRequest.class))).willReturn(list); + + String content = mockMvc.perform(get(ApiConstants.MEMBERS_URI + "?page=0&size=1")) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn() + .getResponse() + .getContentAsString(); + + assertThat(content).isNotBlank(); + + MemberResponseList response = JsonUtils.jsonToObject(content, MemberResponseList.class); + + assertThat(response.getTotalElements()).isEqualTo(1); + assertThat(response.getTotalPages()).isEqualTo(1); + assertThat(response.getNextUri()).isEqualTo("http://localhost/v1/members?size=1&page=1"); + assertThat(response.getPreviousUri()).isEqualTo("http://localhost/v1/members?size=1&page=0"); + assertThat(response.getContent()).isNotEmpty(); + } + + + + @Test + void upDateMember_shouldReturn204() throws Exception{ + + MemberRequest request = MemberRequest.builder() + .name("faa") + .image("faa.image") + .build(); + + willDoNothing().given(memberService).updateEntityIfExists(eq(99L), any(Member.class)); + + mockMvc.perform(put(ApiConstants.MEMBERS_URI + "/99") + .contentType(MediaType.APPLICATION_JSON) + .content(JsonUtils.objectToJson(request))) + .andExpect(status().isNoContent()) + .andDo(print()); + } + + @Test + void deleteMember_shouldReturn204() throws Exception{ + mockMvc.perform(delete(ApiConstants.MEMBERS_URI + "/1")) + .andExpect(status().isNoContent()) + .andDo(print()); + } + + +} \ No newline at end of file