diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go index a2458680..dcc765c9 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go @@ -18,12 +18,10 @@ import ( // ensure that values being tested on never expire. var neverExpire = time.Now().UTC().Add(time.Hour * 24 * 365 * 100) -// StorageFactory is a method for creating a new storage. The returned storage sould be initialized -// but shouldn't have any existing data in it. -type StorageFactory func() storage.Storage - -// RunTestSuite runs a set of conformance tests against a storage. -func RunTestSuite(t *testing.T, sf StorageFactory) { +// RunTests runs a set of conformance tests against a storage. newStorage should +// return an initialized but empty storage. The storage will be closed at the +// end of each test run. +func RunTests(t *testing.T, newStorage func() storage.Storage) { tests := []struct { name string run func(t *testing.T, s storage.Storage) @@ -33,10 +31,13 @@ func RunTestSuite(t *testing.T, sf StorageFactory) { {"ClientCRUD", testClientCRUD}, {"RefreshTokenCRUD", testRefreshTokenCRUD}, {"PasswordCRUD", testPasswordCRUD}, + {"GarbageCollection", testGC}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - test.run(t, sf()) + s := newStorage() + test.run(t, s) + s.Close() }) } } @@ -276,3 +277,92 @@ func testPasswordCRUD(t *testing.T, s storage.Storage) { t.Errorf("after deleting password expected storage.ErrNotFound, got %v", err) } } + +func testGC(t *testing.T, s storage.Storage) { + n := time.Now() + c := storage.AuthCode{ + ID: storage.NewID(), + ClientID: "foobar", + RedirectURI: "https://localhost:80/callback", + Nonce: "foobar", + Scopes: []string{"openid", "email"}, + Expiry: n.Add(time.Second), + ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), + Claims: storage.Claims{ + UserID: "1", + Username: "jane", + Email: "jane.doe@example.com", + EmailVerified: true, + Groups: []string{"a", "b"}, + }, + } + + if err := s.CreateAuthCode(c); err != nil { + t.Fatalf("failed creating auth code: %v", err) + } + + if _, err := s.GarbageCollect(n); err != nil { + t.Errorf("garbage collection failed: %v", err) + } + if _, err := s.GetAuthCode(c.ID); err != nil { + t.Errorf("expected to be able to get auth code after GC: %v", err) + } + + if r, err := s.GarbageCollect(n.Add(time.Minute)); err != nil { + t.Errorf("garbage collection failed: %v", err) + } else if r.AuthCodes != 1 { + t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthCodes) + } + + if _, err := s.GetAuthCode(c.ID); err == nil { + t.Errorf("expected auth code to be GC'd") + } else if err != storage.ErrNotFound { + t.Errorf("expected storage.ErrNotFound, got %v", err) + } + + a := storage.AuthRequest{ + ID: storage.NewID(), + ClientID: "foobar", + ResponseTypes: []string{"code"}, + Scopes: []string{"openid", "email"}, + RedirectURI: "https://localhost:80/callback", + Nonce: "foo", + State: "bar", + ForceApprovalPrompt: true, + LoggedIn: true, + Expiry: n, + ConnectorID: "ldap", + ConnectorData: []byte(`{"some":"data"}`), + Claims: storage.Claims{ + UserID: "1", + Username: "jane", + Email: "jane.doe@example.com", + EmailVerified: true, + Groups: []string{"a", "b"}, + }, + } + + if err := s.CreateAuthRequest(a); err != nil { + t.Fatalf("failed creating auth request: %v", err) + } + + if _, err := s.GarbageCollect(n); err != nil { + t.Errorf("garbage collection failed: %v", err) + } + if _, err := s.GetAuthRequest(a.ID); err != nil { + t.Errorf("expected to be able to get auth code after GC: %v", err) + } + + if r, err := s.GarbageCollect(n.Add(time.Minute)); err != nil { + t.Errorf("garbage collection failed: %v", err) + } else if r.AuthRequests != 1 { + t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthRequests) + } + + if _, err := s.GetAuthRequest(a.ID); err == nil { + t.Errorf("expected auth code to be GC'd") + } else if err != storage.ErrNotFound { + t.Errorf("expected storage.ErrNotFound, got %v", err) + } +}