Skip to content

Commit

Permalink
add tests and GitHub action
Browse files Browse the repository at this point in the history
  • Loading branch information
wisskirchenj committed Jan 2, 2024
1 parent 1784c24 commit 4da8f04
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 13 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle

name: Java CI with Gradle

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: build

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.hyperskill.community.flashcards.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.hyperskill.community.flashcards.registration.UserDto;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
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.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.http.MediaType;
import org.springframework.test.context.aot.DisabledInAotMode;
import org.springframework.test.web.servlet.MockMvc;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@Testcontainers
@AutoConfigureMockMvc
@Disabled // must fix test container connections...
@DisabledInAotMode // bug in Spring 3.2 @MockBean does not work in AOT mode (https://github.com/spring-projects/spring-boot/issues/36997)
class RegisterServerSecurityIT {

@Autowired
MockMvc mockMvc;

@Container
@ServiceConnection
static MongoDBContainer mongoDbContainer = new MongoDBContainer(DockerImageName.parse("mongo:latest"));

@Autowired
ObjectMapper objectMapper;

@BeforeAll
static void setup() {
mongoDbContainer.start();
}

@AfterAll
static void tearDown() {
mongoDbContainer.stop();
}

@Test
void registerUnauthenticatedValidJson_AddsUser() throws Exception {
mockMvc.perform(post("/api/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(
new UserDto("hans.wurst@xyz.de", "12345678"))))
.andExpect(status().isOk());
}

@Test
void registerUnauthenticatedExistingUser_Gives400() throws Exception {
mockMvc.perform(post("/api/register")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(
new UserDto("test@xyz.de", "12345678"))))
.andExpect(status().isOk());
mockMvc.perform(post("/api/register") // and again
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(
new UserDto("test@xyz.de", "12345678"))))
.andExpect(status().isBadRequest());
}

@Test
void registerUnauthenticatedInvalidDto_Gives400() throws Exception {
mockMvc.perform(post("/api/register") // and again
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(
new UserDto("wrong", "1234"))))
.andExpect(status().isBadRequest());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.hyperskill.community.flashcards.service;


