From db70771c23d312f166e26da5bda887e41119c040 Mon Sep 17 00:00:00 2001 From: Manolis Papiomytoglou <4935979+papiomytoglou@users.noreply.github.com> Date: Fri, 9 Feb 2024 14:07:13 +0000 Subject: [PATCH] fix: generalize function calculating normalized distance between date/time values (#2543) --- .../matching/AbstractDateTimeMatchResult.java | 9 ++- .../matching/AfterDateTimePatternTest.java | 15 +++- .../matching/BeforeDateTimePatternTest.java | 33 ++++++--- .../matching/EqualToDateTimePatternTest.java | 15 +++- .../matching/MultiValuePatternTest.java | 73 ++++++++++++++++++- 5 files changed, 119 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractDateTimeMatchResult.java b/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractDateTimeMatchResult.java index b1a008d308..70031b725b 100644 --- a/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractDateTimeMatchResult.java +++ b/src/main/java/com/github/tomakehurst/wiremock/matching/AbstractDateTimeMatchResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Thomas Akehurst + * Copyright (C) 2021-2024 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ public abstract class AbstractDateTimeMatchResult extends MatchResult { + private static final long ONE_YEAR_IN_MILLIS = 365 * 24 * 60 * 60 * 1000L; + private final boolean isZoned; private final boolean isLocal; @@ -83,8 +85,7 @@ public double getDistance() { } private double calculateDistance(Temporal start, Temporal end) { - double distance = ((double) ChronoUnit.YEARS.between(start, end)) / 100; - distance = Math.abs(distance); - return Math.min(distance, 1.0); + long absoluteTimeDifference = Math.abs((ChronoUnit.MILLIS.between(start, end))); + return (double) absoluteTimeDifference / (absoluteTimeDifference + 2 * ONE_YEAR_IN_MILLIS); } } diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java index 6d3a20f036..586562c4d6 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/AfterDateTimePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Thomas Akehurst + * Copyright (C) 2021-2024 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,10 @@ import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; @@ -97,10 +100,14 @@ public void doesNotMatchWhenActualValueIsNull() { @Test public void returnsAReasonableDistanceWhenNoMatchForLocalExpectedZonedActual() { StringValuePattern matcher = WireMock.after("2021-01-01T00:00:00Z"); - assertThat(matcher.match("1971-01-01T00:00:00Z").getDistance(), is(0.5)); - assertThat(matcher.match("1921-01-01T00:00:00Z").getDistance(), is(1.0)); + assertThat(matcher.match("2023-01-01T00:00:00Z").getDistance(), is(0.5)); + assertThat( + matcher.match("1921-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.5), lessThan(1.0))); assertThat(matcher.match(null).getDistance(), is(1.0)); - assertThat(matcher.match("2020-01-01T00:00:00Z").getDistance(), is(0.01)); + assertThat( + matcher.match("2020-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.0), lessThan(0.5))); } @Test diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java index 12e311f077..6d0da74da0 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/BeforeDateTimePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Thomas Akehurst + * Copyright (C) 2021-2024 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,10 @@ import static com.github.tomakehurst.wiremock.common.DateTimeTruncation.*; import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.*; @@ -87,28 +90,38 @@ public void doesNotMatchWhenExpectedValueUnparseable() { @Test public void returnsAReasonableDistanceWhenNoMatchForZonedExpectedZonedActual() { StringValuePattern matcher = WireMock.before("2021-01-01T00:00:00Z"); - assertThat(matcher.match("2071-01-01T00:00:00Z").getDistance(), is(0.5)); - assertThat(matcher.match("2121-01-01T00:00:00Z").getDistance(), is(1.0)); + assertThat(matcher.match("2023-01-01T00:00:00Z").getDistance(), is(0.5)); + assertThat( + matcher.match("2121-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.5), lessThan(1.0))); assertThat(matcher.match(null).getDistance(), is(1.0)); - assertThat(matcher.match("2022-01-01T00:00:00Z").getDistance(), is(0.01)); + assertThat( + matcher.match("2022-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.0), lessThan(0.5))); } @Test public void returnsAReasonableDistanceWhenNoMatchForLocalExpectedZonedActual() { StringValuePattern matcher = WireMock.before("2021-01-01T00:00:00"); - assertThat(matcher.match("2071-01-01T00:00:00Z").getDistance(), is(0.5)); - assertThat(matcher.match("2121-01-01T00:00:00Z").getDistance(), is(1.0)); + assertThat(matcher.match("2023-01-01T00:00:00Z").getDistance(), is(0.5)); + assertThat( + matcher.match("2121-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.5), lessThan(1.0))); assertThat(matcher.match(null).getDistance(), is(1.0)); - assertThat(matcher.match("2022-01-01T00:00:00Z").getDistance(), is(0.01)); + assertThat( + matcher.match("2022-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.0), lessThan(0.5))); } @Test public void returnsAReasonableDistanceWhenNoMatchForLocalExpectedLocalActual() { StringValuePattern matcher = WireMock.before("2021-01-01T00:00:00"); - assertThat(matcher.match("2071-01-01T00:00:00").getDistance(), is(0.5)); - assertThat(matcher.match("2121-01-01T00:00:00").getDistance(), is(1.0)); + assertThat(matcher.match("2023-01-01T00:00:00").getDistance(), is(0.5)); + assertThat( + matcher.match("2121-01-01T00:00:00").getDistance(), allOf(greaterThan(0.5), lessThan(1.0))); assertThat(matcher.match(null).getDistance(), is(1.0)); - assertThat(matcher.match("2022-01-01T00:00:00").getDistance(), is(0.01)); + assertThat( + matcher.match("2022-01-01T00:00:00").getDistance(), allOf(greaterThan(0.0), lessThan(0.5))); } @Test diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java index 9d83bc8611..c92c4d9f01 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/EqualToDateTimePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Thomas Akehurst + * Copyright (C) 2021-2024 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,10 @@ import static java.time.temporal.ChronoUnit.HOURS; import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.client.WireMock; @@ -113,10 +116,14 @@ public void doesNotMatchWhenActualValueIsNull() { @Test public void returnsAReasonableDistanceWhenNoMatchForLocalExpectedZonedActual() { StringValuePattern matcher = WireMock.equalToDateTime("2021-01-01T00:00:00Z"); - assertThat(matcher.match("2071-01-01T00:00:00Z").getDistance(), is(0.5)); - assertThat(matcher.match("2121-01-01T00:00:00Z").getDistance(), is(1.0)); + assertThat(matcher.match("2023-01-01T00:00:00Z").getDistance(), is(0.5)); + assertThat( + matcher.match("2121-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.5), lessThan(1.0))); assertThat(matcher.match(null).getDistance(), is(1.0)); - assertThat(matcher.match("2022-01-01T00:00:00Z").getDistance(), is(0.01)); + assertThat( + matcher.match("2022-01-01T00:00:00Z").getDistance(), + allOf(greaterThan(0.0), lessThan(0.5))); } @Test diff --git a/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java b/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java index af23a54596..a445288d02 100644 --- a/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java +++ b/src/test/java/com/github/tomakehurst/wiremock/matching/MultiValuePatternTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016-2023 Thomas Akehurst + * Copyright (C) 2016-2024 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import static com.github.tomakehurst.wiremock.http.QueryParameter.queryParam; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; import static org.junit.jupiter.api.Assertions.*; import com.github.tomakehurst.wiremock.common.Json; @@ -30,6 +31,20 @@ public class MultiValuePatternTest { + public static final String EXPECTED_DATE_TIME = "2024-01-01T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_MILLI_EARLIER = "2023-12-31T23:59:59.999Z"; + public static final String ACTUAL_DATE_TIME_ONE_MILLI_LATER = "2024-01-01T00:00:00.001Z"; + public static final String ACTUAL_DATE_TIME_TWO_MILLIS_EARLIER = "2023-12-31T23:59:59.998Z"; + public static final String ACTUAL_DATE_TIME_TWO_MILLIS_LATER = "2024-01-01T00:00:00.002Z"; + public static final String ACTUAL_DATE_TIME_ONE_DAY_EARLIER = "2023-12-31T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_DAY_LATER = "2024-01-02T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_YEAR_EARLIER = "2023-01-01T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_YEAR_LATER = "2025-01-01T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_CENTURY_EARLIER = "1924-01-01T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_CENTURY_LATER = "2124-01-01T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_MILLENIUM_EARLIER = "1024-01-01T00:00:00.000Z"; + public static final String ACTUAL_DATE_TIME_ONE_MILLENIUM_LATER = "3024-01-01T00:00:00.000Z"; + @Test public void returnsExactMatchForAbsentHeaderWhenRequiredAbsent() { assertTrue(MultiValuePattern.absent().match(HttpHeader.absent("any-key")).isExactMatch()); @@ -74,7 +89,7 @@ public void returnsNonZeroDistanceWhenHeaderValuesAreSimilar() { } @Test - public void returnsTheBestMatchWhenSeveralValuesAreAvailableAndNoneAreExact() { + public void returnsTheBestMatchWhenSeveralHeaderValuesAreAvailableAndNoneAreExact() { assertThat( MultiValuePattern.of(equalTo("required-value")) .match(httpHeader("any-key", "require1234567", "requi12345", "1234567rrrr")) @@ -82,6 +97,35 @@ public void returnsTheBestMatchWhenSeveralValuesAreAvailableAndNoneAreExact() { is(0.5)); } + @Test + public void returnsTheBestMatchWhenSeveralHeaderDateTimeValuesAreAvailableAndNoneAreExact() { + double distanceOfOneMilliDifference = + MultiValuePattern.of(equalToDateTime(EXPECTED_DATE_TIME)) + .match( + httpHeader( + "any-key", + ACTUAL_DATE_TIME_ONE_MILLI_EARLIER, + ACTUAL_DATE_TIME_ONE_MILLI_LATER)) + .getDistance(); + double distanceOfGreaterThanOneMilliDifference = + MultiValuePattern.of(equalToDateTime(EXPECTED_DATE_TIME)) + .match( + httpHeader( + "any-key", + ACTUAL_DATE_TIME_TWO_MILLIS_EARLIER, + ACTUAL_DATE_TIME_TWO_MILLIS_LATER, + ACTUAL_DATE_TIME_ONE_DAY_EARLIER, + ACTUAL_DATE_TIME_ONE_DAY_LATER, + ACTUAL_DATE_TIME_ONE_YEAR_EARLIER, + ACTUAL_DATE_TIME_ONE_YEAR_LATER, + ACTUAL_DATE_TIME_ONE_CENTURY_EARLIER, + ACTUAL_DATE_TIME_ONE_CENTURY_LATER, + ACTUAL_DATE_TIME_ONE_MILLENIUM_EARLIER, + ACTUAL_DATE_TIME_ONE_MILLENIUM_LATER)) + .getDistance(); + assertThat(distanceOfOneMilliDifference, lessThan(distanceOfGreaterThanOneMilliDifference)); + } + @Test public void returnsTheBestMatchWhenSeveralHeaderValuesAreAvailableAndOneIsExact() { assertTrue( @@ -90,6 +134,29 @@ public void returnsTheBestMatchWhenSeveralHeaderValuesAreAvailableAndOneIsExact( .isExactMatch()); } + @Test + public void returnsTheBestMatchWhenSeveralHeaderDateTimeValuesAreAvailableAndOneIsExact() { + assertTrue( + MultiValuePattern.of(equalToDateTime(EXPECTED_DATE_TIME)) + .match( + httpHeader( + "any-key", + ACTUAL_DATE_TIME_ONE_MILLI_EARLIER, + ACTUAL_DATE_TIME_ONE_MILLI_LATER, + ACTUAL_DATE_TIME_TWO_MILLIS_EARLIER, + ACTUAL_DATE_TIME_TWO_MILLIS_LATER, + ACTUAL_DATE_TIME_ONE_DAY_EARLIER, + ACTUAL_DATE_TIME_ONE_DAY_LATER, + ACTUAL_DATE_TIME_ONE_YEAR_EARLIER, + ACTUAL_DATE_TIME_ONE_YEAR_LATER, + ACTUAL_DATE_TIME_ONE_CENTURY_EARLIER, + ACTUAL_DATE_TIME_ONE_CENTURY_LATER, + ACTUAL_DATE_TIME_ONE_MILLENIUM_EARLIER, + ACTUAL_DATE_TIME_ONE_MILLENIUM_LATER, + EXPECTED_DATE_TIME)) + .isExactMatch()); + } + @Test public void returnsTheBestMatchWhenSeveralQueryParamValuesAreAvailableAndOneIsExact() { assertTrue( @@ -101,7 +168,6 @@ public void returnsTheBestMatchWhenSeveralQueryParamValuesAreAvailableAndOneIsEx @Test public void correctlyRendersEqualToAsJson() throws Exception { String actual = Json.write(MultiValuePattern.of(equalTo("something"))); - System.out.println(actual); JSONAssert.assertEquals( "{ \n" + " \"equalTo\": \"something\" \n" + "}", actual, @@ -111,7 +177,6 @@ public void correctlyRendersEqualToAsJson() throws Exception { @Test public void correctlyRendersAbsentAsJson() throws Exception { String actual = Json.write(MultiValuePattern.absent()); - System.out.println(actual); JSONAssert.assertEquals( "{ \n" + " \"absent\": true \n" + "}", actual, true); }