Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
Introduce unassignFormationGlobal mutation (#3591)
Browse files Browse the repository at this point in the history
* initial implementation

* bump director PR version

* add godoc

* add e2e test

* bump e2e tests pr number

* generalize mutation

* adapt todo

* adapt e2e test and generate examples

* remove unnecessary new test

* address PR comments
  • Loading branch information
ivantenevvasilev authored Jan 30, 2024
1 parent beb066a commit f291ccb
Show file tree
Hide file tree
Showing 20 changed files with 469 additions and 30 deletions.
1 change: 1 addition & 0 deletions chart/compass/charts/director/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ graphql:
deleteFormation: ["formation:write"]
assignFormation: [ "formation:write" ]
unassignFormation: [ "formation:write" ]
unassignFormationGlobal: [ "formation:global_write" ]
resynchronizeFormationNotifications: [ "formation:write" ]
createLabelDefinition: ["label_definition:write"]
updateLabelDefinition: ["label_definition:write"]
Expand Down
1 change: 1 addition & 0 deletions chart/compass/charts/hydrator/static-groups.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
- "certificate_subject_mapping:write"
- "tenant_access:write"
- "bundle_instance_auth:write"
- "formation:global_write"
- "formation:global_read"
{{- end -}}
{{ range $name := regexSplit "," .Values.operatorGroupNames -1 }}
Expand Down
6 changes: 4 additions & 2 deletions chart/compass/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ global:
name: compass-pairing-adapter
director:
dir: dev/incubator/
version: "PR-3622"
version: "PR-3591"
name: compass-director
hydrator:
dir: dev/incubator/
Expand Down Expand Up @@ -228,7 +228,7 @@ global:
name: compass-console
e2e_tests:
dir: dev/incubator/
version: "PR-3618"
version: "PR-3591"
name: compass-e2e-tests
isLocalEnv: false
isForTesting: false
Expand Down Expand Up @@ -1495,6 +1495,7 @@ global:
- "tenant_access:write"
- "formation:read"
- "formation:write"
- "formation:global_write"
- "formation:global_read"
instance_creator:
- "application_template:read"
Expand Down Expand Up @@ -1604,6 +1605,7 @@ global:
- "formation.state:write"
- "tenant_access:write"
- "bundle_instance_auth:write"
- "formation:global_write"
- "formation:global_read"
default:
- "runtime:read"
Expand Down
1 change: 1 addition & 0 deletions components/director/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Below you can find a list of mock GraphQL queries and mutations:
- [unassign runtime context from formation](./unassign-formation/unassign-runtime-context-from-formation.graphql)
- [unassign runtime from formation](./unassign-formation/unassign-runtime-from-formation.graphql)
- [unassign tenant from formation](./unassign-formation/unassign-tenant-from-formation.graphql)
- [unassign application from formation global](./unassign-formation-global/unassign-application-from-formation-global.graphql)
- [unpair application](./unpair-application/unpair-application.graphql)
- [unregister application](./unregister-application/unregister-application.graphql)
- [unregister integration system](./unregister-integration-system/unregister-integration-system.graphql)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Code generated by Compass integration tests, DO NOT EDIT.
mutation {
result: unassignFormationGlobal(
objectID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
objectType: APPLICATION
formation: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
) {
id
name
formationTemplateId
state

formationAssignments(first: 200, after: "") {
data {
id
source
sourceType
target
targetType
state
value
configuration
error
}
pageInfo {
startCursor
endCursor
hasNextPage
}
totalCount
}
status {
condition
errors {
assignmentID
message
errorCode
}
}
}
}
2 changes: 2 additions & 0 deletions components/director/hack/config-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ graphql:
deleteFormation: ["formation:write"]
assignFormation: ["formation:write"]
unassignFormation: ["formation:write"]
unassignFormationGlobal: ["formation:global_write"]
resynchronizeFormationNotifications: ["formation:write"]
createLabelDefinition: ["label_definition:write"]
updateLabelDefinition: ["label_definition:write"]
Expand Down Expand Up @@ -246,6 +247,7 @@ clientCredentialsRegistrationScopes:
- "certificate_subject_mapping:read"
- "certificate_subject_mapping:write"
- "formation.state:write"
- "formation:global_write"
- "formation:global_read"
applicationHideSelectors:
applicationHideSelectorKey:
Expand Down
2 changes: 1 addition & 1 deletion components/director/hack/jwt_generator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function get_token(){
read -r INTERNAL_TENANT_ID <<< $(get_internal_tenant)

local HEADER=$(echo "{ \"alg\": \"none\", \"typ\": \"JWT\" }" | base64 | tr '/+' '_-' | tr -d '=')
local PAYLOAD=$(echo "{ \"scopes\": \"webhook:write formation_template.webhooks:read runtime.webhooks:read application_template.labels:write application.local_tenant_id:write tenant_subscription:write tenant:write fetch-request.auth:read webhooks.auth:read application.auths:read application.webhooks:read application.application_template:read application_template:write application_template:read application_template.webhooks:read document.fetch_request:read event_spec.fetch_request:read api_spec.fetch_request:read runtime.auths:read integration_system.auths:read bundle.instance_auths:read bundle.instance_auths:read application:read automatic_scenario_assignment:read health_checks:read application:write runtime:write label_definition:write label_definition:read runtime:read tenant:read formation:read formation:write internal_visibility:read formation_template:read formation_template:write formation_constraint:read formation_constraint:write certificate_subject_mapping:read certificate_subject_mapping:write formation.state:write tenant_access:write bundle_instance_auth:write formation:global_read\", \"tenant\":\"{\\\"consumerTenant\\\":\\\"$INTERNAL_TENANT_ID\\\",\\\"externalTenant\\\":\\\"3e64ebae-38b5-46a0-b1ed-9ccee153a0ae\\\"}\" }" | base64 | tr '/+' '_-' | tr -d '=')
local PAYLOAD=$(echo "{ \"scopes\": \"webhook:write formation_template.webhooks:read runtime.webhooks:read application_template.labels:write application.local_tenant_id:write tenant_subscription:write tenant:write fetch-request.auth:read webhooks.auth:read application.auths:read application.webhooks:read application.application_template:read application_template:write application_template:read application_template.webhooks:read document.fetch_request:read event_spec.fetch_request:read api_spec.fetch_request:read runtime.auths:read integration_system.auths:read bundle.instance_auths:read bundle.instance_auths:read application:read automatic_scenario_assignment:read health_checks:read application:write runtime:write label_definition:write label_definition:read runtime:read tenant:read formation:read formation:write internal_visibility:read formation_template:read formation_template:write formation_constraint:read formation_constraint:write certificate_subject_mapping:read certificate_subject_mapping:write formation.state:write tenant_access:write bundle_instance_auth:write formation:global_write formation:global_read\", \"tenant\":\"{\\\"consumerTenant\\\":\\\"$INTERNAL_TENANT_ID\\\",\\\"externalTenant\\\":\\\"3e64ebae-38b5-46a0-b1ed-9ccee153a0ae\\\"}\" }" | base64 | tr '/+' '_-' | tr -d '=')
echo "$HEADER.$PAYLOAD."
}

Expand Down
26 changes: 26 additions & 0 deletions components/director/internal/domain/formation/automock/service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,21 @@ var (
runtimeTypeDisplayName = str.Ptr("display name")
defaultTime = time.Time{}

testErr = errors.New("Test error")
emptyCtx = context.Background()
testErr = errors.New("Test error")

formationModelWithoutError = fixFormationModelWithoutError()
modelFormation = model.Formation{
ID: FormationID,
FormationTemplateID: FormationTemplateID,
Name: testFormationName,
}
modelFormationWithTenant = model.Formation{
ID: FormationID,
FormationTemplateID: FormationTemplateID,
Name: testFormationName,
TenantID: TntInternalID,
}
graphqlFormation = graphql.Formation{
ID: FormationID,
FormationTemplateID: FormationTemplateID,
Expand Down
30 changes: 30 additions & 0 deletions components/director/internal/domain/formation/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Service interface {
Get(ctx context.Context, id string) (*model.Formation, error)
GetFormationByName(ctx context.Context, formationName, tnt string) (*model.Formation, error)
List(ctx context.Context, pageSize int, cursor string) (*model.FormationPage, error)
GetGlobalByID(ctx context.Context, id string) (*model.Formation, error)
ListFormationsForObject(ctx context.Context, objectID string) ([]*model.Formation, error)
CreateFormation(ctx context.Context, tnt string, formation model.Formation, templateName string) (*model.Formation, error)
DeleteFormation(ctx context.Context, tnt string, formation model.Formation) (*model.Formation, error)
Expand Down Expand Up @@ -326,6 +327,35 @@ func (r *Resolver) UnassignFormation(ctx context.Context, objectID string, objec
return r.conv.ToGraphQL(newFormation)
}

// UnassignFormationGlobal unassigns the objectID from the provided formation globally
func (r *Resolver) UnassignFormationGlobal(ctx context.Context, objectID string, objectType graphql.FormationObjectType, formationID string) (*graphql.Formation, error) {
tx, err := r.transact.Begin()
if err != nil {
return nil, err
}
defer r.transact.RollbackUnlessCommitted(ctx, tx)

ctx = persistence.SaveToContext(ctx, tx)

formation, err := r.service.GetGlobalByID(ctx, formationID)
if err != nil {
return nil, err
}

ctx = tenant.SaveToContext(ctx, formation.TenantID, "")

newFormation, err := r.service.UnassignFormation(ctx, formation.TenantID, objectID, objectType, *formation)
if err != nil {
return nil, err
}

if err = tx.Commit(); err != nil {
return nil, errors.Wrap(err, "while committing transaction")
}

return r.conv.ToGraphQL(newFormation)
}

// FormationAssignments retrieves a page of FormationAssignments for the specified Formation
func (r *Resolver) FormationAssignments(ctx context.Context, obj *graphql.Formation, first *int, after *graphql.PageCursor) (*graphql.FormationAssignmentPage, error) {
param := dataloader.ParamFormationAssignment{ID: obj.ID, Ctx: ctx, Tenant: obj.TenantID, First: first, After: after}
Expand Down
107 changes: 107 additions & 0 deletions components/director/internal/domain/formation/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,113 @@ func TestUnassignFormation(t *testing.T) {
}
}

func TestUnassignFormationGlobal(t *testing.T) {
testErr := errors.New("test error")
txGen := txtest.NewTransactionContextGenerator(testErr)
testCases := []struct {
Name string
TxFn func() (*persistenceautomock.PersistenceTx, *persistenceautomock.Transactioner)
ServiceFn func() *automock.Service
ConverterFn func() *automock.Converter
FormationAssignmentSvcFn func() *automock.FormationAssignmentService
ExpectedFormation *graphql.Formation
ExpectedError error
}{
{
Name: "successfully unassigned formation",
TxFn: txGen.ThatSucceeds,
ConverterFn: func() *automock.Converter {
conv := &automock.Converter{}
conv.On("ToGraphQL", &modelFormationWithTenant).Return(&graphqlFormation, nil)
return conv
},
ServiceFn: func() *automock.Service {
svc := &automock.Service{}
svc.On("GetGlobalByID", mock.Anything, FormationID).Return(&modelFormationWithTenant, nil)
svc.On("UnassignFormation", mock.Anything, TntInternalID, ApplicationID, graphql.FormationObjectTypeApplication, modelFormationWithTenant).Return(&modelFormationWithTenant, nil)
return svc
},
ExpectedFormation: &graphqlFormation,
},
{
Name: "fails when transaction fails to open",
TxFn: txGen.ThatFailsOnBegin,
ConverterFn: unusedConverter,
ServiceFn: unusedService,
ExpectedError: testErr,
},
{
Name: "returns error when can not start db transaction",
TxFn: txGen.ThatFailsOnBegin,
ConverterFn: unusedConverter,
ServiceFn: unusedService,
ExpectedError: testErr,
}, {
Name: "returns error when commit fails",
TxFn: txGen.ThatFailsOnCommit,
ConverterFn: unusedConverter,
ServiceFn: func() *automock.Service {
svc := &automock.Service{}
svc.On("GetGlobalByID", mock.Anything, FormationID).Return(&modelFormationWithTenant, nil)
svc.On("UnassignFormation", mock.Anything, TntInternalID, ApplicationID, graphql.FormationObjectTypeApplication, modelFormationWithTenant).Return(&modelFormationWithTenant, nil)
return svc
},
ExpectedError: testErr,
},
{
Name: "returns error when unassign formation fails",
TxFn: txGen.ThatDoesntExpectCommit,
ConverterFn: unusedConverter,
ServiceFn: func() *automock.Service {
svc := &automock.Service{}
svc.On("GetGlobalByID", mock.Anything, FormationID).Return(&modelFormationWithTenant, nil)
svc.On("UnassignFormation", mock.Anything, TntInternalID, ApplicationID, graphql.FormationObjectTypeApplication, modelFormationWithTenant).Return(nil, testErr)
return svc
},
ExpectedError: testErr,
},
{
Name: "returns error when get formation fails",
TxFn: txGen.ThatDoesntExpectCommit,
ConverterFn: unusedConverter,
ServiceFn: func() *automock.Service {
svc := &automock.Service{}
svc.On("GetGlobalByID", mock.Anything, FormationID).Return(nil, testErr)
return svc
},
ExpectedError: testErr,
},
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
// GIVEN
persist, transact := testCase.TxFn()
service := testCase.ServiceFn()
converter := testCase.ConverterFn()
formationAssignmentSvc := &automock.FormationAssignmentService{}
if testCase.FormationAssignmentSvcFn != nil {
formationAssignmentSvc = testCase.FormationAssignmentSvcFn()
}

resolver := formation.NewResolver(transact, service, converter, formationAssignmentSvc, nil, nil)

// WHEN
f, err := resolver.UnassignFormationGlobal(emptyCtx, ApplicationID, graphql.FormationObjectTypeApplication, FormationID)

// THEN
if testCase.ExpectedError != nil {
require.Error(t, err)
assert.Contains(t, err.Error(), testCase.ExpectedError.Error())
} else {
assert.NoError(t, err)
}
assert.Equal(t, testCase.ExpectedFormation, f)

mock.AssertExpectationsForObjects(t, persist, service, converter, formationAssignmentSvc)
})
}
}

func TestFormation(t *testing.T) {
testErr := errors.New("test error")

Expand Down
4 changes: 4 additions & 0 deletions components/director/internal/domain/root_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,10 @@ func (r *mutationResolver) UnassignFormation(ctx context.Context, objectID strin
return r.formation.UnassignFormation(ctx, objectID, objectType, formation)
}

func (r *mutationResolver) UnassignFormationGlobal(ctx context.Context, objectID string, objectType graphql.FormationObjectType, formationID string) (*graphql.Formation, error) {
return r.formation.UnassignFormationGlobal(ctx, objectID, objectType, formationID)
}

func (r *mutationResolver) CreateFormation(ctx context.Context, formationInput graphql.FormationInput) (*graphql.Formation, error) {
return r.formation.CreateFormation(ctx, formationInput)
}
Expand Down
5 changes: 5 additions & 0 deletions components/director/pkg/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,11 @@ type Mutation {
unassignFormation(objectID: ID!, objectType: FormationObjectType!, formation: FormationInput!): Formation! @hasScopes(path: "graphql.mutation.unassignFormation")
"""
**Examples**
- [unassign application from formation global](examples/unassign-formation-global/unassign-application-from-formation-global.graphql)
"""
unassignFormationGlobal(objectID: ID!, objectType: FormationObjectType!, formation: ID!): Formation! @hasScopes(path: "graphql.mutation.unassignFormationGlobal")
"""
**Examples**
- [create formation constraint](examples/create-formation-constraint/create-formation-constraint.graphql)
"""
createFormationConstraint(formationConstraint: FormationConstraintInput! @validate): FormationConstraint! @hasScopes(path: "graphql.mutation.createFormationConstraint")
Expand Down
Loading

0 comments on commit f291ccb

Please sign in to comment.