- Notifications
You must be signed in to change notification settings - Fork928
feat: Add database data generator to make fakedbs easier to populate#5922
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Merged
Uh oh!
There was an error while loading.Please reload this page.
Merged
Changes fromall commits
Commits
Show all changes
21 commits Select commitHold shift + click to select a range
50ea251
feat: Add database data generator to make fakedbs easier to populate
Emyrk722194b
Add resources and jobS
Emyrk38eb88e
Support api keys in generator
Emyrkd4fe6b7
Begin refactoring api key tests with generator
Emyrkd423ba2
Refactor api tests to use fake generator
Emyrkfac054a
Refactor workspace apps test
Emyrk387c1bd
Remove unused function
Emyrk50cba39
Refactor provsionerdserver tests
Emyrk396aa4a
Refactor prom metrics test
Emyrk5648499
Add unit tests for generator
Emyrk298ca3e
Remove takeFirstTime
Emyrk3bfd4d6
Add more randomness to names
Emyrk7fc8ead
Use fatal over panic
Emyrk5ba3463
Remove use of panics
Emyrkbc88e41
Remove all state from generator
Emyrk1113d8f
Move generate functions and no more generics
Emyrk5cd9acd
Move to database gen package
Emyrke7d2b5f
Fix all refactored tests to use new methods
Emyrk5e12b78
Remove unused ctx
Emyrk144ef40
Add comment about unused fields
Emyrk61ca1ca
rename to dbgen
EmyrkFile filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
225 changes: 225 additions & 0 deletionscoderd/database/dbgen/generator.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
package dbgen | ||
import ( | ||
"context" | ||
"crypto/sha256" | ||
"database/sql" | ||
"encoding/hex" | ||
"fmt" | ||
"testing" | ||
"time" | ||
"github.com/coder/coder/cryptorand" | ||
"github.com/tabbed/pqtype" | ||
"github.com/coder/coder/coderd/database" | ||
"github.com/google/uuid" | ||
"github.com/moby/moby/pkg/namesgenerator" | ||
"github.com/stretchr/testify/require" | ||
) | ||
// All methods take in a 'seed' object. Any provided fields in the seed will be | ||
// maintained. Any fields omitted will have sensible defaults generated. | ||
func Template(t *testing.T, db database.Store, seed database.Template) database.Template { | ||
template, err := db.InsertTemplate(context.Background(), database.InsertTemplateParams{ | ||
ID: takeFirst(seed.ID, uuid.New()), | ||
CreatedAt: takeFirst(seed.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(seed.UpdatedAt, time.Now()), | ||
OrganizationID: takeFirst(seed.OrganizationID, uuid.New()), | ||
Name: takeFirst(seed.Name, namesgenerator.GetRandomName(1)), | ||
Provisioner: takeFirst(seed.Provisioner, database.ProvisionerTypeEcho), | ||
ActiveVersionID: takeFirst(seed.ActiveVersionID, uuid.New()), | ||
Description: takeFirst(seed.Description, namesgenerator.GetRandomName(1)), | ||
DefaultTTL: takeFirst(seed.DefaultTTL, 3600), | ||
CreatedBy: takeFirst(seed.CreatedBy, uuid.New()), | ||
Icon: takeFirst(seed.Icon, namesgenerator.GetRandomName(1)), | ||
UserACL: seed.UserACL, | ||
GroupACL: seed.GroupACL, | ||
DisplayName: takeFirst(seed.DisplayName, namesgenerator.GetRandomName(1)), | ||
AllowUserCancelWorkspaceJobs: takeFirst(seed.AllowUserCancelWorkspaceJobs, true), | ||
}) | ||
require.NoError(t, err, "insert template") | ||
return template | ||
} | ||
func APIKey(t *testing.T, db database.Store, seed database.APIKey) (key database.APIKey, token string) { | ||
id, _ := cryptorand.String(10) | ||
secret, _ := cryptorand.String(22) | ||
hashed := sha256.Sum256([]byte(secret)) | ||
key, err := db.InsertAPIKey(context.Background(), database.InsertAPIKeyParams{ | ||
ID: takeFirst(seed.ID, id), | ||
// 0 defaults to 86400 at the db layer | ||
LifetimeSeconds: takeFirst(seed.LifetimeSeconds, 0), | ||
HashedSecret: takeFirstBytes(seed.HashedSecret, hashed[:]), | ||
IPAddress: pqtype.Inet{}, | ||
UserID: takeFirst(seed.UserID, uuid.New()), | ||
LastUsed: takeFirst(seed.LastUsed, time.Now()), | ||
ExpiresAt: takeFirst(seed.ExpiresAt, time.Now().Add(time.Hour)), | ||
CreatedAt: takeFirst(seed.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(seed.UpdatedAt, time.Now()), | ||
LoginType: takeFirst(seed.LoginType, database.LoginTypePassword), | ||
Scope: takeFirst(seed.Scope, database.APIKeyScopeAll), | ||
}) | ||
require.NoError(t, err, "insert api key") | ||
return key, fmt.Sprintf("%s-%s", key.ID, secret) | ||
} | ||
func Workspace(t *testing.T, db database.Store, orig database.Workspace) database.Workspace { | ||
workspace, err := db.InsertWorkspace(context.Background(), database.InsertWorkspaceParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
OwnerID: takeFirst(orig.OwnerID, uuid.New()), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(orig.UpdatedAt, time.Now()), | ||
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), | ||
TemplateID: takeFirst(orig.TemplateID, uuid.New()), | ||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), | ||
AutostartSchedule: orig.AutostartSchedule, | ||
Ttl: orig.Ttl, | ||
}) | ||
require.NoError(t, err, "insert workspace") | ||
return workspace | ||
} | ||
func WorkspaceBuild(t *testing.T, db database.Store, orig database.WorkspaceBuild) database.WorkspaceBuild { | ||
build, err := db.InsertWorkspaceBuild(context.Background(), database.InsertWorkspaceBuildParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(orig.UpdatedAt, time.Now()), | ||
WorkspaceID: takeFirst(orig.WorkspaceID, uuid.New()), | ||
TemplateVersionID: takeFirst(orig.TemplateVersionID, uuid.New()), | ||
BuildNumber: takeFirst(orig.BuildNumber, 0), | ||
Transition: takeFirst(orig.Transition, database.WorkspaceTransitionStart), | ||
InitiatorID: takeFirst(orig.InitiatorID, uuid.New()), | ||
JobID: takeFirst(orig.JobID, uuid.New()), | ||
ProvisionerState: takeFirstBytes(orig.ProvisionerState, []byte{}), | ||
Deadline: takeFirst(orig.Deadline, time.Now().Add(time.Hour)), | ||
Reason: takeFirst(orig.Reason, database.BuildReasonInitiator), | ||
}) | ||
require.NoError(t, err, "insert workspace build") | ||
return build | ||
} | ||
func User(t *testing.T, db database.Store, orig database.User) database.User { | ||
user, err := db.InsertUser(context.Background(), database.InsertUserParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
Email: takeFirst(orig.Email, namesgenerator.GetRandomName(1)), | ||
Username: takeFirst(orig.Username, namesgenerator.GetRandomName(1)), | ||
HashedPassword: takeFirstBytes(orig.HashedPassword, []byte{}), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(orig.UpdatedAt, time.Now()), | ||
RBACRoles: []string{}, | ||
LoginType: takeFirst(orig.LoginType, database.LoginTypePassword), | ||
}) | ||
require.NoError(t, err, "insert user") | ||
return user | ||
} | ||
func Organization(t *testing.T, db database.Store, orig database.Organization) database.Organization { | ||
org, err := db.InsertOrganization(context.Background(), database.InsertOrganizationParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), | ||
Description: takeFirst(orig.Description, namesgenerator.GetRandomName(1)), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(orig.UpdatedAt, time.Now()), | ||
}) | ||
require.NoError(t, err, "insert organization") | ||
return org | ||
} | ||
func Group(t *testing.T, db database.Store, orig database.Group) database.Group { | ||
group, err := db.InsertGroup(context.Background(), database.InsertGroupParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), | ||
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), | ||
AvatarURL: takeFirst(orig.AvatarURL, "https://logo.example.com"), | ||
QuotaAllowance: takeFirst(orig.QuotaAllowance, 0), | ||
}) | ||
require.NoError(t, err, "insert group") | ||
return group | ||
} | ||
func ProvisionerJob(t *testing.T, db database.Store, orig database.ProvisionerJob) database.ProvisionerJob { | ||
job, err := db.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(orig.UpdatedAt, time.Now()), | ||
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), | ||
InitiatorID: takeFirst(orig.InitiatorID, uuid.New()), | ||
Provisioner: takeFirst(orig.Provisioner, database.ProvisionerTypeEcho), | ||
StorageMethod: takeFirst(orig.StorageMethod, database.ProvisionerStorageMethodFile), | ||
FileID: takeFirst(orig.FileID, uuid.New()), | ||
Type: takeFirst(orig.Type, database.ProvisionerJobTypeWorkspaceBuild), | ||
Input: takeFirstBytes(orig.Input, []byte("{}")), | ||
Tags: orig.Tags, | ||
}) | ||
require.NoError(t, err, "insert job") | ||
return job | ||
} | ||
func WorkspaceResource(t *testing.T, db database.Store, orig database.WorkspaceResource) database.WorkspaceResource { | ||
resource, err := db.InsertWorkspaceResource(context.Background(), database.InsertWorkspaceResourceParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
JobID: takeFirst(orig.JobID, uuid.New()), | ||
Transition: takeFirst(orig.Transition, database.WorkspaceTransitionStart), | ||
Type: takeFirst(orig.Type, "fake_resource"), | ||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), | ||
Hide: takeFirst(orig.Hide, false), | ||
Icon: takeFirst(orig.Icon, ""), | ||
InstanceType: sql.NullString{ | ||
String: takeFirst(orig.InstanceType.String, ""), | ||
Valid: takeFirst(orig.InstanceType.Valid, false), | ||
}, | ||
DailyCost: takeFirst(orig.DailyCost, 0), | ||
}) | ||
require.NoError(t, err, "insert resource") | ||
return resource | ||
} | ||
func File(t *testing.T, db database.Store, orig database.File) database.File { | ||
file, err := db.InsertFile(context.Background(), database.InsertFileParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
Hash: takeFirst(orig.Hash, hex.EncodeToString(make([]byte, 32))), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
CreatedBy: takeFirst(orig.CreatedBy, uuid.New()), | ||
Mimetype: takeFirst(orig.Mimetype, "application/x-tar"), | ||
Data: takeFirstBytes(orig.Data, []byte{}), | ||
}) | ||
require.NoError(t, err, "insert file") | ||
return file | ||
} | ||
func UserLink(t *testing.T, db database.Store, orig database.UserLink) database.UserLink { | ||
link, err := db.InsertUserLink(context.Background(), database.InsertUserLinkParams{ | ||
UserID: takeFirst(orig.UserID, uuid.New()), | ||
LoginType: takeFirst(orig.LoginType, database.LoginTypeGithub), | ||
LinkedID: takeFirst(orig.LinkedID), | ||
OAuthAccessToken: takeFirst(orig.OAuthAccessToken, uuid.NewString()), | ||
OAuthRefreshToken: takeFirst(orig.OAuthAccessToken, uuid.NewString()), | ||
OAuthExpiry: takeFirst(orig.OAuthExpiry, time.Now().Add(time.Hour*24)), | ||
}) | ||
require.NoError(t, err, "insert link") | ||
return link | ||
} | ||
func TemplateVersion(t *testing.T, db database.Store, orig database.TemplateVersion) database.TemplateVersion { | ||
version, err := db.InsertTemplateVersion(context.Background(), database.InsertTemplateVersionParams{ | ||
ID: takeFirst(orig.ID, uuid.New()), | ||
TemplateID: uuid.NullUUID{ | ||
UUID: takeFirst(orig.TemplateID.UUID, uuid.New()), | ||
Valid: takeFirst(orig.TemplateID.Valid, true), | ||
}, | ||
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()), | ||
CreatedAt: takeFirst(orig.CreatedAt, time.Now()), | ||
UpdatedAt: takeFirst(orig.UpdatedAt, time.Now()), | ||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)), | ||
Readme: takeFirst(orig.Readme, namesgenerator.GetRandomName(1)), | ||
JobID: takeFirst(orig.JobID, uuid.New()), | ||
CreatedBy: takeFirst(orig.CreatedBy, uuid.New()), | ||
}) | ||
require.NoError(t, err, "insert template version") | ||
return version | ||
} |
107 changes: 107 additions & 0 deletionscoderd/database/dbgen/generator_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package dbgen_test | ||
import ( | ||
"context" | ||
"testing" | ||
"github.com/stretchr/testify/require" | ||
"github.com/coder/coder/coderd/database" | ||
"github.com/coder/coder/coderd/database/databasefake" | ||
"github.com/coder/coder/coderd/database/dbgen" | ||
) | ||
func TestGenerator(t *testing.T) { | ||
t.Parallel() | ||
t.Run("APIKey", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp, _ := dbgen.APIKey(t, db, database.APIKey{}) | ||
require.Equal(t, exp, must(db.GetAPIKeyByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("File", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.File(t, db, database.File{}) | ||
require.Equal(t, exp, must(db.GetFileByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("UserLink", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.UserLink(t, db, database.UserLink{}) | ||
require.Equal(t, exp, must(db.GetUserLinkByLinkedID(context.Background(), exp.LinkedID))) | ||
}) | ||
t.Run("WorkspaceResource", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{}) | ||
require.Equal(t, exp, must(db.GetWorkspaceResourceByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("Job", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.ProvisionerJob(t, db, database.ProvisionerJob{}) | ||
require.Equal(t, exp, must(db.GetProvisionerJobByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("Group", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.Group(t, db, database.Group{}) | ||
require.Equal(t, exp, must(db.GetGroupByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("Organization", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.Organization(t, db, database.Organization{}) | ||
require.Equal(t, exp, must(db.GetOrganizationByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("Workspace", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.Workspace(t, db, database.Workspace{}) | ||
require.Equal(t, exp, must(db.GetWorkspaceByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("Template", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.Template(t, db, database.Template{}) | ||
require.Equal(t, exp, must(db.GetTemplateByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("TemplateVersion", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.TemplateVersion(t, db, database.TemplateVersion{}) | ||
require.Equal(t, exp, must(db.GetTemplateVersionByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("WorkspaceBuild", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{}) | ||
require.Equal(t, exp, must(db.GetWorkspaceBuildByID(context.Background(), exp.ID))) | ||
}) | ||
t.Run("User", func(t *testing.T) { | ||
t.Parallel() | ||
db := databasefake.New() | ||
exp := dbgen.User(t, db, database.User{}) | ||
require.Equal(t, exp, must(db.GetUserByID(context.Background(), exp.ID))) | ||
}) | ||
} | ||
func must[T any](value T, err error) T { | ||
if err != nil { | ||
panic(err) | ||
} | ||
return value | ||
} |
29 changes: 29 additions & 0 deletionscoderd/database/dbgen/take.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package dbgen | ||
// takeFirstBytes implements takeFirst for []byte. | ||
// []byte is not a comparable type. | ||
func takeFirstBytes(values ...[]byte) []byte { | ||
return takeFirstF(values, func(v []byte) bool { | ||
return len(v) != 0 | ||
}) | ||
} | ||
// takeFirstF takes the first value that returns true | ||
func takeFirstF[Value any](values []Value, take func(v Value) bool) Value { | ||
var empty Value | ||
for _, v := range values { | ||
if take(v) { | ||
return v | ||
} | ||
} | ||
// If all empty, return empty | ||
return empty | ||
} | ||
// takeFirst will take the first non-empty value. | ||
func takeFirst[Value comparable](values ...Value) Value { | ||
var empty Value | ||
return takeFirstF(values, func(v Value) bool { | ||
return v != empty | ||
}) | ||
} |
Oops, something went wrong.
Uh oh!
There was an error while loading.Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.