From e8a55ae13185c844fa6fbd2749a66a9908bb618e Mon Sep 17 00:00:00 2001 From: Claudio Maggioni Date: Thu, 5 Mar 2020 12:42:53 +0100 Subject: [PATCH] Started work on tests --- build.gradle | 15 +- .../controller/AuthenticationController.java | 11 +- .../smarthut/error/UnauthorizedException.java | 18 ++ .../smarthut/AuthenticationTests.java | 171 ++++++++++++++++++ .../sanmarinoes/smarthut/SmartHutTest.java | 25 +++ .../smarthut/SmarthutApplicationTests.java | 25 ++- src/test/resources/application.properties | 27 +++ 7 files changed, 277 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java create mode 100644 src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java create mode 100644 src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java create mode 100644 src/test/resources/application.properties diff --git a/build.gradle b/build.gradle index 086613b..25e4080 100644 --- a/build.gradle +++ b/build.gradle @@ -23,16 +23,23 @@ dependencies { implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'org.springframework.security:spring-security-web' implementation 'org.postgresql:postgresql' - compile "io.springfox:springfox-swagger2:2.9.2" - compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.9.2' - + implementation 'com.google.code.gson:gson' + compile 'io.springfox:springfox-swagger2:2.9.2' + compile 'io.springfox:springfox-swagger-ui:2.9.2' + implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-json' } - implementation 'com.google.code.gson:gson' + testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } + + testImplementation 'org.springframework.security:spring-security-test' + testImplementation 'com.h2database:h2:1.3.148' + + // Fixes https://stackoverflow.com/a/60455550 + testImplementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.11' } test { diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java index f0e8d7d..1a1e266 100644 --- a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/controller/AuthenticationController.java @@ -4,6 +4,7 @@ import ch.usi.inf.sa4.sanmarinoes.smarthut.config.JWTTokenUtil; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse; import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserUpdateRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UserNotFoundException; import ch.usi.inf.sa4.sanmarinoes.smarthut.models.*; import io.swagger.annotations.Authorization; @@ -44,7 +45,7 @@ public class AuthenticationController { @PostMapping("/login") public JWTResponse login(@Valid @RequestBody JWTRequest authenticationRequest) - throws Exception { + throws UnauthorizedException, UserNotFoundException { final UserDetails userDetails; if (authenticationRequest.getUsernameOrEmail().contains("@")) { // usernameOrEmail contains an email, so fetch the corresponding username @@ -86,16 +87,14 @@ public class AuthenticationController { return userRepository.save(oldUser); } - private void authenticate(String username, String password) throws Exception { + private void authenticate(String username, String password) throws UnauthorizedException { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(username, password)); } catch (DisabledException e) { - e.printStackTrace(); - throw new Exception("USER_DISABLED", e); + throw new UnauthorizedException(true); } catch (BadCredentialsException e) { - e.printStackTrace(); - throw new Exception("INVALID_CREDENTIALS", e); + throw new UnauthorizedException(false); } } } diff --git a/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java new file mode 100644 index 0000000..9176df6 --- /dev/null +++ b/src/main/java/ch/usi/inf/sa4/sanmarinoes/smarthut/error/UnauthorizedException.java @@ -0,0 +1,18 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut.error; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(code = HttpStatus.UNAUTHORIZED) +public class UnauthorizedException extends Exception { + private final boolean isUserDisabled; + + public UnauthorizedException(boolean isDisabled) { + super("Access denied: " + (isDisabled ? "user is disabled" : "wrong credentials")); + this.isUserDisabled = isDisabled; + } + + public boolean isUserDisabled() { + return isUserDisabled; + } +} diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java new file mode 100644 index 0000000..96b67ee --- /dev/null +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/AuthenticationTests.java @@ -0,0 +1,171 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut; + +import static org.assertj.core.api.Assertions.assertThat; + +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.JWTResponse; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.OkResponse; +import ch.usi.inf.sa4.sanmarinoes.smarthut.dto.UserRegistrationRequest; +import ch.usi.inf.sa4.sanmarinoes.smarthut.error.UnauthorizedException; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import java.util.Map; +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.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@AutoConfigureMockMvc +public class AuthenticationTests extends SmartHutTest { + + @Autowired private TestRestTemplate restTemplate; + + @Override + protected void setUp() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Disabled User"); + request.setEmail("disabled@example.com"); + request.setUsername("disabled"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(this.url("/register"), request, OkResponse.class); + assertThat(res.getStatusCode().equals(HttpStatus.OK)); + + final UserRegistrationRequest request2 = new UserRegistrationRequest(); + request2.setName("Enabled User"); + request2.setEmail("enabled@example.com"); + request2.setUsername("enabled"); + request2.setPassword("password"); + + final ResponseEntity res2 = + this.restTemplate.postForEntity(this.url("/register"), request, OkResponse.class); + assertThat(res2.getStatusCode().equals(HttpStatus.OK)); + + // TODO: email confirmation for res2 + } + + @Test + public void registrationShouldReturnBadRequestWithIncorrectFields() { + final Map badJSON = Map.of("luciano", "goretti", "danilo", "malusa"); + + assertThat( + this.restTemplate + .postForEntity(url("/register"), badJSON, JWTResponse.class) + .getStatusCode() + .equals(HttpStatus.BAD_REQUEST)); + } + + @Test + public void registrationShouldReturnBadRequestWithShortPassword() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Mario Goretti"); + request.setEmail("test@example.com"); + request.setUsername("mgo"); + request.setPassword("passw"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("password")); + } + + @Test + public void registrationShouldReturnBadRequestWithWrongEmail() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Mario Goretti"); + request.setEmail("test@example"); + request.setUsername("mgo"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("email")); + } + + @Test + public void registrationShouldReturnBadRequestWithNoName() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setEmail("test@example.com"); + request.setUsername("mgo"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("name")); + } + + @Test + public void registrationShouldReturnBadRequestWithNoUsername() { + final UserRegistrationRequest request = new UserRegistrationRequest(); + request.setName("Mario Goretti"); + request.setEmail("test@example.com"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity(url("/register"), request, JsonObject.class); + assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST)); + assertThat(res.getBody() != null); + + final JsonArray errors = res.getBody().getAsJsonArray("errors"); + assertThat(errors.size() == 1); + assertThat(errors.get(0).getAsJsonObject().get("field").getAsString().equals("username")); + } + + @Test + public void loginShouldReturnBadRequestWithIncorrectFields() { + final Map badJSON = Map.of("badkey", 3, "password", "ciaomamma"); + + assertThat( + this.restTemplate + .postForEntity(url("/auth/login"), badJSON, JWTResponse.class) + .getStatusCode() + .equals(HttpStatus.BAD_REQUEST)); + } + + @Test + public void loginShouldReturnUnauthorizedWithNonExistantUser() { + final JWTRequest request = new JWTRequest(); + request.setUsernameOrEmail("roberto"); + request.setPassword("ciaomamma"); + + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/auth/login"), request, UnauthorizedException.class); + assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)); + assertThat(res.getBody() != null); + assertThat(!res.getBody().isUserDisabled()); + } + + @Test + public void loginShouldReturnUnauthorizedWithDisabledUser() { + final JWTRequest request = new JWTRequest(); + request.setUsernameOrEmail("disabled"); + request.setPassword("password"); + + final ResponseEntity res = + this.restTemplate.postForEntity( + url("/auth/login"), request, UnauthorizedException.class); + assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED)); + assertThat(res.getBody() != null); + assertThat(res.getBody().isUserDisabled()); + } +} diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java new file mode 100644 index 0000000..5c6e097 --- /dev/null +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmartHutTest.java @@ -0,0 +1,25 @@ +package ch.usi.inf.sa4.sanmarinoes.smarthut; + +import org.junit.jupiter.api.BeforeEach; + +public abstract class SmartHutTest { + private boolean setupDone = false; + + protected final String getBaseURL() { + return "http://localhost:2000/"; + } + + protected final String url(final String url) { + return getBaseURL() + url; + } + + protected void setUp() {} + + @BeforeEach + void setUpHack() { + if (!setupDone) { + setUp(); + setupDone = true; + } + } +} diff --git a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java index 5f1f8fd..dbd7e21 100644 --- a/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java +++ b/src/test/java/ch/usi/inf/sa4/sanmarinoes/smarthut/SmarthutApplicationTests.java @@ -1,11 +1,26 @@ package ch.usi.inf.sa4.sanmarinoes.smarthut; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; +import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest -class SmarthutApplicationTests { +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.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@AutoConfigureMockMvc +public class SmarthutApplicationTests extends SmartHutTest { + + @Autowired private TestRestTemplate restTemplate; @Test - void contextLoads() {} + public void anonymousGreetingShouldNotBeAuthorized() throws Exception { + assertThat( + this.restTemplate + .getForEntity(getBaseURL(), Void.class) + .getStatusCode() + .equals(HttpStatus.UNAUTHORIZED)); + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..5022558 --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,27 @@ +spring.http.converters.preferred-json-mapper=gson +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1 +spring.datasource.username=sa +spring.datasource.password=sa + +# Hibernate properties +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl +spring.jpa.properties.hibernate.format_sql=true + +jwt.secret=thiskeymustbeverylongorthethingcomplainssoiamjustgoingtowritehereabunchofgarbageciaomamma + +spring.mail.test-connection=true +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.username=smarthut.sm@gmail.com +spring.mail.password=dcadvbagqfkwbfts +spring.mail.properties.mail.smtp.starttls.required=true +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.connectiontimeout=5000 +spring.mail.properties.mail.smtp.timeout=5000 +spring.mail.properties.mail.smtp.writetimeout=5000 + +server.port = 2000 \ No newline at end of file