package tenant_test

import (
	"context"
	"sort"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/mock"
	"github.com/influxdata/influxdb/v2/tenant"
	itesting "github.com/influxdata/influxdb/v2/testing"
)

type tenantFields struct {
	OrgIDGenerator       platform.IDGenerator
	BucketIDGenerator    platform.IDGenerator
	Users                []*influxdb.User
	Passwords            []string // passwords are indexed against the Users field
	UserResourceMappings []*influxdb.UserResourceMapping
	Organizations        []*influxdb.Organization
	Buckets              []*influxdb.Bucket
}

// TestBoltTenantService tests the tenant service functions.
// These tests stress the relation between the services embedded by the TenantService.
// The individual functionality of services is tested elsewhere.
func TestBoltTenantService(t *testing.T) {
	tests := []struct {
		name string
		fn   func(t *testing.T, init func(*testing.T, tenantFields) (*tenant.Service, func()))
	}{
		{
			name: "Create",
			fn:   Create,
		},
		{
			name: "Delete",
			fn:   Delete,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.fn(t, initBoltTenantService)
		})
	}
}

type bucketsByName []*influxdb.Bucket

func (b bucketsByName) Len() int {
	return len(b)
}

func (b bucketsByName) Less(i, j int) bool {
	return strings.Compare(b[i].Name, b[j].Name) < 0
}

func (b bucketsByName) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}

type urmByResourceID []*influxdb.UserResourceMapping

func (u urmByResourceID) Len() int {
	return len(u)
}

func (u urmByResourceID) Less(i, j int) bool {
	return u[i].ResourceID < u[j].ResourceID
}

func (u urmByResourceID) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

type urmByUserID []*influxdb.UserResourceMapping

func (u urmByUserID) Len() int {
	return len(u)
}

func (u urmByUserID) Less(i, j int) bool {
	return u[i].UserID < u[j].UserID
}

