Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
Emyrk merged 4 commits intomainfromstevenmasley/mock_dbauthz
Aug 8, 2025

Conversation

Emyrk
Copy link
Member

@EmyrkEmyrk commentedAug 8, 2025
edited
Loading

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 variousdatabase.<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 thedb 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 ourmodels 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 havedbmem, and the tooling for setting up fake data was there and easy to use.

An example migrating to a mocked test

- s.Run("GetAPIKeysByUserID", s.Subtest(func(db database.Store, check *expects) {+ s.Run("GetAPIKeysByUserID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {-u1 := dbgen.User(s.T(), db, database.User{})-u2 := dbgen.User(s.T(), db, database.User{})+       u1 := testutil.Fake(s.T(), faker, database.User{})-keyA, _ := dbgen.APIKey(s.T(), db, database.APIKey{UserID: u1.ID, LoginType: database.LoginTypeToken, TokenName: "key-a"})-keyB, _ := dbgen.APIKey(s.T(), db, database.APIKey{UserID: u1.ID, LoginType: database.LoginTypeToken, TokenName: "key-b"})-_, _ = dbgen.APIKey(s.T(), db, database.APIKey{UserID: u2.ID, LoginType: database.LoginTypeToken})+       dbm.EXPECT().GetAPIKeysByUserID(gomock.Any(), gomock.Any()).Return(slice.New(keyA, keyB), nil).AnyTimes()check.Args(database.GetAPIKeysByUserIDParams{LoginType: database.LoginTypeToken, UserID: u1.ID}).Asserts(keyA, policy.ActionRead, keyB, policy.ActionRead).Returns(slice.New(keyA, keyB))}))

Annotated output

// Moved from `s.Subtest` to `s.Mocked`. The mocker comes with a `faker` tools.Run("GetAPIKeysByUserID",s.Mocked(func(dbm*dbmock.MockStore,faker*gofakeit.Faker,check*expects) {// Instead of dbgen, we use `testutil.Fake(` to make populate random datau1:=testutil.Fake(s.T(),faker, database.User{})// The test util keeps non-zero values, so we keep our relations of objectskeyA:=testutil.Fake(s.T(),faker, database.APIKey{UserID:u1.ID,LoginType:database.LoginTypeToken,TokenName:"key-a"})keyB:=testutil.Fake(s.T(),faker, database.APIKey{UserID:u1.ID,LoginType:database.LoginTypeToken,TokenName:"key-b"})// We need to add the mocked return and add 'AnyTimes'. The dbauthz package// runs multiple tests on this query method, so always use 'AnyTimes'.dbm.EXPECT().GetAPIKeysByUserID(gomock.Any(),gomock.Any()).Return(slice.New(keyA,keyB),nil).AnyTimes()check.Args(database.GetAPIKeysByUserIDParams{LoginType:database.LoginTypeToken,UserID:u1.ID}).Returns(slice.New(keyA,keyB))}))

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.
Comment on lines 11 to 62
// 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})
}
}
}
}
Copy link
MemberAuthor

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.

Copy link
Member

@johnstcnjohnstcn left a 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

🙌

Emyrk reacted with heart emoji
Copy link
Member

@deansheatherdeansheather left a 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
@EmyrkEmyrk merged commitce93565 intomainAug 8, 2025
26 checks passed
@EmyrkEmyrk deleted the stevenmasley/mock_dbauthz branchAugust 8, 2025 18:46
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsAug 8, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@johnstcnjohnstcnjohnstcn approved these changes

@deansheatherdeansheatherdeansheather approved these changes

Assignees

@EmyrkEmyrk

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@Emyrk@johnstcn@deansheather

[8]ページ先頭

©2009-2025 Movatter.jp