-
Notifications
You must be signed in to change notification settings - Fork 960
test: start migrating dbauthz tests to mocked db #19257
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
Conversation
DBAuthz tests are taking a long amount of time from the sheer quantity of databases required to be provisioned. This PR adds a framework to move to a mocked db.
testutil/faker.go
Outdated
// Fake will populate any zero fields in the provided struct with fake data. | ||
// Non-zero fields will remain unchanged. | ||
// Usage: | ||
// | ||
// key := Fake(t, faker, database.APIKey{ | ||
// TokenName: "keep-my-name", | ||
// }) | ||
func Fake[T any](t *testing.T, faker *gofakeit.Faker, seed T) T { | ||
t.Helper() | ||
|
||
var tmp T | ||
err := faker.Struct(&tmp) | ||
require.NoError(t, err, "failed to generate fake data for type %T", tmp) | ||
|
||
mergeZero(&seed, tmp) | ||
return seed | ||
} | ||
|
||
// mergeZero merges the fields of src into dst, but only if the field in dst is | ||
// currently the zero value. | ||
func mergeZero[T any](dst *T, src T) { | ||
remain := [][2]reflect.Value{ | ||
{reflect.ValueOf(dst).Elem(), reflect.ValueOf(src)}, | ||
} | ||
|
||
// Traverse the struct fields and set them only if they are currently zero. | ||
// This is a breadth-first traversal of the struct fields. Struct definitions | ||
// Should not be that deep, so we should not hit any stack overflow issues. | ||
for { | ||
if len(remain) == 0 { | ||
return | ||
} | ||
dv, sv := remain[0][0], remain[0][1] | ||
remain = remain[1:] // | ||
for i := 0; i < dv.NumField(); i++ { | ||
df := dv.Field(i) | ||
sf := sv.Field(i) | ||
if !df.CanSet() { | ||
continue | ||
} | ||
if reflect.Value.IsZero(df) { // only write if currently zero | ||
df.Set(sf) | ||
continue | ||
} | ||
|
||
if dv.Field(i).Kind() == reflect.Struct { | ||
// If the field is a struct, we need to traverse it as well. | ||
remain = append(remain, [2]reflect.Value{df, sf}) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably the most objectionable add. It adds a new package https://github.com/brianvoe/gofakeit, and some reflect magic.
But the alternative is to refactor dbgen
or copy it and make a different fake
for each database type. That feels exhausting. This is just so easy to use, and it is in testutil
, so it does not need to be "production worthy".
I debated throwing this into dbauthz_test
, but thought it might be helpful elsewhere too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new approach looks fine to me, and I'm not against adding a library for faking data.
@@ -204,14 +207,15 @@ func defaultIPAddress() pqtype.Inet { | |||
} | |||
|
|||
func (s *MethodTestSuite) TestAPIKey() { | |||
s.Run("DeleteAPIKeyByID", s.Subtest(func(db database.Store, check *expects) { | |||
dbtestutil.DisableForeignKeysAndTriggers(s.T(), db) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉
Depends on reflect under the hood anyway
What this does
Starts work on coder/internal#869
DBAuthz tests are taking a long amount of time from the sheer quantity of databases required to be provisioned. This PR adds a framework to move to a mocked db. And therefore massively speed up these tests.
The biggest challenge was to randomly seed data into the various
database.<type>
structs, as zero values could hide rbac bugs. In the actual db, we usedbgen
. Instead of refactoring out or redoingdbgen
to allow it to work with mocks, I went with https://github.com/brianvoe/gofakeit to randomly seed data.Since we are not testing the
db
or queries themselves, I feel more ok with actual "random" data. Thedbgen
is hand rolled, but those fake values must confine to our data schema.Maybe in the future we can annotate our
models
withfaker
tags and merge these ideas. At present, I don't think we can add field tags to sqlc output.Should we be using a real database?
No, not really. The dbauthz tests are just asserting that all database calls have a corresponding authz check. There is no need to test the db layer here. The only reason we used a database, is because we used to have
dbmem
, and the tooling for setting up fake data was there and easy to use.An example migrating to a mocked test
Annotated output