From 244bd90f3b6f58f7fbd58871c171206ba8ae76cf Mon Sep 17 00:00:00 2001 From: Kian Parvin Date: Thu, 2 Nov 2023 16:25:49 +0200 Subject: [PATCH] Refactored test - Changed env.go so that the PopulateDB function only populates the DB and added PopulateDBAndPermissions to populate the DB and OpenFGA permissions. This clarifies when a test needs only database objects versus when a test needs database objects and permissions --- internal/db/cloud_test.go | 4 +- internal/db/cloudcredential_test.go | 2 +- internal/db/controller_test.go | 6 +- internal/db/model_test.go | 4 +- internal/jimm/applicationoffer_test.go | 5 +- internal/jimm/cloud_test.go | 30 ++-- internal/jimm/cloudcredential_test.go | 20 +-- internal/jimm/controller_test.go | 31 ++-- internal/jimm/jimm_test.go | 36 ++-- internal/jimm/model_status_parser_test.go | 2 +- internal/jimm/model_test.go | 63 +++---- internal/jimm/watcher_test.go | 10 +- internal/jimmtest/env.go | 205 ++++++++++++---------- 13 files changed, 207 insertions(+), 211 deletions(-) diff --git a/internal/db/cloud_test.go b/internal/db/cloud_test.go index 882b5310c..9a80cf250 100644 --- a/internal/db/cloud_test.go +++ b/internal/db/cloud_test.go @@ -244,7 +244,7 @@ controllers: region: test-region priority: 1 `) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) cr, err := s.Database.FindRegion(ctx, "testp", "test-region") c.Assert(err, qt.IsNil) @@ -363,7 +363,7 @@ controllers: region: test-region-2 priority: 1 `) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) cl := dbmodel.Cloud{ Name: "test-cloud-1", diff --git a/internal/db/cloudcredential_test.go b/internal/db/cloudcredential_test.go index 6fdde76d6..98434a335 100644 --- a/internal/db/cloudcredential_test.go +++ b/internal/db/cloudcredential_test.go @@ -281,7 +281,7 @@ func (s *dbSuite) TestForEachCloudCredential(c *qt.C) { env := jimmtest.ParseEnvironment(c, forEachCloudCredentialEnv) err := s.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) for _, test := range forEachCloudCredentialTests { c.Run(test.name, func(c *qt.C) { diff --git a/internal/db/controller_test.go b/internal/db/controller_test.go index 4cf43937d..4b17841be 100644 --- a/internal/db/controller_test.go +++ b/internal/db/controller_test.go @@ -241,7 +241,7 @@ func (s *dbSuite) TestForEachController(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testForEachControllerEnv) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) testError := errors.E("test error") err = s.Database.ForEachController(ctx, func(controller *dbmodel.Controller) error { @@ -426,9 +426,9 @@ func (s *dbSuite) TestForEachControllerModel(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testForEachControllerModelEnv) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) - ctl := env.Controller("test").DBObject(c, *s.Database, nil) + ctl := env.Controller("test").DBObject(c, *s.Database) testError := errors.E("test error") err = s.Database.ForEachControllerModel(ctx, &ctl, func(_ *dbmodel.Model) error { return testError diff --git a/internal/db/model_test.go b/internal/db/model_test.go index b13121834..d6d1a288f 100644 --- a/internal/db/model_test.go +++ b/internal/db/model_test.go @@ -537,7 +537,7 @@ func (s *dbSuite) TestForEachModel(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testForEachModelEnv) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) testError := errors.E("test error") err = s.Database.ForEachModel(ctx, func(m *dbmodel.Model) error { @@ -623,7 +623,7 @@ func (s *dbSuite) TestGetModelsByUUID(c *qt.C) { c.Assert(err, qt.Equals, nil) env := jimmtest.ParseEnvironment(c, testGetModelsByUUIDEnv) - env.PopulateDB(c, *s.Database, nil) + env.PopulateDB(c, *s.Database) modelUUIDs := []string{ "00000002-0000-0000-0000-000000000001", diff --git a/internal/jimm/applicationoffer_test.go b/internal/jimm/applicationoffer_test.go index a29112542..5c465d54e 100644 --- a/internal/jimm/applicationoffer_test.go +++ b/internal/jimm/applicationoffer_test.go @@ -2685,7 +2685,6 @@ func TestListApplicationOffers(t *testing.T) { err = db.Migrate(ctx, false) c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, listApplicationsTestEnv) - env.PopulateDB(c, db, client) j := &jimm.JIMM{ UUID: uuid.NewString(), @@ -2840,7 +2839,7 @@ func TestListApplicationOffers(t *testing.T) { }, }, } - + env.PopulateDBAndPermissions(c, j.ResourceTag(), db, client) tuples := []openfga.Tuple{{ Object: ofganames.ConvertTag(names.NewUserTag("alice@external")), Relation: ofganames.AdministratorRelation, @@ -2881,7 +2880,7 @@ func TestListApplicationOffers(t *testing.T) { err = client.AddRelation(context.Background(), tuples...) c.Assert(err, qt.IsNil) - u := env.User("alice@external").DBObject(c, db, client) + u := env.User("alice@external").DBObject(c, db) _, err = j.ListApplicationOffers(ctx, openfga.NewUser(&u, client)) c.Assert(err, qt.ErrorMatches, `at least one filter must be specified`) diff --git a/internal/jimm/cloud_test.go b/internal/jimm/cloud_test.go index 082f2383b..776157b76 100644 --- a/internal/jimm/cloud_test.go +++ b/internal/jimm/cloud_test.go @@ -613,10 +613,9 @@ func TestAddHostedCloud(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, addHostedCloudTestEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.AddHostedCloud(ctx, user, names.NewCloudTag(test.cloudName), test.cloud, false) @@ -894,10 +893,9 @@ func TestAddCloudToController(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, addHostedCloudTestEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) // Note that the force flag has no effect here because the Juju responses are mocked. @@ -1054,9 +1052,9 @@ func TestGrantCloudAccess(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(tt.username).DBObject(c, j.Database, client) + dbUser := env.User(tt.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.GrantCloudAccess(ctx, user, names.NewCloudTag(tt.cloud), names.NewUserTag(tt.targetUsername), tt.access) @@ -1354,7 +1352,7 @@ func TestRevokeCloudAccess(t *testing.T) { err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) if tt.extraInitialTuples != nil && len(tt.extraInitialTuples) > 0 { err = client.AddRelation(ctx, tt.extraInitialTuples...) @@ -1369,7 +1367,7 @@ func TestRevokeCloudAccess(t *testing.T) { } } - dbUser := env.User(tt.username).DBObject(c, j.Database, client) + dbUser := env.User(tt.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.RevokeCloudAccess(ctx, user, names.NewCloudTag(tt.cloud), names.NewUserTag(tt.targetUsername), tt.access) @@ -1507,9 +1505,9 @@ func TestRemoveCloud(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.RemoveCloud(ctx, user, names.NewCloudTag(test.cloud)) @@ -1747,9 +1745,9 @@ func TestUpdateCloud(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) tag := names.NewCloudTag(test.cloud) @@ -1941,9 +1939,9 @@ func TestRemoveFromControllerCloud(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.RemoveCloudFromController(ctx, user, test.controllerName, names.NewCloudTag(test.cloud)) diff --git a/internal/jimm/cloudcredential_test.go b/internal/jimm/cloudcredential_test.go index 2899768db..b26413899 100644 --- a/internal/jimm/cloudcredential_test.go +++ b/internal/jimm/cloudcredential_test.go @@ -834,9 +834,8 @@ users: err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) - u := env.User("alice@external").DBObject(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + u := env.User("alice@external").DBObject(c, j.Database) user := openfga.NewUser(&u, client) _, err = j.UpdateCloudCredential(ctx, user, jimm.UpdateCloudCredentialArgs{ CredentialTag: names.NewCloudCredentialTag("test-cloud/bob@external/test"), @@ -1481,8 +1480,8 @@ func TestForEachUserCloudCredential(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) - u := env.User(test.username).DBObject(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + u := env.User(test.username).DBObject(c, j.Database) var credentials []string if test.f == nil { @@ -1601,14 +1600,13 @@ func TestGetCloudCredentialAttributes(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) - u := env.User("bob@external").DBObject(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) + u := env.User("bob@external").DBObject(c, j.Database) userBob := openfga.NewUser(&u, client) cred, err := j.GetCloudCredential(ctx, userBob, names.NewCloudCredentialTag("test-cloud/bob@external/cred-1")) c.Assert(err, qt.IsNil) - u = env.User(test.username).DBObject(c, j.Database, client) + u = env.User(test.username).DBObject(c, j.Database) userTest := openfga.NewUser(&u, client) attr, redacted, err := j.GetCloudCredentialAttributes(ctx, userTest, cred, test.hidden) if test.expectError != "" { @@ -1654,9 +1652,9 @@ func TestCloudCredentialAttributeStore(t *testing.T) { regions: - name: test-region `) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - u := env.User("alice@external").DBObject(c, j.Database, client) + u := env.User("alice@external").DBObject(c, j.Database) user := openfga.NewUser(&u, client) args := jimm.UpdateCloudCredentialArgs{ CredentialTag: names.NewCloudCredentialTag("test/alice@external/cred-1"), diff --git a/internal/jimm/controller_test.go b/internal/jimm/controller_test.go index 65524ad14..de1d5527c 100644 --- a/internal/jimm/controller_test.go +++ b/internal/jimm/controller_test.go @@ -413,7 +413,7 @@ func TestEarliestControllerVersion(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testEarliestControllerVersionEnv) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) v, err := j.EarliestControllerVersion(ctx) c.Assert(err, qt.Equals, nil) @@ -913,10 +913,9 @@ func TestImportModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testImportModelEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.user).DBObject(c, j.Database, client) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.ImportModel(ctx, user, test.controllerName, names.NewModelTag(test.modelUUID), test.newOwner) if test.expectedError == "" { @@ -1014,10 +1013,9 @@ func TestSetControllerConfig(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testControllerConfigEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.user).DBObject(c, j.Database, client) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.SetControllerConfig(ctx, user, test.args) if test.expectedError == "" { @@ -1090,13 +1088,12 @@ func TestGetControllerConfig(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testImportModelEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbSuperuser := env.User("alice@external").DBObject(c, j.Database, client) + dbSuperuser := env.User("alice@external").DBObject(c, j.Database) superuser := openfga.NewUser(&dbSuperuser, client) - dbUser := env.User(test.user).DBObject(c, j.Database, client) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.SetControllerConfig(ctx, superuser, jujuparams.ControllerConfigSet{ @@ -1241,10 +1238,9 @@ func TestUpdateMigratedModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testUpdateMigratedModelEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.user).DBObject(c, j.Database, client) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.UpdateMigratedModel(ctx, user, test.model, test.targetController) @@ -1295,10 +1291,9 @@ func TestGetControllerAccess(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testGetControllerAccessEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User("alice@external").DBObject(c, j.Database, client) + dbUser := env.User("alice@external").DBObject(c, j.Database) alice := openfga.NewUser(&dbUser, client) access, err := j.GetJimmControllerAccess(ctx, alice, names.NewUserTag("alice@external")) @@ -1313,7 +1308,7 @@ func TestGetControllerAccess(t *testing.T) { c.Assert(err, qt.IsNil) c.Check(access, qt.Equals, "login") - dbUser = env.User("bob@external").DBObject(c, j.Database, client) + dbUser = env.User("bob@external").DBObject(c, j.Database) alice = openfga.NewUser(&dbUser, client) access, err = j.GetJimmControllerAccess(ctx, alice, names.NewUserTag("bob@external")) c.Assert(err, qt.IsNil) diff --git a/internal/jimm/jimm_test.go b/internal/jimm/jimm_test.go index 82389cdbd..e15a6e614 100644 --- a/internal/jimm/jimm_test.go +++ b/internal/jimm/jimm_test.go @@ -215,8 +215,7 @@ func TestListControllers(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testListCoControllersEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) tests := []struct { about string @@ -225,19 +224,19 @@ func TestListControllers(t *testing.T) { expectedError string }{{ about: "superuser can list controllers", - user: env.User("alice@external").DBObject(c, j.Database, client), + user: env.User("alice@external").DBObject(c, j.Database), expectedControllers: []dbmodel.Controller{ - env.Controller("test1").DBObject(c, j.Database, client), - env.Controller("test2").DBObject(c, j.Database, client), - env.Controller("test3").DBObject(c, j.Database, client), + env.Controller("test1").DBObject(c, j.Database), + env.Controller("test2").DBObject(c, j.Database), + env.Controller("test3").DBObject(c, j.Database), }, }, { about: "add-model user can not list controllers", - user: env.User("bob@external").DBObject(c, j.Database, client), + user: env.User("bob@external").DBObject(c, j.Database), expectedError: "unauthorized", }, { about: "user withouth access rights cannot list controllers", - user: env.User("eve@external").DBObject(c, j.Database, client), + user: env.User("eve@external").DBObject(c, j.Database), expectedError: "unauthorized", }} @@ -300,8 +299,7 @@ func TestSetControllerDeprecated(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, testSetControllerDeprecatedEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) tests := []struct { about string @@ -310,15 +308,15 @@ func TestSetControllerDeprecated(t *testing.T) { expectedError string }{{ about: "superuser can deprecate a controller", - user: env.User("alice@external").DBObject(c, j.Database, client), + user: env.User("alice@external").DBObject(c, j.Database), deprecated: true, }, { about: "superuser can deprecate a controller", - user: env.User("alice@external").DBObject(c, j.Database, client), + user: env.User("alice@external").DBObject(c, j.Database), deprecated: false, }, { about: "user withouth access rights cannot deprecate a controller", - user: env.User("eve@external").DBObject(c, j.Database, client), + user: env.User("eve@external").DBObject(c, j.Database), expectedError: "unauthorized", deprecated: true, }} @@ -444,15 +442,14 @@ func TestRemoveController(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, removeControllerTestEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.user).DBObject(c, j.Database, client) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) if test.unavailableSince != nil { // make the controller unavailable - controller := env.Controller("controller-1").DBObject(c, j.Database, client) + controller := env.Controller("controller-1").DBObject(c, j.Database) controller.UnavailableSince = sql.NullTime{ Valid: true, Time: *test.unavailableSince, @@ -612,10 +609,9 @@ func TestFullModelStatus(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, fullModelStatusTestEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.user).DBObject(c, j.Database, client) + dbUser := env.User(test.user).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) status, err := j.FullModelStatus(ctx, user, names.NewModelTag(test.modelUUID), nil) diff --git a/internal/jimm/model_status_parser_test.go b/internal/jimm/model_status_parser_test.go index d10336552..e5bceff91 100644 --- a/internal/jimm/model_status_parser_test.go +++ b/internal/jimm/model_status_parser_test.go @@ -433,7 +433,7 @@ func TestQueryModelsJq(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, crossModelQueryEnv) - env.PopulateDB(c, j.Database, nil) + env.PopulateDB(c, j.Database) modelUUIDs := []string{ "10000000-0000-0000-0000-000000000000", diff --git a/internal/jimm/model_test.go b/internal/jimm/model_test.go index ab0900137..1ec703686 100644 --- a/internal/jimm/model_test.go +++ b/internal/jimm/model_test.go @@ -824,10 +824,9 @@ func TestAddModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) args := jimm.ModelCreateArgs{} @@ -1170,8 +1169,7 @@ func TestModelInfo(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) dbUser := &dbmodel.User{ Username: test.username, @@ -1312,10 +1310,9 @@ func TestModelStatus(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) ms, err := j.ModelStatus(context.Background(), user, names.NewModelTag(test.uuid)) @@ -1466,10 +1463,9 @@ func TestForEachUserModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, forEachModelTestEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User("bob@external").DBObject(c, j.Database, client) + dbUser := env.User("bob@external").DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) var res []jujuparams.ModelSummaryResult @@ -1606,10 +1602,9 @@ func TestForEachModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, forEachModelTestEnv) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User("bob@external").DBObject(c, j.Database, client) + dbUser := env.User("bob@external").DBObject(c, j.Database) bob := openfga.NewUser(&dbUser, client) err = j.ForEachModel(ctx, bob, func(_ *dbmodel.Model, _ jujuparams.UserAccessPermission) error { @@ -1618,7 +1613,7 @@ func TestForEachModel(t *testing.T) { c.Check(err, qt.ErrorMatches, `unauthorized`) c.Assert(errors.ErrorCode(err), qt.Equals, errors.CodeUnauthorized) - dbUser = env.User("alice@external").DBObject(c, j.Database, client) + dbUser = env.User("alice@external").DBObject(c, j.Database) alice := openfga.NewUser(&dbUser, client) var models []string @@ -1877,9 +1872,9 @@ func TestGrantModelAccess(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(tt.username).DBObject(c, j.Database, client) + dbUser := env.User(tt.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.GrantModelAccess(ctx, user, names.NewModelTag(tt.uuid), names.NewUserTag(tt.targetUsername), jujuparams.UserAccessPermission(tt.access)) @@ -2596,7 +2591,7 @@ func TestRevokeModelAccess(t *testing.T) { } err = j.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) if tt.extraInitialTuples != nil && len(tt.extraInitialTuples) > 0 { err = client.AddRelation(ctx, tt.extraInitialTuples...) @@ -2611,7 +2606,7 @@ func TestRevokeModelAccess(t *testing.T) { } } - dbUser := env.User(tt.username).DBObject(c, j.Database, client) + dbUser := env.User(tt.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.RevokeModelAccess(ctx, user, names.NewModelTag(tt.uuid), names.NewUserTag(tt.targetUsername), jujuparams.UserAccessPermission(tt.access)) @@ -2784,10 +2779,9 @@ func TestDestroyModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.DestroyModel(ctx, user, names.NewModelTag(test.uuid), test.destroyStorage, test.force, test.maxWait, test.timeout) @@ -2911,10 +2905,9 @@ func TestDumpModel(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) s, err := j.DumpModel(ctx, user, names.NewModelTag(test.uuid), test.simplified) @@ -3025,10 +3018,9 @@ func TestDumpModelDB(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) dump, err := j.DumpModelDB(ctx, user, names.NewModelTag(test.uuid)) @@ -3145,10 +3137,9 @@ func TestValidateModelUpgrade(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.ValidateModelUpgrade(ctx, user, names.NewModelTag(test.uuid), test.force) @@ -3367,10 +3358,9 @@ func TestUpdateModelCredential(t *testing.T) { c.Assert(err, qt.IsNil) env := jimmtest.ParseEnvironment(c, test.env) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User(test.username).DBObject(c, j.Database, client) + dbUser := env.User(test.username).DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) err = j.ChangeModelCredential( @@ -3484,10 +3474,9 @@ controllers: priority: 1 ` env := jimmtest.ParseEnvironment(c, envDefinition) - env.PopulateDB(c, j.Database, client) - env.AddJIMMRelations(c, j.ResourceTag(), j.Database, client) + env.PopulateDBAndPermissions(c, j.ResourceTag(), j.Database, client) - dbUser := env.User("alice@external").DBObject(c, j.Database, client) + dbUser := env.User("alice@external").DBObject(c, j.Database) user := openfga.NewUser(&dbUser, client) controller := dbmodel.Controller{ diff --git a/internal/jimm/watcher_test.go b/internal/jimm/watcher_test.go index ec77c6ee1..dcfc0e3c8 100644 --- a/internal/jimm/watcher_test.go +++ b/internal/jimm/watcher_test.go @@ -576,7 +576,7 @@ func TestWatcher(t *testing.T) { env := jimmtest.ParseEnvironment(c, testWatcherEnv) err := w.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, w.Database, nil) + env.PopulateDB(c, w.Database) if test.initDB != nil { test.initDB(c, w.Database) @@ -736,7 +736,7 @@ func TestModelSummaryWatcher(t *testing.T) { env := jimmtest.ParseEnvironment(c, testWatcherEnv) err := w.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, w.Database, nil) + env.PopulateDB(c, w.Database) var wg sync.WaitGroup wg.Add(1) @@ -782,7 +782,7 @@ func TestWatcherSetsControllerUnavailable(t *testing.T) { env := jimmtest.ParseEnvironment(c, testWatcherEnv) err := w.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, w.Database, nil) + env.PopulateDB(c, w.Database) var wg sync.WaitGroup wg.Add(1) @@ -846,7 +846,7 @@ func TestWatcherRemoveDyingModelsOnStartup(t *testing.T) { env := jimmtest.ParseEnvironment(c, testWatcherEnv) err := w.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, w.Database, nil) + env.PopulateDB(c, w.Database) var wg sync.WaitGroup wg.Add(1) @@ -948,7 +948,7 @@ func TestWatcherIgnoreDeltasForModelsFromIncorrectController(t *testing.T) { env := jimmtest.ParseEnvironment(c, testWatcherIgnoreDeltasForModelsFromIncorrectControllerEnv) err := w.Database.Migrate(ctx, false) c.Assert(err, qt.IsNil) - env.PopulateDB(c, w.Database, nil) + env.PopulateDB(c, w.Database) m1 := dbmodel.Model{ UUID: sql.NullString{ diff --git a/internal/jimmtest/env.go b/internal/jimmtest/env.go index a5f8d8068..f38c5b9f5 100644 --- a/internal/jimmtest/env.go +++ b/internal/jimmtest/env.go @@ -118,49 +118,131 @@ func (e *Environment) User(name string) *User { return &e.Users[len(e.Users)-1] } -func (e *Environment) AddJIMMRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { - for _, user := range e.Users { - if user.ControllerAccess == "superuser" { - dbUser := user.DBObject(c, db, client) - u := openfga.NewUser(&dbUser, client) - err := u.SetControllerAccess(context.Background(), jimmTag, ofganames.AdministratorRelation) +// addUserRelations adds permissions the user should have. +func (u User) addUserRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { + if u.ControllerAccess == "superuser" { + dbUser := u.DBObject(c, db) + u := openfga.NewUser(&dbUser, client) + err := u.SetControllerAccess(context.Background(), jimmTag, ofganames.AdministratorRelation) + c.Assert(err, qt.IsNil) + } +} + +// addCloudRelations adds permissions the cloud should have and adds permissions for users to the cloud. +func (cl Cloud) addCloudRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { + for _, u := range cl.Users { + dbUser := cl.env.User(u.User).DBObject(c, db) + var relation openfga.Relation + switch u.Access { + case "admin": + relation = ofganames.AdministratorRelation + case "add-model": + relation = ofganames.CanAddModelRelation + default: + c.Fatalf("unknown cloud access level: %s", u.Access) + } + if client != nil { + user := openfga.NewUser(&dbUser, client) + err := user.SetCloudAccess(context.Background(), cl.dbo.ResourceTag(), relation) c.Assert(err, qt.IsNil) } } +} + +// addModelRelations adds permissions the model should have and adds permissions for users to the model. +func (m Model) addModelRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { + owner := openfga.NewUser(&m.dbo.Owner, client) + err := owner.SetModelAccess(context.Background(), m.dbo.ResourceTag(), ofganames.AdministratorRelation) + c.Assert(err, qt.IsNil) + + for _, u := range m.Users { + dbUser := m.env.User(u.User).DBObject(c, db) + var relation openfga.Relation + switch u.Access { + case "admin": + relation = ofganames.AdministratorRelation + case "write": + relation = ofganames.WriterRelation + case "read": + relation = ofganames.ReaderRelation + default: + c.Fatalf("unknown model access: %s %s", dbUser.Username, u.Access) + } + user := openfga.NewUser(&dbUser, client) + err := user.SetModelAccess(context.Background(), m.dbo.ResourceTag(), relation) + c.Assert(err, qt.IsNil) + } + + err = client.AddControllerModel(context.Background(), m.dbo.Controller.ResourceTag(), m.dbo.ResourceTag()) + c.Assert(err, qt.IsNil) +} + +// addControllerRelations adds permissions the model should have and adds permissions for users to the controller. +func (ctl Controller) addControllerRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { + if ctl.dbo.AdminUser != "" { + user := openfga.NewUser(&dbmodel.User{ + Username: ctl.dbo.AdminUser, + }, client) + err := user.SetControllerAccess(context.Background(), ctl.dbo.ResourceTag(), ofganames.AdministratorRelation) + c.Assert(err, qt.IsNil) + } + err := client.AddCloudController(context.Background(), names.NewCloudTag(ctl.Cloud), ctl.dbo.ResourceTag()) + c.Assert(err, qt.IsNil) +} + +func (e *Environment) addJIMMRelations(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { + for _, user := range e.Users { + user.addUserRelations(c, jimmTag, db, client) + } for _, controller := range e.Controllers { client.AddController(context.Background(), jimmTag, controller.dbo.ResourceTag()) } + for _, cl := range e.Clouds { + cl.addCloudRelations(c, jimmTag, db, client) + } + for _, m := range e.Models { + m.addModelRelations(c, jimmTag, db, client) + } + for _, ctl := range e.Controllers { + ctl.addControllerRelations(c, jimmTag, db, client) + } +} + +func (e *Environment) PopulateDBAndPermissions(c *qt.C, jimmTag names.ControllerTag, db db.Database, client *openfga.OFGAClient) { + e.PopulateDB(c, db) + c.Assert(client, qt.IsNotNil) + e.addJIMMRelations(c, jimmTag, db, client) } -func (e *Environment) PopulateDB(c *qt.C, db db.Database, client *openfga.OFGAClient) { +func (e *Environment) PopulateDB(c *qt.C, db db.Database) { for i := range e.Users { e.Users[i].env = e - e.Users[i].DBObject(c, db, client) + e.Users[i].DBObject(c, db) } for i := range e.Clouds { e.Clouds[i].env = e - e.Clouds[i].DBObject(c, db, client) + e.Clouds[i].DBObject(c, db) } for i := range e.CloudCredentials { e.CloudCredentials[i].env = e - e.CloudCredentials[i].DBObject(c, db, client) + e.CloudCredentials[i].DBObject(c, db) } for i := range e.CloudDefaults { e.CloudDefaults[i].env = e - e.CloudDefaults[i].DBObject(c, db, client) + e.CloudDefaults[i].DBObject(c, db) } for i := range e.Controllers { e.Controllers[i].env = e - e.Controllers[i].DBObject(c, db, client) + e.Controllers[i].DBObject(c, db) } for i := range e.Models { e.Models[i].env = e - e.Models[i].DBObject(c, db, client) + e.Models[i].DBObject(c, db) } for i := range e.UserDefaults { e.UserDefaults[i].env = e - e.UserDefaults[i].DBObject(c, db, client) + e.UserDefaults[i].DBObject(c, db) } } @@ -173,12 +255,12 @@ type UserDefaults struct { dbo dbmodel.UserModelDefaults } -func (cd *UserDefaults) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.UserModelDefaults { +func (cd *UserDefaults) DBObject(c *qt.C, db db.Database) dbmodel.UserModelDefaults { if cd.dbo.ID != 0 { return cd.dbo } - cd.dbo.User = cd.env.User(cd.User).DBObject(c, db, client) + cd.dbo.User = cd.env.User(cd.User).DBObject(c, db) cd.dbo.Defaults = cd.Defaults err := db.SetUserModelDefaults(context.Background(), &cd.dbo) @@ -197,13 +279,13 @@ type CloudDefaults struct { dbo dbmodel.CloudDefaults } -func (cd *CloudDefaults) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.CloudDefaults { +func (cd *CloudDefaults) DBObject(c *qt.C, db db.Database) dbmodel.CloudDefaults { if cd.dbo.ID != 0 { return cd.dbo } - cd.dbo.User = cd.env.User(cd.User).DBObject(c, db, client) - cd.dbo.Cloud = cd.env.Cloud(cd.Cloud).DBObject(c, db, client) + cd.dbo.User = cd.env.User(cd.User).DBObject(c, db) + cd.dbo.Cloud = cd.env.Cloud(cd.Cloud).DBObject(c, db) cd.dbo.Region = cd.Region cd.dbo.Defaults = cd.Defaults @@ -232,7 +314,7 @@ type CloudRegion struct { // DBObject returns a database object for the specified cloud, suitable // for adding to the database. -func (cl *Cloud) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.Cloud { +func (cl *Cloud) DBObject(c *qt.C, db db.Database) dbmodel.Cloud { if cl.dbo.ID != 0 { return cl.dbo } @@ -245,23 +327,6 @@ func (cl *Cloud) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) d Name: r.Name, }) } - for _, u := range cl.Users { - dbUser := cl.env.User(u.User).DBObject(c, db, client) - var relation openfga.Relation - switch u.Access { - case "admin": - relation = ofganames.AdministratorRelation - case "add-model": - relation = ofganames.CanAddModelRelation - default: - c.Fatalf("unknown cloud access level: %s", u.Access) - } - if client != nil { - user := openfga.NewUser(&dbUser, client) - err := user.SetCloudAccess(context.Background(), cl.dbo.ResourceTag(), relation) - c.Assert(err, qt.IsNil) - } - } err := db.AddCloud(context.Background(), &cl.dbo) c.Assert(err, qt.IsNil) @@ -281,14 +346,14 @@ type CloudCredential struct { dbo dbmodel.CloudCredential } -func (cc *CloudCredential) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.CloudCredential { +func (cc *CloudCredential) DBObject(c *qt.C, db db.Database) dbmodel.CloudCredential { if cc.dbo.ID != 0 { return cc.dbo } cc.dbo.Name = cc.Name - cc.dbo.Cloud = cc.env.Cloud(cc.Cloud).DBObject(c, db, client) + cc.dbo.Cloud = cc.env.Cloud(cc.Cloud).DBObject(c, db) cc.dbo.CloudName = cc.dbo.Cloud.Name - cc.dbo.Owner = cc.env.User(cc.Owner).DBObject(c, db, client) + cc.dbo.Owner = cc.env.User(cc.Owner).DBObject(c, db) cc.dbo.OwnerUsername = cc.dbo.Owner.Username cc.dbo.AuthType = cc.AuthType cc.dbo.Attributes = cc.Attributes @@ -314,7 +379,7 @@ type Controller struct { dbo dbmodel.Controller } -func (ctl *Controller) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.Controller { +func (ctl *Controller) DBObject(c *qt.C, db db.Database) dbmodel.Controller { if ctl.dbo.ID != 0 { return ctl.dbo } @@ -327,7 +392,7 @@ func (ctl *Controller) DBObject(c *qt.C, db db.Database, client *openfga.OFGACli ctl.dbo.CloudRegion = ctl.CloudRegion ctl.dbo.CloudRegions = make([]dbmodel.CloudRegionControllerPriority, len(ctl.CloudRegions)) for i, cr := range ctl.CloudRegions { - cl := ctl.env.Cloud(cr.Cloud).DBObject(c, db, client) + cl := ctl.env.Cloud(cr.Cloud).DBObject(c, db) ctl.dbo.CloudRegions[i] = dbmodel.CloudRegionControllerPriority{ CloudRegion: cl.Region(cr.Region), Priority: cr.Priority, @@ -337,19 +402,6 @@ func (ctl *Controller) DBObject(c *qt.C, db db.Database, client *openfga.OFGACli err := db.AddController(context.Background(), &ctl.dbo) c.Assert(err, qt.IsNil) - if ctl.dbo.AdminUser != "" && client != nil { - user := openfga.NewUser(&dbmodel.User{ - Username: ctl.dbo.AdminUser, - }, client) - err = user.SetControllerAccess(context.Background(), ctl.dbo.ResourceTag(), ofganames.AdministratorRelation) - c.Assert(err, qt.IsNil) - } - - if client != nil { - err = client.AddCloudController(context.Background(), names.NewCloudTag(ctl.Cloud), ctl.dbo.ResourceTag()) - c.Assert(err, qt.IsNil) - } - return ctl.dbo } @@ -386,45 +438,19 @@ type Model struct { dbo dbmodel.Model } -func (m *Model) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.Model { +func (m *Model) DBObject(c *qt.C, db db.Database) dbmodel.Model { if m.dbo.ID != 0 { return m.dbo } m.dbo.Name = m.Name - m.dbo.Owner = m.env.User(m.Owner).DBObject(c, db, client) + m.dbo.Owner = m.env.User(m.Owner).DBObject(c, db) if m.UUID != "" { m.dbo.UUID.String = m.UUID m.dbo.UUID.Valid = true } - - if client != nil { - owner := openfga.NewUser(&m.dbo.Owner, client) - err := owner.SetModelAccess(context.Background(), m.dbo.ResourceTag(), ofganames.AdministratorRelation) - c.Assert(err, qt.IsNil) - } - - m.dbo.Controller = m.env.Controller(m.Controller).DBObject(c, db, client) - for _, u := range m.Users { - user := m.env.User(u.User).DBObject(c, db, client) - var relation openfga.Relation - switch u.Access { - case "admin": - relation = ofganames.AdministratorRelation - case "write": - relation = ofganames.WriterRelation - case "read": - relation = ofganames.ReaderRelation - default: - c.Fatalf("unknown model access: %s %s", user.Username, u.Access) - } - if client != nil { - user := openfga.NewUser(&user, client) - err := user.SetModelAccess(context.Background(), m.dbo.ResourceTag(), relation) - c.Assert(err, qt.IsNil) - } - } - m.dbo.CloudRegion = m.env.Cloud(m.Cloud).DBObject(c, db, client).Region(m.CloudRegion) - m.dbo.CloudCredential = m.env.CloudCredential(m.Owner, m.Cloud, m.CloudCredential).DBObject(c, db, client) + m.dbo.Controller = m.env.Controller(m.Controller).DBObject(c, db) + m.dbo.CloudRegion = m.env.Cloud(m.Cloud).DBObject(c, db).Region(m.CloudRegion) + m.dbo.CloudCredential = m.env.CloudCredential(m.Owner, m.Cloud, m.CloudCredential).DBObject(c, db) m.dbo.Type = m.Type m.dbo.DefaultSeries = m.DefaultSeries @@ -440,11 +466,6 @@ func (m *Model) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) db err := db.AddModel(context.Background(), &m.dbo) c.Assert(err, qt.IsNil) - - if client != nil { - err = client.AddControllerModel(context.Background(), m.dbo.Controller.ResourceTag(), m.dbo.ResourceTag()) - c.Assert(err, qt.IsNil) - } return m.dbo } @@ -457,7 +478,7 @@ type User struct { dbo dbmodel.User } -func (u *User) DBObject(c *qt.C, db db.Database, client *openfga.OFGAClient) dbmodel.User { +func (u *User) DBObject(c *qt.C, db db.Database) dbmodel.User { if u.dbo.ID != 0 { return u.dbo }