func (u urmByUserID) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// Create tests various cases of creation for the services in the TenantService.
// For example, when you create a user, do you create system buckets? How are URMs organized?
func Create(t *testing.T, init func(*testing.T, tenantFields) (*tenant.Service, func())) {
	t.Helper()

	// Blank fields, we are testing creation.
	fields := func() tenantFields {
		return tenantFields{
			OrgIDGenerator:    mock.NewIncrementingIDGenerator(1),
			BucketIDGenerator: mock.NewIncrementingIDGenerator(1),
		}
	}

	// NOTE(affo)(*kv.Service): tests that contain s.CreateOrganization() generate error in logs:
	//   Failed to make user owner of organization: {"error": "could not find authorizer on context when adding user to resource type orgs"}.
	// This happens because kv requires an authorization to be in context.
	// This is a bad dependency pattern (store -> auth) and should not be there.
	// Anyways this does not prevent the org to be created. If you add the urm manually you'll obtain the same result.

	// NOTE(affo)(*kv.Service): it also creates urms for the non existing user found in context.
	t.Run("creating an org creates system buckets", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		o := &influxdb.Organization{
			// ID(1)
			Name: "org1",
		}
		if err := s.CreateOrganization(ctx, o); err != nil {
			t.Fatal(err)
		}

		// Check existence
		orgs, norgs, err := s.FindOrganizations(ctx, influxdb.OrganizationFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if norgs != 1 {
			t.Errorf("expected 1 org, got: %v", orgs)
		}
		usrs, nusrs, err := s.FindUsers(ctx, influxdb.UserFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nusrs > 0 {
			t.Errorf("expected no user, got: %v", usrs)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nurms > 0 {
			t.Errorf("expected no urm, got: %+v", urms)
		}
		bs, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 2 {
			t.Errorf("expected 2 buckets, got: %v", bs)
		}
		sort.Sort(bucketsByName(bs))
		if name := bs[0].Name; name != "_monitoring" {
			t.Errorf("unexpected nam for bucket: %s", name)
		}
		if name := bs[1].Name; name != "_tasks" {
			t.Errorf("unexpected nam for bucket: %s", name)
		}
	})

	// NOTE(affo)(*kv.Service): nope, it does create system buckets with invalid OrgIDs.
	t.Run("creating user creates only the user", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// Number of buckets prior to user creation.
		// This is because, for now, system buckets always get returned for compatibility with the old system.
		_, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
		if err != nil {
			t.Fatal(err)
		}

		u := &influxdb.User{
			ID:   1,
			Name: "user1",
		}
		if err := s.CreateUser(ctx, u); err != nil {
			t.Fatal(err)
		}

		// Check existence
		orgs, norgs, err := s.FindOrganizations(ctx, influxdb.OrganizationFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if norgs != 0 {
			t.Errorf("expected no org, got: %v", orgs)
		}
		usrs, nusrs, err := s.FindUsers(ctx, influxdb.UserFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nusrs != 1 {
			t.Errorf("expected 1 user, got: %v", usrs)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nurms > 0 {
			t.Errorf("expected no urm, got: %v", urms)
		}
		bs, nnbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
		if err != nil {
			t.Fatal(err)
		}
		// Compare new number of buckets with the one prior to user creation.
		if nnbs != nbs {
			t.Errorf("expected no bucket created, got: %+v", bs)
		}
	})

	// NOTE(affo)(*kv.Service): nope, it does create a useless URM, no existence check.
	//  Apparently, system buckets are created too :thinking.
	t.Run("creating urm pointing to non existing user fails", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// First create an org and a user.
		u := &influxdb.User{
			ID:   1,
			Name: "user1",
		}
		if err := s.CreateUser(ctx, u); err != nil {
			t.Fatal(err)
		}
		o := &influxdb.Organization{
			// ID(1)
			Name: "org1",
		}
		if err := s.CreateOrganization(ctx, o); err != nil {
			t.Fatal(err)
		}

		checkInvariance := func(nurms int) {
			orgs, norgs, err := s.FindOrganizations(ctx, influxdb.OrganizationFilter{})
			if err != nil {
				t.Fatal(err)
			}
			if norgs != 1 {
				t.Errorf("expected 1 org, got: %v", orgs)
			}
			usrs, nusrs, err := s.FindUsers(ctx, influxdb.UserFilter{})
			if err != nil {
				t.Fatal(err)
			}
			if nusrs != 1 {
				t.Errorf("expected 1 user, got: %v", usrs)
			}
			urms, nnurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
			if err != nil {
				t.Fatal(err)
			}
			if nnurms != nurms {
				t.Errorf("expected %d urms got %d: %+v", nurms, nnurms, urms)
			}
			bs, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
			if err != nil {
				t.Fatal(err)
			}
			if nbs != 2 {
				t.Errorf("expected 2 buckets, got: %v", bs)
			}
		}

		checkInvariance(0)

		// Wrong userID.
		urm := &influxdb.UserResourceMapping{
			UserID:       2,
			UserType:     influxdb.Owner,
			MappingType:  influxdb.UserMappingType,
			ResourceType: influxdb.OrgsResourceType,
			ResourceID:   1,
		}
		if err := s.CreateUserResourceMapping(ctx, urm); err == nil {
			t.Errorf("expected error got none")
		}

		checkInvariance(0)

		// Wrong orgID. The URM gets created successfully.
		urm = &influxdb.UserResourceMapping{
			UserID:       1,
			UserType:     influxdb.Owner,
			MappingType:  influxdb.UserMappingType,
			ResourceType: influxdb.OrgsResourceType,
			ResourceID:   2,
		}
		if err := s.CreateUserResourceMapping(ctx, urm); err != nil {
			t.Errorf("unexpected error: %v", err)
		}

		checkInvariance(1)
	})

	// NOTE(affo)(*kv.Service): errors on bucket creation.
	//  But, apparently, system buckets are created too :thinking.
	t.Run("should not be possible to create bucket without org", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// Number of buckets prior to bucket creation.
		// This is because, for now, system buckets always get returned for compatibility with the old system.
		_, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
		if err != nil {
			t.Fatal(err)
		}

		b := &influxdb.Bucket{
			// ID(1)
			OrgID: 1,
			Name:  "bucket1",
		}
		if err := s.CreateBucket(ctx, b); err == nil {
			t.Errorf("expected error got none")
		}

		// Check existence
		orgs, norgs, err := s.FindOrganizations(ctx, influxdb.OrganizationFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if norgs != 0 {
			t.Errorf("expected no org, got: %v", orgs)
		}
		usrs, nusrs, err := s.FindUsers(ctx, influxdb.UserFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nusrs != 0 {
			t.Errorf("expected no user, got: %v", usrs)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nurms > 0 {
			t.Errorf("expected no urm, got: %v", urms)
		}
		bs, nnbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
		if err != nil {
			t.Fatal(err)
		}
		// Compare new number of buckets with the one prior to bucket creation.
		if nnbs != nbs {
			t.Errorf("expected bucket created, got: %+v", bs)
		}
	})

	t.Run("making user part of org creates mapping to org only", func(t *testing.T) {
		for _, userType := range []influxdb.UserType{influxdb.Owner, influxdb.Member} {
			t.Run(string(userType), func(t *testing.T) {
				data := fields()
				s, done := init(t, data)
				defer done()
				ctx := context.Background()

				u := &influxdb.User{
					ID:   1,
					Name: "user1",
				}
				if err := s.CreateUser(ctx, u); err != nil {
					t.Fatal(err)
				}
				o := &influxdb.Organization{
					// ID(1)
					Name: "org1",
				}
				if err := s.CreateOrganization(ctx, o); err != nil {
					t.Fatal(err)
				}
				urm := &influxdb.UserResourceMapping{
					UserID:       u.ID,
					UserType:     userType,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.OrgsResourceType,
					ResourceID:   o.ID,
				}
				if err := s.CreateUserResourceMapping(ctx, urm); err != nil {
					t.Fatal(err)
				}

				// Check existence
				orgs, norgs, err := s.FindOrganizations(ctx, influxdb.OrganizationFilter{})
				if err != nil {
					t.Fatal(err)
				}
				if norgs != 1 {
					t.Errorf("expected 1 org, got: %v", orgs)
				}
				usrs, nusrs, err := s.FindUsers(ctx, influxdb.UserFilter{})
				if err != nil {
					t.Fatal(err)
				}
				if nusrs != 1 {
					t.Errorf("expected 1 user, got: %v", usrs)
				}

				bs, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{})
				if err != nil {
					t.Fatal(err)
				}
				if nbs != 2 {
					t.Errorf("expected 2 buckets, got: %v", bs)
				}
				sort.Sort(bucketsByName(bs))
				if name := bs[0].Name; name != "_monitoring" {
					t.Errorf("unexpected name for bucket: %s", name)
				}
				if name := bs[1].Name; name != "_tasks" {
					t.Errorf("unexpected name for bucket: %v", name)
				}

				urms, _, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
				if err != nil {
					t.Fatal(err)
				}
				want := []*influxdb.UserResourceMapping{
					{
						UserID:       u.ID,
						UserType:     userType,
						MappingType:  influxdb.UserMappingType,
						ResourceType: influxdb.OrgsResourceType,
						ResourceID:   o.ID,
					},
				}
				sort.Sort(urmByResourceID(want))
				sort.Sort(urmByResourceID(urms))
				if diff := cmp.Diff(want, urms); diff != "" {
					t.Errorf("unexpected urms -want/+got:\n\t%s", diff)
				}

				// Now add a new bucket and check the URMs.
				b := &influxdb.Bucket{
					// ID(1)
					OrgID: o.ID,
					Name:  "bucket1",
				}
				if err := s.CreateBucket(ctx, b); err != nil {
					t.Fatal(err)
				}
				urms, _, err = s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
				if err != nil {
					t.Fatal(err)
				}

				sort.Sort(urmByResourceID(urms))
				if diff := cmp.Diff(want, urms); diff != "" {
					t.Errorf("unexpected urms -want/+got:\n\t%s", diff)
				}
			})
		}
	})
}

// Delete tests various cases of deletion for the services in the TenantService.
// An example: if you delete a bucket the corresponding user resource mapping is not present.
func Delete(t *testing.T, init func(*testing.T, tenantFields) (*tenant.Service, func())) {
	t.Helper()

	fields := func() tenantFields {
		return tenantFields{
			OrgIDGenerator: mock.NewIncrementingIDGenerator(1),
			// URM are userID + resourceID (they do not include resource type)
			// so same IDs across different resources leads to collisions
			// therefore, we need to start bucket IDs at higher offset for
			// test.
			BucketIDGenerator: mock.NewIncrementingIDGenerator(10),
			Users: []*influxdb.User{
				{
					ID:   1,
					Name: "user1",
				},
				{
					ID:   2,
					Name: "user2",
				},
			},
			Passwords: []string{"password1", "password2"},
			Organizations: []*influxdb.Organization{
				{
					// ID(1)
					Name: "org1",
				},
				{
					// ID(2)
					Name: "org2",
				},
			},
			// 2 organizations create 2 system buckets each
			// so start at 14
			Buckets: []*influxdb.Bucket{
				{
					// ID(14)
					OrgID: 1,
					Name:  "bucket1",
				},
				{
					// ID(15)
					OrgID: 2,
					Name:  "bucket2",
				},
			},
			UserResourceMappings: []*influxdb.UserResourceMapping{
				// NOTE(affo): bucket URMs should not be here, create them only for deletion purposes.
				// user 1 owns org1 (and so bucket1)
				{
					UserID:       1,
					UserType:     influxdb.Owner,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.OrgsResourceType,
					ResourceID:   1,
				},
				{
					UserID:       1,
					UserType:     influxdb.Owner,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.BucketsResourceType,
					ResourceID:   14,
				},
				// user 1 is member of org2 (and so bucket2)
				{
					UserID:       1,
					UserType:     influxdb.Member,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.OrgsResourceType,
					ResourceID:   2,
				},
				{
					UserID:       1,
					UserType:     influxdb.Member,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.BucketsResourceType,
					ResourceID:   15,
				},
				// user 2 owns org2 (and so bucket2)
				{
					UserID:       2,
					UserType:     influxdb.Owner,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.OrgsResourceType,
					ResourceID:   2,
				},
				{
					UserID:       2,
					UserType:     influxdb.Owner,
					MappingType:  influxdb.UserMappingType,
					ResourceType: influxdb.BucketsResourceType,
					ResourceID:   15,
				},
			},
		}
	}

	t.Run("deleting bucket deletes urm", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		f := influxdb.UserResourceMappingFilter{
			ResourceID:   data.Buckets[0].ID,
			ResourceType: influxdb.BucketsResourceType,
		}
		urms, n, err := s.FindUserResourceMappings(ctx, f)
		if err != nil {
			t.Fatal(err)
		}
		if n != 1 {
			t.Fatalf("expected 1 urm, got: %v", urms)
		}
		if err := s.DeleteBucket(ctx, data.Buckets[0].ID); err != nil {
			t.Fatal(err)
		}
		f = influxdb.UserResourceMappingFilter{
			ResourceID:   data.Buckets[0].ID,
			ResourceType: influxdb.BucketsResourceType,
		}
		urms, n, err = s.FindUserResourceMappings(ctx, f)
		if err != nil {
			t.Fatal(err)
		}
		if n > 0 {
			t.Fatalf("expected no urm, got: %v", urms)
		}
	})

	// NOTE(affo): those resources could not be dangling (URM could be inferred from an user being in the owner org).
	// We do not want to automatically propagate this kind of delete because an resource will always have an owner org.
	t.Run("deleting bucket urm does create dangling bucket", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// Pre-check the current situation.
		// bucket1 is owned by user1.
		// Check it.
		urms, _, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			ResourceType: influxdb.BucketsResourceType,
			ResourceID:   data.Buckets[0].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		want := []*influxdb.UserResourceMapping{
			{
				UserID:       data.Users[0].ID,
				UserType:     influxdb.Owner,
				MappingType:  influxdb.UserMappingType,
				ResourceType: influxdb.BucketsResourceType,
				ResourceID:   data.Buckets[0].ID,
			},
		}
		sort.Sort(urmByUserID(want))
		sort.Sort(urmByUserID(urms))
		if diff := cmp.Diff(want, urms); diff != "" {
			t.Fatalf("unexpected urms -want/+got:\n\t%s", diff)
		}
		// bucket2 is owned by user2.
		// bucket2 is readable by user2.
		// Check it.
		urms, _, err = s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			ResourceType: influxdb.BucketsResourceType,
			ResourceID:   data.Buckets[1].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		want = []*influxdb.UserResourceMapping{
			{
				UserID:       data.Users[1].ID,
				UserType:     influxdb.Owner,
				MappingType:  influxdb.UserMappingType,
				ResourceType: influxdb.BucketsResourceType,
				ResourceID:   data.Buckets[1].ID,
			},
			{
				UserID:       data.Users[0].ID,
				UserType:     influxdb.Member,
				MappingType:  influxdb.UserMappingType,
				ResourceType: influxdb.BucketsResourceType,
				ResourceID:   data.Buckets[1].ID,
			},
		}
		sort.Sort(urmByUserID(want))
		sort.Sort(urmByUserID(urms))
		if diff := cmp.Diff(want, urms); diff != "" {
			t.Fatalf("unexpected urms -want/+got:\n\t%s", diff)
		}

		// Now delete user2 -> bucket2.
		// Still expect bucket2 to exist (user1 still points to it).
		if err := s.DeleteUserResourceMapping(ctx, data.Buckets[1].ID, data.Users[1].ID); err != nil {
			t.Fatal(err)
		}
		bs, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{ID: &data.Buckets[1].ID})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 1 {
			t.Errorf("expected 1 buckets, got: %v", bs)
		}
		// Now delete user1 -> bucket2.
		// Still expect bucket2 to exist (nobody points to it).
		if err := s.DeleteUserResourceMapping(ctx, data.Buckets[1].ID, data.Users[0].ID); err != nil {
			t.Fatal(err)
		}
		bs, nbs, err = s.FindBuckets(ctx, influxdb.BucketFilter{ID: &data.Buckets[1].ID})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 1 {
			t.Errorf("expected 1 buckets, got: %v", bs)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			ResourceType: influxdb.BucketsResourceType,
			ResourceID:   data.Buckets[1].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		if nurms != 0 {
			t.Errorf("expected bucket2, to be dangling, got: %+v", urms)
		}
		// Now delete user1 -> bucket1.
		// Still expect bucket1 to exist (nobody points to it).
		if err := s.DeleteUserResourceMapping(ctx, data.Buckets[0].ID, data.Users[0].ID); err != nil {
			t.Fatal(err)
		}
		bs, nbs, err = s.FindBuckets(ctx, influxdb.BucketFilter{ID: &data.Buckets[0].ID})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 1 {
			t.Errorf("expected 1 buckets, got: %v", bs)
		}
		urms, nurms, err = s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			ResourceType: influxdb.BucketsResourceType,
			ResourceID:   data.Buckets[0].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		if nurms != 0 {
			t.Errorf("expected bucket1, to be dangling, got: %+v", urms)
		}
	})

	t.Run("deleting a user deletes every related urm and nothing else", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// bucket1 is owned by user1.
		// Check it.
		urms, _, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			ResourceType: influxdb.BucketsResourceType,
			ResourceID:   data.Buckets[0].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		want := []*influxdb.UserResourceMapping{
			{
				UserID:       data.Users[0].ID,
				UserType:     influxdb.Owner,
				MappingType:  influxdb.UserMappingType,
				ResourceType: influxdb.BucketsResourceType,
				ResourceID:   data.Buckets[0].ID,
			},
		}
		sort.Sort(urmByUserID(want))
		sort.Sort(urmByUserID(urms))
		if diff := cmp.Diff(want, urms); diff != "" {
			t.Fatalf("unexpected urms -want/+got:\n\t%s", diff)
		}

		// Delete user1.
		// We expect his urms deleted but not bucket1.
		if err := s.DeleteUser(ctx, data.Users[0].ID); err != nil {
			t.Fatal(err)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			UserID: data.Users[0].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		if nurms > 0 {
			t.Errorf("expected that user deletion would remove dangling urms, got: %+v", urms)
		}
		bs, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{ID: &data.Buckets[0].ID})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 1 {
			t.Errorf("expected 1 buckets, got: %v", bs)
		}
	})

	t.Run("deleting a bucket deletes every related urm", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// Delete bucket2.
		// We expect its urms deleted.
		if err := s.DeleteBucket(ctx, data.Buckets[1].ID); err != nil {
			t.Fatal(err)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			ResourceType: influxdb.BucketsResourceType,
			ResourceID:   data.Buckets[1].ID,
		})
		if err != nil {
			t.Fatal(err)
		}
		if nurms > 0 {
			t.Errorf("expected that bucket deletion would remove dangling urms, got: %+v", urms)
		}
	})

	// NOTE(affo)(*kv.Service): buckets, users, and urms survive.
	t.Run("deleting an organization should delete everything that depends on it", func(t *testing.T) {
		data := fields()
		s, done := init(t, data)
		defer done()
		ctx := context.Background()

		// Delete org1.
		// We expect its buckets to be deleted.
		// We expect urms to those buckets to be deleted too.
		// No user should be deleted.
		preDeletionBuckets, _, err := s.FindBuckets(ctx, influxdb.BucketFilter{OrganizationID: &data.Organizations[0].ID})
		if err != nil {
			t.Fatal(err)
		}
		if err := s.DeleteOrganization(ctx, data.Organizations[0].ID); err != nil {
			t.Fatal(err)
		}
		bs, nbs, err := s.FindBuckets(ctx, influxdb.BucketFilter{OrganizationID: &data.Organizations[0].ID})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 0 {
			t.Errorf("expected org buckets to be deleted, got: %+v", bs)
		}

		urms, _, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{
			UserID:       data.Users[0].ID,
			ResourceType: influxdb.BucketsResourceType,
		})
		if err != nil {
			t.Fatal(err)
		}
		for _, urm := range urms {
			for _, b := range preDeletionBuckets {
				if urm.ResourceID == b.ID {
					t.Errorf("expected this urm to be deleted, got %+v instead", urm)
				}
			}
		}
		if _, err := s.FindUser(ctx, influxdb.UserFilter{ID: &data.Users[0].ID}); err != nil {
			t.Fatal(err)
		}

		// Delete org2.
		// Everything should disappear.
		if err := s.DeleteOrganization(ctx, data.Organizations[1].ID); err != nil {
			t.Fatal(err)
		}
		orgs, norgs, err := s.FindOrganizations(ctx, influxdb.OrganizationFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if norgs != 0 {
			t.Errorf("expected no org, got: %v", orgs)
		}
		usrs, nusrs, err := s.FindUsers(ctx, influxdb.UserFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nusrs != 2 {
			t.Errorf("expected 2 users, got: %v", usrs)
		}
		urms, nurms, err := s.FindUserResourceMappings(ctx, influxdb.UserResourceMappingFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nurms > 0 {
			t.Errorf("expected no urm, got: %v", urms)
		}
		bs, nbs, err = s.FindBuckets(ctx, influxdb.BucketFilter{})
		if err != nil {
			t.Fatal(err)
		}
		if nbs != 0 {
			t.Errorf("expected buckets to be deleted, got: %+v", bs)
		}
	})
}

func initBoltTenantService(t *testing.T, f tenantFields) (*tenant.Service, func()) {
	s, closeBolt := itesting.NewTestBoltStore(t)
	store := tenant.NewStore(s)

	if f.OrgIDGenerator != nil {
		store.OrgIDGen = f.OrgIDGenerator
	}

	if f.BucketIDGenerator != nil {
		store.BucketIDGen = f.BucketIDGenerator
	}

	svc := tenant.NewService(store)

	for _, u := range f.Users {
		if err := svc.CreateUser(context.Background(), u); err != nil {
			t.Fatalf("error populating users: %v", err)
		}
	}

	for i := range f.Passwords {
		if err := svc.SetPassword(context.Background(), f.Users[i].ID, f.Passwords[i]); err != nil {
			t.Fatalf("error setting passsword user, %s %s: %v", f.Users[i].Name, f.Passwords[i], err)
		}
	}

	for _, o := range f.Organizations {
		if err := svc.CreateOrganization(context.Background(), o); err != nil {
			t.Fatalf("failed to populate organizations: %s", err)
		}
	}

	for _, b := range f.Buckets {
		if err := svc.CreateBucket(context.Background(), b); err != nil {
			t.Fatalf("failed to populate buckets: %s", err)
		}
	}

	for _, m := range f.UserResourceMappings {
		if err := svc.CreateUserResourceMapping(context.Background(), m); err != nil {
			t.Fatalf("failed to populate mappings: %v", err)
		}
	}

	return svc, func() {
		for _, u := range f.Users {
			if err := svc.DeleteUser(context.Background(), u.ID); err != nil {
				t.Logf("error removing users: %v", err)
			}
		}

		for _, m := range f.UserResourceMappings {
			if err := svc.DeleteUserResourceMapping(context.Background(), m.ResourceID, m.UserID); err != nil {
				t.Logf("failed to remove user resource mapping: %v", err)
			}
		}

		for _, o := range f.Organizations {
			if err := svc.DeleteOrganization(context.Background(), o.ID); err != nil {
				t.Logf("failed to remove organization: %v", err)
			}
		}

		closeBolt()
	}
}