import org.hyperskill.community.flashcards.registration.RegisterService;
import org.hyperskill.community.flashcards.registration.User;
import org.hyperskill.community.flashcards.registration.UserAlreadyExistsException;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoSettings;
import org.springframework.data.mongodb.core.MongoTemplate;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@MockitoSettings
class RegisterServiceTest {

@Mock
MongoTemplate mongoTemplate;

@InjectMocks
RegisterService service;

@Test
void whenUserExist_RegisterUserThrows() {
when(mongoTemplate.findById("test", User.class)).thenReturn(new User());
var newUser = new User().setUsername("test");
assertThrows(UserAlreadyExistsException.class, () -> service.registerUser(newUser));
verify(mongoTemplate, never()).save(any(User.class));
}

@Test
void whenRegisterUser_UserSaved() {
when(mongoTemplate.findById("test", User.class)).thenReturn(null);
var newUser = new User().setUsername("test");
service.registerUser(newUser);
verify(mongoTemplate).save(newUser);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.hyperskill.community.flashcards.web;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;
import java.util.Set;
import java.util.function.Supplier;

public class JpaUnitTestValidator<T> {

private final Validator validator;
private final Supplier<? extends T> getValidFunction;
private Constructor<? extends T> recordConstructor;

public JpaUnitTestValidator(Supplier<? extends T> getValidObjectFunction, Class<? extends T> dtoClass) {
try (var validatorFactory = Validation.buildDefaultValidatorFactory()) {
this.validator = validatorFactory.getValidator();
}
this.getValidFunction = getValidObjectFunction;
initRecordConstructorIfNeeded(dtoClass);
}

private void initRecordConstructorIfNeeded(Class<? extends T> dtoClass) {
if (dtoClass.isRecord()) {
Class<?>[] componentTypes = Arrays.stream(dtoClass.getRecordComponents())
.map(RecordComponent::getType)
.toArray(Class<?>[]::new);
try {
recordConstructor = dtoClass.getDeclaredConstructor(componentTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}

public Set<ConstraintViolation<T>> getConstraintViolationsOnUpdate(String fieldToUpdate, Object newValue)
throws Exception {
T object = modifyDto(getValidFunction.get(), fieldToUpdate, newValue);
return validator.validate(object);
}

private T modifyDto(T dto, String fieldName, Object value) throws Exception {
if (recordConstructor != null) {
return createModifiedRecord(dto, fieldName, value);
} else {
return updateFieldByReflection(dto, fieldName, value);
}
}

private T updateFieldByReflection(T dto, String fieldName, Object value) throws Exception {
Field field = dto.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(dto, value);
return dto;
}

private T createModifiedRecord(T dto, String fieldName, Object value) throws Exception {
var recordComponents = dto.getClass().getRecordComponents();
Object[] modifiedValues = new Object[recordComponents.length];
for (int i = 0; i < recordComponents.length; i++) {
modifiedValues[i] = recordComponents[i].getName().equals(fieldName)
? value : recordComponents[i].getAccessor().invoke(dto);
}
return recordConstructor.newInstance(modifiedValues);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.hyperskill.community.flashcards.web;

import org.hyperskill.community.flashcards.registration.UserDto;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class RegisterControllerValidationUnitTest {

JpaUnitTestValidator<UserDto> validator = new JpaUnitTestValidator<>(this::getValidUser, UserDto.class);

UserDto getValidUser() {
return new UserDto("hans.wurst@google.com", "long_password");
}

@ParameterizedTest
@MethodSource
void whenValidUserDto_NoError(String fieldName, Object validValue) throws Exception {
assertTrue(validator.getConstraintViolationsOnUpdate(fieldName, validValue).isEmpty());
}

static Stream<Arguments> whenValidUserDto_NoError() {
return Stream.of(
Arguments.of("email", "a@b.c"),
Arguments.of("email", "hans@lmu.de"),
Arguments.of("email", "hans_josef.schmitz.ext4@main_google.com"),
Arguments.of("password", "12345678"),
Arguments.of("password", "12345678_very_long_one!\"§$%&/()=?.,-,),")
);
}

@ParameterizedTest
@MethodSource
void whenInvalidUserDto_ValidationError(String fieldName, Object validValue) throws Exception {
assertFalse(validator.getConstraintViolationsOnUpdate(fieldName, validValue).isEmpty());
}

static Stream<Arguments> whenInvalidUserDto_ValidationError() {
return Stream.of(
Arguments.of("email", " "),
Arguments.of("email", ""),
Arguments.of("email", null),
Arguments.of("email", "a@b"),
Arguments.of("email", "hans@lmu."),
Arguments.of("email", "hans.josef.schmitz.ext4@main_google.com"),
Arguments.of("email", "hans-josef@main_google.com"),
Arguments.of("email", ".ext4@main_google.com"),
Arguments.of("email", "hans.josef@.com"),
Arguments.of("email", "hans..josef@google.com"),
Arguments.of("email", "hansjosefgoogle.com"),
Arguments.of("email", "hansjosef@dd"),
Arguments.of("password", "1234567"),
Arguments.of("password", " "),
Arguments.of("password", " "),
Arguments.of("password", ""),
Arguments.of("password", null)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.hyperskill.community.flashcards.web;

import org.hyperskill.community.flashcards.registration.UserDto;
import org.hyperskill.community.flashcards.registration.UserMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class UserMapperTest {
UserDto dto;
UserMapper mapper;
PasswordEncoder passwordEncoder;

@BeforeEach
void setup() {
passwordEncoder = new BCryptPasswordEncoder();
mapper = new UserMapper(passwordEncoder);
dto = new UserDto("a@b.c", "secret");
}

@Test
void toDocument() {
var mapped = mapper.toDocument(dto);
assertEquals(dto.email(), mapped.getUsername());
assertTrue(passwordEncoder.matches(dto.password(), mapped.getPassword()));
}
}

0 comments on commit 4da8f04

Please sign in to comment.