package vault

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/go-test/deep"
	"github.com/hashicorp/vault/helper/identity"
	"github.com/hashicorp/vault/helper/namespace"
	"github.com/hashicorp/vault/sdk/logical"
)

func TestIdentityStore_Groups_AddByNameEntityUpdate(t *testing.T) {
	c, _, _ := TestCoreUnsealed(t)
	ctx := namespace.RootContext(nil)

	// Create an entity and get its ID
	entityRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "entity",
	}
	resp, err := c.identityStore.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID := resp.Data["id"].(string)

	// Create a group containing the entity
	groupName := "group-name"
	expectedMemberEntityIDs := []string{entityID}
	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":              groupName,
			"member_entity_ids": expectedMemberEntityIDs,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}

	// Remove the entity from the group
	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":              groupName,
			"member_entity_ids": []string{},
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}

	// Make sure the member no longer thinks it's in the group
	entityIDReq := &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "entity/id/" + entityID,
	}
	resp, err = c.identityStore.HandleRequest(ctx, entityIDReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	expectedGroupIDs := []string{}
	actualGroupIDs := resp.Data["direct_group_ids"]
	if !reflect.DeepEqual(expectedGroupIDs, actualGroupIDs) {
		t.Fatalf("bad: direct_group_ids:\nexpected: %#v\nactual: %#v", expectedGroupIDs, actualGroupIDs)
	}
}

func TestIdentityStore_FixOverwrittenMemberGroupIDs(t *testing.T) {
	c, _, _ := TestCoreUnsealed(t)
	ctx := namespace.RootContext(nil)

	// Create a group
	resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": "group1",
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}

	groupID := resp.Data["id"].(string)

	expectedMemberGroupIDs := []string{groupID}

	// Create another group and add the above created group as its member
	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":             "group2",
			"policies":         "default",
			"member_group_ids": expectedMemberGroupIDs,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}

	// Create another group and add the above created group as its member
	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/group2",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"policies": "default,another-policy",
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}

	// Read the group and check if the member_group_ids is intact
	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/group2",
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}

	if !reflect.DeepEqual(resp.Data["member_group_ids"], expectedMemberGroupIDs) {
		t.Fatalf("bad: member_group_ids; expected: %#v\n, actual: %#v", expectedMemberGroupIDs, resp.Data["member_group_ids"])
	}
}

func TestIdentityStore_GroupEntityMembershipUpgrade(t *testing.T) {
	c, keys, rootToken := TestCoreUnsealed(t)

	// Create a group
	resp, err := c.identityStore.HandleRequest(namespace.RootContext(nil), &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": "testgroup",
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
	}

	// Create a memdb transaction
	txn := c.identityStore.db.Txn(true)
	defer txn.Abort()

	// Fetch the above created group
	group, err := c.identityStore.MemDBGroupByNameInTxn(namespace.RootContext(nil), txn, "testgroup", true)
	if err != nil {
		t.Fatal(err)
	}

	// Manually add an invalid entity as the group's member
	group.MemberEntityIDs = []string{"invalidentityid"}

	ctx := namespace.RootContext(nil)

	// Persist the group
	err = c.identityStore.UpsertGroupInTxn(ctx, txn, group, true)
	if err != nil {
		t.Fatal(err)
	}

	txn.Commit()

	// Perform seal and unseal forcing an upgrade
	err = c.Seal(rootToken)
	if err != nil {
		t.Fatal(err)
	}
	for i, key := range keys {
		unseal, err := TestCoreUnseal(c, key)
		if err != nil {
			t.Fatal(err)
		}
		if i+1 == len(keys) && !unseal {
			t.Fatalf("failed to unseal")
		}
	}

	// Read the group and ensure that invalid entity id is cleaned up
	group, err = c.identityStore.MemDBGroupByName(namespace.RootContext(nil), "testgroup", false)
	if err != nil {
		t.Fatal(err)
	}

	if len(group.MemberEntityIDs) != 0 {
		t.Fatalf("bad: member entity IDs; expected: none, actual: %#v", group.MemberEntityIDs)
	}
}

