package service_test import ( "context" "testing" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "go.infratographer.com/x/events" "go.infratographer.com/x/gidx" "go.equinixmetal.net/infra9-metal-bridge/internal/metal/models" "go.equinixmetal.net/infra9-metal-bridge/internal/metal/providers" "go.equinixmetal.net/infra9-metal-bridge/internal/permissions" "go.equinixmetal.net/infra9-metal-bridge/internal/service" ) type mockPublisher struct { mock.Mock } func (p *mockPublisher) PublishChange(ctx context.Context, subjectType string, change events.ChangeMessage) error { args := p.Called(subjectType, change) return args.Error(0) } func TestTouchOrganizationEmpty(t *testing.T) { rootTenantID := gidx.PrefixedID("tnntten-root1") roleMap := map[string][]string{ "owner": { "action1", "action2", }, } userID := gidx.PrefixedID("idntusr-2s-9kVNPJBaInlHpRs3lAMsvU_kVkLaSlD4R_RhavDw") user := &models.UserDetails{ ID: "usr1", } projectID := gidx.PrefixedID("metlprj-prj1") project := &models.ProjectDetails{ ID: "prj1", } orgID := gidx.PrefixedID("metlorg-org1") org := &models.OrganizationDetails{ ID: "org1", Projects: []*models.ProjectDetails{ project, }, Memberships: []*models.Membership[models.OrganizationDetails]{ { User: user, Roles: []string{ "owner", }, }, }, } var ( mMetal = new(providers.MockProvider) mPerms = new(permissions.MockClient) mPublisher = new(mockPublisher) ) // Relationships mMetal.On("GetOrganizationDetails", orgID).Return(org, nil) mPerms.On("ListResourceRelationshipsFrom", orgID).Return([]permissions.ResourceRelationship{}, nil) mPerms.On("ListResourceRelationshipsTo", orgID).Return([]permissions.ResourceRelationship{}, nil) orgTenantChangeMessage := events.ChangeMessage{ SubjectID: orgID, EventType: string(events.CreateChangeType), AdditionalSubjectIDs: []gidx.PrefixedID{ rootTenantID, }, } relateProjectChangeMessage := events.ChangeMessage{ SubjectID: projectID, EventType: string(events.CreateChangeType), AdditionalSubjectIDs: []gidx.PrefixedID{ orgID, }, } mPublisher.On("PublishChange", "metalorganization", orgTenantChangeMessage).Return(nil) mPublisher.On("PublishChange", "metalorganization", relateProjectChangeMessage).Return(nil) // Memberships newRoleID := gidx.PrefixedID("permrol-role1") mPerms.On("ListResourceRoles", orgID).Return(permissions.ResourceRoles{}, nil) mPerms.On("CreateRole", orgID, roleMap["owner"]).Return(newRoleID, nil) mPerms.On("AssignRole", newRoleID, userID).Return(nil) // Run scenario svc, err := service.New(mPublisher, mMetal, mPerms, service.WithRootTenant(rootTenantID.String()), service.WithRoles(roleMap), ) require.NoError(t, err) err = svc.TouchOrganization(context.Background(), orgID) require.NoError(t, err) require.True(t, mMetal.AssertExpectations(t), "unexpected calls to metal provider") require.True(t, mPerms.AssertExpectations(t), "unexpected calls to permissions client") require.True(t, mPublisher.AssertExpectations(t), "unexpected calls to events publisher") } func TestTouchOrganizationCleanup(t *testing.T) { oldRootTenantID := gidx.PrefixedID("tnntten-root1") newRootTenantID := gidx.PrefixedID("tnntten-root2") roleMap := map[string][]string{ "owner": { "action1", "action2", }, "collaborator": { "action1", }, } oldUserID := gidx.PrefixedID("idntusr-2s-9kVNPJBaInlHpRs3lAMsvU_kVkLaSlD4R_RhavDw") deadProjectID := gidx.PrefixedID("metlprj-prj1") userID := gidx.PrefixedID("idntusr-RnKvdujrwqm4o1dBDgfgaqeCpKFMaGeOtGnNbZky0Kg") user := &models.UserDetails{ ID: "usr2", } projectID := gidx.PrefixedID("metlprj-prj2") project := &models.ProjectDetails{ ID: "prj2", } orgID := gidx.PrefixedID("metlorg-org1") org := &models.OrganizationDetails{ ID: "org1", Projects: []*models.ProjectDetails{ project, }, Memberships: []*models.Membership[models.OrganizationDetails]{ { User: user, Roles: []string{ "owner", }, }, }, } var ( mMetal = new(providers.MockProvider) mPerms = new(permissions.MockClient) mPublisher = new(mockPublisher) ) // Relationships mMetal.On("GetOrganizationDetails", orgID).Return(org, nil) existingRelsFrom := []permissions.ResourceRelationship{ { Relation: string(service.RelateParent), SubjectID: oldRootTenantID, }, } mPerms.On("ListResourceRelationshipsFrom", orgID).Return(existingRelsFrom, nil) existingRelsTo := []permissions.ResourceRelationship{ { ResourceID: deadProjectID, Relation: string(service.RelateParent), }, } mPerms.On("ListResourceRelationshipsTo", orgID).Return(existingRelsTo, nil) mPerms.On("DeleteResourceRelationship", orgID, string(service.RelateParent), oldRootTenantID).Return(nil) mPerms.On("DeleteResourceRelationship", deadProjectID, string(service.RelateParent), orgID).Return(nil) orgTenantChangeMessage := events.ChangeMessage{ SubjectID: orgID, EventType: string(events.CreateChangeType), AdditionalSubjectIDs: []gidx.PrefixedID{ newRootTenantID, }, } relateProjectChangeMessage := events.ChangeMessage{ SubjectID: projectID, EventType: string(events.CreateChangeType), AdditionalSubjectIDs: []gidx.PrefixedID{ orgID, }, } mPublisher.On("PublishChange", "metalorganization", orgTenantChangeMessage).Return(nil) mPublisher.On("PublishChange", "metalorganization", relateProjectChangeMessage).Return(nil) // Memberships oldRoleID := gidx.PrefixedID("permrol-role1") newRoleID := gidx.PrefixedID("permrol-role2") existingRoles := permissions.ResourceRoles{ { ID: oldRoleID, Actions: roleMap["collaborator"], }, } mPerms.On("ListResourceRoles", orgID).Return(existingRoles, nil) existingRoleAssignments := []gidx.PrefixedID{ oldUserID, } mPerms.On("ListRoleAssignments", oldRoleID).Return(existingRoleAssignments, nil) mPerms.On("CreateRole", orgID, roleMap["owner"]).Return(newRoleID, nil) mPerms.On("DeleteRole", oldRoleID).Return(nil) mPerms.On("AssignRole", newRoleID, userID).Return(nil) mPerms.On("UnassignRole", oldRoleID, oldUserID).Return(nil) // Run scenario svc, err := service.New(mPublisher, mMetal, mPerms, service.WithRootTenant(newRootTenantID.String()), service.WithRoles(roleMap), ) require.NoError(t, err) err = svc.TouchOrganization(context.Background(), orgID) require.NoError(t, err) require.True(t, mMetal.AssertExpectations(t), "unexpected calls to metal provider") require.True(t, mPerms.AssertExpectations(t), "unexpected calls to permissions client") require.True(t, mPublisher.AssertExpectations(t), "unexpected calls to events publisher") } func TestTouchOrganizationNoChange(t *testing.T) { rootTenantID := gidx.PrefixedID("tnntten-root1") roleMap := map[string][]string{ "owner": { "action1", "action2", }, } userID := gidx.PrefixedID("idntusr-2s-9kVNPJBaInlHpRs3lAMsvU_kVkLaSlD4R_RhavDw") user := &models.UserDetails{ ID: "usr1", } projectID := gidx.PrefixedID("metlprj-prj1") project := &models.ProjectDetails{ ID: "prj1", } orgID := gidx.PrefixedID("metlorg-org1") org := &models.OrganizationDetails{ ID: "org1", Projects: []*models.ProjectDetails{ project, }, Memberships: []*models.Membership[models.OrganizationDetails]{ { User: user, Roles: []string{ "owner", }, }, }, } var ( mMetal = new(providers.MockProvider) mPerms = new(permissions.MockClient) mPublisher = new(mockPublisher) ) // Relationships mMetal.On("GetOrganizationDetails", orgID).Return(org, nil) existingRelsFrom := []permissions.ResourceRelationship{ { Relation: string(service.RelateParent), SubjectID: rootTenantID, }, } mPerms.On("ListResourceRelationshipsFrom", orgID).Return(existingRelsFrom, nil) existingRelsTo := []permissions.ResourceRelationship{ { ResourceID: projectID, Relation: string(service.RelateParent), }, } mPerms.On("ListResourceRelationshipsTo", orgID).Return(existingRelsTo, nil) // Memberships roleID := gidx.PrefixedID("permrol-role1") existingRoles := permissions.ResourceRoles{ { ID: roleID, Actions: roleMap["owner"], }, } mPerms.On("ListResourceRoles", orgID).Return(existingRoles, nil) existingRoleAssignments := []gidx.PrefixedID{ userID, } mPerms.On("ListRoleAssignments", roleID).Return(existingRoleAssignments, nil) // Run scenario svc, err := service.New(mPublisher, mMetal, mPerms, service.WithRootTenant(rootTenantID.String()), service.WithRoles(roleMap), ) require.NoError(t, err) err = svc.TouchOrganization(context.Background(), orgID) require.NoError(t, err) require.True(t, mMetal.AssertExpectations(t), "unexpected calls to metal provider") require.True(t, mPerms.AssertExpectations(t), "unexpected calls to permissions client") require.True(t, mPublisher.AssertExpectations(t), "unexpected calls to events publisher") }