Skip to content

Commit

Permalink
Consolidate invite api functions, adjust InviteView error message dis…
Browse files Browse the repository at this point in the history
…play, adjust InviteView unit test
  • Loading branch information
wilwong89 committed Mar 13, 2024
1 parent 16fc8c8 commit 0f1c7e4
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 85 deletions.
8 changes: 4 additions & 4 deletions frontend/src/components/common/InviteButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import ShareLinkContent from '@/components/object/share/ShareLinkContent.vue';
import { Button, Dialog, TabView, TabPanel, RadioButton, InputText, useToast, InputSwitch } from '@/lib/primevue';
import { useConfigStore, useObjectStore, useBucketStore } from '@/store';
import { permissionService } from '@/services';
import { inviteService } from '@/services';
import type { Ref } from 'vue';
import type { COMSObject, Bucket } from '@/types';
Expand Down Expand Up @@ -74,7 +74,7 @@ async function sendInvite() {
if (formData.value.expiresAt) {
expiresAt = Math.floor(Date.now() / 1000) + formData.value.expiresAt;
}
const permissionToken = await permissionService.createInvite(
const permissionToken = await inviteService.createInvite(
props.bucketId,
formData.value.email,
expiresAt,
Expand All @@ -89,9 +89,9 @@ async function sendInvite() {
}
onMounted(() => {
if (props.objectId) {
obj.value = objectStore.findObjectById(props.objectId);
obj.value = objectStore.getObject(props.objectId);
} else if (props.bucketId) {
bucket.value = bucketStore.findBucketById(props.bucketId);
bucket.value = bucketStore.getBucket(props.bucketId);
}
});
</script>
Expand Down
22 changes: 14 additions & 8 deletions frontend/src/services/inviteService.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import { comsAxios } from './interceptors';

import type { InviteCreateOptions } from '@/types';

const PATH = 'permission/invite';

export default {
/**
* @function getInvite
* Use an invite token
* @returns {Promise} An axios response
* @function createInvite
* Post an Invite, exclusive or on bucketId and objectId
* @param {string} bucketId
* @param {string} objectId
* @param {string} email to be used for access
* @param {string} expiration timestamp for token
* @returns {string} uuid token of the invite
*/
createInvite(params: InviteCreateOptions): Promise<string> {
return comsAxios().get(`${PATH}`, {
params: params
createInvite(bucketId?: string, email?: string, expiresAt?: number, objectId?: string) {
return comsAxios().post(`${PATH}/invite`, {
bucketId: bucketId || undefined,
email: email || undefined,
expiresAt: expiresAt || undefined,
objectId: objectId || undefined
});
},

/**
* @function getInvite
* Use an invite token
* @param {string} token uuid
* @returns {Promise} An axios response
*/
getInvite(token: string): Promise<any> {
Expand Down
17 changes: 0 additions & 17 deletions frontend/src/services/permissionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,5 @@ export default {
*/
objectGetPermissions(objectId: string, params?: ObjectGetPermissionsOptions) {
return comsAxios().get(`${PATH}/${OBJECT}/${objectId}`, { params: params });
},

/**
* @function createInvite
* Post an Invite
* @param {string} bucketId
* @param {string} email to be used for access
* @param {string} expiration timestamp for token
* @returns {string} objectId
*/
async createInvite(bucketId?: string, email?: string, expiresAt?: number, objectId?: string) {
return comsAxios().post(`${PATH}/invite`, {
bucketId: bucketId || undefined,
email: email || undefined,
expiresAt: expiresAt || undefined,
objectId: objectId || undefined
});
}
};
64 changes: 29 additions & 35 deletions frontend/src/views/invite/InviteView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ import { RouteNames } from '@/utils/constants';
import type { Ref } from 'vue';
// Types
interface ErrorType {
[key: string]: {
title: string;
detail: string;
};
}
// Props
type Props = {
token: string;
Expand All @@ -35,35 +27,39 @@ const { getConfig } = storeToRefs(useConfigStore());
// State
const invalidInvite: Ref<boolean> = ref(false);
const title: Ref<string> = ref('Invalid Invite');
const detail: Ref<string> = ref('Please check your link again');
const title: Ref<string> = ref('');
const detail: Ref<string> = ref('');
// Actions
const toast = useToast();
const errorMessage: ErrorType = {
401: {
title: 'Unauthorized',
detail: 'Invalid authorization credentials'
},
403: {
// wrong email
title: 'Forbidden',
detail: 'The person who sent you the link may have restricted it to another email address'
},
404: {
// invitation not found/expired
title: 'Not found',
detail: 'Invitation not found or has expired'
},
409: {
// bucket/object not found
// TODO: display specifically object or bucket
title: 'Not found',
detail: 'Invalid object or bucket'
const setErrorMessage = (errorCode: number): void => {
switch (errorCode) {
case 401:
title.value = 'Unauthorized';
detail.value = 'Invalid authorization credentials';
break;
case 403:
title.value = 'Forbidden';
detail.value = 'The person who sent you the link may have restricted it to another email address';
break;
case 404:
title.value = 'Not found';
detail.value = 'Invitation not found or has expired';
break;
case 409:
title.value = 'Not found';
detail.value = 'Invalid object or bucket';
break;
case 410:
title.value = 'Gone';
detail.value = 'Invitation has expired';
break;
default:
title.value = 'Invalid Invite';
detail.value = 'This invite link is not valid or has expired';
}
};
onMounted(() => {
inviteService
.getInvite(props.token)
Expand All @@ -86,12 +82,10 @@ onMounted(() => {
})
.catch((error: any) => {
const errData = error?.response?.data;
if (errData) {
if (errData.status) {
toast.error(errData.status, errData.detail);
title.value = errorMessage[errData.status]['title'];
detail.value = errorMessage[errData.status]['detail'];
setErrorMessage(errData.status);
}
invalidInvite.value = true;
});
});
Expand Down
50 changes: 29 additions & 21 deletions frontend/tests/unit/components/invite/InviteView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const mockToast = vi.fn();
const useToastSpy = vi.spyOn(primevue, 'useToast');
const useInviteService = vi.spyOn(inviteService, 'getInvite');

vi.mock('vue-router', () => ({
useRouter: () => ({
replace: vi.fn()
})
}));

const emptyTestPinia = () => {
return createTestingPinia({
initialState: {
Expand Down Expand Up @@ -99,8 +105,16 @@ const notFoundBucketOrObjectError: errorResponse = {
}
};

const mockRouter = {
replace: vi.fn()
const invitationGoneError: errorResponse = {
response: {
data: {
type: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410',
title: 'Gone',
status: 410,
detail: 'string',
instance: 'string'
}
}
};

beforeEach(() => {
Expand Down Expand Up @@ -171,30 +185,24 @@ describe('InviteView.vue', async () => {
expect(wrapper.find('h1').text()).toBe(notFoundBucketOrObjectError.response.data.title);
});

it('redirects on valid response', async () => {
it('renders not found invitation error', async () => {
useInviteService.mockImplementation(() => Promise.reject(invitationGoneError));

const wrapper = shallowMount(InviteView, inviteViewWrapperSettings());

await flushPromises();
expect(wrapper.find('h1').text()).toBe(invitationGoneError.response.data.title);
});

it('does not trigger errors on valid response', async () => {
useInviteService.mockImplementation(
() => new Promise((resolve) => resolve({ type: BUCKET_ID, resource: 'placeholderUUID' }))
() => new Promise((resolve) => resolve({ data: { type: BUCKET_ID, resource: 'placeholderUUID' } }))
);

const wrapper = shallowMount(InviteView, {
props: {
token: 'placeholder'
},
global: {
plugins: [emptyTestPinia(), PrimeVue, ToastService],
stubs: {
RouterLink: RouterLinkStub,
'font-awesome-icon': true
},
mocks: {
$router: mockRouter
}
}
});
const wrapper = shallowMount(InviteView, inviteViewWrapperSettings());
expect(wrapper.find('h1').text()).toBe('Processing...');

await flushPromises();

expect(wrapper).toBeTruthy();
expect(wrapper.find('h1').text()).toBe('Processing...');
});
});

0 comments on commit 0f1c7e4

Please sign in to comment.