Skip to content

Commit

Permalink
refactor: added update and upsert to zoho api
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed Nov 7, 2024
1 parent c7debdf commit dd04295
Show file tree
Hide file tree
Showing 8 changed files with 557 additions and 81 deletions.
2 changes: 2 additions & 0 deletions packages/util/src/lib/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface UniqueModel {
id?: ModelKey;
}

export type UniqueModelWithId = Required<UniqueModel>;

export interface TypedModel<M extends ModelTypeString = ModelTypeString> {
type: M;
}
Expand Down
301 changes: 296 additions & 5 deletions packages/zoho/nestjs/src/lib/recruit/recruit.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,35 @@ import { Test, TestingModule } from '@nestjs/testing';
import { ZohoRecruitApi } from './recruit.api';
import { fileZohoAccountsAccessTokenCacheService, ZohoAccountsAccessTokenCacheService } from '../accounts/accounts.service';
import { expectFail, itShouldFail, jestExpectFailAssertErrorType } from '@dereekb/util/test';
import { ZOHO_DUPLICATE_DATA_ERROR_CODE, ZOHO_MANDATORY_NOT_FOUND_ERROR_CODE, ZohoNewRecruitRecord, ZohoRecruitRecordCrudDuplicateDataError, ZohoRecruitRecordCrudMandatoryFieldNotFoundError, ZohoRecruitRecordNoContentError } from '@dereekb/zoho';
import { randomNumber } from '@dereekb/util';
import { ZOHO_DUPLICATE_DATA_ERROR_CODE, ZOHO_MANDATORY_NOT_FOUND_ERROR_CODE, NewZohoRecruitRecordData, ZohoRecruitRecordCrudDuplicateDataError, ZohoRecruitRecordCrudMandatoryFieldNotFoundError, ZohoRecruitRecordNoContentError, ZohoRecruitRecord, ZohoRecruitRecordCrudNoMatchingRecordError, ZOHO_INVALID_DATA_ERROR_CODE, ZohoRecruitSearchRecordsCriteriaEntry, ZohoRecruitSearchRecordsCriteriaEntryArray, ZohoInvalidQueryError } from '@dereekb/zoho';
import { Getter, cachedGetter, randomNumber } from '@dereekb/util';

// NOTE: Should have test canidates available on the Zoho Sandbox that is being used. Use test_candidates.csv to generate if needed.

const cacheService = fileZohoAccountsAccessTokenCacheService();

const NON_EXISTENT_CANDIDATE_ID = '01';
const NON_EXISTENT_CANDIDATE_ID = '576777777777777712';

/**
* For the tests, atleast one account should have this email domain/suffix.
*/
const TEST_ACCOUNT_EXPORT_SUFFIX = 'components.dereekb.com';
const TEST_ACCOUNT_INSERT_EXPORT_SUFFIX = `insert.${TEST_ACCOUNT_EXPORT_SUFFIX}`;
const TEST_ACCOUNT_UPSERT_EXPORT_SUFFIX = `upsert.${TEST_ACCOUNT_EXPORT_SUFFIX}`;

/**
* This candidate is only avaialble within the specific testing sandbox used for tests.
*/
const TEST_CANDIDATE_ID = '576214000000574340';
const TEST_CANDIDATE_EMAIL_ADDRESS = '[email protected]';
const UPSERT_TEST_FIRST_NAME_PREFIX = `Upsert`;
const UPSERT_TEST_LAST_NAME = `Upsert`;

interface TestCandidate {
Email: string;
First_Name: string;
Last_Name: string;
}