func TestIdentityStore_MemberGroupIDDelete(t *testing.T) {
	ctx := namespace.RootContext(nil)
	i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	// Create a child group
	resp, err := i.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": "child",
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	childGroupID := resp.Data["id"].(string)

	// Create a parent group with the above group ID as its child
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":             "parent",
			"member_group_ids": []string{childGroupID},
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}

	// Ensure that member group ID is properly updated
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/parent",
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	memberGroupIDs := resp.Data["member_group_ids"].([]string)
	if len(memberGroupIDs) != 1 && memberGroupIDs[0] != childGroupID {
		t.Fatalf("bad: member group ids; expected: %#v, actual: %#v", []string{childGroupID}, memberGroupIDs)
	}

	// Clear the member group IDs from the parent group
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/parent",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"member_group_ids": []string{},
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}

	// Ensure that member group ID is properly deleted
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/parent",
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	memberGroupIDs = resp.Data["member_group_ids"].([]string)
	if len(memberGroupIDs) != 0 {
		t.Fatalf("bad: length of member group ids; expected: %d, actual: %d", 0, len(memberGroupIDs))
	}
}

func TestIdentityStore_CaseInsensitiveGroupName(t *testing.T) {
	ctx := namespace.RootContext(nil)
	i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	testGroupName := "testGroupName"

	// Create an group with case sensitive name
	resp, err := i.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": testGroupName,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
	}
	groupID := resp.Data["id"].(string)

	// Lookup the group by ID and check that name returned is case sensitive
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/id/" + groupID,
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err:%v\nresp: %#v", err, resp)
	}
	groupName := resp.Data["name"].(string)
	if groupName != testGroupName {
		t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
	}

	// Lookup the group by case sensitive name
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/" + testGroupName,
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}
	groupName = resp.Data["name"].(string)
	if groupName != testGroupName {
		t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
	}

	// Lookup the group by case insensitive name
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/" + strings.ToLower(testGroupName),
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}
	groupName = resp.Data["name"].(string)
	if groupName != testGroupName {
		t.Fatalf("bad group name; expected: %q, actual: %q", testGroupName, groupName)
	}

	// Ensure that there is only one group
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name",
		Operation: logical.ListOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
	}
	if len(resp.Data["keys"].([]string)) != 1 {
		t.Fatalf("bad length of groups; expected: 1, actual: %d", len(resp.Data["keys"].([]string)))
	}
}

func TestIdentityStore_GroupByName(t *testing.T) {
	ctx := namespace.RootContext(nil)
	i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	// Create an entity using the "name" endpoint
	resp, err := i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.UpdateOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	if resp == nil {
		t.Fatalf("expected a non-nil response")
	}

	// Test the read by name endpoint
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	if resp == nil || resp.Data["name"].(string) != "testgroupname" {
		t.Fatalf("bad entity response: %#v", resp)
	}

	// Update group metadata using the name endpoint
	groupMetadata := map[string]string{
		"foo": "bar",
	}
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"metadata": groupMetadata,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}

	// Check the updated result
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	if resp == nil || !reflect.DeepEqual(resp.Data["metadata"].(map[string]string), groupMetadata) {
		t.Fatalf("bad group response: %#v", resp)
	}

	// Delete the group using the name endpoint
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.DeleteOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}

	// Check if deletion was successful
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.ReadOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	if resp != nil {
		t.Fatalf("expected a nil response")
	}

	// Create 2 entities
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname",
		Operation: logical.UpdateOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	if resp == nil {
		t.Fatalf("expected a non-nil response")
	}
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name/testgroupname2",
		Operation: logical.UpdateOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}
	if resp == nil {
		t.Fatalf("expected a non-nil response")
	}

	// List the entities by name
	resp, err = i.HandleRequest(ctx, &logical.Request{
		Path:      "group/name",
		Operation: logical.ListOperation,
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
	}

	expected := []string{"testgroupname2", "testgroupname"}
	sort.Strings(expected)
	actual := resp.Data["keys"].([]string)
	sort.Strings(actual)
	if !reflect.DeepEqual(expected, actual) {
		t.Fatalf("bad: group list response; expected: %#v\nactual: %#v", expected, actual)
	}
}

func TestIdentityStore_Groups_TypeMembershipAdditions(t *testing.T) {
	var err error
	var resp *logical.Response

	ctx := namespace.RootContext(nil)
	i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
	groupReq := &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"type":              "external",
			"member_entity_ids": "sampleentityid",
		},
	}

	resp, err = i.HandleRequest(ctx, groupReq)
	if err != nil {
		t.Fatal(err)
	}
	if !resp.IsError() {
		t.Fatalf("expected an error")
	}

	groupReq.Data = map[string]interface{}{
		"type":             "external",
		"member_group_ids": "samplegroupid",
	}

	resp, err = i.HandleRequest(ctx, groupReq)
	if err != nil {
		t.Fatal(err)
	}
	if !resp.IsError() {
		t.Fatalf("expected an error")
	}
}

