@@ -2,10 +2,16 @@ package verifier
22
33import (
44 "testing"
5+ "time"
56
67 common "github.com/fiware/VCVerifier/common"
78)
89
10+ // fixedClock is a test double that always returns the configured instant.
11+ type fixedClock struct { t time.Time }
12+
13+ func (fc fixedClock ) Now () time.Time { return fc .t }
14+
915func TestGetKeyFromMethod (t * testing.T ) {
1016 type test struct {
1117 testName string
@@ -224,3 +230,149 @@ func TestSupportedModes(t *testing.T) {
224230 }
225231 }
226232}
233+
234+ // ---------------------------------------------------------------------------
235+ // Temporal validity tests
236+ // ---------------------------------------------------------------------------
237+
238+ // baseTime is a fixed "now" used across all temporal tests so results are deterministic.
239+ var baseTime = time .Date (2024 , 6 , 15 , 12 , 0 , 0 , 0 , time .UTC )
240+
241+ func makeCredential (validFrom , validUntil * time.Time ) * common.Credential {
242+ c , _ := common .CreateCredential (common.CredentialContents {
243+ Issuer : & common.Issuer {ID : "did:web:example.com" },
244+ Types : []string {"VerifiableCredential" },
245+ Subject : []common.Subject {{CustomFields : map [string ]interface {}{"name" : "test" }}},
246+ ValidFrom : validFrom ,
247+ ValidUntil : validUntil ,
248+ }, common.CustomFields {})
249+ return c
250+ }
251+
252+ func tp (t time.Time ) * time.Time { return & t }
253+
254+ func TestValidateCredentialContent_TemporalValidity (t * testing.T ) {
255+ past := baseTime .Add (- 24 * time .Hour )
256+ future := baseTime .Add (24 * time .Hour )
257+
258+ tests := []struct {
259+ name string
260+ validFrom * time.Time
261+ validUntil * time.Time
262+ wantErr error
263+ }{
264+ {
265+ name : "no_dates_always_valid" ,
266+ wantErr : nil ,
267+ },
268+ {
269+ name : "valid_from_past_no_expiry" ,
270+ validFrom : tp (past ),
271+ wantErr : nil ,
272+ },
273+ {
274+ name : "valid_until_future_no_issued" ,
275+ validUntil : tp (future ),
276+ wantErr : nil ,
277+ },
278+ {
279+ name : "both_in_valid_window" ,
280+ validFrom : tp (past ),
281+ validUntil : tp (future ),
282+ wantErr : nil ,
283+ },
284+ {
285+ name : "expired_credential" ,
286+ validFrom : tp (past .Add (- 48 * time .Hour )),
287+ validUntil : tp (past ),
288+ wantErr : ErrorCredentialExpired ,
289+ },
290+ {
291+ name : "not_yet_valid" ,
292+ validFrom : tp (future ),
293+ wantErr : ErrorCredentialNotYetValid ,
294+ },
295+ {
296+ name : "not_yet_valid_with_future_expiry" ,
297+ validFrom : tp (future ),
298+ validUntil : tp (future .Add (24 * time .Hour )),
299+ wantErr : ErrorCredentialNotYetValid ,
300+ },
301+ }
302+
303+ for _ , mode := range []string {ValidationModeCombined , ValidationModeJsonLd , ValidationModeBaseContext } {
304+ for _ , tc := range tests {
305+ t .Run (mode + "/" + tc .name , func (t * testing.T ) {
306+ cred := makeCredential (tc .validFrom , tc .validUntil )
307+ validator := CredentialValidator {validationMode : mode , clock : fixedClock {t : baseTime }}
308+ _ , err := validator .ValidateVC (cred , nil )
309+ if tc .wantErr != nil {
310+ if err == nil {
311+ t .Fatalf ("expected error %v, got nil" , tc .wantErr )
312+ }
313+ if ! isErr (err , tc .wantErr ) {
314+ t .Fatalf ("expected error %v, got %v" , tc .wantErr , err )
315+ }
316+ } else if err != nil {
317+ t .Fatalf ("expected no error, got %v" , err )
318+ }
319+ })
320+ }
321+ }
322+ }
323+
324+ func TestValidateCredentialContent_NoneMode_StillChecksDates (t * testing.T ) {
325+ past := baseTime .Add (- 1 * time .Hour )
326+ // Even in "none" mode, expired credentials must be rejected.
327+ cred := makeCredential (nil , tp (past ))
328+ validator := CredentialValidator {validationMode : ValidationModeNone , clock : fixedClock {t : baseTime }}
329+ result , err := validator .ValidateVC (cred , nil )
330+ if result || ! isErr (err , ErrorCredentialExpired ) {
331+ t .Fatalf ("none mode should still reject expired credential, got result=%v err=%v" , result , err )
332+ }
333+ }
334+
335+ func TestValidateCredentialContent_ExactBoundary (t * testing.T ) {
336+ // validFrom == now is still valid (inclusive).
337+ fromCred := makeCredential (tp (baseTime ), nil )
338+ fromValidator := CredentialValidator {validationMode : ValidationModeCombined , clock : fixedClock {t : baseTime }}
339+ if _ , err := fromValidator .ValidateVC (fromCred , nil ); err != nil {
340+ t .Fatalf ("credential starting exactly at now should be valid, got %v" , err )
341+ }
342+
343+ // validUntil == now is still valid (inclusive).
344+ untilCred := makeCredential (nil , tp (baseTime ))
345+ untilValidator := CredentialValidator {validationMode : ValidationModeCombined , clock : fixedClock {t : baseTime }}
346+ if _ , err := untilValidator .ValidateVC (untilCred , nil ); err != nil {
347+ t .Fatalf ("credential expiring exactly at now should be valid, got %v" , err )
348+ }
349+ }
350+
351+ func TestValidateCredentialContent_ZeroLengthValidityPeriod (t * testing.T ) {
352+ // validFrom == validUntil is always rejected, regardless of now.
353+ cred := makeCredential (tp (baseTime ), tp (baseTime ))
354+ validator := CredentialValidator {validationMode : ValidationModeCombined , clock : fixedClock {t : baseTime }}
355+ _ , err := validator .ValidateVC (cred , nil )
356+ if ! isErr (err , ErrorCredentialInvalidValidityPeriod ) {
357+ t .Fatalf ("credential with validFrom == validUntil should be rejected, got %v" , err )
358+ }
359+ }
360+
361+ // isErr reports whether err wraps or equals target.
362+ func isErr (err , target error ) bool {
363+ if err == target {
364+ return true
365+ }
366+ type unwrapper interface { Unwrap () error }
367+ for err != nil {
368+ if err == target {
369+ return true
370+ }
371+ u , ok := err .(unwrapper )
372+ if ! ok {
373+ break
374+ }
375+ err = u .Unwrap ()
376+ }
377+ return false
378+ }
0 commit comments