jeudi 4 mars 2021

Always first integration test fail when execute all tests (only the first, no more)

If I execute only IT tests, all works. But, if I run all tests (unit and integration), the first IT test of class "CategoryControllerIT" fails. Only the first. If tests order change, the new first test fails. If I comment that test, the next first fails. But only one, the rest works fine. When fails, it returns a 403 instead of 200.

Im using mockmvc to make the requests and testcontainers to run mysql 8 database. When I run tests usin maven, all works. Only fails executing from IntelliJ.

There is a similar question, but I have decided to publish mine with more code trying to bring some light.

One example.

This is one IT test of "CategoryControllerIT". If it is executed at first, it fails:

@Test
void whenPost_thenStatus200_andShouldSaveIt() throws Exception {
    int expensesBefore = expenseRepository.findAll().size();
    Category c = categoryRepository.findAll().get(0);
    String json = "{\n" +
            "\t\"amount\": 999999999.99,\n" +
            "\t\"date\": \"2023-04-02\",\n" +
            "\t\"concept\": \"Concepto\",\n" +
            "\t\"category\": {\n" +
            "\t\t\"id\": " + c.getId() + "\n" +
            "\t}\n" +
            "}";

    mvc.perform(post("/expenses")
            .header(HEADER_AUTHORIZATION_KEY, token)
            .contentType(MediaType.APPLICATION_JSON)
            .content(json))
            .andExpect(status().isOk());

    int expensesAfter = expenseRepository.findAll().size();
    assertEquals(expensesBefore, expensesAfter - 1);
}

The before each method:

@BeforeEach
void prepareDatabaseAndObtainAccessToken() throws Exception {
    insertUser(passwordEncoder, monexUserRepository);
    token = obtainToken(mvc);
    prepareDatabase();
}

Some explanation to understand the behavior:

It must exist a user to do anything. At first, the user create categories. Then, the user can insert an expense. The expense should contain a category, and that category have to be owned by the logged user. If you try to insert an expense with a category owned by another user, the system won't let you do that.

The @BeforeEach method do this: create a user, log in with that user and obtains the JWT token, and prepare the database inserting some categories which owner is the inserted user. The object "MonexUserRepository" contains the UserDetails to works fine with Spring Security authentication.

All works fine. When I run the API and make requests with Postman, all works as expected. If I run unit tests, all well. If I run IT tests, all good too. But, if I run all tests, only fails the first IT test inside ExpenseControllerIT. The CategoryControllerIT never fails (note: tests inside CategoryControllerIT are very very similar to ExpenseControllerIT tests).

The error is 403 forbidden.

Here more related code, can be helpful:

@Entity
public class Category implements BaseObject {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@NotNull
private String name;

private String description;

@ManyToOne
@NotNull
private MonexUser owner;

@Entity
public class Expense implements BaseObject {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@Column(scale = 2)
private @NotNull @Digits(integer = 9, fraction = 2) BigDecimal amount;

@NotNull
@JsonFormat(pattern = "dd-MM-yyyy")
@Temporal(TemporalType.DATE)
private Date date;

@NotNull
private String concept;

@ManyToOne
@NotNull
private Category category;

@ManyToOne
@NotNull
private MonexUser owner;

public interface BaseObject {

long getId();

MonexUser getOwner();

void setOwner(MonexUser owner);
}

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
    super(authenticationManager);
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
FilterChain chain) throws IOException, ServletException {
    String header = request.getHeader(HEADER_AUTHORIZATION_KEY);
    if (header == null || !header.startsWith(TOKEN_BEARER_PREFIX)) {
        chain.doFilter(request, response);
        return;
    }
    UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(request, response);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
    String token = request.getHeader(HEADER_AUTHORIZATION_KEY);
    if (token != null) {
        // Se procesa el token y se recupera el usuario.
        String user = Jwts.parser()
                .setSigningKey(SUPER_SECRET_KEY)
                .parseClaimsJws(token.replace(TOKEN_BEARER_PREFIX, ""))
                .getBody()
                .getSubject();

        if (user != null) {
            return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
        }
        return null;
    }
    return null;
}

}


public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

private AuthenticationManager authenticationManager;

private ObjectMapper objectMapper;

public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
    this.authenticationManager = authenticationManager;
    this.objectMapper = new ObjectMapper();
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse 
response) throws AuthenticationException {
    try {
        MonexUser credentials = objectMapper.readValue(request.getInputStream(), 
MonexUser.class);

        UsernamePasswordAuthenticationToken token = new 
UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());

        return authenticationManager.authenticate(token);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse 
response, FilterChain chain, Authentication auth) {
    String token = Jwts.builder().setIssuer(ISSUER_INFO)
            .setSubject(auth.getName())
            .claim("authorities", auth.getAuthorities())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + TOKEN_EXPIRATION_TIME))
            .signWith(SignatureAlgorithm.HS512, SUPER_SECRET_KEY).compact();

    response.addHeader(HEADER_AUTHORIZATION_KEY, TOKEN_BEARER_PREFIX + token);
}

}


Here you can see the result executing all tests:

Executing unit and integration tests

And here you can see success result, but executing only IT tests (selected one fails in previous image):

Executing only IT tests

Really thanks. Im desperate.

Aucun commentaire:

Enregistrer un commentaire