Skip to content

Commit 0a54e9e

Browse files
committed
Stabilize Date tests against clock drift
Capture today() before and after operations and assert ranges or parse ISO8601 values instead of exact equality. Snapshot today around loops to avoid flakiness and improve failure messages
1 parent 637d692 commit 0a54e9e

1 file changed

Lines changed: 90 additions & 26 deletions

File tree

test/neo_faker/date_test.exs

Lines changed: 90 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,68 @@ defmodule NeoFaker.DateTest do
44
alias NeoFaker.Date, as: FakeDate
55

66
defp today, do: NaiveDateTime.to_date(NaiveDateTime.local_now())
7-
defp today_iso, do: Date.to_iso8601(today())
87

98
describe "add/2" do
109
test "returns a Date struct when adding 0 days" do
11-
assert FakeDate.add(0..0) == today()
10+
before = today()
11+
result = FakeDate.add(0..0)
12+
after_date = today()
13+
14+
assert %Date{} = result
15+
assert Date.compare(result, before) != :lt
16+
assert Date.compare(result, after_date) != :gt
1217
end
1318

1419
test "returns an ISO 8601 string when format: :iso8601" do
15-
assert FakeDate.add(0..0, format: :iso8601) == today_iso()
20+
before = today()
21+
result = FakeDate.add(0..0, format: :iso8601)
22+
after_date = today()
23+
24+
assert {:ok, parsed} = Date.from_iso8601(result)
25+
assert Date.compare(parsed, before) != :lt
26+
assert Date.compare(parsed, after_date) != :gt
1627
end
1728

1829
test "returns a date within a positive range from today" do
30+
before = today()
1931
result = FakeDate.add(0..30)
32+
after_date = today()
2033

2134
assert %Date{} = result
22-
assert Date.compare(result, today()) != :lt
23-
assert Date.compare(result, Date.add(today(), 30)) != :gt
35+
assert Date.compare(result, before) != :lt
36+
assert Date.compare(result, Date.add(after_date, 30)) != :gt
2437
end
2538

2639
test "returns a date within a negative range from today" do
40+
before = today()
2741
result = FakeDate.add(-30..0)
42+
after_date = today()
2843

2944
assert %Date{} = result
30-
assert Date.compare(result, Date.add(today(), -30)) != :lt
31-
assert Date.compare(result, today()) != :gt
45+
assert Date.compare(result, Date.add(before, -30)) != :lt
46+
assert Date.compare(result, after_date) != :gt
3247
end
3348
end
3449

3550
describe "between/3" do
3651
test "returns a Date struct equal to today when start and end are the same" do
37-
assert FakeDate.between(today(), today()) == today()
52+
before = today()
53+
result = FakeDate.between(before, before)
54+
after_date = today()
55+
56+
assert %Date{} = result
57+
assert Date.compare(result, before) != :lt
58+
assert Date.compare(result, after_date) != :gt
3859
end
3960

4061
test "returns an ISO 8601 string when format: :iso8601" do
41-
assert FakeDate.between(today(), today(), format: :iso8601) == today_iso()
62+
before = today()
63+
result = FakeDate.between(before, before, format: :iso8601)
64+
after_date = today()
65+
66+
assert {:ok, parsed} = Date.from_iso8601(result)
67+
assert Date.compare(parsed, before) != :lt
68+
assert Date.compare(parsed, after_date) != :gt
4269
end
4370

4471
test "returns a date between the given start and end dates" do
@@ -63,40 +90,73 @@ defmodule NeoFaker.DateTest do
6390
assert String.match?(result, ~r/^\d{4}-\d{2}-\d{2}$/)
6491
end
6592

66-
test "result is not in the future" do
93+
test "result falls within the 18..65 age window" do
94+
before = today()
6795
result = FakeDate.birthday(18, 65)
96+
after_date = today()
97+
98+
lower = before |> Date.shift(year: -66) |> Date.add(1)
99+
upper = Date.shift(after_date, year: -18)
68100

69-
refute Date.after?(result, today())
101+
refute Date.before?(result, lower),
102+
"#{result} is before lower bound #{lower} (before=#{before})"
103+
104+
refute Date.after?(result, upper),
105+
"#{result} is after upper bound #{upper} (after_date=#{after_date})"
70106
end
71107

