Skip to content

Commit

Permalink
add new mutations and service methods (#7147)
Browse files Browse the repository at this point in the history
* add new mutations and service methods

* add tests

* remove extra comment

* gql lint

* update args

* add missing unit test

* lint

* rename

* one more
  • Loading branch information
fzhao99 authored Jan 10, 2024
1 parent 7b923b5 commit 260b8ff
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -206,4 +206,15 @@ public Facility markFacilityAsDeleted(@Argument UUID facilityId, @Argument boole
}
return organizationService.markFacilityAsDeleted(facilityId, deleted);
}

/**
* Method HARD DELETES an Okta group without touching any of the application organization data.
* THIS SHOULD ONLY BE USED TO CLEAN UP ORGS CREATED IN OKTA FOR E2E TESTS. DON'T USE THIS METHOD
* FOR ANY LIVE OKTA API CALLS
*/
@Transactional
@MutationMapping
public Organization deleteE2EOktaOrganizations(@Argument String orgExternalId) {
return organizationService.deleteE2EOktaOrganization(orgExternalId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ public List<ApiOrganization> organizations(@Argument Boolean identityVerified) {
*/
@QueryMapping
@AuthorizationConfiguration.RequireGlobalAdminUser
public List<ApiOrganization> organizationsByName(@Argument String name) {
List<Organization> orgs = _organizationService.getOrganizationsByName(name);
public List<ApiOrganization> organizationsByName(
@Argument String name, @Argument Boolean isDeleted) {
List<Organization> orgs = _organizationService.getOrganizationsByName(name, isDeleted);
if (orgs.isEmpty()) {
return List.of();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public interface OrganizationRepository extends EternalAuditedEntityRepository<O
@Query(EternalAuditedEntityRepository.BASE_QUERY + " and e.externalId = :externalId")
Optional<Organization> findByExternalId(String externalId);

@Query(EternalAuditedEntityRepository.BASE_ALLOW_DELETED_QUERY + " e.externalId = :externalId")
Optional<Organization> findByExternalIdIncludingDeleted(String externalId);

@Query(EternalAuditedEntityRepository.BASE_QUERY + " and e.externalId in (:externalIds)")
List<Organization> findAllByExternalId(Collection<String> externalIds);

Expand All @@ -23,4 +26,9 @@ public interface OrganizationRepository extends EternalAuditedEntityRepository<O
@Query(
EternalAuditedEntityRepository.BASE_QUERY + " and UPPER(e.organizationName) = UPPER(:name)")
List<Organization> findAllByName(String name);

@Query(
EternalAuditedEntityRepository.BASE_ALLOW_DELETED_QUERY
+ " UPPER(e.organizationName) = UPPER(:name) and e.isDeleted = :isDeleted")
List<Organization> findAllByNameAndDeleted(String name, Boolean isDeleted);
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@ public List<Organization> getOrganizationsByName(String name) {
return organizationRepository.findAllByName(name);
}

public List<Organization> getOrganizationsByName(String name, Boolean isDeleted) {
return organizationRepository.findAllByNameAndDeleted(name, isDeleted);
}

@AuthorizationConfiguration.RequireGlobalAdminUser
public List<Organization> getOrganizations(Boolean identityVerified) {
return identityVerified == null
Expand Down Expand Up @@ -493,4 +497,19 @@ public List<UUID> getOrgAdminUserIds(UUID orgId) {
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

/**
* Method HARD DELETES an Okta group without touching any of the application organization data.
* SHOULD ONLY BE USED TO CLEAN UP ORGS CREATED IN OKTA FOR E2E TESTS. DON'T USE THIS METHOD FOR
* ANY LIVE OKTA API CALLS
*/
@AuthorizationConfiguration.RequireGlobalAdminUser
public Organization deleteE2EOktaOrganization(String orgExternalId) {
Organization orgToDelete =
organizationRepository
.findByExternalIdIncludingDeleted(orgExternalId)
.orElseThrow(NonexistentOrgException::new);
oktaRepository.deleteOrganization(orgToDelete);
return orgToDelete;
}
}
3 changes: 2 additions & 1 deletion backend/src/main/resources/graphql/admin.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# which is enforced in the API not in the schema validator.
extend type Query {
organizations(identityVerified: Boolean): [Organization!]!
organizationsByName(name: String!): [Organization]
organizationsByName(name: String!, isDeleted: Boolean = false): [Organization]
pendingOrganizations: [PendingOrganization!]!
organization(id: ID!): Organization
facilityStats(facilityId: ID!): FacilityStats
Expand Down Expand Up @@ -65,4 +65,5 @@ extend type Mutation {
facilities: [ID] = [],
role: Role!): User!
updateFeatureFlag(name: String!, value: Boolean!):FeatureFlag
deleteE2EOktaOrganizations(orgExternalId: String!): Organization
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;

import gov.cdc.usds.simplereport.api.graphql.BaseGraphqlTest;
import gov.cdc.usds.simplereport.test_util.SliceTestConfiguration;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -31,4 +32,19 @@ void organizationQuery_asOrgAdmin_fails() {
useOrgAdmin();
runQuery("organization-query", Map.of("id", org.getInternalId()), "Unauthorized");
}

@Test
@SliceTestConfiguration.WithSimpleReportSiteAdminUser
void organizationNameQueryIsDeleted_asSuperUser_passes() {
var org = _orgService.getOrganizationsByName("Dis Organization").get(0);
useSuperUser();
_orgService.markOrganizationAsDeleted(org.getInternalId(), true);

var result =
runQuery(
"organization-by-name-query",
Map.of("name", org.getOrganizationName(), "isDeleted", true));
assertThat(result.get("organizationsByName").get(0).get("internalId").asText())
.contains(org.getInternalId().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,36 @@ void organization_null() {
void organizationsByName_success() {
String orgName = "org name";
Organization org = new Organization(orgName, "type", "123", true);
when(organizationService.getOrganizationsByName(orgName)).thenReturn(List.of(org));
when(organizationService.getOrganizationsByName(orgName, false)).thenReturn(List.of(org));

organizationMutationResolver.organizationsByName(orgName);
organizationMutationResolver.organizationsByName(orgName, false);

verify(organizationService).getOrganizationsByName(orgName);
verify(organizationService).getOrganizationsByName(orgName, false);
verify(organizationService).getFacilities(org);
}

@Test
void organizationsByName_null() {
String orgName = "org name";
Organization org = new Organization(orgName, "type", "123", true);
when(organizationService.getOrganizationsByName(orgName)).thenReturn(List.of());
when(organizationService.getOrganizationsByName(orgName, false)).thenReturn(List.of());

List<ApiOrganization> actual = organizationMutationResolver.organizationsByName(orgName);
List<ApiOrganization> actual = organizationMutationResolver.organizationsByName(orgName, false);

assertThat(actual).isEmpty();
verify(organizationService).getOrganizationsByName(orgName);
verify(organizationService).getOrganizationsByName(orgName, false);
verify(organizationService, never()).getFacilities(org);
}

@Test
void organizationsByNameIsDeleted_success() {
String orgName = "org name";
Organization org = new Organization(orgName, "type", "123", true);
when(organizationService.getOrganizationsByName(orgName, true)).thenReturn(List.of(org));

organizationMutationResolver.organizationsByName(orgName, true);

verify(organizationService).getOrganizationsByName(orgName, true);
verify(organizationService).getFacilities(org);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import gov.cdc.usds.simplereport.api.model.FacilityStats;
Expand Down Expand Up @@ -498,4 +500,14 @@ void getOrgAdminUserIds_skipsUser_forNonExistentUserInOrg() {
List<UUID> adminIds = _service.getOrgAdminUserIds(createdOrg.getInternalId());
assertThat(adminIds).isEqualTo(expectedIds);
}

@Test
@WithSimpleReportSiteAdminUser
void deleteE2EOktaOrganization_succeeds() {
Organization createdOrg = _dataFactory.saveValidOrganization();
Organization deletedOrg = _service.deleteE2EOktaOrganization(createdOrg.getExternalId());

assertThat(deletedOrg).isEqualTo(createdOrg);
verify(oktaRepository, times(1)).deleteOrganization(createdOrg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
query OrganizationByName ($name: String!, $isDeleted: Boolean){
organizationsByName(name: $name, isDeleted: $isDeleted) {
internalId
name
type
externalId
identityVerified
patientSelfRegistrationLink
facilities{
id
name
}
id
}
}
6 changes: 6 additions & 0 deletions frontend/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export type Mutation = {
createFacilityRegistrationLink?: Maybe<Scalars["String"]["output"]>;
createOrganizationRegistrationLink?: Maybe<Scalars["String"]["output"]>;
createSpecimenType?: Maybe<SpecimenType>;
deleteE2EOktaOrganizations?: Maybe<Organization>;
editPendingOrganization?: Maybe<Scalars["String"]["output"]>;
editQueueItem?: Maybe<TestOrder>;
markDeviceTypeAsDeleted?: Maybe<DeviceType>;
Expand Down Expand Up @@ -336,6 +337,10 @@ export type MutationCreateSpecimenTypeArgs = {
input: CreateSpecimenType;
};

export type MutationDeleteE2EOktaOrganizationsArgs = {
orgExternalId: Scalars["String"]["input"];
};

export type MutationEditPendingOrganizationArgs = {
adminEmail?: InputMaybe<Scalars["String"]["input"]>;
adminFirstName?: InputMaybe<Scalars["String"]["input"]>;
Expand Down Expand Up @@ -765,6 +770,7 @@ export type QueryOrganizationsArgs = {
};

export type QueryOrganizationsByNameArgs = {
isDeleted?: InputMaybe<Scalars["Boolean"]["input"]>;
name: Scalars["String"]["input"];
};

Expand Down

0 comments on commit 260b8ff

Please sign in to comment.