From 555d4a6004c5c399ed102e68de63c83fd2bd2aba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Fri, 15 Mar 2024 13:27:23 +0100 Subject: [PATCH] Add AssertJ support for cookies This commit adds AssertJ compatible assertions for cookies See gh-21178 Co-authored-by: Brian Clozel --- .../web/servlet/assertj/CookieMapAssert.java | 173 +++++++++++++++++ .../servlet/assertj/CookieMapAssertTests.java | 182 ++++++++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/assertj/CookieMapAssert.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/assertj/CookieMapAssertTests.java diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/CookieMapAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/CookieMapAssert.java new file mode 100644 index 000000000000..803399b1538b --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/CookieMapAssert.java @@ -0,0 +1,173 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.servlet.assertj; + +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +import jakarta.servlet.http.Cookie; +import org.assertj.core.api.AbstractMapAssert; +import org.assertj.core.api.Assertions; + +/** + * AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to + * {@link Cookie cookies}. + * + * @author Brian Clozel + * @author Stephane Nicoll + * @since 6.2 + */ +public class CookieMapAssert extends AbstractMapAssert, String, Cookie> { + + public CookieMapAssert(Cookie[] actual) { + super(mapCookies(actual), CookieMapAssert.class); + as("Cookies"); + } + + private static Map mapCookies(Cookie[] cookies) { + Map map = new LinkedHashMap<>(); + for (Cookie cookie : cookies) { + map.putIfAbsent(cookie.getName(), cookie); + } + return map; + } + + /** + * Verify that the actual cookies contain a cookie with the given {@code name}. + * @param name the name of an expected cookie + * @see #containsKey + */ + public CookieMapAssert containsCookie(String name) { + return containsKey(name); + } + + /** + * Verify that the actual cookies contain the cookies with the given + * {@code names}. + * @param names the names of expected cookies + * @see #containsKeys + */ + public CookieMapAssert containsCookies(String... names) { + return containsKeys(names); + } + + /** + * Verify that the actual cookies do not contain a cookie with the + * given {@code name}. + * @param name the name of a cookie that should not be present + * @see #doesNotContainKey + */ + public CookieMapAssert doesNotContainCookie(String name) { + return doesNotContainKey(name); + } + + /** + * Verify that the actual cookies do not contain any of the cookies with + * the given {@code names}. + * @param names the names of cookies that should not be present + * @see #doesNotContainKeys + */ + public CookieMapAssert doesNotContainCookies(String... names) { + return doesNotContainKeys(names); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} that satisfy given {@code cookieRequirements}. + * the specified names. + * @param name the name of an expected cookie + * @param cookieRequirements the requirements for the cookie + */ + public CookieMapAssert hasCookieSatisfying(String name, Consumer cookieRequirements) { + return hasEntrySatisfying(name, cookieRequirements); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} whose {@linkplain Cookie#getValue() value} is equal to the + * given one. + * @param name the name of the cookie + * @param expected the expected value of the cookie + */ + public CookieMapAssert hasValue(String name, String expected) { + return hasCookieSatisfying(name, cookie -> + Assertions.assertThat(cookie.getValue()).isEqualTo(expected)); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} whose {@linkplain Cookie#getMaxAge() max age} is equal to + * the given one. + * @param name the name of the cookie + * @param expected the expected max age of the cookie + */ + public CookieMapAssert hasMaxAge(String name, Duration expected) { + return hasCookieSatisfying(name, cookie -> + Assertions.assertThat(Duration.ofSeconds(cookie.getMaxAge())).isEqualTo(expected)); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} whose {@linkplain Cookie#getPath() path} is equal to + * the given one. + * @param name the name of the cookie + * @param expected the expected path of the cookie + */ + public CookieMapAssert hasPath(String name, String expected) { + return hasCookieSatisfying(name, cookie -> + Assertions.assertThat(cookie.getPath()).isEqualTo(expected)); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} whose {@linkplain Cookie#getDomain() domain} is equal to + * the given one. + * @param name the name of the cookie + * @param expected the expected path of the cookie + */ + public CookieMapAssert hasDomain(String name, String expected) { + return hasCookieSatisfying(name, cookie -> + Assertions.assertThat(cookie.getDomain()).isEqualTo(expected)); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} whose {@linkplain Cookie#getSecure() secure flag} is equal + * to the given one. + * @param name the name of the cookie + * @param expected whether the cookie is secure + */ + public CookieMapAssert isSecure(String name, boolean expected) { + return hasCookieSatisfying(name, cookie -> + Assertions.assertThat(cookie.getSecure()).isEqualTo(expected)); + } + + /** + * Verify that the actual cookies contain a cookie with the given + * {@code name} whose {@linkplain Cookie#isHttpOnly() http only flag} is + * equal to the given one. + * @param name the name of the cookie + * @param expected whether the cookie is http only + */ + public CookieMapAssert isHttpOnly(String name, boolean expected) { + return hasCookieSatisfying(name, cookie -> + Assertions.assertThat(cookie.isHttpOnly()).isEqualTo(expected)); + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/CookieMapAssertTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/CookieMapAssertTests.java new file mode 100644 index 000000000000..0bcdabb01019 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/CookieMapAssertTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2002-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.servlet.assertj; + + +import java.time.Duration; +import java.util.List; + +import jakarta.servlet.http.Cookie; +import org.assertj.core.api.AssertProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link CookieMapAssert}. + * + * @author Brian Clozel + */ +class CookieMapAssertTests { + + static Cookie[] cookies; + + @BeforeAll + static void setup() { + Cookie framework = new Cookie("framework", "spring"); + framework.setSecure(true); + framework.setHttpOnly(true); + Cookie age = new Cookie("age", "value"); + age.setMaxAge(1200); + Cookie domain = new Cookie("domain", "value"); + domain.setDomain("spring.io"); + Cookie path = new Cookie("path", "value"); + path.setPath("/spring"); + cookies = List.of(framework, age, domain, path).toArray(new Cookie[0]); + } + + @Test + void containsCookieWhenCookieExistsShouldPass() { + assertThat(forCookies()).containsCookie("framework"); + } + + @Test + void containsCookieWhenCookieMissingShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).containsCookie("missing")); + } + + @Test + void containsCookiesWhenCookiesExistShouldPass() { + assertThat(forCookies()).containsCookies("framework", "age"); + } + + @Test + void containsCookiesWhenCookieMissingShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).containsCookies("framework", "missing")); + } + + @Test + void doesNotContainCookieWhenCookieMissingShouldPass() { + assertThat(forCookies()).doesNotContainCookie("missing"); + } + + @Test + void doesNotContainCookieWhenCookieExistsShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).doesNotContainCookie("framework")); + } + + @Test + void doesNotContainCookiesWhenCookiesMissingShouldPass() { + assertThat(forCookies()).doesNotContainCookies("missing", "missing2"); + } + + @Test + void doesNotContainCookiesWhenAtLeastOneCookieExistShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).doesNotContainCookies("missing", "framework")); + } + + @Test + void hasValueEqualsWhenCookieValueMatchesShouldPass() { + assertThat(forCookies()).hasValue("framework", "spring"); + } + + @Test + void hasValueEqualsWhenCookieValueDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).hasValue("framework", "other")); + } + + @Test + void hasCookieSatisfyingWhenCookieValueMatchesShouldPass() { + assertThat(forCookies()).hasCookieSatisfying("framework", cookie -> + assertThat(cookie.getValue()).startsWith("spr")); + } + + @Test + void hasCookieSatisfyingWhenCookieValueDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).hasCookieSatisfying("framework", cookie -> + assertThat(cookie.getValue()).startsWith("not"))); + } + + @Test + void hasMaxAgeWhenCookieAgeMatchesShouldPass() { + assertThat(forCookies()).hasMaxAge("age", Duration.ofMinutes(20)); + } + + @Test + void hasMaxAgeWhenCookieAgeDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).hasMaxAge("age", Duration.ofMinutes(30))); + } + + @Test + void pathWhenCookiePathMatchesShouldPass() { + assertThat(forCookies()).hasPath("path", "/spring"); + } + + @Test + void pathWhenCookiePathDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).hasPath("path", "/other")); + } + + @Test + void hasDomainWhenCookieDomainMatchesShouldPass() { + assertThat(forCookies()).hasDomain("domain", "spring.io"); + } + + @Test + void hasDomainWhenCookieDomainDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).hasDomain("domain", "example.org")); + } + + @Test + void isSecureWhenCookieSecureMatchesShouldPass() { + assertThat(forCookies()).isSecure("framework", true); + } + + @Test + void isSecureWhenCookieSecureDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).isSecure("domain", true)); + } + + @Test + void isHttpOnlyWhenCookieHttpOnlyMatchesShouldPass() { + assertThat(forCookies()).isHttpOnly("framework", true); + } + + @Test + void isHttpOnlyWhenCookieHttpOnlyDiffersShouldFail() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + assertThat(forCookies()).isHttpOnly("domain", true)); + } + + + private AssertProvider forCookies() { + return () -> new CookieMapAssert(cookies); + } + +}