72108
test "lower bound is one day after today shifted back by max_age+1 years" do
73109
max_age = 30
74-
expected_lower = today() |> Date.shift(year: -(max_age + 1)) |> Date.add(1)
110+
# Capture before/after around the entire loop so that the expected_lower
111+
# bound and all birthday/3 calls inside use a consistent date.
112+
before = today()
75113

76-
for _ <- 1..30 do
77-
result = FakeDate.birthday(max_age, max_age)
114+
results = for _ <- 1..30, do: FakeDate.birthday(max_age, max_age)
78115

116+
after_date = today()
117+
118+
# The lower bound is derived from before (the earliest possible "today"
119+
# that the function could have seen), giving the widest valid window.
120+
expected_lower = before |> Date.shift(year: -(max_age + 1)) |> Date.add(1)
121+
122+
# The upper bound is derived from after_date (the latest possible "today").
123+
expected_upper = Date.shift(after_date, year: -max_age)
124+
125+
for result <- results do
79126
refute Date.before?(result, expected_lower),
80-
"#{result} is before the lower bound #{expected_lower} for max_age=#{max_age}"
127+
"#{result} is before lower bound #{expected_lower} for max_age=#{max_age}"
128+
129+
refute Date.after?(result, expected_upper),
130+
"#{result} is after upper bound #{expected_upper} for max_age=#{max_age}"
81131
end
82132
end
83133

84134
test "upper bound is today shifted back by min_age years" do
85135
min_age = 18
86-
expected_upper = Date.shift(today(), year: -min_age)
87136

88-
for _ <- 1..30 do
89-
result = FakeDate.birthday(min_age, min_age)
137+
before = today()
138+
results = for _ <- 1..30, do: FakeDate.birthday(min_age, min_age)
139+
after_date = today()
140+
141+
expected_lower = before |> Date.shift(year: -(min_age + 1)) |> Date.add(1)
142+
expected_upper = Date.shift(after_date, year: -min_age)
143+
144+
for result <- results do
145+
refute Date.before?(result, expected_lower),
146+
"#{result} is before lower bound #{expected_lower} for min_age=#{min_age}"
90147

91148
refute Date.after?(result, expected_upper),
92-
"#{result} is after the upper bound #{expected_upper} for min_age=#{min_age}"
149+
"#{result} is after upper bound #{expected_upper} for min_age=#{min_age}"
93150
end
94151
end
95152

96153
test "result falls within the full age window for default min/max" do
154+
before = today()
97155
result = FakeDate.birthday()
98-
lower = today() |> Date.shift(year: -66) |> Date.add(1)
99-
upper = Date.shift(today(), year: -18)
156+
after_date = today()
157+
158+
lower = before |> Date.shift(year: -66) |> Date.add(1)
159+
upper = Date.shift(after_date, year: -18)
100160

101161
refute Date.before?(result, lower),
102162
"#{result} is before lower bound #{lower}"
@@ -107,22 +167,26 @@ defmodule NeoFaker.DateTest do
107167

108168
test "window covers exactly one year when min_age equals max_age" do
109169
age = 25
110-
lower = today() |> Date.shift(year: -(age + 1)) |> Date.add(1)
111-
upper = Date.shift(today(), year: -age)
170+
snapshot = today()
171+
lower = snapshot |> Date.shift(year: -(age + 1)) |> Date.add(1)
172+
upper = Date.shift(snapshot, year: -age)
112173

113174
assert Date.diff(upper, lower) in 364..365,
114175
"expected window of ~365 days for age=#{age}, got #{Date.diff(upper, lower)}"
115176
end
116177

117178
test "birthday(0, 0) returns a date within the last year" do
179+
before = today()
118180
result = FakeDate.birthday(0, 0)
119-
lower = today() |> Date.shift(year: -1) |> Date.add(1)
181+
after_date = today()
182+
183+
lower = before |> Date.shift(year: -1) |> Date.add(1)
120184

121185
refute Date.before?(result, lower),
122186
"#{result} is before lower bound #{lower} for age=0"
123187

124-
refute Date.after?(result, today()),
125-
"#{result} is after today for age=0"
188+
refute Date.after?(result, after_date),
189+
"#{result} is after after_date=#{after_date} for age=0"
126190
end
127191
end
128192
end

0 commit comments

Comments
 (0)