44 "context"
55 "crypto/ed25519"
66 "encoding/hex"
7+ "fmt"
78 "strings"
89 "testing"
910 "time"
@@ -54,7 +55,7 @@ type MockSigner struct {
5455 mock.Mock
5556}
5657
57- func (m * MockSigner ) Sign (ctx context.Context , keyID [] byte , data []byte ) ([]byte , error ) {
58+ func (m * MockSigner ) Sign (ctx context.Context , keyID string , data []byte ) ([]byte , error ) {
5859 args := m .Called (ctx , keyID , data )
5960 return args .Get (0 ).([]byte ), args .Error (1 )
6061}
@@ -71,13 +72,13 @@ func TestRotatingAuth(t *testing.T) {
7172 dummySignature := ed25519 .Sign (privKey , []byte ("test data" ))
7273
7374 mockSigner .
74- On ("Sign" , mock .Anything , mock .MatchedBy (func (keyID [] byte ) bool {
75- return string ( keyID ) == string (pubKey ) // Verify correct public key is passed
75+ On ("Sign" , mock .Anything , mock .MatchedBy (func (keyID string ) bool {
76+ return keyID == hex . EncodeToString (pubKey ) // Verify correct public key hex is passed
7677 }), mock .Anything ).
7778 Return (dummySignature , nil )
7879
7980 ttl := 5 * time .Minute
80- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
81+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
8182
8283 headers , err := auth .Headers (t .Context ())
8384 require .NoError (t , err )
@@ -112,7 +113,7 @@ func TestRotatingAuth(t *testing.T) {
112113 Maybe ()
113114
114115 ttl := 5 * time .Minute
115- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
116+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
116117
117118 headers1 , err := auth .Headers (t .Context ())
118119 require .NoError (t , err )
@@ -135,7 +136,7 @@ func TestRotatingAuth(t *testing.T) {
135136 Return ([]byte {}, expectedErr )
136137
137138 ttl := 5 * time .Minute
138- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
139+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
139140
140141 headers , err := auth .Headers (t .Context ())
141142 require .Error (t , err )
@@ -157,7 +158,7 @@ func TestRotatingAuth(t *testing.T) {
157158 Maybe ()
158159
159160 ttl := 5 * time .Minute
160- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
161+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
161162
162163 creds := auth .Credentials ()
163164 require .NotNil (t , creds )
@@ -183,16 +184,52 @@ func TestRotatingAuth(t *testing.T) {
183184
184185 ttl := 5 * time .Minute
185186 // transport security required
186- authSecure := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , true )
187+ authSecure := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , true , nil )
187188 credsSecure := authSecure .Credentials ()
188189 assert .True (t , credsSecure .RequireTransportSecurity ())
189190 // transport security not required
190- authInsecure := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
191+ authInsecure := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
191192 credsInsecure := authInsecure .Credentials ()
192193 assert .False (t , credsInsecure .RequireTransportSecurity ())
193194
194195 mockSigner .AssertExpectations (t )
195196 })
197+
198+ t .Run ("uses initial headers until TTL expires" , func (t * testing.T ) {
199+ mockSigner := & MockSigner {}
200+
201+ // Create initial headers with v2 format
202+ ts := time .Now ()
203+ signature := ed25519 .Sign (privKey , []byte ("initial" ))
204+ initialHeaders := map [string ]string {
205+ "X-Beholder-Node-Auth-Token" : "2:" + hex .EncodeToString (pubKey ) + ":" + fmt .Sprintf ("%d" , ts .UnixNano ()) + ":" + hex .EncodeToString (signature ),
206+ }
207+
208+ // Use a very short TTL so it expires quickly
209+ ttl := 1 * time .Millisecond
210+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , initialHeaders )
211+
212+ // First call should return the initial headers without calling Sign
213+ headers1 , err := auth .Headers (t .Context ())
214+ require .NoError (t , err )
215+ assert .Equal (t , initialHeaders , headers1 )
216+
217+ // Wait for TTL to expire
218+ time .Sleep (5 * time .Millisecond )
219+
220+ // Now the signer should be called to generate new headers
221+ newSignature := ed25519 .Sign (privKey , []byte ("new" ))
222+ mockSigner .
223+ On ("Sign" , mock .Anything , mock .Anything , mock .Anything ).
224+ Return (newSignature , nil ).
225+ Once ()
226+
227+ headers2 , err := auth .Headers (t .Context ())
228+ require .NoError (t , err )
229+ assert .NotEqual (t , initialHeaders , headers2 , "Should generate new headers after TTL expires" )
230+
231+ mockSigner .AssertExpectations (t )
232+ })
196233}
197234
198235// BenchmarkRotatingAuth_Headers_CachedPath benchmarks the fast path where headers are cached and within TTL.
@@ -212,7 +249,7 @@ func BenchmarkRotatingAuth_Headers_CachedPath(b *testing.B) {
212249
213250 // Use a long TTL so headers don't expire during the benchmark
214251 ttl := 1 * time .Hour
215- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
252+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
216253
217254 // Prime the cache by calling Headers once
218255 ctx := b .Context ()
@@ -249,7 +286,7 @@ func BenchmarkRotatingAuth_Headers_ExpiredPath(b *testing.B) {
249286
250287 // Use a TTL of 0 to force regeneration on every call
251288 ttl := 0 * time .Second
252- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
289+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
253290
254291 ctx := b .Context ()
255292
@@ -283,7 +320,7 @@ func BenchmarkRotatingAuth_Headers_ParallelCached(b *testing.B) {
283320
284321 // Use a long TTL so headers don't expire during the benchmark
285322 ttl := 1 * time .Hour
286- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
323+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
287324
288325 // Prime the cache
289326 ctx := b .Context ()
@@ -323,7 +360,7 @@ func BenchmarkRotatingAuth_Headers_ParallelExpired(b *testing.B) {
323360
324361 // Use a short TTL to cause periodic regeneration
325362 ttl := 10 * time .Millisecond
326- auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false )
363+ auth := beholder .NewRotatingAuth (pubKey , mockSigner , ttl , false , nil )
327364
328365 ctx := b .Context ()
329366
0 commit comments