Skip to content

Commit 0a6db99

Browse files
committed
Refactor Zendesk Client Test Suite
- Introduced SOLID principles to organize and refactor the `executeJob` function. - Utilized a `JobRunner` class for encapsulating the job execution process. - Enhanced test suite readability and maintainability by isolating client setup, job execution, and data validation. - Improved test output clarity by adding custom assertion messages. - Increased robustness of tests by adding error handling for bulk delete operations. Note: Moved from a monolithic test structure to a modular approach based on single responsibility and dependency inversion.
1 parent 56d25a6 commit 0a6db99

File tree

6 files changed

+123
-138
lines changed

6 files changed

+123
-138
lines changed

test/authentication.test.js

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
import process from 'node:process';
22
import dotenv from 'dotenv';
33
import {describe, expect, it} from 'vitest';
4-
import {createClient} from '../src/index.js';
4+
import {initializeClient} from './setup.js';
55

66
dotenv.config();
77

88
const username = process.env.ZENDESK_USERNAME;
99
const password = process.env.ZENDESK_PASSWORD;
10-
const subdomain = process.env.ZENDESK_SUBDOMAIN;
1110
const token = process.env.ZENDESK_TOKEN;
1211
const oauthAccessToken = process.env.ZENDESK_OAUTH_ACCESS_TOKEN;
1312
const TEST_USER = process.env.ZENDESK_FULL_NAME;
1413

