Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DDO-3280] TestData mechanism #348

Merged
merged 22 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions sherlock/internal/models/chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ func (s *modelSuite) TestChartValidationSqlValid() {
}

func (s *modelSuite) TestChartCiIdentifiers() {
chart := Chart{Name: "name", ChartRepo: utils.PointerTo("repo")}
s.NoError(s.DB.Create(&chart).Error)
chart := s.TestData.Chart_Leonardo()
ciIdentifier := chart.GetCiIdentifier()
s.NoError(s.DB.Create(&ciIdentifier).Error)
s.NotZero(ciIdentifier.ID)
Expand Down
6 changes: 1 addition & 5 deletions sherlock/internal/models/chart_version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,7 @@ func (s *modelSuite) TestChartVersionValid() {
}

func (s *modelSuite) TestChartVersionCiIdentifiers() {
s.SetNonSuitableTestUserForDB()
chart := Chart{Name: "name", ChartRepo: utils.PointerTo("repo")}
s.NoError(s.DB.Create(&chart).Error)
chartVersion := ChartVersion{ChartID: chart.ID, ChartVersion: "version"}
s.NoError(s.DB.Create(&chartVersion).Error)
chartVersion := s.TestData.ChartVersion_Leonardo_V1()
ciIdentifier := chartVersion.GetCiIdentifier()
s.NoError(s.DB.Create(&ciIdentifier).Error)
s.NotZero(ciIdentifier.ID)
Expand Down
124 changes: 124 additions & 0 deletions sherlock/internal/models/test_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package models

import (
"fmt"
"github.com/broadinstitute/sherlock/go-shared/pkg/utils"
"github.com/broadinstitute/sherlock/sherlock/internal/authentication/test_users"
"github.com/rs/zerolog/log"
)

// TestData offers convenience methods for example data for usage in testing.
// 1. The data returned from these methods will exist in the database, along
// with any necessary dependencies, at the time of the first return.
// 2. These methods cache within the context of a test function. Subsequent
// calls to a method will not contact the database.
type TestData interface {
User_Suitable() User
User_NonSuitable() User

Chart_Leonardo() Chart

ChartVersion_Leonardo_V1() ChartVersion
ChartVersion_Leonardo_V2() ChartVersion
}

// testDataImpl contains the caching for TestData and a (back-)reference to
// TestSuiteHelper to actually interact with the database. TestSuiteHelper
// uses testDataImpl to provide TestData in the context of a test function.
type testDataImpl struct {
h *TestSuiteHelper

user_suitable User
user_nonSuitable User

chart_leonardo Chart

chartVersion_leonardo_v1 ChartVersion
chartVersion_leonardo_v2 ChartVersion
}

// User_Suitable essentially defers to the authentication and
// authorization packages: it returns a User based on the
// authentication package's test_users.SuitableTestUserEmail,
// which the authorization package will recognize when appropriate.
//
// The benefit of this approach is the identity of the test suitable
// user is kept consistent, regardless of whether it comes from here
// or from mock authentication middleware
func (td *testDataImpl) User_Suitable() User {
if td.user_suitable.ID == 0 {
td.user_suitable = User{
Email: test_users.SuitableTestUserEmail,
GoogleID: test_users.SuitableTestUserGoogleID,
}
td.create(&td.user_suitable)
}
return td.user_suitable
}

// User_NonSuitable is like User_Suitable but for a non-suitable User
func (td *testDataImpl) User_NonSuitable() User {
if td.user_nonSuitable.ID == 0 {
td.user_nonSuitable = User{
Email: test_users.NonSuitableTestUserEmail,
GoogleID: test_users.NonSuitableTestUserGoogleID,
}
td.create(&td.user_nonSuitable)
}
return td.user_nonSuitable
}

func (td *testDataImpl) Chart_Leonardo() Chart {
if td.chart_leonardo.ID == 0 {
td.chart_leonardo = Chart{
Name: "leonardo",
ChartRepo: utils.PointerTo("terra-helm"),
AppImageGitRepo: utils.PointerTo("DataBiosphere/leonardo"),
AppImageGitMainBranch: utils.PointerTo("main"),
ChartExposesEndpoint: utils.PointerTo(true),
DefaultSubdomain: utils.PointerTo("leonardo"),
DefaultProtocol: utils.PointerTo("https"),
DefaultPort: utils.PointerTo[uint](443),
}
td.create(&td.chart_leonardo)
}
return td.chart_leonardo
}

func (td *testDataImpl) ChartVersion_Leonardo_V1() ChartVersion {
if td.chartVersion_leonardo_v1.ID == 0 {
td.chartVersion_leonardo_v1 = ChartVersion{
ChartID: td.Chart_Leonardo().ID,
ChartVersion: "0.1.0",
}
td.h.SetSuitableTestUserForDB()
td.create(&td.chartVersion_leonardo_v1)
}
return td.chartVersion_leonardo_v1
}

func (td *testDataImpl) ChartVersion_Leonardo_V2() ChartVersion {
if td.chartVersion_leonardo_v2.ID == 0 {
td.chartVersion_leonardo_v2 = ChartVersion{
ChartID: td.Chart_Leonardo().ID,
ChartVersion: "0.2.0",
ParentChartVersionID: utils.PointerTo(td.ChartVersion_Leonardo_V1().ID),
}
td.h.SetSuitableTestUserForDB()
td.create(&td.chartVersion_leonardo_v2)
}
return td.chartVersion_leonardo_v2
}

// create is a helper function for creating TestData entries in the database.
// It will forcibly exit if it encounters an error.
func (td *testDataImpl) create(pointer any) {
// We do FirstOrCreate on the off-chance that what we're inserting already exists.
// That'll basically never happen... except for when Sherlock is trying to be helpful.
// Middleware will upsert users, the database layer will auto-populate resources, etc.
if err := td.h.DB.Where(pointer).FirstOrCreate(pointer).Error; err != nil {
err = fmt.Errorf("error creating %T in TestData: %w", pointer, err)
log.Error().Err(err).Caller(2).Send()
panic(err)
}
}
43 changes: 17 additions & 26 deletions sherlock/internal/models/test_helper.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package models

import (
"github.com/broadinstitute/sherlock/go-shared/pkg/utils"
"github.com/broadinstitute/sherlock/sherlock/internal/authentication/authentication_method"
"github.com/broadinstitute/sherlock/sherlock/internal/authentication/test_users"
"github.com/broadinstitute/sherlock/sherlock/internal/config"
"github.com/broadinstitute/sherlock/sherlock/internal/db"
"gorm.io/gorm"
Expand All @@ -14,6 +14,7 @@ import (
type TestSuiteHelper struct {
DB *gorm.DB
internalDB *gorm.DB
TestData TestData
}

// SetupSuite runs once before all tests. It connects to the
Expand Down Expand Up @@ -64,46 +65,36 @@ func (h *TestSuiteHelper) SetupSuite() {
// changes.
func (h *TestSuiteHelper) SetupTest() {
h.DB = h.internalDB.Begin()
h.TestData = &testDataImpl{h: h}
}

// SetUserForDB is intended to be called from within a test function.
// It will upsert a User with the given email and googleID and
// set it as the DB's current user.
func (h *TestSuiteHelper) SetUserForDB(email, googleID string) *User {
var user User
if err := h.DB.
Where(&User{Email: email, GoogleID: googleID}).
FirstOrCreate(&user).Error; err != nil {
panic(err)
} else {
user.AuthenticationMethod = authentication_method.TEST
h.DB = SetCurrentUserForDB(h.DB, &user)
return &user
}
// SetUserForDB is a low-level helper function, setting with given user as
// the current principal for the database. You'll usually want to call
// SetSuitableTestUserForDB or SetNonSuitableTestUserForDB instead.
func (h *TestSuiteHelper) SetUserForDB(user *User) *User {
user.AuthenticationMethod = authentication_method.TEST
h.DB = SetCurrentUserForDB(h.DB, user)
return user
}

// SetSuitableTestUserForDB is intended to be called from within
// a test function. It calls UseUser with the suitable test user
// info from the test_users package, which will be recognized
// as suitable by the authorization package.
// SetSuitableTestUserForDB is a helper function, calling SetUserForDB with
// TestData.User_Suitable
func (h *TestSuiteHelper) SetSuitableTestUserForDB() *User {
return h.SetUserForDB(test_users.SuitableTestUserEmail, test_users.SuitableTestUserGoogleID)
return h.SetUserForDB(utils.PointerTo(h.TestData.User_Suitable()))
}

// SetNonSuitableTestUserForDB is intended to be called from
// within a test function. It calls UseUser with the
// non-suitable test user from the test_users package, which
// will be recognized but considered non-suitable by the
// authorization package.
// SetNonSuitableTestUserForDB is a helper function, calling SetUserForDB with
// TestData.User_NonSuitable
func (h *TestSuiteHelper) SetNonSuitableTestUserForDB() *User {
return h.SetUserForDB(test_users.NonSuitableTestUserEmail, test_users.NonSuitableTestUserGoogleID)
return h.SetUserForDB(utils.PointerTo(h.TestData.User_NonSuitable()))
}

// TearDownTest takes advantage of SetupTest having begun a
// transaction to roll back the entire test's changes. It
// sets the modelSuite's main database reference to nil to
// help surface any concurrency issues.
func (h *TestSuiteHelper) TearDownTest() {
h.TestData = nil
h.DB.Rollback()
h.DB = nil
}
Expand Down
Loading