From d337ef127a254784ecc3f6aac2f86edd28f99559 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:24:49 -0500 Subject: [PATCH 1/8] Fix DummyController * Add @RestController annotation so it is used * The tests expected the controller to work for /api/user/current but it was mapped for /api/sayHello. This updates the DummyController to handle every URL since there are no other controllers. --- src/main/java/com/example/DummyController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/DummyController.java b/src/main/java/com/example/DummyController.java index 767d222..daad187 100644 --- a/src/main/java/com/example/DummyController.java +++ b/src/main/java/com/example/DummyController.java @@ -1,13 +1,15 @@ package com.example; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; import java.security.Principal; +@RestController public class DummyController { - @GetMapping("/api/sayHello") + @GetMapping("/**") public Mono sayHello(Mono pM) { return pM .map(Principal::getName) From 6c60d75b20ea2c9ea0857298ef595df0ae131159 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:25:49 -0500 Subject: [PATCH 2/8] Use the SESSION cookie that is returned --- src/test/java/com/example/FormLoginTest.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/example/FormLoginTest.java b/src/test/java/com/example/FormLoginTest.java index 1291cfa..1bbd934 100644 --- a/src/test/java/com/example/FormLoginTest.java +++ b/src/test/java/com/example/FormLoginTest.java @@ -9,6 +9,7 @@ import org.springframework.http.MediaType; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -45,11 +46,12 @@ public void setup() { @Test public void returnOwnUser() throws Exception { - this.formLogin("user1", "user1"); + String sessionId = this.formLogin("user1", "user1"); this.rest .get() .uri("/api/user/current") + .cookie("SESSION", sessionId) .exchange() .expectStatus().is2xxSuccessful() .expectBody(String.class) @@ -59,12 +61,14 @@ public void returnOwnUser() throws Exception { this.rest .get() .uri("/logout") + .cookie("SESSION", sessionId) .exchange() .expectStatus().is2xxSuccessful(); this.rest .get() .uri("/api/user/current") + .cookie("SESSION", sessionId) .exchange() .expectStatus().isEqualTo(403) ; @@ -72,21 +76,25 @@ public void returnOwnUser() throws Exception { } - private void formLogin(String user, String password) { + private String formLogin(String user, String password) { this.rest .get() .uri("/login") .exchange() .expectStatus().is2xxSuccessful(); - this.rest + FluxExchangeResult result = this.rest .mutateWith(csrf()) .post() .uri("/login") - .body(BodyInserters.fromFormData(new FormData(user, password).toParamList())) + .body(BodyInserters + .fromFormData(new FormData(user, password).toParamList())) .accept(MediaType.TEXT_HTML) .exchange() .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/"); + .expectHeader().valueEquals("Location", "/") + .returnResult(String.class); + + return result.getResponseCookies().getFirst("SESSION").getValue(); } public static final class FormData { From ff91f2942a48deecce20a580f128cf1e1efed0ff Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:27:23 -0500 Subject: [PATCH 3/8] Fix logout portion of the test * Use a POST because GET /logout just presents a log out form * Since a POST is used we must include a valid CSRF token * Log out success performs a redirect --- src/test/java/com/example/FormLoginTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/example/FormLoginTest.java b/src/test/java/com/example/FormLoginTest.java index 1bbd934..d849bd0 100644 --- a/src/test/java/com/example/FormLoginTest.java +++ b/src/test/java/com/example/FormLoginTest.java @@ -59,11 +59,12 @@ public void returnOwnUser() throws Exception { ; this.rest - .get() + .mutateWith(csrf()) + .post() .uri("/logout") .cookie("SESSION", sessionId) .exchange() - .expectStatus().is2xxSuccessful(); + .expectStatus().is3xxRedirection(); this.rest .get() From ddbe96cfad0620d074d19485604be316907d1437 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:28:11 -0500 Subject: [PATCH 4/8] Fix /api/user/current when logged out We don't expect a 403, we expect a redirect to the log in page --- src/test/java/com/example/FormLoginTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/example/FormLoginTest.java b/src/test/java/com/example/FormLoginTest.java index d849bd0..bc90b80 100644 --- a/src/test/java/com/example/FormLoginTest.java +++ b/src/test/java/com/example/FormLoginTest.java @@ -71,7 +71,7 @@ public void returnOwnUser() throws Exception { .uri("/api/user/current") .cookie("SESSION", sessionId) .exchange() - .expectStatus().isEqualTo(403) + .expectStatus().is3xxRedirection(); ; } From 3086d9c397a5cb28d81d98cd76b427e3623c7ec9 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:28:21 -0500 Subject: [PATCH 5/8] Polish imports --- src/test/java/com/example/FormLoginTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/example/FormLoginTest.java b/src/test/java/com/example/FormLoginTest.java index bc90b80..900614f 100644 --- a/src/test/java/com/example/FormLoginTest.java +++ b/src/test/java/com/example/FormLoginTest.java @@ -16,7 +16,6 @@ import org.springframework.web.reactive.function.BodyInserters; import java.time.Duration; -import java.util.Arrays; import java.util.Collections; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; From a8f36bb1e928c7bf4d9170142ce1f2549ce1d3e4 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:31:46 -0500 Subject: [PATCH 6/8] Use @AutoConfigureWebTestClient * Previously the tests were using WebEnvironment.RANDOM_PORT which means an actual server is started up. However, WebTestClient was still being invoked without an HTTP request which means there is no need to start the server. * Rather than manually creating the WebTestClient, use @AutoConfigureWebTestClient --- src/test/java/com/example/FormLoginTest.java | 23 +++----------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/src/test/java/com/example/FormLoginTest.java b/src/test/java/com/example/FormLoginTest.java index 900614f..f956fd7 100644 --- a/src/test/java/com/example/FormLoginTest.java +++ b/src/test/java/com/example/FormLoginTest.java @@ -1,13 +1,11 @@ package com.example; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; import org.springframework.http.MediaType; -import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; @@ -15,34 +13,19 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserters; -import java.time.Duration; import java.util.Collections; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; +@SpringBootTest @RunWith(SpringRunner.class) -@ContextConfiguration(classes = Application.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureWebTestClient public class FormLoginTest { @Autowired - ApplicationContext context; - private WebTestClient rest; - @Before - public void setup() { - this.rest = WebTestClient - .bindToApplicationContext(this.context) - .apply(springSecurity()) - .configureClient() - .responseTimeout(Duration.ofDays(1)) - .build(); - } - - @Test public void returnOwnUser() throws Exception { String sessionId = this.formLogin("user1", "user1"); From bc169ccf6ab975b735891431f946f454a95a9bda Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:32:09 -0500 Subject: [PATCH 7/8] Polish .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a1c2a23..fddeabf 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +.idea +.gradle \ No newline at end of file From e33b76aa9e46312182971702aa42dabd3fa29eb2 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 5 Sep 2018 08:47:37 -0500 Subject: [PATCH 8/8] Split up tests It is better to split up all the tests into focussed tests. --- src/test/java/com/example/FormLoginTest.java | 70 +++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/test/java/com/example/FormLoginTest.java b/src/test/java/com/example/FormLoginTest.java index f956fd7..14c08c7 100644 --- a/src/test/java/com/example/FormLoginTest.java +++ b/src/test/java/com/example/FormLoginTest.java @@ -6,6 +6,7 @@ import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; @@ -13,11 +14,12 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserters; +import java.time.Duration; import java.util.Collections; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; - @SpringBootTest @RunWith(SpringRunner.class) @AutoConfigureWebTestClient @@ -27,57 +29,47 @@ public class FormLoginTest { private WebTestClient rest; @Test - public void returnOwnUser() throws Exception { - String sessionId = this.formLogin("user1", "user1"); - - this.rest - .get() - .uri("/api/user/current") - .cookie("SESSION", sessionId) - .exchange() - .expectStatus().is2xxSuccessful() - .expectBody(String.class) - .isEqualTo("Hello user1!") - ; - - this.rest + public void formLoginWhenValidCredentialsThenSessionCreated() { + FluxExchangeResult result = this.rest .mutateWith(csrf()) .post() - .uri("/logout") - .cookie("SESSION", sessionId) + .uri("/login") + .body(BodyInserters + .fromFormData(new FormData("user1", "user1").toParamList())) + .accept(MediaType.TEXT_HTML) .exchange() - .expectStatus().is3xxRedirection(); + .expectStatus().is3xxRedirection() + .expectHeader().valueEquals("Location", "/") + .returnResult(String.class); + + assertThat(result.getResponseCookies().keySet()).contains("SESSION"); + } - this.rest - .get() + @Test + @WithMockUser("user1") + public void apiWhenWithMockUserThenSaysHello() throws Exception { + this.rest.get() .uri("/api/user/current") - .cookie("SESSION", sessionId) .exchange() - .expectStatus().is3xxRedirection(); - ; - + .expectStatus().is2xxSuccessful() + .expectBody(String.class).isEqualTo("Hello user1!"); } + @Test + @WithMockUser + public void logoutWhenSuccessThenDeletesSession() throws Exception { - private String formLogin(String user, String password) { - this.rest - .get() - .uri("/login") - .exchange() - .expectStatus().is2xxSuccessful(); FluxExchangeResult result = this.rest .mutateWith(csrf()) .post() - .uri("/login") - .body(BodyInserters - .fromFormData(new FormData(user, password).toParamList())) - .accept(MediaType.TEXT_HTML) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/") - .returnResult(String.class); + .uri("/logout") + .cookie("SESSION", "any") + .exchange().expectStatus() + .is3xxRedirection().returnResult(String.class); + + assertThat(result.getResponseCookies().getFirst("SESSION").getMaxAge()).isEqualTo( + Duration.ZERO); - return result.getResponseCookies().getFirst("SESSION").getValue(); } public static final class FormData {