diff --git a/ui/package.json b/ui/package.json index 9f2ba453..5e28fb6f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,6 +16,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-slot": "^1.0.2", "axios": "^1.6.7", + "axios-retry": "^4.0.0", "camaro": "^6.2.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", diff --git a/ui/src/access/User.ts b/ui/src/access/User.ts index 71d1fea7..f0fcf25b 100644 --- a/ui/src/access/User.ts +++ b/ui/src/access/User.ts @@ -1,13 +1,9 @@ +import { MemberResult } from '@/services/GroupingsApiResults'; import Role from './Role'; type User = { - name: string, - firstName: string, - lastName: string, - uid: string, - uhUuid: string, roles: Role[] -} +} & MemberResult export const AnonymousUser: User = { name: '', diff --git a/ui/src/services/GroupingsApiResults.ts b/ui/src/services/GroupingsApiResults.ts new file mode 100644 index 00000000..f428f0d0 --- /dev/null +++ b/ui/src/services/GroupingsApiResults.ts @@ -0,0 +1,151 @@ +export type Announcement = { + message: string, + start: string, + end: string, + state: string +}; + +export type Announcements = { + resultCode: string, + announcements: Announcement[] +}; + +export type GroupingResult = { + resultCode: string, + groupPath: string +}; + +export type MemberResult = { + uid: string, + uhUuid: string, + name: string, + firstName: string, + lastName: string +}; + +export type GroupingPath = { + path: string, + name: string, + description: string +}; + +export type GroupingPaths = { + resultCode: string, + groupingPaths: GroupingPath[] +} + +export type Membership = { + inBasis: boolean, + inInclude: boolean, + inExclude: boolean, + inOwner: boolean, + inBasisAndInclude: boolean, + optOutEnabled: boolean, + optInEnabled: boolean, + selfOpted: boolean +} & GroupingPath + +export type MembershipResults = { + resultCode: string, + results: Membership[] +} + +export type GroupingMember = { + whereListed: string +} & MemberResult; + +export type GroupingMembers = { + members: GroupingMember[] +}; + +export type GroupingGroupMember = { + resultCode: string +} & MemberResult; + +export type GroupingGroupMembers = { + members: GroupingGroupMember[] +} & GroupingResult; + +export type GroupingGroupsMembers = { + groupsMembersList: GroupingGroupMembers[] + isBasis: boolean, + isInclude: boolean, + isExclude: boolean, + isOwners: boolean, + paginationComplete: boolean, + allMembers: GroupingMembers, + pageNumber: number +} & GroupingResult; + +export type GroupingDescription = { + description: string +} & GroupingResult; + +export type GroupingSyncDestination = { + name: string, + description: string, + tooltip: string, + synced: boolean, + hidden: boolean +} + +export type GroupingSyncDestinations = { + resultCode: string, + syncDestinations: GroupingSyncDestination[] +} + +export type GroupingOptAttributes = { + optInOn: boolean, + optOutOn: boolean +} & GroupingResult; + +export type GroupingUpdateDescriptionResult = { + currentDescription: string, + updatedDescription: string +} & GroupingResult; + +export type GroupingAddResult = GroupingResult | MemberResult; + +export type GroupingAddResults = { + results: GroupingAddResult[] +} & GroupingResult + +export type GroupingRemoveResult = GroupingResult | MemberResult; + +export type GroupingRemoveResults = { + results: GroupingRemoveResult[] +} & GroupingResult + +export type GroupingMoveMemberResult = { + addResult: GroupingAddResult, + removeResult: GroupingRemoveResult +} & GroupingResult; + +export type GroupingMoveMembersResult = { + addResults: GroupingAddResults, + removeResults: GroupingRemoveResults +} & GroupingResult; + +export type MemberAttributeResults = { + resultCode: string, + invalid: string[], + result: MemberResult[] +} + +export type ApiSubError = { + message: string +}; + +export type ApiValidationError = { + object: string, + field: string, + rejectedValue: unknown +} & ApiSubError; + +export type ApiError = { + status: string, + timestamp: string, + message: string, + debugMessage: string, + subErrors: ApiValidationError[] +}; diff --git a/ui/src/services/GroupingsApiService.ts b/ui/src/services/GroupingsApiService.ts new file mode 100644 index 00000000..bc76fb45 --- /dev/null +++ b/ui/src/services/GroupingsApiService.ts @@ -0,0 +1,655 @@ +'use server'; + +import { getCurrentUser } from '@/access/AuthenticationService'; +import { + Announcements, + ApiError, + GroupingAddResult, + GroupingAddResults, + GroupingDescription, + GroupingGroupMembers, + GroupingGroupsMembers, + GroupingMoveMemberResult, + GroupingMoveMembersResult, + GroupingOptAttributes, + GroupingPaths, + GroupingRemoveResult, + GroupingRemoveResults, + GroupingSyncDestinations, + GroupingUpdateDescriptionResult, + MemberAttributeResults, + MembershipResults +} from './GroupingsApiResults'; +import axios from 'axios'; +import axiosRetry from 'axios-retry'; + +const baseUrl = process.env.NEXT_PUBLIC_API_2_1_BASE_URL as string; +const maxRetries = 3; + +// TODO: +// The setOptIn, setOptOut, setSyncDest service functions will be up to the person who works on +// implementing Opt Attributes and Sync Desinations into our React UI. + +/** + * Polls to getAsyncJobResult API endpoint until the async job has completed with a result. + * + * @param jobId - the jobId returned from the response of an async endpoint + * + * @returns The result T + */ +const poll = async (jobId: number): Promise => { + const delay = async (ms = 5000) => new Promise((res) => setTimeout(res, ms)); + + const currentUser = await getCurrentUser(); + return axios.get(`${baseUrl}/jobs/${jobId}`, { headers: { 'current_user': currentUser.uid } }) + .then(async (response) => { + if (response.data.status === 'COMPLETED') { + return response.data.result; + } else { + await delay(); + return poll(jobId); + } + }) + .catch(error => error.response.data); +} + +/** + * Get a list of announcements to display on the home page. + * + * @returns The announcements + */ +export const getAnnouncements = (): Promise => { + const endpoint = `${baseUrl}/announcements`; + return axios.get(endpoint) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get all the members of an owned grouping through paginated calls. + * + * @param groupPaths - The paths to the groups + * @param page - The page number + * @param size - The size of the page + * @param sortString - String to sort by column name + * @param isAscending - On true the data returns in ascending order + * + * @returns The members of an owned grouping + */ +export const ownedGrouping = async ( + groupPaths: string[], + page: number, + size: number, + sortString: string, + isAscending: boolean +): Promise => { + const currentUser = await getCurrentUser(); + const params = new URLSearchParams({ + page: page.toString(), + size: size.toString(), + sortString, + isAscending: isAscending.toString() + }); + const endpoint = `${baseUrl}/groupings/group?${params.toString()}`; + + axiosRetry(axios, { retries: maxRetries, retryDelay: axiosRetry.exponentialDelay }); + return axios.post(endpoint, groupPaths, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the description of a grouping. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping description + */ +export const groupingDescription = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/description`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the sync destinations of a grouping. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping sync destinations + */ +export const groupingSyncDest = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/groupings-sync-destinations`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the opt attributes of a grouping. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping opt attributes + */ +export const groupingOptAttributes = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/opt-attributes`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Update the description of grouping at path. + * + * @param description - The new description + * @param groupingPath - The path of the grouping + * + * @returns The result of updating the description + */ +export const updateDescription = async ( + description: string, + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/description`; + return axios.post(endpoint, description, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get a list of admins. + * + * @returns The grouping admins + */ +export const groupingAdmins = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/grouping-admins`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get a list of all grouping paths. + * + * @returns All the grouping paths + */ +export const getAllGroupings = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/all-groupings`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Add members to the include group of a grouping. + * + * @param uhIdentifiers - The list of uhIdentifiers to add + * @param groupingPath - The path of the grouping + * + * @returns The grouping move member result + */ +export const addIncludeMembers = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/include-members`; + return axios.put(endpoint, uhIdentifiers, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Add members to the include group of a grouping asynchronously using polling. + * + * @param uhIdentifiers - The list of uhIdentifiers to add to include + * @param groupingPath - The path of the grouping + * + * @returns The grouping move member result + */ +export const addIncludeMembersAsync = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/include-members/async`; + return axios.put(endpoint, uhIdentifiers, { headers: { 'current_user': currentUser.uid } }) + .then(response => poll(response.data)) + .catch(error => error.response.data); +} + +/** + * Add members to the exclude group of a grouping. + * + * @param uhIdentifiers - The list of uhIdentifiers to add to exclude + * @param groupingPath - The path of the grouping + * + * @returns The grouping move member result + */ +export const addExcludeMembers = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/exclude-members`; + return axios.put(endpoint, uhIdentifiers, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Add members to the exclude group of a grouping asynchronously using polling. + * + * @param uhIdentifiers - The list of uhIdentifiers to add to exclude + * @param groupingPath - The path of the grouping + * + * @returns The grouping move member result + */ +export const addExcludeMembersAsync = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/exclude-members/async`; + return axios.put(endpoint, uhIdentifiers, { headers: { 'current_user': currentUser.uid } }) + .then(response => poll(response.data)) + .catch(error => error.response.data); +} + +/** + * Add an owner to the owners group of a grouping. + * + * @param uhIdentifiers - The uhIdentifiers to add to owners + * @param groupingPath - The path of the grouping + * + * @returns The grouping add results + */ +export const addOwners = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifiers}`; + return axios.post(endpoint, undefined, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Add an admin to the UH Groupings admins. + * + * @param uhIdentifier - The uhIdentifier to add to admins + * + * @returns The grouping add results + */ +export const addAdmin = async ( + uhIdentifier: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/admins/${uhIdentifier}`; + return axios.post(endpoint, undefined, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove a uhIdentifier from multiple groupings. + * + * @param uhIdentifier - The uhIdentifier to remove from groups + * @param groupPaths - The paths to the groups to remove from + * + * @returns The grouping remove results + */ +export const removeFromGroups = async ( + uhIdentifier: string, + groupPaths: string[], +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/admins/${groupPaths}/${uhIdentifier}`; + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove members from include group of grouping. + * + * @param uhIdentifiers - The uhIdentifiers to remove from the include + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const removeIncludeMembers = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/include-members`; + return axios.delete(endpoint, { data: uhIdentifiers, headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove members from exclude group of grouping. + * + * @param uhIdentifiers - The uhIdentifiers to remove from the exclude + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const removeExcludeMembers = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/exclude-members`; + return axios.delete(endpoint, { data: uhIdentifiers, headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove owners from owners group of grouping. + * + * @param uhIdentifiers - The uhIdentifiers to remove from owners + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const removeOwners = async ( + uhIdentifiers: string[], + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifiers}`; + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove an admin from the UH Groupings admins. + * + * @param uhIdentifier - The uhIdentifier to remove from admins + * + * @returns The grouping remove result + */ +export const removeAdmin = async ( + uhIdentifier: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/admins/${uhIdentifier}`; + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the attributes of a user, which includes their uid, uhUuid, name, firstName, and lastName. + * + * @param uhIdentifiers - The uhIdentifiers to get the attributes of + * + * @returns The member attribute results + */ +export const memberAttributeResults = async ( + uhIdentifiers: string[] +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/members`; + return axios.post(endpoint, uhIdentifiers, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the attributes of a user, which includes their uid, uhUuid, name, firstName, and lastName + * asynchronously using polling. + * + * @param uhIdentifiers - The uhIdentifiers to get the attributes of + * + * @returns The member attribute results + */ +export const memberAttributeResultsAsync = async ( + uhIdentifiers: string[] +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/members/async`; + return axios.post(endpoint, uhIdentifiers, { headers: { 'current_user': currentUser.uid } }) + .then(response => poll(response.data)) + .catch(error => error.response.data); +} + +/** + * Opt a user into a grouping. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping move member result + */ +export const optIn = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/include-members/${currentUser.uid}/self`; + return axios.put(endpoint, undefined, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Opt a member out of a grouping. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping move member result + */ +export const optOut = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/exclude-members/${currentUser.uid}/self`; + return axios.put(endpoint, undefined, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get a list of memberships that the current user is associated with. + * + * @returns The membership results + */ +export const membershipResults = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/members/${currentUser.uid}/memberships`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get a list of all groupings that a user is associated with. + * + * @param uhIdentifier - The uhIdentifier to search in Manage Person + * + * @returns The membership results + */ +export const managePersonResults = async ( + uhIdentifier: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/members/${uhIdentifier}/groupings`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the number of memberships the current user has + * + * @returns The number of memberships + */ +export const getNumberOfMemberships = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/members/memberships/count`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get a list of grouping paths that the current user can opt into. + * + * @returns The grouping paths + */ +export const optInGroupingPaths = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/members/${currentUser.uid}/opt-in-groups`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove all members from the include group. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const resetIncludeGroup = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/include` + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove all members from the include group asynchronously using polling. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const resetIncludeGroupAsync = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/include/async` + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => poll(response.data)) + .catch(error => error.response.data); +} + +/** + * Remove all members from the exclude group. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const resetExcludeGroup = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/exclude` + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Remove all members from the exclude group asynchronously using polling. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping remove results + */ +export const resetExcludeGroupAsync = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/exclude/async` + return axios.delete(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => poll(response.data)) + .catch(error => error.response.data); +} + +/** + * Get a list of owners in the current path. + * + * @param groupingPath - The path of the grouping + * + * @returns The grouping group members + */ +export const groupingOwners = async ( + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/grouping/${groupingPath}/owners`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the groupings the current user owns. + * + * @returns The grouping paths + */ +export const ownerGroupings = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/owners/${currentUser.uid}/groupings`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Get the number of groupings the current user owns. + * + * @returns The number of groupings + */ +export const getNumberOfGroupings = async (): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/owners/${currentUser.uid}/groupings/count`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} + +/** + * Checks if the owner of a grouping is the sole owner. + * + * @param uhIdentifier - The uhIdentifier to check + * @param groupingPath - The path of the grouping + * + * @returns True if uhIdentifier is the sole owner of a grouping + */ +export const isSoleOwner = async ( + uhIdentifier: string, + groupingPath: string +): Promise => { + const currentUser = await getCurrentUser(); + const endpoint = `${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifier}`; + return axios.get(endpoint, { headers: { 'current_user': currentUser.uid } }) + .then(response => response.data) + .catch(error => error.response.data); +} diff --git a/ui/tests/groupings/GroupingsApiService.test.ts b/ui/tests/groupings/GroupingsApiService.test.ts new file mode 100644 index 00000000..7e267f8c --- /dev/null +++ b/ui/tests/groupings/GroupingsApiService.test.ts @@ -0,0 +1,925 @@ +import { + addAdmin, + addExcludeMembers, + addExcludeMembersAsync, + addIncludeMembers, + addIncludeMembersAsync, + addOwners, + getAllGroupings, + getAnnouncements, + getNumberOfGroupings, + getNumberOfMemberships, + groupingAdmins, + groupingDescription, + groupingOptAttributes, + groupingOwners, + groupingSyncDest, + isSoleOwner, + managePersonResults, + memberAttributeResults, + memberAttributeResultsAsync, + membershipResults, + optIn, + optInGroupingPaths, + optOut, + ownedGrouping, + ownerGroupings, + removeAdmin, + removeExcludeMembers, + removeFromGroups, + removeIncludeMembers, + removeOwners, + resetExcludeGroup, + resetExcludeGroupAsync, + resetIncludeGroup, + resetIncludeGroupAsync, + updateDescription +} from '@/services/GroupingsApiService'; +import axios from 'axios'; +import * as AuthenticationService from '@/access/AuthenticationService'; +import User from '@/access/User'; +import MockAdapter from 'axios-mock-adapter'; + +const baseUrl = process.env.NEXT_PUBLIC_API_2_1_BASE_URL as string; +const testUser: User = JSON.parse(process.env.TEST_USER_A as string); + +jest.mock('@/access/AuthenticationService'); + +describe('GroupingsService', () => { + + const currentUser = testUser; + const headers = { headers: { 'current_user': currentUser.uid } }; + + const uhIdentifier = 'testiwta'; + const uhIdentifiers = ['testiwta', 'testiwtb']; + const groupingPath = 'tmp:testiwta:testiwta-aux'; + const groupPaths = [ + `${groupingPath}:include`, + `${groupingPath}:include`, + `${groupingPath}:exclude`, + `${groupingPath}:owners` + ]; + + const mockResponse = { + data: { + resultCode: 'SUCCESS' + } + } + const mockAsyncCompletedResponse = { + data: { + status: 'COMPLETED', + result: { + resultCode: 'SUCCESS' + } + } + } + const mockAsyncInProgressResponse = { + data: { + status: 'IN_PROGRESS' + } + } + const mockError = { + response: { + data: { + resultCode: 'FAILURE' + } + } + } + + beforeAll(() => { + jest.spyOn(AuthenticationService, 'getCurrentUser').mockResolvedValue(testUser); + }) + + beforeEach(() => { + new MockAdapter(axios); + }); + + describe('getAnnouncements', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await getAnnouncements(); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/announcements`); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await getAnnouncements(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await getAnnouncements(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('ownedGrouping', () => { + const page = 1; + const size = 700; + const sortString = 'name'; + const isAscending = true; + + it('should make a POST request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'post'); + await ownedGrouping(groupPaths, page, size, sortString, isAscending); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/group?` + + `page=${page}&size=${size}&sortString=${sortString}&isAscending=${isAscending}`, + groupPaths, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'post').mockResolvedValue(mockResponse); + const res = await ownedGrouping(groupPaths, page, size, sortString, isAscending); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'post').mockRejectedValue(mockError); + const res = await ownedGrouping(groupPaths, page, size, sortString, isAscending); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('groupingDescription', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await groupingDescription(groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/description`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await groupingDescription(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await groupingDescription(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('groupingSyncDest', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await groupingSyncDest(groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/groupings-sync-destinations`, + headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await groupingSyncDest(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await groupingSyncDest(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('groupingOptAttributes', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await groupingOptAttributes(groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/opt-attributes`, + headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await groupingOptAttributes(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await groupingOptAttributes(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('updateDescription', () => { + const description = 'description'; + + it('should make a POST request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'post'); + await updateDescription(description, groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/description`, description, + headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'post').mockResolvedValue(mockResponse); + const res = await updateDescription(description, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'post').mockRejectedValue(mockError); + const res = await updateDescription(description, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('groupingAdmins', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await groupingAdmins(); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/grouping-admins`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await groupingAdmins(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await groupingAdmins(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('getAllGroupings', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await getAllGroupings(); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/all-groupings`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await getAllGroupings(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await getAllGroupings(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('AddIncludeMembers', () => { + it('should make a PUT request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'put'); + await addIncludeMembers(uhIdentifiers, groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/include-members`, uhIdentifiers, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(mockResponse); + const res = await addIncludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(mockError); + const res = await addIncludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('addIncludeMembersAsync', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should make a PUT request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'put'); + await addIncludeMembersAsync(uhIdentifiers, groupingPath); + expect(requestSpy).toHaveBeenCalledWith( + `${baseUrl}/groupings/${groupingPath}/include-members/async`, uhIdentifiers, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncInProgressResponse); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncCompletedResponse); + + const res = addIncludeMembersAsync(uhIdentifiers, groupingPath); + await jest.advanceTimersByTimeAsync(5000); + + expect(await res).toEqual(mockAsyncCompletedResponse.data.result); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(mockError); + const res = await addIncludeMembersAsync(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + + jest.spyOn(axios, 'put').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res2 = addIncludeMembersAsync(uhIdentifiers, groupingPath); + await jest.advanceTimersByTimeAsync(5000); + expect(await res2).toEqual(mockError.response.data); + }); + }); + + describe('addExcludeMembers', () => { + it('should make a PUT request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'put'); + await addExcludeMembers(uhIdentifiers, groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/exclude-members`, uhIdentifiers, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(mockResponse); + const res = await addExcludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(mockError); + const res = await addExcludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('addExcludeMembersAsync', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should make a PUT request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'put'); + await addExcludeMembersAsync(uhIdentifiers, groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/exclude-members/async`, + uhIdentifiers, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncInProgressResponse); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncCompletedResponse); + + const res = addExcludeMembersAsync(uhIdentifiers, groupingPath); + await jest.advanceTimersByTimeAsync(5000); + + expect(await res).toEqual(mockAsyncCompletedResponse.data.result); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(mockError); + const res = await addExcludeMembersAsync(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + + jest.spyOn(axios, 'put').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res2 = addExcludeMembersAsync(uhIdentifiers, groupingPath); + await jest.advanceTimersByTimeAsync(5000); + expect(await res2).toEqual(mockError.response.data); + }); + }); + + describe('addOwners', () => { + it('should make a POST request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'post'); + await addOwners(uhIdentifiers, groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifiers}`, + undefined, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'post').mockResolvedValue(mockResponse); + const res = await addOwners(uhIdentifiers, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'post').mockRejectedValue(mockError); + const res = await addOwners(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('addAdmin', () => { + it('should make a POST request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'post'); + await addAdmin(uhIdentifier); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/admins/${uhIdentifier}`, undefined, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'post').mockResolvedValue(mockResponse); + const res = await addAdmin(uhIdentifier); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'post').mockRejectedValue(mockError); + const res = await addAdmin(uhIdentifier); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('removeFromGroups', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await removeFromGroups(uhIdentifier, groupPaths); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/admins/${groupPaths}/${uhIdentifier}`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await removeFromGroups(uhIdentifier, groupPaths); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await removeFromGroups(uhIdentifier, groupPaths); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('removeIncludeMembers', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await removeIncludeMembers(uhIdentifiers, groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/include-members`, + { data: uhIdentifiers, ...headers }); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await removeIncludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await removeIncludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('removeExcludeMembers', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await removeExcludeMembers(uhIdentifiers, groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/exclude-members`, + { data: uhIdentifiers, ...headers }); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await removeExcludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await removeExcludeMembers(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('removeOwners', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await removeOwners(uhIdentifiers, groupingPath); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifiers}`, + headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await removeOwners(uhIdentifiers, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await removeOwners(uhIdentifiers, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('removeAdmin', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await removeAdmin(uhIdentifier); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/admins/${uhIdentifier}`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await removeAdmin(uhIdentifier); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await removeAdmin(uhIdentifier); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('memberAttributeResults', () => { + it('should make a POST request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'post'); + await memberAttributeResults(uhIdentifiers); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/members`, uhIdentifiers, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'post').mockResolvedValue(mockResponse); + const res = await memberAttributeResults(uhIdentifiers); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'post').mockRejectedValue(mockError); + const res = await memberAttributeResults(uhIdentifiers); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('memberAttributeResultsAsync', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should make a POST request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'post'); + await memberAttributeResultsAsync(uhIdentifiers); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/members/async`, uhIdentifiers, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'post').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncInProgressResponse); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncCompletedResponse); + + const res = memberAttributeResultsAsync(uhIdentifiers); + await jest.advanceTimersByTimeAsync(5000); + + expect(await res).toEqual(mockAsyncCompletedResponse.data.result); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'post').mockRejectedValue(mockError); + const res = await memberAttributeResultsAsync(uhIdentifiers); + expect(res).toEqual(mockError.response.data); + + jest.spyOn(axios, 'post').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res2 = memberAttributeResultsAsync(uhIdentifiers); + await jest.advanceTimersByTimeAsync(5000); + expect(await res2).toEqual(mockError.response.data); + }); + }); + + describe('optIn', () => { + it('should make a PUT request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'put'); + await optIn(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/include-members/${currentUser.uid}/self`, + undefined, headers) + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(mockResponse); + const res = await optIn(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(mockError); + const res = await optIn(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('optOut', () => { + it('should make a PUT request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'put'); + await optOut(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/exclude-members/${currentUser.uid}/self`, + undefined, headers) + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'put').mockResolvedValue(mockResponse); + const res = await optOut(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'put').mockRejectedValue(mockError); + const res = await optOut(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('membershipResults', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await membershipResults(); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/members/${currentUser.uid}/memberships`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await membershipResults(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await membershipResults(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('managePersonResults', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await managePersonResults(uhIdentifier); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/members/${uhIdentifier}/groupings`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await managePersonResults(uhIdentifier); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await managePersonResults(uhIdentifier); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('getNumberOfMemberships', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await getNumberOfMemberships(); + expect(requestSpy).toHaveBeenCalledWith(`${baseUrl}/members/memberships/count`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await getNumberOfMemberships(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await getNumberOfMemberships(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('optInGroupingPaths', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await optInGroupingPaths(); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/members/${currentUser.uid}/opt-in-groups`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await optInGroupingPaths(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await optInGroupingPaths(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('resetIncludeGroup', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await resetIncludeGroup(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/include`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await resetIncludeGroup(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await resetIncludeGroup(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('resetIncludeGroupAsync', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await resetIncludeGroupAsync(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/include/async`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncInProgressResponse); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncCompletedResponse); + + const res = resetIncludeGroupAsync(groupingPath); + await jest.advanceTimersByTimeAsync(5000); + + expect(await res).toEqual(mockAsyncCompletedResponse.data.result); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await resetIncludeGroupAsync(groupingPath); + expect(res).toEqual(mockError.response.data); + + jest.spyOn(axios, 'delete').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res2 = resetIncludeGroupAsync(groupingPath); + await jest.advanceTimersByTimeAsync(5000); + expect(await res2).toEqual(mockError.response.data); + }); + }); + + describe('resetExcludeGroup', () => { + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await resetExcludeGroup(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/exclude`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(mockResponse); + const res = await resetExcludeGroup(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await resetExcludeGroup(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('resetExcludeGroupAsync', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should make a DELETE request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'delete'); + await resetExcludeGroupAsync(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/exclude/async`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'delete').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncInProgressResponse); + jest.spyOn(axios, 'get').mockResolvedValueOnce(mockAsyncCompletedResponse); + + const res = resetExcludeGroupAsync(groupingPath); + await jest.advanceTimersByTimeAsync(5000); + + expect(await res).toEqual(mockAsyncCompletedResponse.data.result); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'delete').mockRejectedValue(mockError); + const res = await resetExcludeGroupAsync(groupingPath); + expect(res).toEqual(mockError.response.data); + + jest.spyOn(axios, 'delete').mockResolvedValue(0); + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res2 = resetExcludeGroupAsync(groupingPath); + await jest.advanceTimersByTimeAsync(5000); + expect(await res2).toEqual(mockError.response.data); + }); + }); + + describe('groupingOwners', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await groupingOwners(groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/grouping/${groupingPath}/owners`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await groupingOwners(groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await groupingOwners(groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('ownersGroupings', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await ownerGroupings(); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/owners/${currentUser.uid}/groupings`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await ownerGroupings(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await ownerGroupings(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('getNumberOfGroupings', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await getNumberOfGroupings(); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/owners/${currentUser.uid}/groupings/count`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await getNumberOfGroupings(); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await getNumberOfGroupings(); + expect(res).toEqual(mockError.response.data); + }); + }); + + describe('isSoleOwner', () => { + it('should make a GET request at the correct endpoint', async () => { + const requestSpy = jest.spyOn(axios, 'get'); + await isSoleOwner(uhIdentifier, groupingPath); + expect(requestSpy) + .toHaveBeenCalledWith(`${baseUrl}/groupings/${groupingPath}/owners/${uhIdentifier}`, headers); + }); + + it('should handle the successful response', async () => { + jest.spyOn(axios, 'get').mockResolvedValue(mockResponse); + const res = await isSoleOwner(uhIdentifier, groupingPath); + expect(res).toEqual(mockResponse.data); + }); + + it('should handle the error response', async () => { + jest.spyOn(axios, 'get').mockRejectedValue(mockError); + const res = await isSoleOwner(uhIdentifier, groupingPath); + expect(res).toEqual(mockError.response.data); + }); + }); + +});