1514
describe('Zendesk Client Authentication', () => {
16-
const setupClient = (config) => {
17-
return createClient({username, subdomain, ...config});
18-
};
15+
const setupClient = initializeClient;
1916

2017
const verifyUser = async (client, expectedName) => {
2118
const {result: user} = await client.users.me();
@@ -38,7 +35,7 @@ describe('Zendesk Client Authentication', () => {
3835
});
3936

4037
it('should throw an error for an invalid subdomain', async () => {
41-
const client = createClient({
38+
const client = initializeClient({
4239
username,
4340
token,
4441
subdomain: 'invalidSubdomain',

test/job-runner.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import {expect} from 'vitest';
2+
3+
class JobInitiator {
4+
async initiate(initiateFunction) {
5+
const {result} = await initiateFunction();
6+
return result;
7+
}
8+
}
9+
10+
class JobStatusVerifier {
11+
verifyInitialStatus(result) {
12+
expect(result.job_status).toHaveProperty('id');
13+
expect(result.job_status.status).toBe('queued');
14+
}
15+
16+
verifyFinalStatus(finalJobResults) {
17+
expect(finalJobResults.status).toBe('completed');
18+
}
19+
}
20+
21+
class JobWatcher {
22+
constructor(client) {
23+
this.client = client;
24+
}
25+
26+
async watch(jobStatusId) {
27+
return this.client.jobstatuses.watch(jobStatusId, 1000, 30);
28+
}
29+
}
30+
31+
export class JobRunner {
32+
constructor(client) {
33+
this.client = client;
34+
this.initiator = new JobInitiator();
35+
this.verifier = new JobStatusVerifier();
36+
this.watcher = new JobWatcher(client);
37+
}
38+
39+
async run(initiateFunction, validateJobDetails) {
40+
const result = await this.initiator.initiate(initiateFunction);
41+
42+
this.verifier.verifyInitialStatus(result);
43+
44+
const finalJobResults = await this.watcher.watch(result.job_status.id);
45+
46+
this.verifier.verifyFinalStatus(finalJobResults);
47+
48+
await validateJobDetails(finalJobResults);
49+
}
50+
}

test/organizations.many.test.js

Lines changed: 30 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,22 @@
11
/* eslint-disable no-await-in-loop */
2-
import process from 'node:process';
3-
import crypto from 'node:crypto';
42
import dotenv from 'dotenv';
53
import {describe, expect, it} from 'vitest';
6-
import {createClient} from '../src/index.js';
4+
import {setupClient, generateMultipleOrganizations} from './setup.js';
5+
import {JobRunner} from './job-runner.js';
76

87
dotenv.config();
98

10-
const username = process.env.ZENDESK_USERNAME;
11-
const subdomain = process.env.ZENDESK_SUBDOMAIN;
12-
const token = process.env.ZENDESK_TOKEN;
13-
14-
describe('Zendesk Client Organizations(many)', () => {
9+
describe('Zendesk Client Organizations(many/bulk)', () => {
1510
const testOrganizations = [];
16-
1711
let organizationsToCreate = [];
1812

19-
function generateOrganizationName() {
20-
const id = crypto.randomBytes(16).toString('hex');
21-
return `Test Organization ${id}`;
22-
}
23-
24-
function generateMultipleOrganizations(n) {
25-
const organizations = [];
26-
for (let i = 0; i < n; i++) {
27-
organizations.push({
28-
name: generateOrganizationName(),
29-
});
30-
}
31-
32-
return organizations;
33-
}
34-
35-
const setupClient = (config) => {
36-
return createClient({username, subdomain, token, ...config});
37-
};
38-
3913
const client = setupClient();
40-
41-
async function executeJob(initiateFunction, validateJobDetails) {
42-
const {result} = await initiateFunction();
43-
44-
expect(result.job_status).toHaveProperty('id');
45-
expect(result.job_status.status).toBe('queued');
46-
47-
const finalJobResults = await client.jobstatuses.watch(
48-
result.job_status.id,
49-
1000,
50-
30,
51-
);
52-
expect(finalJobResults.status).toBe('completed');
53-
54-
await validateJobDetails(finalJobResults);
55-
}
14+
const jobs = new JobRunner(client);
5615

5716
it(
5817
'should create multiple organizations',
5918
async () => {
60-
await executeJob(
19+
await jobs.run(
6120
async () => {
6221
organizationsToCreate = generateMultipleOrganizations(30);
6322
return client.organizations.createMany({
@@ -68,13 +27,18 @@ describe('Zendesk Client Organizations(many)', () => {
6827
const createdOrgIDs = finalJobResults.results.map((org) => org.id);
6928
const orgDetails = await client.organizations.showMany(createdOrgIDs);
7029

71-
expect(orgDetails.length).toBe(createdOrgIDs.length);
30+
expect(orgDetails.length).toBe(
31+
createdOrgIDs.length,
32+
`Expected ${createdOrgIDs.length} organizations, but got ${orgDetails.length}`,
33+
);
7234

7335
for (const org of organizationsToCreate) {
7436
const orgDetail = orgDetails.find(
7537
(detail) => detail.name === org.name,
7638
);
77-
expect(orgDetail).toBeTruthy();
39+
expect(orgDetail).toBeTruthy(
40+
`Expected to find organization with name ${org.name}, but did not.`,
41+
);
7842
testOrganizations.push(orgDetail);
7943
}
8044
},
@@ -86,7 +50,7 @@ describe('Zendesk Client Organizations(many)', () => {
8650
it(
8751
'should update multiple organizations',
8852
async () => {
89-
await executeJob(
53+
await jobs.run(
9054
async () => {
9155
const ids = testOrganizations.map((org) => org.id);
9256
return client.organizations.updateMany({
@@ -104,35 +68,43 @@ describe('Zendesk Client Organizations(many)', () => {
10468
await client.organizations.showMany(updatedOrgIDs);
10569

10670
const updatedNotes = updatedOrgDetails.map((org) => org.notes);
107-
expect(updatedNotes).toContain('updatedFoo');
108-
expect(updatedNotes).toContain('updatedBar');
71+
expect(updatedNotes).toContain(
72+
'updatedFoo',
73+
`Expected notes to contain 'updatedFoo', but found ${updatedNotes}`,
74+
);
75+
expect(updatedNotes).toContain(
76+
'updatedBar',
77+
`Expected notes to contain 'updatedBar', but found ${updatedNotes}`,
78+
);
10979
},
11080
);
11181
},
11282
{timeout: 20_000},
11383
);
84+
11485
it(
11586
'should bulk delete organizations',
11687
async () => {
117-
await executeJob(
88+
await jobs.run(
11889
async () => {
11990
const ids = testOrganizations.map((org) => org.id);
12091
return client.organizations.bulkDelete(ids);
12192
},
12293
async (finalDeleteJobResults) => {
123-
// Assuming the deletion job also returns the IDs of the deleted items
12494
const deletedOrgIDs = finalDeleteJobResults.results.map(
12595
(org) => org.id,
12696
);
127-
128-
// For validation, we try fetching them. If they're gone, they should return null or throw a not found error
12997
for (const orgId of deletedOrgIDs) {
13098
try {
13199
const orgDetail = await client.organizations.show(orgId);
132-
expect(orgDetail).toBeNull();
100+
expect(orgDetail).toBeNull(
101+
`Expected organization with ID ${orgId} to be deleted, but it was found.`,
102+
);
133103
} catch (error) {
134-
// Assuming 404 or similar status for not found entities
135-
expect(error.message).toContain('Item not found');
104+
expect(error.message).toContain(
105+
'Item not found',
106+
`Expected 'Item not found' error, but got: ${error.message}`,
107+
);
136108
}
137109
}
138110
},

test/organizations.test.js

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
/* eslint-disable camelcase */
2-
import process from 'node:process';
32
import crypto from 'node:crypto';
43
import dotenv from 'dotenv';
5-
import {/* afterAll, */ describe, expect, it} from 'vitest';
6-
import {createClient} from '../src/index.js';
4+
import {describe, expect, it} from 'vitest';
5+
import {setupClient, generateOrganizationName} from './setup.js';
76

87
dotenv.config();
98

10-
const username = process.env.ZENDESK_USERNAME;
11-
const subdomain = process.env.ZENDESK_SUBDOMAIN;
12-
const token = process.env.ZENDESK_TOKEN;
13-
149
describe('Zendesk Client Organizations', () => {
15-
const id = crypto.randomBytes(16).toString('hex');
16-
const organizationName = `Test Organization ${id}`;
10+
const organizationName = generateOrganizationName();
1711
const randomExternalID = crypto.randomInt(1, 1000);
1812
let testOrganization = {
1913
url: '',
@@ -32,17 +26,6 @@ describe('Zendesk Client Organizations', () => {
3226
organization_fields: {},
3327
};
3428

35-
// Const testOrganizations = []; // Holds all created test organizations
36-
37-
// function generateOrganizationName() {
38-
// const id = crypto.randomBytes(16).toString('hex');
39-
// return `Test Organization ${id}`;
40-
// }
41-
42-
const setupClient = (config) => {
43-
return createClient({username, subdomain, token, ...config});
44-
};
45-
4629
const client = setupClient();
4730

4831
it('should successfully create a new organization with the expected name', async () => {
@@ -93,42 +76,6 @@ describe('Zendesk Client Organizations', () => {
9376
expect(Array.isArray(organizations)).toBe(true);
9477
});
9578

96-
// These need to use job status, which is not yet tested
97-
// it('should create multiple organizations', async () => {
98-
// const organizationName1 = generateOrganizationName();
99-
// const organizationName2 = generateOrganizationName();
100-
101-
// const {result} = await client.organizations.createMany({
102-
// organizations: [{name: organizationName1}, {name: organizationName2}],
103-
// });
104-
// console.dir(results)
105-
106-
// const orgNames = organizations.map((org) => org.name);
107-
// expect(orgNames).toContain(organizationName1);
108-
// expect(orgNames).toContain(organizationName2);
109-
110-
// // Storing the ids for cleanup later
111-
// for (const org of organizations) {
112-
// testOrganizations.push(org);
113-
// }
114-
// });
115-
116-
// it('should update multiple organizations', async () => {
117-
// const ids = testOrganizations.map((org) => org.id);
118-
119-
// const {results: updatedOrganizations} =
120-
// await client.organizations.updateMany({
121-
// organizations: [
122-
// {id: ids[0], notes: 'updatedFoo'},
123-
// {id: ids[1], notes: 'updatedBar'},
124-
// ],
125-
// });
126-
127-
// const updatedNotes = updatedOrganizations.map((org) => org.notes);
128-
// expect(updatedNotes).toContain('updatedFoo');
129-
// expect(updatedNotes).toContain('updatedBar');
130-
// });
131-
13279
// Delete technically returns an error, but it's a 204 No Content error and ths not thrown
13380
it('should successfully delete the created organization', async () => {
13481
const {result} = await client.organizations.delete(testOrganization.id);
@@ -140,10 +87,4 @@ describe('Zendesk Client Organizations', () => {
14087
client.organizations.delete(testOrganization.id),
14188
).rejects.toThrowError('Item not found');
14289
});
143-
144-
// AfterAll(async () => {
145-
// for (const org of testOrganizations) {
146-
// await client.organizations.delete(org.id);
147-
// }
148-
// });
14990
});

test/pagination.test.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
1-
import process from 'node:process';
21
import crypto from 'node:crypto';
32
import dotenv from 'dotenv';
43
import {beforeAll, afterAll, describe, expect, it} from 'vitest';
5-
import {createClient} from '../src/index.js';
4+
import {setupClient} from './setup.js';
65

76
dotenv.config();
87

9-
const {ZENDESK_USERNAME, ZENDESK_SUBDOMAIN, ZENDESK_TOKEN} = process.env;
10-
118
describe('Zendesk Client Pagination', () => {
129
const testOrganizations = [];
1310

1411
const uniqueOrgName = () =>
1512
`Test Organization ${crypto.randomBytes(16).toString('hex')}`;
1613

17-
const setupClient = (config = {}) =>
18-
createClient({
19-
username: ZENDESK_USERNAME,
20-
subdomain: ZENDESK_SUBDOMAIN,
21-
token: ZENDESK_TOKEN,
22-
...config,
23-
});
24-
2514
const defaultClient = setupClient();
2615

2716
async function createTestOrganization() {

test/setup.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Setup.js
2+
3+
import process from 'node:process';
4+
import crypto from 'node:crypto';
5+
import dotenv from 'dotenv';
6+
import {createClient} from '../src/index.js';
7+
8+
dotenv.config();
9+
10+
const username = process.env.ZENDESK_USERNAME;
11+
const subdomain = process.env.ZENDESK_SUBDOMAIN;
12+
const token = process.env.ZENDESK_TOKEN;
13+
14+
export const initializeClient = (config) => {
15+
return createClient({username, subdomain, ...config});
16+
};
17+
18+
export const setupClient = (config = {}) => {
19+
return initializeClient({token, ...config});
20+
};
21+
22+
export const generateOrganizationName = () => {
23+
const id = crypto.randomBytes(16).toString('hex');
24+
return `Test Organization ${id}`;
25+
};
26+
27+
export const generateMultipleOrganizations = (n) => {
28+
const organizations = [];
29+
for (let i = 0; i < n; i++) {
30+
organizations.push({
31+
name: generateOrganizationName(),
32+
});
33+
}
34+
35+
return organizations;
36+
};

0 commit comments

Comments
 (0)