func TestIdentityStore_Groups_TypeImmutability(t *testing.T) {
	var err error
	var resp *logical.Response

	ctx := namespace.RootContext(nil)
	i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
	groupReq := &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
	}

	resp, err = i.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	internalGroupID := resp.Data["id"].(string)

	groupReq.Data = map[string]interface{}{
		"type": "external",
	}
	resp, err = i.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	externalGroupID := resp.Data["id"].(string)

	// Try to mark internal group as external
	groupReq.Data = map[string]interface{}{
		"type": "external",
	}
	groupReq.Path = "group/id/" + internalGroupID
	resp, err = i.HandleRequest(ctx, groupReq)
	if err != nil {
		t.Fatal(err)
	}
	if !resp.IsError() {
		t.Fatalf("expected an error")
	}

	// Try to mark internal group as external
	groupReq.Data = map[string]interface{}{
		"type": "internal",
	}
	groupReq.Path = "group/id/" + externalGroupID
	resp, err = i.HandleRequest(ctx, groupReq)
	if err != nil {
		t.Fatal(err)
	}
	if !resp.IsError() {
		t.Fatalf("expected an error")
	}
}

func TestIdentityStore_MemDBGroupIndexes(t *testing.T) {
	var err error
	ctx := namespace.RootContext(nil)
	i, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	// Create a dummy group
	group := &identity.Group{
		ID:   "testgroupid",
		Name: "testgroupname",
		Metadata: map[string]string{
			"testmetadatakey1": "testmetadatavalue1",
			"testmetadatakey2": "testmetadatavalue2",
		},
		ParentGroupIDs:  []string{"testparentgroupid1", "testparentgroupid2"},
		MemberEntityIDs: []string{"testentityid1", "testentityid2"},
		Policies:        []string{"testpolicy1", "testpolicy2"},
		BucketKey:       i.groupPacker.BucketKey("testgroupid"),
	}

	// Insert it into memdb
	txn := i.db.Txn(true)
	defer txn.Abort()
	err = i.MemDBUpsertGroupInTxn(txn, group)
	if err != nil {
		t.Fatal(err)
	}
	txn.Commit()

	// Insert another dummy group
	group = &identity.Group{
		ID:   "testgroupid2",
		Name: "testgroupname2",
		Metadata: map[string]string{
			"testmetadatakey2": "testmetadatavalue2",
			"testmetadatakey3": "testmetadatavalue3",
		},
		ParentGroupIDs:  []string{"testparentgroupid2", "testparentgroupid3"},
		MemberEntityIDs: []string{"testentityid2", "testentityid3"},
		Policies:        []string{"testpolicy2", "testpolicy3"},
		BucketKey:       i.groupPacker.BucketKey("testgroupid2"),
	}

	// Insert it into memdb

	txn = i.db.Txn(true)
	defer txn.Abort()
	err = i.MemDBUpsertGroupInTxn(txn, group)
	if err != nil {
		t.Fatal(err)
	}
	txn.Commit()

	var fetchedGroup *identity.Group

	// Fetch group given the name
	fetchedGroup, err = i.MemDBGroupByName(namespace.RootContext(nil), "testgroupname", false)
	if err != nil {
		t.Fatal(err)
	}
	if fetchedGroup == nil || fetchedGroup.Name != "testgroupname" {
		t.Fatalf("failed to fetch an indexed group")
	}

	// Fetch group given the ID
	fetchedGroup, err = i.MemDBGroupByID("testgroupid", false)
	if err != nil {
		t.Fatal(err)
	}
	if fetchedGroup == nil || fetchedGroup.Name != "testgroupname" {
		t.Fatalf("failed to fetch an indexed group")
	}

	var fetchedGroups []*identity.Group
	// Fetch the subgroups of a given group ID
	fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid1", false)
	if err != nil {
		t.Fatal(err)
	}
	if len(fetchedGroups) != 1 || fetchedGroups[0].Name != "testgroupname" {
		t.Fatalf("failed to fetch an indexed group")
	}

	fetchedGroups, err = i.MemDBGroupsByParentGroupID("testparentgroupid2", false)
	if err != nil {
		t.Fatal(err)
	}
	if len(fetchedGroups) != 2 {
		t.Fatalf("failed to fetch a indexed groups")
	}

	// Fetch groups based on member entity ID
	fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid1", false, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(fetchedGroups) != 1 || fetchedGroups[0].Name != "testgroupname" {
		t.Fatalf("failed to fetch an indexed group")
	}

	fetchedGroups, err = i.MemDBGroupsByMemberEntityID("testentityid2", false, false)
	if err != nil {
		t.Fatal(err)
	}

	if len(fetchedGroups) != 2 {
		t.Fatalf("failed to fetch groups by entity ID")
	}
}

