diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationMutationResolver.java b/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationMutationResolver.java index 03f3a17a8f..c05e3f9ad0 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationMutationResolver.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationMutationResolver.java @@ -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); + } } diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolver.java b/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolver.java index 9d93603f48..3ff465004f 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolver.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolver.java @@ -74,8 +74,9 @@ public List organizations(@Argument Boolean identityVerified) { */ @QueryMapping @AuthorizationConfiguration.RequireGlobalAdminUser - public List organizationsByName(@Argument String name) { - List orgs = _organizationService.getOrganizationsByName(name); + public List organizationsByName( + @Argument String name, @Argument Boolean isDeleted) { + List orgs = _organizationService.getOrganizationsByName(name, isDeleted); if (orgs.isEmpty()) { return List.of(); } else { diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/db/repository/OrganizationRepository.java b/backend/src/main/java/gov/cdc/usds/simplereport/db/repository/OrganizationRepository.java index 374304a5ef..9adb44a305 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/db/repository/OrganizationRepository.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/db/repository/OrganizationRepository.java @@ -12,6 +12,9 @@ public interface OrganizationRepository extends EternalAuditedEntityRepository findByExternalId(String externalId); + @Query(EternalAuditedEntityRepository.BASE_ALLOW_DELETED_QUERY + " e.externalId = :externalId") + Optional findByExternalIdIncludingDeleted(String externalId); + @Query(EternalAuditedEntityRepository.BASE_QUERY + " and e.externalId in (:externalIds)") List findAllByExternalId(Collection externalIds); @@ -23,4 +26,9 @@ public interface OrganizationRepository extends EternalAuditedEntityRepository findAllByName(String name); + + @Query( + EternalAuditedEntityRepository.BASE_ALLOW_DELETED_QUERY + + " UPPER(e.organizationName) = UPPER(:name) and e.isDeleted = :isDeleted") + List findAllByNameAndDeleted(String name, Boolean isDeleted); } diff --git a/backend/src/main/java/gov/cdc/usds/simplereport/service/OrganizationService.java b/backend/src/main/java/gov/cdc/usds/simplereport/service/OrganizationService.java index b1fe498b24..a46b6811f6 100644 --- a/backend/src/main/java/gov/cdc/usds/simplereport/service/OrganizationService.java +++ b/backend/src/main/java/gov/cdc/usds/simplereport/service/OrganizationService.java @@ -153,6 +153,10 @@ public List getOrganizationsByName(String name) { return organizationRepository.findAllByName(name); } + public List getOrganizationsByName(String name, Boolean isDeleted) { + return organizationRepository.findAllByNameAndDeleted(name, isDeleted); + } + @AuthorizationConfiguration.RequireGlobalAdminUser public List getOrganizations(Boolean identityVerified) { return identityVerified == null @@ -493,4 +497,19 @@ public List 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; + } } diff --git a/backend/src/main/resources/graphql/admin.graphqls b/backend/src/main/resources/graphql/admin.graphqls index aa0e426857..68b6d0bb03 100644 --- a/backend/src/main/resources/graphql/admin.graphqls +++ b/backend/src/main/resources/graphql/admin.graphqls @@ -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 @@ -65,4 +65,5 @@ extend type Mutation { facilities: [ID] = [], role: Role!): User! updateFeatureFlag(name: String!, value: Boolean!):FeatureFlag + deleteE2EOktaOrganizations(orgExternalId: String!): Organization } diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationIntegrationTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationIntegrationTest.java index c32b750d49..978d45b0bf 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationIntegrationTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationIntegrationTest.java @@ -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; @@ -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()); + } } diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolverTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolverTest.java index 14022156b1..0a179a529f 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolverTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/api/organization/OrganizationResolverTest.java @@ -82,11 +82,11 @@ 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); } @@ -94,12 +94,24 @@ void organizationsByName_success() { 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 actual = organizationMutationResolver.organizationsByName(orgName); + List 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); + } } diff --git a/backend/src/test/java/gov/cdc/usds/simplereport/service/OrganizationServiceTest.java b/backend/src/test/java/gov/cdc/usds/simplereport/service/OrganizationServiceTest.java index 9caa3a84fb..30d11e8fa4 100644 --- a/backend/src/test/java/gov/cdc/usds/simplereport/service/OrganizationServiceTest.java +++ b/backend/src/test/java/gov/cdc/usds/simplereport/service/OrganizationServiceTest.java @@ -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; @@ -498,4 +500,14 @@ void getOrgAdminUserIds_skipsUser_forNonExistentUserInOrg() { List 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); + } } diff --git a/backend/src/test/resources/graphql-test/organization-by-name-query.graphql b/backend/src/test/resources/graphql-test/organization-by-name-query.graphql new file mode 100644 index 0000000000..a6e0258374 --- /dev/null +++ b/backend/src/test/resources/graphql-test/organization-by-name-query.graphql @@ -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 + } +} \ No newline at end of file diff --git a/frontend/src/generated/graphql.tsx b/frontend/src/generated/graphql.tsx index 8be0fd0bb9..b1af770cfb 100644 --- a/frontend/src/generated/graphql.tsx +++ b/frontend/src/generated/graphql.tsx @@ -201,6 +201,7 @@ export type Mutation = { createFacilityRegistrationLink?: Maybe; createOrganizationRegistrationLink?: Maybe; createSpecimenType?: Maybe; + deleteE2EOktaOrganizations?: Maybe; editPendingOrganization?: Maybe; editQueueItem?: Maybe; markDeviceTypeAsDeleted?: Maybe; @@ -336,6 +337,10 @@ export type MutationCreateSpecimenTypeArgs = { input: CreateSpecimenType; }; +export type MutationDeleteE2EOktaOrganizationsArgs = { + orgExternalId: Scalars["String"]["input"]; +}; + export type MutationEditPendingOrganizationArgs = { adminEmail?: InputMaybe; adminFirstName?: InputMaybe; @@ -765,6 +770,7 @@ export type QueryOrganizationsArgs = { }; export type QueryOrganizationsByNameArgs = { + isDeleted?: InputMaybe; name: Scalars["String"]["input"]; };