holidays 9.1.0 → 9.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +2 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +5 -0
- data/Makefile +14 -3
- data/README.md +1 -2
- data/Rakefile +19 -0
- data/doc/MAINTAINERS.md +2 -4
- data/holidays.gemspec +3 -3
- data/lib/generated_definitions/bg.rb +3 -2
- data/lib/generated_definitions/cy.rb +2 -1
- data/lib/generated_definitions/europe.rb +2 -1
- data/lib/holidays/definition/context/function_processor.rb +1 -1
- data/lib/holidays/definition/context/merger.rb +20 -4
- data/lib/holidays/definition/repository/custom_methods.rb +37 -9
- data/lib/holidays/finder/context/search.rb +43 -24
- data/lib/holidays/version.rb +1 -1
- data/lib/holidays.rb +9 -4
- data/test/coverage_report.rb +3 -5
- data/test/data/test_date_transform_conflict_region_1.yaml +14 -0
- data/test/data/test_date_transform_conflict_region_2.yaml +14 -0
- data/test/e2e/README.md +52 -0
- data/test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml +38 -0
- data/test/{integration → e2e}/test_holidays.rb +3 -2
- data/test/e2e/test_multiple_regions_with_conflict.rb +228 -0
- data/test/holidays/definition/context/test_function_processor.rb +2 -2
- data/test/holidays/definition/context/test_merger.rb +1 -1
- data/test/holidays/finder/context/test_search.rb +58 -0
- data/test/integration/README.md +45 -6
- data/test/integration/test_custom_holidays.rb +1 -1
- data/test/integration/test_custom_informal_holidays.rb +1 -1
- data/test/smoke/README.md +31 -0
- data/test/{integration → smoke}/test_available_regions.rb +0 -5
- data/test/smoke/test_smoke.rb +74 -0
- metadata +46 -34
- data/test/integration/test_multiple_regions_with_conflict.rb +0 -29
- /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_1.yaml +0 -0
- /data/test/{data → e2e/data}/test_multiple_regions_with_conflicts_region_2.yaml +0 -0
- /data/test/{integration → e2e}/test_all_regions.rb +0 -0
- /data/test/{integration → e2e}/test_any_holidays_during_work_week.rb +0 -0
- /data/test/{integration → e2e}/test_holidays_between.rb +0 -0
- /data/test/{integration → e2e}/test_multiple_regions.rb +0 -0
- /data/test/{integration → e2e}/test_nonstandard_regions.rb +0 -0
- /data/test/{data → integration/data}/test_custom_govt_holiday_defs.yaml +0 -0
- /data/test/{data → integration/data}/test_custom_informal_holidays_defs.yaml +0 -0
- /data/test/{data → integration/data}/test_multiple_custom_holiday_defs.yaml +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
# See https://github.com/holidays/holidays/issues/344 for more info on why
|
|
4
|
+
# these specific integration tests exist.
|
|
5
|
+
class MultipleRegionsWithConflictsTests < Test::Unit::TestCase
|
|
6
|
+
def test_corpus_christi_returns_correctly_for_co_even_if_br_is_loaded_first
|
|
7
|
+
result = Holidays.on(Date.new(2014, 6, 19), :br)
|
|
8
|
+
assert_equal 1, result.count
|
|
9
|
+
assert_equal 'Corpus Christi', result.first[:name]
|
|
10
|
+
|
|
11
|
+
result = Holidays.on(Date.new(2014, 6, 23), :co)
|
|
12
|
+
assert_equal 1, result.count
|
|
13
|
+
assert_equal 'Corpus Christi', result.first[:name]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_custom_loaded_region_returns_correct_value_with_function_modifier_conflict_even_if_conflict_definition_is_loaded_first
|
|
17
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
18
|
+
result = Holidays.on(Date.new(2019, 6, 20), :multiple_with_conflict_1)
|
|
19
|
+
assert_equal 1, result.count
|
|
20
|
+
assert_equal 'With Function Modifier', result.first[:name]
|
|
21
|
+
|
|
22
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
23
|
+
result = Holidays.on(Date.new(2019, 6, 24), :multiple_with_conflict_2)
|
|
24
|
+
assert_equal 1, result.count
|
|
25
|
+
assert_equal 'With Function Modifier', result.first[:name]
|
|
26
|
+
|
|
27
|
+
# Region 1 must still return the correct date even though region 2 was
|
|
28
|
+
# loaded afterwards with a different function modifier.
|
|
29
|
+
result = Holidays.on(Date.new(2019, 6, 20), :multiple_with_conflict_1)
|
|
30
|
+
assert_equal 1, result.count
|
|
31
|
+
assert_equal 'With Function Modifier', result.first[:name]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_custom_loaded_region_returns_correct_value_when_two_regions_share_function_name_but_have_different_logic
|
|
35
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
36
|
+
|
|
37
|
+
result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
|
|
38
|
+
assert_equal 1, result.count
|
|
39
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
40
|
+
|
|
41
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
42
|
+
|
|
43
|
+
result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
|
|
44
|
+
assert_equal 1, result.count
|
|
45
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
46
|
+
|
|
47
|
+
# Region 1 must still return the correct date even though region 2 was
|
|
48
|
+
# loaded afterwards with the same function name but different logic.
|
|
49
|
+
result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
|
|
50
|
+
assert_equal 1, result.count
|
|
51
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Load region 2 first, then region 1, which is the opposite of the test above.
|
|
55
|
+
# The first-loaded method lands in @custom_methods; the second goes into
|
|
56
|
+
# @regional_overrides. This reversal exercises the fallback path for the
|
|
57
|
+
# first-loaded region rather than the override path.
|
|
58
|
+
def test_custom_loaded_region_returns_correct_value_when_load_order_is_reversed
|
|
59
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
60
|
+
|
|
61
|
+
result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
|
|
62
|
+
assert_equal 1, result.count
|
|
63
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
64
|
+
|
|
65
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
66
|
+
|
|
67
|
+
result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
|
|
68
|
+
assert_equal 1, result.count
|
|
69
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
70
|
+
|
|
71
|
+
# Region 2 must still return the correct date even though region 1 was
|
|
72
|
+
# loaded afterwards with the same function name but different logic.
|
|
73
|
+
result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
|
|
74
|
+
assert_equal 1, result.count
|
|
75
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Both regions define holidays with the same custom function name but
|
|
79
|
+
# different Ruby logic and different holiday names. Because the names differ,
|
|
80
|
+
# holidays_by_month keeps them as separate entries (no region merge). Conflict
|
|
81
|
+
# resolution must still route each holiday to its own function implementation.
|
|
82
|
+
def test_custom_loaded_region_returns_correct_value_when_function_name_is_shared_but_holiday_names_differ
|
|
83
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
84
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
85
|
+
|
|
86
|
+
result = Holidays.on(Date.new(2019, 9, 15), :multiple_with_conflict_1)
|
|
87
|
+
assert_equal 1, result.count
|
|
88
|
+
assert_equal 'With Function Only Same Function Name - Region 1', result.first[:name]
|
|
89
|
+
|
|
90
|
+
result = Holidays.on(Date.new(2019, 11, 15), :multiple_with_conflict_2)
|
|
91
|
+
assert_equal 1, result.count
|
|
92
|
+
assert_equal 'With Function Only Same Function Name - Region 2', result.first[:name]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# When two regions are queried together and both have a holiday that uses the
|
|
96
|
+
# same function name but different logic, the search must evaluate the function
|
|
97
|
+
# independently for each queried region and return the union of all matches.
|
|
98
|
+
def test_simultaneous_multi_region_query_evaluates_each_region_function_independently
|
|
99
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
100
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
101
|
+
|
|
102
|
+
result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_1, :multiple_with_conflict_2)
|
|
103
|
+
assert_equal 1, result.count
|
|
104
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
105
|
+
|
|
106
|
+
result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1, :multiple_with_conflict_2)
|
|
107
|
+
assert_equal 1, result.count
|
|
108
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# https://www.github.com/holidays/holidays/issues/344 scenario: two regions
|
|
112
|
+
# share a function name but each implements it differently, so a single between() call
|
|
113
|
+
# spanning both result dates must return one holiday entry per region.
|
|
114
|
+
#
|
|
115
|
+
# Region 1's function returns Sept 1; region 2's function returns Nov 1.
|
|
116
|
+
# Querying Sept 1 to Nov 1 with both regions should produce two separate
|
|
117
|
+
# 'With Function Only Same Function Name' entries, one per region.
|
|
118
|
+
def test_simultaneous_multi_region_query_returns_one_result_per_region_in_a_single_call
|
|
119
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
120
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
121
|
+
|
|
122
|
+
result = Holidays.between(
|
|
123
|
+
Date.new(2019, 9, 1),
|
|
124
|
+
Date.new(2019, 11, 1),
|
|
125
|
+
:multiple_with_conflict_1,
|
|
126
|
+
:multiple_with_conflict_2,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
matching = result.select { |h| h[:name] == 'With Function Only Same Function Name' }
|
|
130
|
+
assert_equal 2, matching.count
|
|
131
|
+
assert matching.any? { |h| h[:date] == Date.new(2019, 9, 1) }, 'expected region 1 result on Sept 1'
|
|
132
|
+
assert matching.any? { |h| h[:date] == Date.new(2019, 11, 1) }, 'expected region 2 result on Nov 1'
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Tests the conflict resolution path for functions that take a date argument
|
|
136
|
+
# and return a shifted date, as opposed to functions that return a fixed date
|
|
137
|
+
# for a given year. This is the canonical scenario from https://www.github.com/holidays/holidays/issues/344:
|
|
138
|
+
# two regions define the same function name with different shift logic.
|
|
139
|
+
#
|
|
140
|
+
# Region 1: shifts Jan 1 to the nearest upcoming Saturday.
|
|
141
|
+
# Region 2: shifts Jan 1 to the nearest upcoming Sunday.
|
|
142
|
+
#
|
|
143
|
+
# Jan 1, 2025 is a Wednesday, so:
|
|
144
|
+
# Region 1 -> Jan 4, 2025 (Saturday, +3 days)
|
|
145
|
+
# Region 2 -> Jan 5, 2025 (Sunday, +4 days)
|
|
146
|
+
def test_date_transforming_functions_with_conflicting_logic_are_each_evaluated_independently
|
|
147
|
+
Holidays.load_custom('test/data/test_date_transform_conflict_region_1.yaml')
|
|
148
|
+
Holidays.load_custom('test/data/test_date_transform_conflict_region_2.yaml')
|
|
149
|
+
|
|
150
|
+
# Single-region: each region resolves to its own shifted date.
|
|
151
|
+
result = Holidays.on(Date.new(2025, 1, 4), :date_transform_conflict_1)
|
|
152
|
+
assert_equal 1, result.count
|
|
153
|
+
assert_equal 'Weekend Holiday', result.first[:name]
|
|
154
|
+
|
|
155
|
+
result = Holidays.on(Date.new(2025, 1, 5), :date_transform_conflict_2)
|
|
156
|
+
assert_equal 1, result.count
|
|
157
|
+
assert_equal 'Weekend Holiday', result.first[:name]
|
|
158
|
+
|
|
159
|
+
# Multi-region: a single call spanning both shifted dates must return one
|
|
160
|
+
# result per region, each evaluated with its own function logic.
|
|
161
|
+
result = Holidays.between(
|
|
162
|
+
Date.new(2025, 1, 4),
|
|
163
|
+
Date.new(2025, 1, 5),
|
|
164
|
+
:date_transform_conflict_1,
|
|
165
|
+
:date_transform_conflict_2,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
matching = result.select { |h| h[:name] == 'Weekend Holiday' }
|
|
169
|
+
assert_equal 2, matching.count
|
|
170
|
+
assert matching.any? { |h| h[:date] == Date.new(2025, 1, 4) }, 'expected region 1 result on Jan 4 (Saturday)'
|
|
171
|
+
assert matching.any? { |h| h[:date] == Date.new(2025, 1, 5) }, 'expected region 2 result on Jan 5 (Sunday)'
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Three regions all share the same function name but each implements it with
|
|
175
|
+
# different logic (region 1 -> Sept 1, region 2 -> Nov 1, region 3 -> Mar 1).
|
|
176
|
+
# After all three are loaded, every region must still resolve to its own date.
|
|
177
|
+
def test_three_regions_with_same_function_name_each_return_correct_result
|
|
178
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
179
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
180
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml')
|
|
181
|
+
|
|
182
|
+
result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
|
|
183
|
+
assert_equal 1, result.count
|
|
184
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
185
|
+
|
|
186
|
+
result = Holidays.on(Date.new(2019, 11, 1), :multiple_with_conflict_2)
|
|
187
|
+
assert_equal 1, result.count
|
|
188
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
189
|
+
|
|
190
|
+
result = Holidays.on(Date.new(2019, 3, 1), :multiple_with_conflict_3)
|
|
191
|
+
assert_equal 1, result.count
|
|
192
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# A single between() call across all three regions must return one result per
|
|
196
|
+
# region, each evaluated with its own function implementation.
|
|
197
|
+
def test_simultaneous_three_region_query_returns_one_result_per_region
|
|
198
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
199
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_2.yaml')
|
|
200
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_3.yaml')
|
|
201
|
+
|
|
202
|
+
result = Holidays.between(
|
|
203
|
+
Date.new(2019, 3, 1),
|
|
204
|
+
Date.new(2019, 11, 1),
|
|
205
|
+
:multiple_with_conflict_1,
|
|
206
|
+
:multiple_with_conflict_2,
|
|
207
|
+
:multiple_with_conflict_3,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
matching = result.select { |h| h[:name] == 'With Function Only Same Function Name' }
|
|
211
|
+
assert_equal 3, matching.count
|
|
212
|
+
assert matching.any? { |h| h[:date] == Date.new(2019, 3, 1) }, 'expected region 3 result on Mar 1'
|
|
213
|
+
assert matching.any? { |h| h[:date] == Date.new(2019, 9, 1) }, 'expected region 1 result on Sept 1'
|
|
214
|
+
assert matching.any? { |h| h[:date] == Date.new(2019, 11, 1) }, 'expected region 2 result on Nov 1'
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Verifies the safe-overwrite path in CustomMethods#add: when the same
|
|
218
|
+
# source is added again the method is simply overwritten and the holiday
|
|
219
|
+
# definition repo de-duplicates via uniq!, so exactly one result is returned.
|
|
220
|
+
def test_loading_the_same_custom_file_twice_does_not_duplicate_or_break_results
|
|
221
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
222
|
+
Holidays.load_custom('test/e2e/data/test_multiple_regions_with_conflicts_region_1.yaml')
|
|
223
|
+
|
|
224
|
+
result = Holidays.on(Date.new(2019, 9, 1), :multiple_with_conflict_1)
|
|
225
|
+
assert_equal 1, result.count
|
|
226
|
+
assert_equal 'With Function Only Same Function Name', result.first[:name]
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -19,7 +19,7 @@ class FunctionProcessorTests < Test::Unit::TestCase
|
|
|
19
19
|
|
|
20
20
|
@custom_func = mock()
|
|
21
21
|
|
|
22
|
-
@custom_methods_repo.expects(:find).at_most_once.with(@func_id).returns(@custom_func)
|
|
22
|
+
@custom_methods_repo.expects(:find).at_most_once.with(@func_id, @region).returns(@custom_func)
|
|
23
23
|
@proc_result_cache_repo.expects(:lookup).at_most_once.with(@custom_func, @year).returns(Date.civil(@year, @month, @day))
|
|
24
24
|
|
|
25
25
|
@subject = Holidays::Definition::Context::FunctionProcessor.new(
|
|
@@ -46,7 +46,7 @@ class FunctionProcessorTests < Test::Unit::TestCase
|
|
|
46
46
|
|
|
47
47
|
def test_unknown_function_id_returns_error
|
|
48
48
|
bad_id = "some-bad-id"
|
|
49
|
-
@custom_methods_repo.expects(:find).at_most_once.with(bad_id).returns(nil)
|
|
49
|
+
@custom_methods_repo.expects(:find).at_most_once.with(bad_id, @region).returns(nil)
|
|
50
50
|
|
|
51
51
|
assert_raises Holidays::FunctionNotFound do
|
|
52
52
|
@subject.call(@input, bad_id, @func_args, @func_modifier)
|
|
@@ -18,7 +18,7 @@ class MergerTests < Test::Unit::TestCase
|
|
|
18
18
|
def test_repos_are_called_to_add_regions_and_holidays
|
|
19
19
|
@holidays_repo.expects(:add).with(@target_holidays)
|
|
20
20
|
@regions_repo.expects(:add).with(@target_regions)
|
|
21
|
-
@custom_methods_repo.expects(:add).with(@target_custom_methods)
|
|
21
|
+
@custom_methods_repo.expects(:add).with(@target_custom_methods, {}, {})
|
|
22
22
|
|
|
23
23
|
@subject.call(@target_regions, @target_holidays, @target_custom_methods)
|
|
24
24
|
end
|
|
@@ -229,4 +229,62 @@ class FinderSearchTests < Test::Unit::TestCase
|
|
|
229
229
|
@subject.call(@dates_driver, @regions, @options)
|
|
230
230
|
)
|
|
231
231
|
end
|
|
232
|
+
|
|
233
|
+
def test_nil_returned_from_one_region_function_does_not_suppress_valid_result_from_other_region
|
|
234
|
+
two_regions = [:r1, :r2]
|
|
235
|
+
|
|
236
|
+
@holidays_by_month_repo.expects(:find_by_month).at_most_once.returns(
|
|
237
|
+
[{ mday: 1, name: "Test", regions: two_regions, function: "func-id", function_arguments: [:year], function_modifier: nil }]
|
|
238
|
+
)
|
|
239
|
+
@in_region_rule.expects(:call).with(two_regions, two_regions).returns(true)
|
|
240
|
+
|
|
241
|
+
@custom_method_processor.expects(:call).with(
|
|
242
|
+
{ year: 2015, month: 1, day: 1, region: :r1 }, "func-id", [:year], nil,
|
|
243
|
+
).returns(nil)
|
|
244
|
+
|
|
245
|
+
@custom_method_processor.expects(:call).with(
|
|
246
|
+
{ year: 2015, month: 1, day: 1, region: :r2 }, "func-id", [:year], nil,
|
|
247
|
+
).returns(Date.civil(2015, 6, 15))
|
|
248
|
+
|
|
249
|
+
result = @subject.call(@dates_driver, two_regions, [])
|
|
250
|
+
assert_equal 1, result.count
|
|
251
|
+
assert_equal Date.civil(2015, 6, 15), result.first[:date]
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def test_conflicting_function_in_two_regions_evaluates_each_independently
|
|
255
|
+
two_regions = [:r1, :r2]
|
|
256
|
+
|
|
257
|
+
@holidays_by_month_repo.expects(:find_by_month).at_most_once.returns(
|
|
258
|
+
[{ mday: 1, name: "Test", regions: two_regions, function: "func-id", function_arguments: [:year], function_modifier: nil }]
|
|
259
|
+
)
|
|
260
|
+
@in_region_rule.expects(:call).with(two_regions, two_regions).returns(true)
|
|
261
|
+
|
|
262
|
+
@custom_method_processor.expects(:call).with(
|
|
263
|
+
{ year: 2015, month: 1, day: 1, region: :r1 }, "func-id", [:year], nil,
|
|
264
|
+
).returns(Date.civil(2015, 3, 10))
|
|
265
|
+
|
|
266
|
+
@custom_method_processor.expects(:call).with(
|
|
267
|
+
{ year: 2015, month: 1, day: 1, region: :r2 }, "func-id", [:year], nil,
|
|
268
|
+
).returns(Date.civil(2015, 6, 15))
|
|
269
|
+
|
|
270
|
+
result = @subject.call(@dates_driver, two_regions, [])
|
|
271
|
+
assert_equal 2, result.count
|
|
272
|
+
assert result.any? { |h| h[:date] == Date.civil(2015, 3, 10) }
|
|
273
|
+
assert result.any? { |h| h[:date] == Date.civil(2015, 6, 15) }
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def test_any_region_query_with_function_evaluates_function_exactly_once
|
|
277
|
+
@holidays_by_month_repo.expects(:find_by_month).at_most_once.returns(
|
|
278
|
+
[{ mday: 1, name: "Test", regions: [:r1], function: "func-id", function_arguments: [:year], function_modifier: nil }]
|
|
279
|
+
)
|
|
280
|
+
@in_region_rule.expects(:call).with([:any], [:r1]).returns(true)
|
|
281
|
+
|
|
282
|
+
@custom_method_processor.expects(:call).with(
|
|
283
|
+
{ year: 2015, month: 1, day: 1, region: :any }, "func-id", [:year], nil,
|
|
284
|
+
).returns(Date.civil(2015, 3, 10))
|
|
285
|
+
|
|
286
|
+
result = @subject.call(@dates_driver, [:any], [])
|
|
287
|
+
assert_equal 1, result.count
|
|
288
|
+
assert_equal Date.civil(2015, 3, 10), result.first[:date]
|
|
289
|
+
end
|
|
232
290
|
end
|
data/test/integration/README.md
CHANGED
|
@@ -1,9 +1,48 @@
|
|
|
1
1
|
# Integration tests
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
True gem integration tests that verify how the gem's components work together
|
|
4
|
+
(loader, calculator, observed-date logic, sub-region inheritance, caching, etc.)
|
|
5
|
+
using **local fixture YAMLs**. Integration-only fixtures live in
|
|
6
|
+
`test/integration/data/`. Fixtures shared with other test categories live in
|
|
7
|
+
`test/data/`.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
These tests pin specific inputs (the fixture YAMLs) and assert specific outputs,
|
|
12
|
+
so they verify gem behavior independently of whatever the upstream definitions
|
|
13
|
+
repo currently ships. They do not break when upstream definitions change.
|
|
14
|
+
|
|
15
|
+
## Integration tests vs. unit tests
|
|
16
|
+
|
|
17
|
+
Unit tests live alongside the code they cover and verify a single file or
|
|
18
|
+
class in isolation. They are the right place for testing the internals of one
|
|
19
|
+
specific component (e.g. a date calculator, a custom method validator) with
|
|
20
|
+
mocks and minimal setup.
|
|
21
|
+
|
|
22
|
+
Integration tests in this directory are the opposite: they wire multiple gem
|
|
23
|
+
components together and verify that they cooperate correctly across the full
|
|
24
|
+
parse → calculate → return pipeline. If you are testing one file's internal
|
|
25
|
+
behavior, write a unit test. If you are testing how several pieces of the gem
|
|
26
|
+
behave together against a known input, write an integration test here.
|
|
27
|
+
|
|
28
|
+
## What belongs here
|
|
29
|
+
|
|
30
|
+
- Tests that exercise gem internals end-to-end (parsing, calculation, output)
|
|
31
|
+
using controlled inputs from `test/integration/data/` (or `test/data/` for
|
|
32
|
+
fixtures shared with other test categories).
|
|
33
|
+
- Tests for custom-method evaluation, year-range handling, informal flag
|
|
34
|
+
filtering, observed-date shifting, sub-region inheritance, etc., using fixtures.
|
|
35
|
+
|
|
36
|
+
## What does NOT belong here
|
|
37
|
+
|
|
38
|
+
- Any test that loads or asserts against real definitions in `/definitions`.
|
|
39
|
+
Those belong in `e2e/`.
|
|
40
|
+
- Pure structural checks that don't assert specific behavior. Those belong in
|
|
41
|
+
`smoke/`.
|
|
42
|
+
- Tests of a single file or class in isolation. Those are unit tests and belong
|
|
43
|
+
next to the code they cover.
|
|
44
|
+
|
|
45
|
+
The advantage of fixture-based tests: a definitions change cannot break them.
|
|
46
|
+
If you find yourself writing a test that needs to load `:ca` or `:us` directly,
|
|
47
|
+
ask whether the same behavior could be tested with a small fixture YAML, and
|
|
48
|
+
if so, write the fixture instead.
|
|
@@ -31,7 +31,7 @@ class CustomHolidaysTest < Test::Unit::TestCase
|
|
|
31
31
|
Holidays.on(Date.civil(2013,3,1), :custom_multiple_files_govt)
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
Holidays.load_custom('test/data/test_multiple_custom_holiday_defs.yaml', 'test/data/test_custom_govt_holiday_defs.yaml')
|
|
34
|
+
Holidays.load_custom('test/integration/data/test_multiple_custom_holiday_defs.yaml', 'test/integration/data/test_custom_govt_holiday_defs.yaml')
|
|
35
35
|
|
|
36
36
|
assert_not_equal [], Holidays.on(Date.civil(2013,10,5), :custom_multiple_files)
|
|
37
37
|
assert_not_equal [], Holidays.on(Date.civil(2013,3,1), :custom_multiple_files)
|
|
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
|
|
|
3
3
|
class CustomHolidaysTest < Test::Unit::TestCase
|
|
4
4
|
|
|
5
5
|
def test_custom_region_informal_day_parsing
|
|
6
|
-
Holidays.load_custom('test/data/test_custom_informal_holidays_defs.yaml')
|
|
6
|
+
Holidays.load_custom('test/integration/data/test_custom_informal_holidays_defs.yaml')
|
|
7
7
|
|
|
8
8
|
assert_not_equal [], Holidays.on(Date.new(2018,1,1), :custom_informal, :informal)
|
|
9
9
|
assert_equal [], Holidays.on(Date.new(2018,1,1), :custom_informal, :observed)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Smoke tests
|
|
2
|
+
|
|
3
|
+
Structural-only checks across every region loaded from `/definitions`. These tests
|
|
4
|
+
verify that the gem can load and process all definitions without raising errors.
|
|
5
|
+
They make no assertions about specific holiday names, dates, or counts.
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
These tests are run from the upstream `holidays/definitions` repo's CI as the
|
|
10
|
+
downstream check. The contract is narrow on purpose: a definitions change should
|
|
11
|
+
only fail this suite if it actually breaks the gem's ability to load or process
|
|
12
|
+
definitions, never because of intentional content changes (renaming a holiday,
|
|
13
|
+
adding a region, shifting a date, etc.).
|
|
14
|
+
|
|
15
|
+
## What belongs here
|
|
16
|
+
|
|
17
|
+
- Tests that exercise the gem's API surface (`Holidays.on`, `Holidays.between`,
|
|
18
|
+
flags like `:observed` and `:informal`) across all regions and assert only
|
|
19
|
+
that nothing raises.
|
|
20
|
+
- Tests that verify the *shape* of return values (e.g. `available_regions` is
|
|
21
|
+
an Array of Symbols).
|
|
22
|
+
- Cross-region invariants that hold regardless of content (e.g. `:any` count is
|
|
23
|
+
≥ each individual region's count).
|
|
24
|
+
|
|
25
|
+
## What does NOT belong here
|
|
26
|
+
|
|
27
|
+
- Any assertion about a specific holiday name, date, or count.
|
|
28
|
+
- Any check that depends on the contents of a particular region's YAML.
|
|
29
|
+
|
|
30
|
+
If a check would fail when someone legitimately renames "Labour Day" to
|
|
31
|
+
"Labor Day" in `ca.yaml`, it does not belong in `smoke/`. It belongs in `e2e/`.
|
|
@@ -15,9 +15,4 @@ class AvailableRegionsTests < Test::Unit::TestCase
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# This test might fail if we add new regions. Since this is an integration test
|
|
19
|
-
# I am fine with that!
|
|
20
|
-
def test_available_regions_returns_correct_number_of_regions
|
|
21
|
-
assert_equal 286, Holidays.available_regions.count
|
|
22
|
-
end
|
|
23
18
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/../test_helper'
|
|
2
|
+
|
|
3
|
+
class SmokeTests < Test::Unit::TestCase
|
|
4
|
+
SAMPLE_DATES = [
|
|
5
|
+
Date.civil(2023, 1, 1),
|
|
6
|
+
Date.civil(2023, 7, 4),
|
|
7
|
+
Date.civil(2023, 12, 25),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
def setup
|
|
11
|
+
Holidays.load_all
|
|
12
|
+
@regions = Holidays.available_regions
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_all_regions_load_without_error
|
|
16
|
+
assert @regions.is_a?(Array)
|
|
17
|
+
assert @regions.count > 0
|
|
18
|
+
assert @regions.all? { |r| r.is_a?(Symbol) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_holidays_on_does_not_raise_for_any_region
|
|
22
|
+
@regions.each do |region|
|
|
23
|
+
SAMPLE_DATES.each do |date|
|
|
24
|
+
assert_nothing_raised("Holidays.on raised for :#{region} on #{date}") do
|
|
25
|
+
result = Holidays.on(date, region)
|
|
26
|
+
assert result.is_a?(Array)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_holidays_between_does_not_raise_for_any_region
|
|
33
|
+
start_date = Date.civil(2023, 1, 1)
|
|
34
|
+
end_date = Date.civil(2023, 12, 31)
|
|
35
|
+
|
|
36
|
+
@regions.each do |region|
|
|
37
|
+
assert_nothing_raised("Holidays.between raised for :#{region}") do
|
|
38
|
+
result = Holidays.between(start_date, end_date, region)
|
|
39
|
+
assert result.is_a?(Array)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_observed_flag_does_not_raise_for_any_region
|
|
45
|
+
@regions.each do |region|
|
|
46
|
+
SAMPLE_DATES.each do |date|
|
|
47
|
+
assert_nothing_raised("Holidays.on with :observed raised for :#{region} on #{date}") do
|
|
48
|
+
Holidays.on(date, region, :observed)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_informal_flag_does_not_raise_for_any_region
|
|
55
|
+
@regions.each do |region|
|
|
56
|
+
SAMPLE_DATES.each do |date|
|
|
57
|
+
assert_nothing_raised("Holidays.on with :informal raised for :#{region} on #{date}") do
|
|
58
|
+
Holidays.on(date, region, :informal)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_any_region_count_meets_or_exceeds_each_individual_region
|
|
65
|
+
start_date = Date.civil(2023, 1, 1)
|
|
66
|
+
end_date = Date.civil(2023, 12, 31)
|
|
67
|
+
any_count = Holidays.between(start_date, end_date, :any).count
|
|
68
|
+
|
|
69
|
+
@regions.each do |region|
|
|
70
|
+
region_count = Holidays.between(start_date, end_date, region).count
|
|
71
|
+
assert any_count >= region_count, ":any count (#{any_count}) is less than :#{region} count (#{region_count})"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|