func TestIdentityStore_GroupsCreateUpdate(t *testing.T) {
	var resp *logical.Response
	var err error

	ctx := namespace.RootContext(nil)
	is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	// Create an entity and get its ID
	entityRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "entity",
	}
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID1 := resp.Data["id"].(string)

	// Create another entity and get its ID
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID2 := resp.Data["id"].(string)

	// Create a group with the above created 2 entities as its members
	groupData := map[string]interface{}{
		"policies":          "testpolicy1,testPolicy1 , testpolicy2",
		"metadata":          []string{"testkey1=testvalue1", "testkey2=testvalue2"},
		"member_entity_ids": []string{entityID1, entityID2},
	}

	// Create a group and get its ID
	groupReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group",
		Data:      groupData,
	}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	memberGroupID1 := resp.Data["id"].(string)

	// Create another group and get its ID
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	memberGroupID2 := resp.Data["id"].(string)

	// Create a group with the above 2 groups as its members
	groupData["member_group_ids"] = []string{memberGroupID1, memberGroupID2}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	groupID := resp.Data["id"].(string)

	// Read the group using its iD and check if all the fields are properly
	// set
	groupReq = &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "group/id/" + groupID,
	}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	expectedData := map[string]interface{}{
		"policies": []string{"testpolicy1", "testpolicy2"},
		"metadata": map[string]string{
			"testkey1": "testvalue1",
			"testkey2": "testvalue2",
		},
		"parent_group_ids": []string(nil),
	}
	expectedData["id"] = resp.Data["id"]
	expectedData["type"] = resp.Data["type"]
	expectedData["name"] = resp.Data["name"]
	expectedData["member_group_ids"] = resp.Data["member_group_ids"]
	expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
	expectedData["creation_time"] = resp.Data["creation_time"]
	expectedData["last_update_time"] = resp.Data["last_update_time"]
	expectedData["modify_index"] = resp.Data["modify_index"]
	expectedData["alias"] = resp.Data["alias"]
	expectedData["namespace_id"] = "root"

	if diff := deep.Equal(expectedData, resp.Data); diff != nil {
		t.Fatal(diff)
	}

	// Update the policies and metadata in the group
	groupReq.Operation = logical.UpdateOperation
	groupReq.Data = groupData

	// Update by setting ID in the param
	groupData["id"] = groupID
	groupData["policies"] = "updatedpolicy1,updatedpolicy2"
	groupData["metadata"] = []string{"updatedkey=updatedvalue"}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	// Check if updates are reflected
	groupReq.Operation = logical.ReadOperation
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
	expectedData["metadata"] = map[string]string{
		"updatedkey": "updatedvalue",
	}
	expectedData["last_update_time"] = resp.Data["last_update_time"]
	expectedData["modify_index"] = resp.Data["modify_index"]
	if !reflect.DeepEqual(expectedData, resp.Data) {
		t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
	}
}

