How to test same authorization logic in many rest controllers - spring

So I have many rest controllers and I would like to write some reusable test approach for authorization
#RestController //1
public class PolicyController {
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/group")
ResponseEntity subgrups(String policy) {
// impl
}
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/participants")
ResponseEntity participants(String policy) {
// impl
}
}
#RestController//2
public class GroupController {
#PreAuthorize("#securityService.hasAccess(#policy)")
#GetMapping("policy/{policy}/group/{group}"
ResponseEntity subgroups(String policy, String group) {
// impl
}
}
#RestController //...n
When we follow good practice we should write test for every line of code, so probably I should write exactly so many duplicated tests as amount of controller * amount of methods so it would be a huge amount of such duplicated code
#WebMvcTest(controllers = PolicyController.class)
public class PolicyControllerTest {
//...mock all controller dependencies
#Autowired
private MockMvc mockMvc;
#MockBean
private PolicySecurity policyApi;
#Autowired
private SecurityService securityService;
#Test
public void whenSearchingForGroupAndHasAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(false);
mockMvc.perform(get("/policies/{policy}/group", "123")
.contentType("application/json"))
.andExpect(status().isOk());
}
#Test
public void whenSearchingForGroupAndHasntAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(true);
mockMvc.perform(get("/policies/{policy}/group", "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
#Test
public void whenSearchingForParticipantsAndHasAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(false);
mockMvc.perform(get("/policies/{policy}/participants", "123")
.contentType("application/json"))
.andExpect(status().isOk());
}
#Test
public void whenSearchingForParticipantsAndHasntAccessToPolicy() throws Exception {
when(policyApi.isActive(any())).thenReturn(true);
mockMvc.perform(get("/policies/{policy}/participants", "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
}
this is only one controller with two methods, just imagine how many code there will be for 5 controllers and 30 endpoints, any idea how to write it in more maintainable way ?

Few pointers on what can be done. You can group your APIs based on the security configuration and then maintain a list of APIs for that group. This way you can just call one write one method for each authorization configuration where you will iterate through each API and run the mockMvc perform API method.
for(api in api_list) {
mockMvc.perform(get(api, "123")
.contentType("application/json"))
.andExpect(status().isForbidden());
}
If the security configuration changes for any one API in future, then the test case for the whole group will fail. Only problem with this approach is that maintaining the test cases will be difficult over time because you would need to keep updating the API lists everytime you change the authorization.
Note that it is always better to write isolated test cases for each API method to test only that unit.

Related

MockMvc not working for DataIntegrityViolationException

We Spring developers know that if one tries to delete an entity that has other associated entities, a DataIntegrityViolationException is thrown.
I wrote a delete method catching both EmptyResultDataAccessException and DataIntegrityViolationException exceptions, throwing custom service-level exceptions for each case:
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
repository.deleteById(id); // returns 204
}
catch (EmptyResultDataAccessException e) {
throw new ResourceNotFoundException("Id not found " + id); // returns 404
}
catch (DataIntegrityViolationException e) {
throw new DatabaseException("Integrity violation"); // returns 400
}
}
}
I've set all up so the first scenario returns 204, the second scenario returns 404, and the third scenario returns 400. Everything is working fine when I test it on Postman.
However, when I try to write an integrated test using MockMvc, the DataIntegrityViolationException scenario doesn't work! (the other two scenarios work).
#SpringBootTest
#AutoConfigureMockMvc
#Transactional
public class CityControllerIT {
#Autowired
private MockMvc mockMvc;
(...)
#Test
public void deleteShouldReturnBadRequestEventWhenDependentId() throws Exception {
mockMvc.perform(delete("/cities/{id}", 1L))
.andExpect(status().isBadRequest());
}
}
It's returning 204 instead of 400! I have printed some messages inside the try block and I have found that it is really not throwing an exception. The try block executes entirely, as there was no integrity violation.
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
System.out.println("START");
repository.deleteById(id);
System.out.println("FINISH");
}
(...)
I am missing something about MockMvc fundamentals? Why integrity violation is being ignored when executing that MockMvc test?
I've saved a minimum-H2-just-clone-and-run project on Github:
https://github.com/acenelio/mockmvc-dataintegrity
DataIntegrityViolationException does NOT work properly with #Transactional, even in a common service method like:
#Service
public class MyService {
#Autowired
private MyRepository repository;
#Transactional
public void delete(Long id) {
try {
repository.deleteById(id);
}
catch (DataIntegrityViolationException e) {
// do something
}
}
}
Similarly, if you want to automate test a DataIntegrityViolationException scenario, you should NOT annotate your test class with #Transactional.
So if your writing transactional integrated tests (which rollback database for each test), you may want to create another test class without #Transactional annotation to test your DataIntegrityViolationException scenario.

