@@ -34,6 +34,66 @@ module RotationCoordinatorTests
34
34
assert_nil roundtrip ( "message" , codec , obsolete_codec )
35
35
end
36
36
37
+ test "raises when building a codec and no rotations are configured" do
38
+ assert_raises { make_coordinator [ "salt" ] }
39
+ end
40
+
41
+ test "#rotate supports a block" do
42
+ coordinator = make_coordinator . rotate do |salt |
43
+ { digest : salt == "salt" ? "SHA1" : "MD5" }
44
+ end
45
+
46
+ sha1_coordinator = make_coordinator . rotate ( digest : "SHA1" )
47
+ md5_coordinator = make_coordinator . rotate ( digest : "MD5" )
48
+
49
+ assert_equal "message" , roundtrip ( "message" , coordinator [ "salt" ] , sha1_coordinator [ "salt" ] )
50
+ assert_nil roundtrip ( "message" , coordinator [ "salt" ] , md5_coordinator [ "salt" ] )
51
+
52
+ assert_equal "message" , roundtrip ( "message" , coordinator [ "other salt" ] , md5_coordinator [ "other salt" ] )
53
+ assert_nil roundtrip ( "message" , coordinator [ "other salt" ] , sha1_coordinator [ "other salt" ] )
54
+ end
55
+
56
+ test "#rotate block receives salt in its original form" do
57
+ coordinator = make_coordinator . rotate do |salt |
58
+ assert_equal :salt , salt
59
+ { }
60
+ end
61
+
62
+ coordinator [ :salt ]
63
+ end
64
+
65
+ test "#rotate raises when both a block and options are provided" do
66
+ assert_raises ArgumentError do
67
+ make_coordinator . rotate ( digest : "MD5" ) { { } }
68
+ end
69
+ end
70
+
71
+ test "#rotate block can return nil to skip a rotation for specific salts" do
72
+ coordinator = make_coordinator . rotate ( digest : "SHA1" )
73
+ coordinator . rotate do |salt |
74
+ { digest : "MD5" } if salt == "salt"
75
+ end
76
+
77
+ sha1_coordinator = make_coordinator . rotate ( digest : "SHA1" )
78
+ md5_coordinator = make_coordinator . rotate ( digest : "MD5" )
79
+
80
+ assert_equal "message" , roundtrip ( "message" , sha1_coordinator [ "salt" ] , coordinator [ "salt" ] )
81
+ assert_equal "message" , roundtrip ( "message" , md5_coordinator [ "salt" ] , coordinator [ "salt" ] )
82
+
83
+ assert_equal "message" , roundtrip ( "message" , sha1_coordinator [ "other salt" ] , coordinator [ "other salt" ] )
84
+ assert_nil roundtrip ( "message" , md5_coordinator [ "other salt" ] , coordinator [ "other salt" ] )
85
+ end
86
+
87
+ test "raises when building a codec and no rotations are configured for a specific salt" do
88
+ coordinator = make_coordinator . rotate do |salt |
89
+ { digest : "MD5" } if salt == "salt"
90
+ end
91
+
92
+ assert_nothing_raised { coordinator [ "salt" ] }
93
+ error = assert_raises { coordinator [ "other salt" ] }
94
+ assert_match "other salt" , error . message
95
+ end
96
+
37
97
test "#transitional swaps the first two rotations when enabled" do
38
98
coordinator = make_coordinator . rotate ( digest : "SHA1" )
39
99
coordinator . rotate ( digest : "MD5" )
@@ -65,6 +125,48 @@ module RotationCoordinatorTests
65
125
end
66
126
end
67
127
128
+ test "#transitional treats a nil first rotation as a new rotation" do
129
+ coordinator = make_coordinator
130
+ coordinator . rotate do |salt | # (3) Finally, one salt upgraded to SHA1
131
+ { digest : "SHA1" } if salt == "salt"
132
+ end
133
+ coordinator . rotate ( digest : "MD5" ) # (2) Then, everything upgraded to MD5
134
+ coordinator . rotate ( digest : "MD4" ) # (1) Originally, everything used MD4
135
+ coordinator . transitional = true
136
+
137
+ sha1_coordinator = make_coordinator . rotate ( digest : "SHA1" )
138
+ md5_coordinator = make_coordinator . rotate ( digest : "MD5" )
139
+
140
+ # "salt" encodes with MD5 and can decode SHA1 (i.e. [SHA1, MD5, MD4] => [MD5, SHA1, MD4])
141
+ assert_equal "message" , roundtrip ( "message" , coordinator [ "salt" ] , md5_coordinator [ "salt" ] )
142
+ assert_equal "message" , roundtrip ( "message" , sha1_coordinator [ "salt" ] , coordinator [ "salt" ] )
143
+
144
+ # "other salt" encodes with MD5 and cannot decode SHA1 (i.e. [nil, MD5, MD4] => [MD5, MD4])
145
+ assert_equal "message" , roundtrip ( "message" , coordinator [ "other salt" ] , md5_coordinator [ "other salt" ] )
146
+ assert_nil roundtrip ( "message" , sha1_coordinator [ "other salt" ] , coordinator [ "other salt" ] )
147
+ end
148
+
149
+ test "#transitional swaps the first rotation with the next non-nil rotation" do
150
+ coordinator = make_coordinator
151
+ coordinator . rotate ( digest : "SHA1" ) # (3) Finally, everything upgraded to SHA1
152
+ coordinator . rotate do |salt | # (2) Then, one salt upgraded to SHA1
153
+ { digest : "SHA1" } if salt == "salt"
154
+ end
155
+ coordinator . rotate ( digest : "MD5" ) # (1) Originally, everything used MD5
156
+ coordinator . transitional = true
157
+
158
+ sha1_coordinator = make_coordinator . rotate ( digest : "SHA1" )
159
+ md5_coordinator = make_coordinator . rotate ( digest : "MD5" )
160
+
161
+ # "salt" encodes with SHA1 and can decode SHA1 (i.e. [SHA1, SHA1, MD5] => [SHA1, MD5])
162
+ assert_equal "message" , roundtrip ( "message" , coordinator [ "salt" ] , sha1_coordinator [ "salt" ] )
163
+ assert_equal "message" , roundtrip ( "message" , sha1_coordinator [ "salt" ] , coordinator [ "salt" ] )
164
+
165
+ # "other salt" encodes with MD5 and can decode SHA1 (i.e. [SHA1, nil, MD5] => [MD5, SHA1])
166
+ assert_equal "message" , roundtrip ( "message" , coordinator [ "other salt" ] , md5_coordinator [ "other salt" ] )
167
+ assert_equal "message" , roundtrip ( "message" , sha1_coordinator [ "other salt" ] , coordinator [ "other salt" ] )
168
+ end
169
+
68
170
test "can clear rotations" do
69
171
@coordinator . clear_rotations . rotate ( digest : "MD5" )
70
172
codec = @coordinator [ "salt" ]
@@ -84,6 +186,24 @@ module RotationCoordinatorTests
84
186
assert_equal 1 , rotated
85
187
end
86
188
189
+ test "rotation options are deduped" do
190
+ coordinator = make_coordinator
191
+ coordinator . rotate ( digest : "SHA1" ) # (3) Finally, everything upgraded to SHA1
192
+ coordinator . rotate do |salt | # (2) Then, one salt upgraded to SHA1
193
+ { digest : "SHA1" } if salt == "salt"
194
+ end
195
+ coordinator . rotate ( digest : "MD5" ) # (1) Originally, everything used MD5
196
+
197
+ rotated = 0
198
+ coordinator . on_rotation { rotated += 1 }
199
+
200
+ codec = coordinator [ "salt" ]
201
+ md5_codec = ( make_coordinator . rotate ( digest : "MD5" ) ) [ "salt" ]
202
+
203
+ assert_equal "message" , roundtrip ( "message" , md5_codec , codec )
204
+ assert_equal 1 , rotated # SHA1 tried only once
205
+ end
206
+
87
207
test "prevents adding a rotation after rotations have been applied" do
88
208
@coordinator [ "salt" ]
89
209
assert_raises { @coordinator . rotate ( digest : "MD5" ) }
@@ -98,10 +218,5 @@ module RotationCoordinatorTests
98
218
@coordinator [ "salt" ]
99
219
assert_raises { @coordinator . on_rotation { "this block will not be evaluated" } }
100
220
end
101
-
102
- test "raises when building an codec and no rotations are configured" do
103
- @coordinator . clear_rotations
104
- assert_raises { @coordinator [ "salt" ] }
105
- end
106
221
end
107
222
end
0 commit comments