func TestIdentityStore_GroupsCreateUpdateDuplicatePolicy(t *testing.T) {
	var resp *logical.Response
	var err error

	ctx := namespace.RootContext(nil)
	is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	// Create a group with the above created 2 entities as its members
	groupData := map[string]interface{}{
		"policies": []string{"testpolicy1", "testpolicy2"},
		"metadata": []string{"testkey1=testvalue1", "testkey2=testvalue2"},
	}

	// Create a group and get its ID
	groupReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group",
		Data:      groupData,
	}

	// Create a group with the above 2 groups as its members
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	groupID := resp.Data["id"].(string)

	// Read the group using its iD and check if all the fields are properly
	// set
	groupReq = &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "group/id/" + groupID,
	}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	expectedData := map[string]interface{}{
		"policies": []string{"testpolicy1", "testpolicy2"},
		"metadata": map[string]string{
			"testkey1": "testvalue1",
			"testkey2": "testvalue2",
		},
		"parent_group_ids": []string(nil),
	}
	expectedData["id"] = resp.Data["id"]
	expectedData["type"] = resp.Data["type"]
	expectedData["name"] = resp.Data["name"]
	expectedData["creation_time"] = resp.Data["creation_time"]
	expectedData["last_update_time"] = resp.Data["last_update_time"]
	expectedData["modify_index"] = resp.Data["modify_index"]
	expectedData["alias"] = resp.Data["alias"]
	expectedData["namespace_id"] = "root"
	expectedData["member_group_ids"] = resp.Data["member_group_ids"]
	expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]

	if diff := deep.Equal(expectedData, resp.Data); diff != nil {
		t.Fatal(diff)
	}

	// Update the policies and metadata in the group
	groupReq.Operation = logical.UpdateOperation
	groupReq.Data = groupData

	// Update by setting ID in the param
	groupData["id"] = groupID
	groupData["policies"] = []string{"updatedpolicy1", "updatedpolicy2", "updatedpolicy2"}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	// Check if updates are reflected
	groupReq.Operation = logical.ReadOperation
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
	expectedData["last_update_time"] = resp.Data["last_update_time"]
	expectedData["modify_index"] = resp.Data["modify_index"]
	if !reflect.DeepEqual(expectedData, resp.Data) {
		t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
	}
}

func TestIdentityStore_GroupsCRUD_ByID(t *testing.T) {
	var resp *logical.Response
	var err error
	ctx := namespace.RootContext(nil)
	is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)

	// Create an entity and get its ID
	entityRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "entity",
	}
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID1 := resp.Data["id"].(string)

	// Create another entity and get its ID
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID2 := resp.Data["id"].(string)

	// Create a group with the above created 2 entities as its members
	groupData := map[string]interface{}{
		"policies":          "testpolicy1,testpolicy2",
		"metadata":          []string{"testkey1=testvalue1", "testkey2=testvalue2"},
		"member_entity_ids": []string{entityID1, entityID2},
	}

	// Create a group and get its ID
	groupRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group",
		Data:      groupData,
	}
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	memberGroupID1 := resp.Data["id"].(string)

	// Create another group and get its ID
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	memberGroupID2 := resp.Data["id"].(string)

	// Create a group with the above 2 groups as its members
	groupData["member_group_ids"] = []string{memberGroupID1, memberGroupID2}
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	groupID := resp.Data["id"].(string)

	// Read the group using its name and check if all the fields are properly
	// set
	groupReq := &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "group/id/" + groupID,
	}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	expectedData := map[string]interface{}{
		"policies": []string{"testpolicy1", "testpolicy2"},
		"metadata": map[string]string{
			"testkey1": "testvalue1",
			"testkey2": "testvalue2",
		},
		"parent_group_ids": []string(nil),
	}
	expectedData["id"] = resp.Data["id"]
	expectedData["type"] = resp.Data["type"]
	expectedData["name"] = resp.Data["name"]
	expectedData["member_group_ids"] = resp.Data["member_group_ids"]
	expectedData["member_entity_ids"] = resp.Data["member_entity_ids"]
	expectedData["creation_time"] = resp.Data["creation_time"]
	expectedData["last_update_time"] = resp.Data["last_update_time"]
	expectedData["modify_index"] = resp.Data["modify_index"]
	expectedData["alias"] = resp.Data["alias"]
	expectedData["namespace_id"] = "root"

	if diff := deep.Equal(expectedData, resp.Data); diff != nil {
		t.Fatal(diff)
	}

	// Update the policies and metadata in the group
	groupReq.Operation = logical.UpdateOperation
	groupReq.Data = groupData
	groupData["policies"] = "updatedpolicy1,updatedpolicy2"
	groupData["metadata"] = []string{"updatedkey=updatedvalue"}
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	// Check if updates are reflected
	groupReq.Operation = logical.ReadOperation
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	expectedData["policies"] = []string{"updatedpolicy1", "updatedpolicy2"}
	expectedData["metadata"] = map[string]string{
		"updatedkey": "updatedvalue",
	}
	expectedData["last_update_time"] = resp.Data["last_update_time"]
	expectedData["modify_index"] = resp.Data["modify_index"]
	if !reflect.DeepEqual(expectedData, resp.Data) {
		t.Fatalf("bad: group data; expected: %#v\n actual: %#v\n", expectedData, resp.Data)
	}

	// Check if delete is working properly
	groupReq.Operation = logical.DeleteOperation
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	groupReq.Operation = logical.ReadOperation
	resp, err = is.HandleRequest(ctx, groupReq)
	if err != nil {
		t.Fatal(err)
	}
	if resp != nil {
		t.Fatalf("expected a nil response")
	}
}

