I'm experimenting with Quarkus and Quarkus testing with RestEasy and I'm running into an issue while setting up dummy data before each of the tests.
I'm trying to insert a User using the Active Record pattern before calling the endpoint. Right after calling user.persistAndFlush() (or simply just calling .persist()) when the asking the User entity for the count of the users, it correctly gives back that there is one record in the database. However, when calling the endpoint through RestEasy, it throws back to me that the user is not found with that given identifier and indeed, when logging out the number of users in that certain method call in the service, there are 0 users according to Quarkus.
It looks to me as if the method with the #Test annotation and the service itself were running in a different context? It's a bit strange and I'm sure missing something obvious.
Here's what my test class looks like:
#QuarkusTest
#QuarkusTestResource(PostgreSQLDatabaseResource.class)
public class AuthenticationResourceTest {
#TestHTTPEndpoint(AuthenticationResource.class)
#TestHTTPResource
URL url;
#BeforeEach
#Transactional
public void setUp() {
User.deleteAll();
}
#Test
public void testWithNonExistingEmail() {
AuthenticationChallenge invalidAuthRequest = AuthenticationChallenge.builder()
.email("admin#admin.com")
.password("something")
.build();
given()
.when()
.contentType(ContentType.JSON)
.body(invalidAuthRequest)
.post(url)
.then()
.statusCode(401);
}
#Test
#Transactional
public void testWithValidAuthenticationChallenge() {
User user = User.builder()
.name("admin")
.role(Roles.ADMIN)
.status(Status.ACTIVE)
.password(BCrypt.withDefaults().hashToString(10, "admin".toCharArray()))
.email("admin#admin.com")
.build();
user.persistAndFlush();
given()
.when()
.contentType(ContentType.JSON)
.body(AuthenticationChallenge.builder().email("admin#admin.com").password("admin").build())
.post(url)
.then()
.statusCode(200)
.log()
.all();
}
}
Your user won't be saved to the database until the end of the test method as it's transactional and the transaction is committed at the end.
With your REST call, you query your database outside of the transaction so you won't have the data.
If you want to initialize some data visible to your REST call, you need to create the data in the #BeforeEach method.
Related
I have a method:
#GetMapping("/foo")
public void> foo(JwtAuthenticationToken token) throws ExecutionException, InterruptedException {
Object object = ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
.toFuture()
.get();
System.out.println(object);
JwtAuthenticationToken object which is method argument is succesfully autowired and not null but
result of
Object object = ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
.toFuture()
.get();
is null.
Could you please explain why ? Is there way to fix it ?
related topic: How to get jwt token value in spring webflux? (to exchange it with Minio STS token)
It is not necessary to use ReactiveSecurityContextHolder to get the Jwt instance. For example, if JwtAuthenticationToken is non-null, you can get the Jwt instance by doing:
public Mono<Void> foo(JwtAuthenticationToken token) throws ExecutionException, InterruptedException {
Jwt jwt = token.getToken();
// ...
}
Or, you can translate it with #AuthenticationPrincipal like so:
public Mono<Void> foo(#AuthenticationPrincipal Jwt jwt) throws ExecutionException, InterruptedException {
// ...
}
Further, it is not possible to use block() with ReactiveSecurityContextHolder in the way the OP describes. block() subscribes immediately, and there is nothing immediately available in the Reactor Context at this point.
The reactive stack works somewhat in the inverse to perhaps what you are thinking. While there is a filter in Spring Security called ReactorContextWebFilter that populates the Reactor Context with the SecurityContext, its work is deferred until the HTTP response is subscribed to, for example by the browser. block() at this point states (correctly) that the Reactor Context is empty. Instead, if you participate in the existing subscription (instead of calling block()), then you are also deferring your work in the same way and will be able to use ReactiveSecurityContextHolder.
EDIT: Having read the additional context about the OP's situation from How to get jwt token value in spring webflux? (to exchange it with Minio STS token), it's clear now that the OP is aware of these ways to get the instance of a Jwt, and does not want to use them. I'll leave the answer here for completeness anyway.
Not really sure how token is related to the result of the bucketExists and why do you convert it to the CompletableFuture but here is an example how you can get token from the context.
#GetMapping(value = "/someEndpoint")
public Mono<Boolean> foo() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal())
.cast(Jwt.class)
.map(jwt -> useToken(jwt));
}
Here is a test using org.springframework.security:spring-security-test
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
class RouteConfigTest {
#Autowired
private WebTestClient client;
#Test
void test() {
this.client.mutateWith(mockJwt())
.get()
.uri("/someEndpoint")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk();
}
}
When using MockRestServiceServer with andExpect to test
mockServer.expect(requestTo("/hotels/42")).andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("{ \"id\" : \"42\", \"name\" : \"Holiday Inn\"}", MediaType.APPLICATION_JSON));
Then test failed if found unexpected behavior,
For example no further requests expected: HTTP if sent to unexpected URL
My Config:
#SpringBootApplication(scanBasePackages = { "..." })
public class MyConfig extends SpringBootServletInitializer {
My Test class
#ContextConfiguration( classes = {MyConfig.class})
#ActiveProfiles("local")
#WebAppConfiguration
public class My Test extends AbstractTestNGSpringContextTests {
#Autowired
#InjectMocks
private ServiceUnderMock serviceUnderMock;
private AutoCloseable closeable;
#BeforeClass
public void initMocks() {
closeable = MockitoAnnotations.openMocks(this);
mockServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).build();
}
#AfterClass
public void releaseMocks() throws Exception {
closeable.close();
}
#Autowired
private RestTemplate restTemplate;
private MockRestServiceServer mockServer;
#Test
public void test() {
try {
mockServer.expect(ExpectedCount.min(1),
requestTo(new URI("https://www.google.com")))
.andExpect(method(HttpMethod.GET));
} catch (URISyntaxException e) {
Assert.fail("failed to create mock");
}
serviceUnderMock.doSomething();
}
So why we need to add the mockServer.verify()?
At the end of the test use verify() to ensure all expected requests were actually performed.
The idea of MockRestServiceServer is that it allows you mock the external server such that the RestTemplate does not really need to send the requests to the actual server during the testing. Instead it just sends the requests to this MockRestServiceServer (think that it is a kind of in-memory server) and it will return the configured mocked responses for the corresponding requests.
You have to configure all the expected requests that the MockRestServiceServer will received and its corresponding responds before the test.
So basically there are two things needed to be verified which are :
For every request sent by RestTemplate , there should be a mocked response configured for that request in the MockRestServiceServer
For all the requests that are to be expected to be received on the MockRestServiceServer , the RestTemplate should really send out all of these expected requests.
(1) will be verified automatically whenever the RestTemplate send out a request. The exception no further requests expected: HTTP that you mentioned is because it fails (1) (i.e. forget to stub this request in the MockRestServiceServer)
(2) will not be verified automatically . You have to call MockRestServiceServer.verify() manually in order to verify it.
An example :
mockServer.expect(requestTo(new URI("https://www.yahoo.com")))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess());
mockServer.expect(requestTo(new URI("https://www.google.com")))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess());
mockServer.expect(requestTo(new URI("https://www.stackoverflow.com")))
.andExpect(method(HttpMethod.GET)).andRespond(withSuccess());
restTemplate.getForEntity("https://www.yahoo.com", String.class);
restTemplate.getForEntity("https://www.google.com", String.class);
Without mockServer.verify() , the test still passes although RestTemplate does not send the request to https://www.stackoverflow.com which the MockServer is expected to be received.
But with mockServer.verify() , it can check that and hence fails the test.
My application has several microservices including: Auth, Game and Group.
When I press a button on front end (Play) I'm calling Group rest api method, let's say foo().
In order to implement foo() inside Group I need to GET call Game rest api, but it's secured.
When I login in on React app I get the JWT token from /login (Auth) and store it in localstorage. Then I successfully use it to call foo() from Group but in foo() implementation I also need to use the jwt token in order to be able to get information from Game.
#Configuration
//+component scans...
public class GroupConfiguration {
#Bean
#LoadBalanced
public WebClient.Builder buildWebClientBuilder() {
return WebClient.builder();
}
}
#RestController
#RequestMapping("/groups")
public class Controller {
private final Logger logger = LogManager.getLogger();
#Autowired
private WebClient.Builder webClientBuilder;
private int getMinimumNumberOfPlayers(int gameId) {
try {
return webClientBuilder.build()
.get()
.uri("http://game-service/games/minimumNumberOfPlayers/2")
.retrieve()
.bodyToMono(Integer.class)
.block();
} catch (NullPointerException|WebClientResponseException e) {
e.printStackTrace();
return 0;
}
}
...
// foo() frontend calls foo(). foo it's using getMinimumNumberOfPlayers
getMinimumNumberOfPlayers() is used in foo() method from Group. It's supposed to retrieve the minimum number of players of a game by id, but the game microservice is jwt secured and i get an unauthorized error.
So my question is how can i make Group microservice be able to call Game microservice.
Thanks.
Edit: RestTemplate Interceptor
This is how I solved it.
Even if I have some security issues, I will answer this question :
What you could do is :
add an interceptor for incoming calls on your Group service that will store the JWT in the request context.
add an interceptor for outgoing calls to your Game service that will get the JWT in the request context and add it to the request headers.
What is important here is the request context.
I need to test controllers that are secured with:
#Security.Authenticated(Secured.class).
Even after i log in i get unauthorized,i read that i need to copy the cookie from the log in response and send it with every request to secured method. tried that without any luck.
any idea how to solve that?
Assuming that you are using Helper.route method for testing, logged in behavior can be accomplished by using FakeRequest.withSession method.
For instance, if you are using email as authentication token in your Secured class;
#Override
public String getUsername(Http.Context ctx) {
return ctx.session().get("email");
}
Your test method would be like this;
#Test
public void testPage() {
FakeRequest testRequest = new FakeRequest(Helpers.GET, "/page")
.withSession("email", "mail#example.com");
Result result = Helpers.route(testRequest);
assertThat(Helpers.status(result)).isEqualTo(Helpers.OK);
}
I'm trying to find some manual how to test POST methods using jersey framework, only got examples for GET method.
Here's example:
#POST
#Path("add")
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public Response addUser(JAXBElement<User> user) {
int code = userService.addUser(user.getValue());
if (code == 500) {
return Response.status(500).build();
}
return Response.status(code).entity(user).build();
}
Could you please post some POST method test example?
Thank you in advance.
After research I did it!
Here's my solution, it works just fine.
And it's rather integration test, but we can write unit tests in similar manner.
public class RestTest extends JerseyTest{
#Override
protected Application configure() {
return new Your_Resource_Config(); //Your resource config with registered classes
}
//#Before and/or #After for db preparing etc. - if you want integration tests
#Test
public void addUserTest() {
User user = new User();
user.setEmail("user2#mail.com");
user.setName("Jane Doe");
user.getUserRoles().getRoles().add("supertester");
Entity<User> userEntity = Entity.entity(user, MediaType.APPLICATION_XML_TYPE);
target("users/add").request().post(userEntity); //Here we send POST request
Response response = target("users/find").queryParam("email", "user2#mail.com").request().get(); //Here we send GET request for retrieving results
Assert.assertEquals("user2#mail.com", response.readEntity(User.class).getEmail());
}