Merge branch '21-test-endpoints' into 'dev'

Resolve "Test endpoints"

Closes #21

See merge request sa4-2020/the-sanmarinoes/backend!26
This commit is contained in:
Claudio Maggioni 2020-03-09 23:15:50 +01:00
commit 78abd0c7b0
16 changed files with 350 additions and 52 deletions

View file

@ -23,17 +23,24 @@ 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'
compile "org.springframework.boot:spring-boot-configuration-processor"
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 {

View file

@ -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);
}
}
}

View file

@ -33,10 +33,6 @@ public class ButtonDimmerSaveRequest {
this.lights = newLights;
}
public void setId(long id) {
this.id = id;
}
public void setRoom(Room room) {
this.room = room;
}

View file

@ -28,10 +28,6 @@ public class DimmableLightSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoom(Room room) {
this.room = room;
}

View file

@ -1,8 +1,10 @@
package ch.usi.inf.sa4.sanmarinoes.smarthut.dto;
import javax.validation.constraints.NotNull;
public class JWTRequest {
private String usernameOrEmail;
private String password;
@NotNull private String usernameOrEmail;
@NotNull private String password;
public String getUsernameOrEmail() {
return this.usernameOrEmail;
@ -22,9 +24,13 @@ public class JWTRequest {
@Override
public String toString() {
return "JWTRequest{" +
"usernameOrEmail='" + usernameOrEmail + '\'' +
", password='" + password + '\'' +
'}';
return "JWTRequest{"
+ "usernameOrEmail='"
+ usernameOrEmail
+ '\''
+ ", password='"
+ password
+ '\''
+ '}';
}
}

View file

@ -21,10 +21,6 @@ public class KnobDimmerSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}

View file

@ -17,10 +17,6 @@ public class MotionSensorSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}

View file

@ -18,10 +18,6 @@ public class RegularLightSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}

View file

@ -43,10 +43,6 @@ public class SensorSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}

View file

@ -18,10 +18,6 @@ public class SmartPlugSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}

View file

@ -18,10 +18,6 @@ public class SwitchSaveRequest {
/** The name of the device as assigned by the user (e.g. 'Master bedroom light') */
@NotNull private String name;
public void setId(long id) {
this.id = id;
}
public void setRoomId(Long roomId) {
this.roomId = roomId;
}

View file

@ -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;
}
}

View file

@ -0,0 +1,233 @@
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.DuplicateRegistrationException;
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;
private UserRegistrationRequest getDisabledUser() {
final UserRegistrationRequest disabledUser = new UserRegistrationRequest();
disabledUser.setName("Disabled User");
disabledUser.setEmail("disabled@example.com");
disabledUser.setUsername("disabled");
disabledUser.setPassword("password");
return disabledUser;
}
private static final UserRegistrationRequest enabledUser = new UserRegistrationRequest();
static {
enabledUser.setName("Enabled User");
enabledUser.setEmail("enabled@example.com");
enabledUser.setUsername("enabled");
enabledUser.setPassword("password");
}
@Override
protected void setUp() {
final ResponseEntity<OkResponse> res =
this.restTemplate.postForEntity(
this.url("/register"), getDisabledUser(), OkResponse.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK));
final ResponseEntity<OkResponse> res2 =
this.restTemplate.postForEntity(
this.url("/register"), enabledUser, OkResponse.class);
assertThat(res2.getStatusCode().equals(HttpStatus.OK));
// TODO: email confirmation for enabledUser
}
@Test
public void registrationShouldReturnBadRequestWithIncorrectFields() {
final Map<String, Object> 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<JsonObject> 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<JsonObject> 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<JsonObject> 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<JsonObject> 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 registrationShouldReturnBadRequestWithDuplicateData() {
{
final ResponseEntity<DuplicateRegistrationException> res =
this.restTemplate.postForEntity(
url("/register"),
getDisabledUser(),
DuplicateRegistrationException.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST));
assertThat(res.getBody() != null);
}
{
final UserRegistrationRequest disabledUserDifferentMail = getDisabledUser();
enabledUser.setEmail("another@example.com");
final ResponseEntity<DuplicateRegistrationException> res =
this.restTemplate.postForEntity(
url("/register"),
disabledUserDifferentMail,
DuplicateRegistrationException.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST));
assertThat(res.getBody() != null);
}
{
final UserRegistrationRequest disabledUserDifferentUsername = getDisabledUser();
enabledUser.setUsername("another");
final ResponseEntity<DuplicateRegistrationException> res =
this.restTemplate.postForEntity(
url("/register"),
disabledUserDifferentUsername,
DuplicateRegistrationException.class);
assertThat(res.getStatusCode().equals(HttpStatus.BAD_REQUEST));
assertThat(res.getBody() != null);
}
}
@Test
public void registrationShouldReturnOkWithCorrectData() {
final UserRegistrationRequest request = new UserRegistrationRequest();
request.setName("Registration Test");
request.setUsername("smarthut");
request.setEmail("smarthut.sm@example.com");
request.setPassword("password");
final ResponseEntity<OkResponse> res =
this.restTemplate.postForEntity(url("/register"), request, OkResponse.class);
assertThat(res.getStatusCode().equals(HttpStatus.OK));
assertThat(res.getBody() != null);
}
@Test
public void loginShouldReturnBadRequestWithIncorrectFields() {
final Map<String, Object> 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<UnauthorizedException> 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<UnauthorizedException> res =
this.restTemplate.postForEntity(
url("/auth/login"), request, UnauthorizedException.class);
assertThat(res.getStatusCode().equals(HttpStatus.UNAUTHORIZED));
assertThat(res.getBody() != null);
assertThat(res.getBody().isUserDisabled());
}
}

View file

@ -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;
}
}
}

View file

@ -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));
}
}

View file

@ -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