- Notifications
You must be signed in to change notification settings - Fork1k
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
DBAuthz tests are taking a long amount of time from the sheerquantity of databases required to be provisioned. This PRadds a framework to move to a mocked db.
// 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", | ||
//}) | ||
funcFake[Tany](t*testing.T,faker*gofakeit.Faker,seedT)T { | ||
t.Helper() | ||
vartmpT | ||
err:=faker.Struct(&tmp) | ||
require.NoError(t,err,"failed to generate fake data for type %T",tmp) | ||
mergeZero(&seed,tmp) | ||
returnseed | ||
} | ||
// mergeZero merges the fields of src into dst, but only if the field in dst is | ||
// currently the zero value. | ||
funcmergeZero[Tany](dst*T,srcT) { | ||
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 { | ||
iflen(remain)==0 { | ||
return | ||
} | ||
dv,sv:=remain[0][0],remain[0][1] | ||
remain=remain[1:]// | ||
fori:=0;i<dv.NumField();i++ { | ||
df:=dv.Field(i) | ||
sf:=sv.Field(i) | ||
if!df.CanSet() { | ||
continue | ||
} | ||
ifreflect.Value.IsZero(df) {// only write if currently zero | ||
df.Set(sf) | ||
continue | ||
} | ||
ifdv.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 packagehttps://github.com/brianvoe/gofakeit, and some reflect magic.
But the alternative is to refactordbgen
or copy it and make a differentfake
for each database type. That feels exhausting. This is just so easy to use, and it is intestutil
, so it does not need to be "production worthy".
I debated throwing this intodbauthz_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.
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.
🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉
Uh oh!
There was an error while loading.Please reload this page.
Depends on reflect under the hood anyway
ce93565
intomainUh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
What this does
Starts work oncoder/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 withhttps://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