How to mock test REST Controller that uses OAUTH2

How do I test a REST Controller that uses Oauth2 (client)? I need to mock the oauth2 and I am stuck. Any help would be appreciated.
Hope that this answer may help.
Actually, when using OAuth2 with a ResourceServerConfiguration, you will have a stateless security, which will throw away any effort in mocking users beforehand.
What you should do to mock users is:
Create a TestOnly loaded ResourceServerConfiguration which overrides your standard one in this way:
public class TestConfiguration extends ResourceServerConfiguration {
#Override
public void configure(ResourceServerSecurityConfigurer res) {
res.stateless(false);
super.configure(resources);
}
}
add the #WithMockUser to the tests:
#Test
#WithMockUser(username ="username_admin", authorities = {"I_AM_LEGEND"})
public void myTestWithUser() throws Exception {
this.mvc.perform(get("/getUsername")).andExpect(content().text().contains("username_admin"));
}
#Test
public void myTestWithNoUser() throws Exception {
this.mvc.perform(get("/getUsername")).andExpect(status().isUnauthorized());
}
NOTE: I wrote the code from memory

How do I verify that an event has been published using Spring, JUnit, and Mockito?

I'm using Spring 4.3.8.RELEASE with JUnit 4.12 and Mockito 1.10.18. I have a service that publishes events ...
#Service("organizationService")
#Transactional
public class OrganizationServiceImpl implements OrganizationService, ApplicationEventPublisherAware
publisher.publishEvent(new ZincOrganizationEvent(id));
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher)
{
this.publisher = publisher;
}
...
#Override
public void save(Organization organization)
{
...
publisher.publishEvent(new ThirdPartyEvent(organization.getId()));
My question is, how do I verify in a JUnit test that an event has actually been published?
#Test
public void testUpdate()
{
m_orgSvc.save(org);
// Want to verify event publishing here
I prefer the opposite approach, which is more integration test-ey:
🧙‍♂️Mock the ApplicationListener using Mockito
đź”—Register the mock application listener onto the ConfigurableApplicationContext
🔨Do work
✔Verify the mock has received the event
With this approach you are testing that an event has been published by means that someone is receiving it.
Here is the code of a rudimental authentication test. Among other conditions, I test that a login event has occurred
#Test
public void testX509Authentication() throws Exception
{
ApplicationListener<UserLoginEvent> loginListener = mock(ApplicationListener.class);
configurableApplicationContext.addApplicationListener(loginListener);
getMockMvc().perform(get("/").with(x509(getDemoCrt())))//
.andExpect(status().is3xxRedirection())//
.andExpect(redirectedUrlPattern("/secure/**"));
getErrorCollector().checkSucceeds(() -> {
verify(loginListener, atLeastOnce()).onApplicationEvent(any(UserLoginEvent.class));
return null;
});
}
My advice is to unleash the power of Mockito to deeply verify the event arguments. In my case, I will extend my code to:
Check that the username in the login event matches the authenticated principal
Perform additional tests where the user blatantly fails to login and I will expect one of the various login failure events
If you want to test if you didn't forget to call publishEvent method inside your OrganizationServiceImpl you can use something like this:
class OrganizationServiceImplTest {
private OrganizationServiceImpl organizationService;
private ApplicationEventPublisher eventPublisher;
#Before
public void setUp() {
eventPublisher = mock(ApplicationEventPublisher.class);
organizationService = new OrganizationServiceImpl();
organizationService.setApplicationEventPublisher(eventPublisher)
}
#Test
public void testSave() {
/* ... */
organizationService.save(organization);
verify(eventPublisher).publishEvent(any(ThirdPartyEvent.class));
}
}
Test case above will verify whether or not there was an invocation of publishEvent method.
For more check the documentation.
Regarding:
My question is, how do I verify in a JUnit test that an event has actually been published?
You have to test ApplicationEventPublisher implementation and probably without mocks if you want to verify actual sending.

Spring Boot Testing: exception in REST controller

I have a Spring Boot application and want to cover my REST controllers by integration test.
Here is my controller:
#RestController
#RequestMapping("/tools/port-scan")
public class PortScanController {
private final PortScanService service;
public PortScanController(final PortScanService portScanService) {
service = portScanService;
}
#GetMapping("")
public final PortScanInfo getInfo(
#RequestParam("address") final String address,
#RequestParam(name = "port") final int port)
throws InetAddressException, IOException {
return service.scanPort(address, port);
}
}
In one of test cases I want to test that endpoint throws an exception in some circumstances. Here is my test class:
#RunWith(SpringRunner.class)
#WebMvcTest(PortScanController.class)
public class PortScanControllerIT {
#Autowired
private MockMvc mvc;
private static final String PORT_SCAN_URL = "/tools/port-scan";
#Test
public void testLocalAddress() throws Exception {
mvc.perform(get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53")).andExpect(status().isInternalServerError());
}
}
What is the best way to do that? Current implementation doesn't handle InetAddressException which is thrown from PortScanController.getInfo() and when I start test, I receive and error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported
It is not possible to specify expected exception in #Test annotation since original InetAddressException is wrapped with NestedServletException.
Spring Boot Test package comes with AssertJ that has very convenient way of verifying thrown exceptions.
To verify cause:
#Test
public void shouldThrowException() {
assertThatThrownBy(() -> methodThrowingException()).hasCause(InetAddressException .class);
}
There are also few more methods that you may be interested in. I suggest having a look in docs.
In order to test the wrapped exception (i.e., InetAddressException), you can create a JUnit Rule using ExpectedException class and then set the expectMessage() (received from NestedServletException's getMessage(), which contains the actual cause), you can refer the below code for the same:
#Rule
public ExpectedException inetAddressExceptionRule = ExpectedException.none();
#Test
public void testLocalAddress() {
//Set the message exactly as returned by NestedServletException
inetAddressExceptionRule.expectMessage("Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported");
//or you can check below for actual cause
inetAddressExceptionRule.expectCause(org.hamcrest.Matchers.any(InetAddressException.class))
//code for throwing InetAddressException here (wrapped by Spring's NestedServletException)
}
You can refer the ExpectedException API here:
http://junit.org/junit4/javadoc/4.12/org/junit/rules/ExpectedException.html
You could define an exception handler
#ExceptionHandler(InetAddressException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handledInvalidAddressException(InetAddressException e)
{
log e
return getValidationErrorResponse(e);
}
and then in your test you could do
mvc.perform(get(PORT_SCAN_URL)
.param("address", "192.168.1.100")
.param("port", "53"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.response").exists())
.andExpect(jsonPath("$.response.code", is(400)))
.andExpect(jsonPath("$.response.errors[0].message", is("Site local IP is not supported")));
I had the same issue and i fix it with org.assertj.core.api.Assertions.assertThatExceptionOfType :
#Test
public void shouldThrowInetAddressException() {
assertThatExceptionOfType(InetAddressException.class)
.isThrownBy(() -> get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53"));
}
I hope it's help you !

register multiple resource instances of same type

I have a resource endpoint that injects a #PathParam into constructor, i.e., different instance per #PathParam value. It all works fine in Jetty. But now I'm trying to write unit tests using Jersey Test Framework, and it seems that the test framework only supports one registered endpoint per type.
So if I do something like this:
#Path("/users")
public class MyResource {
public MyResource(#PathParam("userId") int userId) {
}
#Path("{userId}")
public String get() {
}
}
public class MyTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected Application configure() {
return new ResourceConfig()
.register(new MyResource(1))
.register(new MyResource(2));
}
#Test
public void test2() {
target("/users/1").request().get();
}
#Test
public void test2() {
target("/users/2").request().get();
}
}
I see that both test1 and test2 are invoking the instance of MyResource(1). Is this expected? Is there a solution to invoke the correct instance?
You should register the resource as a class. Jersey will create it for you. And handle all the injections.
"The example I posted is dumbed down. In reality, my resource constructor has another injected object that I need to mock. So how would I specify a mocked object parameter for the constructor?"
You can do something like
#Mock
private Service service;
#Override
public ResourceConfig configure() {
MockitoAnnotations.initMocks(this);
return new ResourceConfig()
.register(MyResource.class)
.register(new AbstractBinder() {
#Override
protected configure() {
bind(service).to(Service.class);
}
});
}
#Test
public void test() {
when(service.getSomething()).thenReturn("Something");
// test
}
Assuming you are already using the built in HK2 DI, and have an #Inject annotation on the constructor of your resource class, this should work. In the AbstractBinder we are making the mock object injectable. So now Jersey can inject it into your resource.
See Also:
Jersey - How to mock service

Resources