func TestIdentityStore_GroupMultiCase(t *testing.T) {
	var resp *logical.Response
	var err error
	ctx := namespace.RootContext(nil)
	is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
	groupRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group",
	}

	// Create 'build' group
	buildGroupData := map[string]interface{}{
		"name":     "build",
		"policies": "buildpolicy",
	}
	groupRegisterReq.Data = buildGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	buildGroupID := resp.Data["id"].(string)

	// Create 'deploy' group
	deployGroupData := map[string]interface{}{
		"name":     "deploy",
		"policies": "deploypolicy",
	}
	groupRegisterReq.Data = deployGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	deployGroupID := resp.Data["id"].(string)

	// Create an entity ID
	entityRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "entity",
	}
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID1 := resp.Data["id"].(string)

	// Add the entity as a member of 'build' group
	entityIDReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group/id/" + buildGroupID,
		Data: map[string]interface{}{
			"member_entity_ids": []string{entityID1},
		},
	}
	resp, err = is.HandleRequest(ctx, entityIDReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	// Add the entity as a member of the 'deploy` group
	entityIDReq.Path = "group/id/" + deployGroupID
	resp, err = is.HandleRequest(ctx, entityIDReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	policiesResult, err := is.groupPoliciesByEntityID(entityID1)
	if err != nil {
		t.Fatal(err)
	}

	policies := []string{}
	for _, nsPolicies := range policiesResult {
		policies = append(policies, nsPolicies...)
	}
	sort.Strings(policies)
	expected := []string{"deploypolicy", "buildpolicy"}
	sort.Strings(expected)
	if !reflect.DeepEqual(expected, policies) {
		t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
	}
}

/*
Test groups hierarchy:

	             ------- eng(entityID3) -------
	             |                            |
	      ----- vault -----        -- ops(entityID2) --
	      |               |        |                  |
	kube(entityID1)    identity    build            deploy
*/
func TestIdentityStore_GroupHierarchyCases(t *testing.T) {
	var resp *logical.Response
	var err error
	ctx := namespace.RootContext(nil)
	is, _, _ := testIdentityStoreWithGithubAuth(ctx, t)
	groupRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group",
	}

	// Create 'kube' group
	kubeGroupData := map[string]interface{}{
		"name":     "kube",
		"policies": "kubepolicy",
	}
	groupRegisterReq.Data = kubeGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	kubeGroupID := resp.Data["id"].(string)

	// Create 'identity' group
	identityGroupData := map[string]interface{}{
		"name":     "identity",
		"policies": "identitypolicy",
	}
	groupRegisterReq.Data = identityGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	identityGroupID := resp.Data["id"].(string)

	// Create 'build' group
	buildGroupData := map[string]interface{}{
		"name":     "build",
		"policies": "buildpolicy",
	}
	groupRegisterReq.Data = buildGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	buildGroupID := resp.Data["id"].(string)

	// Create 'deploy' group
	deployGroupData := map[string]interface{}{
		"name":     "deploy",
		"policies": "deploypolicy",
	}
	groupRegisterReq.Data = deployGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	deployGroupID := resp.Data["id"].(string)

	// Create 'vault' with 'kube' and 'identity' as member groups
	vaultMemberGroupIDs := []string{kubeGroupID, identityGroupID}
	vaultGroupData := map[string]interface{}{
		"name":             "vault",
		"policies":         "vaultpolicy",
		"member_group_ids": vaultMemberGroupIDs,
	}
	groupRegisterReq.Data = vaultGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	vaultGroupID := resp.Data["id"].(string)

	// Create 'ops' group with 'build' and 'deploy' as member groups
	opsMemberGroupIDs := []string{buildGroupID, deployGroupID}
	opsGroupData := map[string]interface{}{
		"name":             "ops",
		"policies":         "opspolicy",
		"member_group_ids": opsMemberGroupIDs,
	}
	groupRegisterReq.Data = opsGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	opsGroupID := resp.Data["id"].(string)

	// Create 'eng' group with 'vault' and 'ops' as member groups
	engMemberGroupIDs := []string{vaultGroupID, opsGroupID}
	engGroupData := map[string]interface{}{
		"name":             "eng",
		"policies":         "engpolicy",
		"member_group_ids": engMemberGroupIDs,
	}

	groupRegisterReq.Data = engGroupData
	resp, err = is.HandleRequest(ctx, groupRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	engGroupID := resp.Data["id"].(string)

	/*
		fmt.Printf("engGroupID: %#v\n", engGroupID)
		fmt.Printf("vaultGroupID: %#v\n", vaultGroupID)
		fmt.Printf("opsGroupID: %#v\n", opsGroupID)
		fmt.Printf("kubeGroupID: %#v\n", kubeGroupID)
		fmt.Printf("identityGroupID: %#v\n", identityGroupID)
		fmt.Printf("buildGroupID: %#v\n", buildGroupID)
		fmt.Printf("deployGroupID: %#v\n", deployGroupID)
	*/

	var memberGroupIDs []string
	// Fetch 'eng' group
	engGroup, err := is.MemDBGroupByID(engGroupID, false)
	if err != nil {
		t.Fatal(err)
	}
	memberGroupIDs, err = is.memberGroupIDsByID(engGroup.ID)
	if err != nil {
		t.Fatal(err)
	}
	sort.Strings(memberGroupIDs)
	sort.Strings(engMemberGroupIDs)
	if !reflect.DeepEqual(engMemberGroupIDs, memberGroupIDs) {
		t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", engMemberGroupIDs, memberGroupIDs)
	}

	vaultGroup, err := is.MemDBGroupByID(vaultGroupID, false)
	if err != nil {
		t.Fatal(err)
	}
	memberGroupIDs, err = is.memberGroupIDsByID(vaultGroup.ID)
	if err != nil {
		t.Fatal(err)
	}
	sort.Strings(memberGroupIDs)
	sort.Strings(vaultMemberGroupIDs)
	if !reflect.DeepEqual(vaultMemberGroupIDs, memberGroupIDs) {
		t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", vaultMemberGroupIDs, memberGroupIDs)
	}

	opsGroup, err := is.MemDBGroupByID(opsGroupID, false)
	if err != nil {
		t.Fatal(err)
	}
	memberGroupIDs, err = is.memberGroupIDsByID(opsGroup.ID)
	if err != nil {
		t.Fatal(err)
	}
	sort.Strings(memberGroupIDs)
	sort.Strings(opsMemberGroupIDs)
	if !reflect.DeepEqual(opsMemberGroupIDs, memberGroupIDs) {
		t.Fatalf("bad: group membership IDs; expected: %#v\n actual: %#v\n", opsMemberGroupIDs, memberGroupIDs)
	}

	groupUpdateReq := &logical.Request{
		Operation: logical.UpdateOperation,
	}

	// Adding 'engGroupID' under 'kubeGroupID' should fail
	groupUpdateReq.Path = "group/name/kube"
	groupUpdateReq.Data = kubeGroupData
	kubeGroupData["member_group_ids"] = []string{engGroupID}
	resp, err = is.HandleRequest(ctx, groupUpdateReq)
	if err != nil || resp == nil || !resp.IsError() {
		t.Fatalf("expected an error response")
	}

	// Create an entity ID
	entityRegisterReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "entity",
	}
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID1 := resp.Data["id"].(string)

	// Add the entity as a member of 'kube' group
	entityIDReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "group/id/" + kubeGroupID,
		Data: map[string]interface{}{
			"member_entity_ids": []string{entityID1},
		},
	}
	resp, err = is.HandleRequest(ctx, entityIDReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	// Create a second entity ID
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID2 := resp.Data["id"].(string)

	// Add the entity as a member of 'ops' group
	entityIDReq.Path = "group/id/" + opsGroupID
	entityIDReq.Data = map[string]interface{}{
		"member_entity_ids": []string{entityID2},
	}
	resp, err = is.HandleRequest(ctx, entityIDReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	// Create a third entity ID
	resp, err = is.HandleRequest(ctx, entityRegisterReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}
	entityID3 := resp.Data["id"].(string)

	// Add the entity as a member of 'eng' group
	entityIDReq.Path = "group/id/" + engGroupID
	entityIDReq.Data = map[string]interface{}{
		"member_entity_ids": []string{entityID3},
		"member_group_ids":  engMemberGroupIDs,
	}
	resp, err = is.HandleRequest(ctx, entityIDReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: resp: %#v, err: %v", resp, err)
	}

	policiesResult, err := is.groupPoliciesByEntityID(entityID1)
	if err != nil {
		t.Fatal(err)
	}
	var policies []string
	for _, nsPolicies := range policiesResult {
		policies = append(policies, nsPolicies...)
	}
	sort.Strings(policies)
	expected := []string{"kubepolicy", "vaultpolicy", "engpolicy"}
	sort.Strings(expected)
	if !reflect.DeepEqual(expected, policies) {
		t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
	}

	policiesResult, err = is.groupPoliciesByEntityID(entityID2)
	if err != nil {
		t.Fatal(err)
	}
	policies = nil
	for _, nsPolicies := range policiesResult {
		policies = append(policies, nsPolicies...)
	}
	sort.Strings(policies)
	expected = []string{"opspolicy", "engpolicy"}
	sort.Strings(expected)
	if !reflect.DeepEqual(expected, policies) {
		t.Fatalf("bad: policies; expected: %#v\nactual:%#v", expected, policies)
	}

	policiesResult, err = is.groupPoliciesByEntityID(entityID3)
	if err != nil {
		t.Fatal(err)
	}
	policies = nil
	for _, nsPolicies := range policiesResult {
		policies = append(policies, nsPolicies...)
	}

	if len(policies) != 1 && policies[0] != "engpolicy" {
		t.Fatalf("bad: policies; expected: 'engpolicy'\nactual:%#v", policies)
	}

	groups, inheritedGroups, err := is.groupsByEntityID(entityID1)
	if err != nil {
		t.Fatal(err)
	}
	if len(groups) != 1 {
		t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
	}
	if len(inheritedGroups) != 2 {
		t.Fatalf("bad: length of inheritedGroups; expected: 2, actual: %d", len(inheritedGroups))
	}

	groups, inheritedGroups, err = is.groupsByEntityID(entityID2)
	if err != nil {
		t.Fatal(err)
	}
	if len(groups) != 1 {
		t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
	}
	if len(inheritedGroups) != 1 {
		t.Fatalf("bad: length of inheritedGroups; expected: 1, actual: %d", len(inheritedGroups))
	}

	groups, inheritedGroups, err = is.groupsByEntityID(entityID3)
	if err != nil {
		t.Fatal(err)
	}
	if len(groups) != 1 {
		t.Fatalf("bad: length of groups; expected: 1, actual: %d", len(groups))
	}
	if len(inheritedGroups) != 0 {
		t.Fatalf("bad: length of inheritedGroups; expected: 0, actual: %d", len(inheritedGroups))
	}
}

func TestIdentityStore_GroupCycleDetection(t *testing.T) {
	c, _, _ := TestCoreUnsealed(t)
	ctx := namespace.RootContext(nil)

	group1Name := "group1"
	group2Name := "group2"
	group3Name := "group3"

	resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": group1Name,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("failed to create group %q, err: %v, resp: %#v", group1Name, err, resp)
	}

	group1Id := resp.Data["id"].(string)

	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": group2Name,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("failed to create group %q, err: %v, resp: %#v", group2Name, err, resp)
	}

	group2Id := resp.Data["id"].(string)

	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name": group3Name,
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("failed to create group %q, err: %v, resp: %#v", group3Name, err, resp)
	}

	group3Id := resp.Data["id"].(string)

	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":             group1Name,
			"member_group_ids": []string{},
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("failed to update group %q, err: %v, resp: %#v", group1Name, err, resp)
	}

	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":             group2Name,
			"member_group_ids": []string{group3Id},
		},
	})
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("failed to update group %q, err: %v, resp: %#v", group2Name, err, resp)
	}

	resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
		Path:      "group",
		Operation: logical.UpdateOperation,
		Data: map[string]interface{}{
			"name":             group3Name,
			"member_group_ids": []string{group1Id, group2Id},
		},
	})

	if err != nil || resp == nil {
		t.Fatalf("unexpected group update error for group %q, err: %v, resp: %#v", group3Name, err, resp)
	}
	if !resp.IsError() || resp.Error().Error() != fmt.Sprintf("%s %q", errCycleDetectedPrefix, group2Id) {
		t.Fatalf("expected update to group %q to fail due to cycle, resp: %#v", group3Id, resp)
	}
}