describe('recruit.api', () => {
let nest: TestingModule;
Expand Down Expand Up @@ -53,6 +62,31 @@ describe('recruit.api', () => {
describe('ZohoRecruitApi', () => {
let api: ZohoRecruitApi;

const GURANTEED_NUMBER_OF_UPSERT_TEST_RECORDS = 2;

/**
* Cached getter across all test runs. These records should always exist and can be used for updating.
*/
const loadTestRecords: Getter<Promise<ZohoRecruitRecord[]>> = cachedGetter(async () => {
const upsertResult = await api.upsertRecord({
module: 'Candidates',
data: [
{
First_Name: `${UPSERT_TEST_FIRST_NAME_PREFIX}_1`,
Last_Name: UPSERT_TEST_LAST_NAME,
Email: `upsert+1@${TEST_ACCOUNT_UPSERT_EXPORT_SUFFIX}`
},
{
First_Name: `${UPSERT_TEST_FIRST_NAME_PREFIX}_2`,
Last_Name: UPSERT_TEST_LAST_NAME,
Email: `upsert+2@${TEST_ACCOUNT_UPSERT_EXPORT_SUFFIX}`
}
]
});

return upsertResult.successItems.map((item) => item.result.details);
});

beforeEach(() => {
api = nest.get(ZohoRecruitApi);
});
Expand Down Expand Up @@ -136,7 +170,7 @@ describe('recruit.api', () => {
it('should return error items for records that could not be created', async () => {
const createNumber = randomNumber({ min: 1000000000000, max: 10000000000000 });

const data: ZohoNewRecruitRecord[] = [
const data: NewZohoRecruitRecordData[] = [
{
First_Name: `Create_${createNumber}`,
lastNameFieldMissing: 'Candidate', // field missing
Expand All @@ -162,6 +196,207 @@ describe('recruit.api', () => {
});
});
});

describe('updateRecord()', () => {
describe('single record', () => {
it('should update a record and return the updated record details', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

const number = randomNumber({ min: 1000000000000, max: 10000000000000 });
const First_Name = `Updated For Test ${number}`;

const updateResult = await api.updateRecord({
module: 'Candidates',
data: {
id: recordToUpdate.id,
First_Name
}
});

expect(updateResult.id).toBe(recordToUpdate.id);

const updatedRecord = await api.getRecordById({ module: 'Candidates', id: recordToUpdate.id });
expect(updatedRecord.First_Name).toBe(First_Name);
});

itShouldFail('if attempting to update a value that does not exist', async () => {
await expectFail(
() =>
api.updateRecord({
module: 'Candidates',
data: {
id: NON_EXISTENT_CANDIDATE_ID,
First_Name: 'Failure'
}
}),
jestExpectFailAssertErrorType(ZohoRecruitRecordCrudNoMatchingRecordError)
);
});

itShouldFail('if attempting to update a unique value to an existing value', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

await expectFail(
() =>
api.updateRecord({
module: 'Candidates',
data: {
id: recordToUpdate.id,
Email: TEST_CANDIDATE_EMAIL_ADDRESS
}
}),
jestExpectFailAssertErrorType(ZohoRecruitRecordCrudDuplicateDataError)
);
});
});

describe('multiple record', () => {
it('should update multiple records and return the results in an array', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

const number = randomNumber({ min: 1000000000000, max: 10000000000000 });
const First_Name = `Updated For Test ${number}`;

const updateResult = await api.updateRecord({
module: 'Candidates',
data: [
{
id: recordToUpdate.id,
First_Name
}
]
});

expect(updateResult.successItems).toHaveLength(1);
expect(updateResult.successItems[0].result.details.id).toBe(recordToUpdate.id);

const updatedRecord = await api.getRecordById({ module: 'Candidates', id: recordToUpdate.id });
expect(updatedRecord.First_Name).toBe(First_Name);
});

it('should return error items for the items that failed updating', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

const data = [
{
id: NON_EXISTENT_CANDIDATE_ID, // invalid data issue
First_Name: 'Failure'
},
{
id: recordToUpdate.id,
Email: TEST_CANDIDATE_EMAIL_ADDRESS // duplicate issue
}
];

const result = await api.updateRecord({
module: 'Candidates',
data
});

expect(result.errorItems).toHaveLength(2);
expect(result.errorItems[0].input).toBe(data[0]);
expect(result.errorItems[1].input).toBe(data[1]);
expect(result.errorItems[0].result.code).toBe(ZOHO_INVALID_DATA_ERROR_CODE);
expect(result.errorItems[1].result.code).toBe(ZOHO_DUPLICATE_DATA_ERROR_CODE);
});
});
});

describe('upsertRecord()', () => {
describe('single record', () => {
it('should update a record and return the updated record details', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

const number = randomNumber({ min: 1000000000000, max: 10000000000000 });
const First_Name = `Updated For Test ${number}`;

const updateResult = await api.upsertRecord({
module: 'Candidates',
data: {
id: recordToUpdate.id,
First_Name
}
});

expect(updateResult.id).toBe(recordToUpdate.id);

const updatedRecord = await api.getRecordById({ module: 'Candidates', id: recordToUpdate.id });
expect(updatedRecord.First_Name).toBe(First_Name);
});

itShouldFail('if attempting to update a value that does not exist', async () => {
await expectFail(
() =>
api.updateRecord({
module: 'Candidates',
data: {
id: NON_EXISTENT_CANDIDATE_ID,
First_Name: 'Failure'
}
}),
jestExpectFailAssertErrorType(ZohoRecruitRecordCrudNoMatchingRecordError)
);
});
});

describe('multiple record', () => {
it('should update multiple records and return the results in an array', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

const number = randomNumber({ min: 1000000000000, max: 10000000000000 });
const First_Name = `Updated For Test ${number}`;

const updateResult = await api.upsertRecord({
module: 'Candidates',
data: [
{
id: recordToUpdate.id,
First_Name
}
]
});

expect(updateResult.successItems).toHaveLength(1);
expect(updateResult.successItems[0].result.details.id).toBe(recordToUpdate.id);

const updatedRecord = await api.getRecordById({ module: 'Candidates', id: recordToUpdate.id });
expect(updatedRecord.First_Name).toBe(First_Name);
});

it('should return error items for the items that failed updating', async () => {
const testRecords = await loadTestRecords();
const recordToUpdate = testRecords[0];

const data = [
{
id: NON_EXISTENT_CANDIDATE_ID, // invalid data issue
First_Name: 'Failure'
},
{
id: recordToUpdate.id,
Email: TEST_CANDIDATE_EMAIL_ADDRESS // duplicate issue
}
];

const result = await api.upsertRecord({
module: 'Candidates',
data
});

expect(result.errorItems).toHaveLength(2);
expect(result.errorItems[0].input).toBe(data[0]);
expect(result.errorItems[1].input).toBe(data[1]);
expect(result.errorItems[0].result.code).toBe(ZOHO_INVALID_DATA_ERROR_CODE);
expect(result.errorItems[1].result.code).toBe(ZOHO_DUPLICATE_DATA_ERROR_CODE);
});
});
});
});

describe('read', () => {
Expand Down Expand Up @@ -202,7 +437,7 @@ describe('recruit.api', () => {
});

describe('searchRecords()', () => {
it('should return a page of search results', async () => {
it('should search results by email', async () => {
const limit = 3;
const result = await api.searchRecords({
module: 'Candidates',
Expand All @@ -213,6 +448,62 @@ describe('recruit.api', () => {
expect(result).toBeDefined();
expect(result.data.length).toBeLessThanOrEqual(limit);
});

it('should search results by a specific field', async () => {
const limit = GURANTEED_NUMBER_OF_UPSERT_TEST_RECORDS;

const result = await api.searchRecords<TestCandidate>({
module: 'Candidates',
criteria: [{ field: 'Last_Name', filter: 'starts_with', value: UPSERT_TEST_LAST_NAME }],
per_page: limit
});

expect(result).toBeDefined();
expect(result.data).toHaveLength(GURANTEED_NUMBER_OF_UPSERT_TEST_RECORDS);

expect(result.data[0].Last_Name).toBe(UPSERT_TEST_LAST_NAME);
expect(result.data[1].Last_Name).toBe(UPSERT_TEST_LAST_NAME);
});

it('should search results by a specific field', async () => {
const limit = GURANTEED_NUMBER_OF_UPSERT_TEST_RECORDS;

const result = await api.searchRecords<TestCandidate>({
module: 'Candidates',
criteria: [{ field: 'Last_Name', filter: 'starts_with', value: UPSERT_TEST_LAST_NAME }],
per_page: limit
});

expect(result).toBeDefined();
expect(result.data).toHaveLength(GURANTEED_NUMBER_OF_UPSERT_TEST_RECORDS);

expect(result.data[0].Last_Name).toBe(UPSERT_TEST_LAST_NAME);
expect(result.data[1].Last_Name).toBe(UPSERT_TEST_LAST_NAME);
});

it('should return no values if there are no results', async () => {
const limit = GURANTEED_NUMBER_OF_UPSERT_TEST_RECORDS;

const result = await api.searchRecords<TestCandidate>({
module: 'Candidates',
criteria: [{ field: 'Last_Name', filter: 'starts_with', value: 'Should Not Return Any Results' }],
per_page: limit
});

expect(result).toBeDefined();
expect(result.data).toHaveLength(0);
});

itShouldFail('if the criteria is invalid', async () => {
await expectFail(
() =>
api.searchRecords<TestCandidate>({
module: 'Candidates',
criteria: [{ field: 'Last_Name', filter: 'STARTS_WITH_WRONG' as any, value: 'Should Not Return Any Results' }]
}),
jestExpectFailAssertErrorType(ZohoInvalidQueryError)
);
});
});
});
});
Expand Down
10 changes: 9 additions & 1 deletion packages/zoho/nestjs/src/lib/recruit/recruit.api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common';
import { ZohoRecruit, ZohoRecruitContext, getRecordById, getRecords, insertRecord, searchRecords, zohoRecruitFactory } from '@dereekb/zoho';
import { ZohoRecruit, ZohoRecruitContext, getRecordById, getRecords, insertRecord, searchRecords, updateRecord, upsertRecord, zohoRecruitFactory } from '@dereekb/zoho';
import { ZohoRecruitServiceConfig } from './recruit.config';
import { ZohoAccountsApi } from '../accounts/accounts.api';

Expand All @@ -23,6 +23,14 @@ export class ZohoRecruitApi {
return insertRecord(this.recruitContext);
}

get upsertRecord() {
return upsertRecord(this.recruitContext);
}

get updateRecord() {
return updateRecord(this.recruitContext);
}

get getRecordById() {
return getRecordById(this.recruitContext);
}
Expand Down
Loading

0 comments on commit dd04295

Please sign in to comment.