Skip to content

Commit

Permalink
feat: adds cron tab and handler functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Ajeyakrishna-k committed Dec 11, 2023
1 parent 5c8a942 commit 847c72b
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 9 deletions.
48 changes: 40 additions & 8 deletions src/handlers/scheduledEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { KVNamespace } from '@cloudflare/workers-types';

import { handleConfig } from '../config/config';
import config from '../config/config';
import { NAMESPACE_NAME } from '../constants';
import { env, NicknameUpdateResponseType } from '../types/global.types';
import { updateUserRoles } from '../services/discordBotServices';
import { getMissedUpdatesUsers } from '../services/rdsBackendService';
import { DiscordUserRole, env, NicknameUpdateResponseType } from '../types/global.types';
import { chunks } from '../utils/arrayUtils';
import { generateJwt } from '../utils/generateJwt';

export async function ping(env: env) {
const url = handleConfig(env);
const response = await fetch(`${url.baseUrl}/healthcheck`);
const url = config(env).RDS_BASE_API_URL;
const response = await fetch(`${url}/healthcheck`);
return response;
}

Expand All @@ -27,15 +30,15 @@ export async function callDiscordNicknameBatchUpdate(env: env) {
throw err;
}

const url = handleConfig(env);
const url = config(env).RDS_BASE_API_URL;
let token;
try {
token = await generateJwt(env);
} catch (err) {
console.error(`Error while generating JWT token: ${err}`);
throw err;
}
const response = await fetch(`${url.baseUrl}/discord-actions/nickname/status`, {
const response = await fetch(`${url}/discord-actions/nickname/status`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
Expand All @@ -54,8 +57,6 @@ export async function callDiscordNicknameBatchUpdate(env: env) {
throw new Error("Error while trying to update users' discord nickname");
}

console.log(data);

try {
await namespace.put('DISCORD_NICKNAME_UPDATED_TIME', Date.now().toString());
} catch (err) {
Expand All @@ -64,3 +65,34 @@ export async function callDiscordNicknameBatchUpdate(env: env) {

return data;
}

export const addMissedUpdatesRole = async (env: env) => {
const MAX_ROLE_UPDATE = 25;
try {
let cursor: boolean | string = true;
let index = 25;
while (!!cursor && index > 0) {
const missedUpdatesUsers = await getMissedUpdatesUsers(env);

if (!!missedUpdatesUsers && missedUpdatesUsers.usersToAddRole?.length > 1) {
const discordUserIdRoleIdList: DiscordUserRole[] = missedUpdatesUsers.usersToAddRole.map((userId) => ({
userid: userId,
roleid: config(env).MISSED_UPDATES_ROLE_ID,
}));

const discordUserRoleChunks = chunks(discordUserIdRoleIdList, MAX_ROLE_UPDATE);
for (const discordUserRoleList of discordUserRoleChunks) {
try {
await updateUserRoles(env, discordUserRoleList);
} catch (error) {
console.error('Error occurred while updating discord users', error);
}
}
}
cursor = missedUpdatesUsers?.cursor;
index--;
}
} catch (err) {
console.error('Error while adding missed updates roles');
}
};
27 changes: 27 additions & 0 deletions src/services/discordBotServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import config from '../config/config';
import { DiscordRoleUpdatedList, DiscordUserRole, env } from '../types/global.types';
import { generateDiscordBotJwt } from '../utils/generateJwt';

export const updateUserRoles = async (env: env, payload: DiscordUserRole[]): Promise<DiscordRoleUpdatedList> => {
try {
const url = config(env).DISCORD_BOT_API_URL;
const token = await generateDiscordBotJwt(env);

const response = await env.DISCORD_BOT.fetch(`${url}/roles?action=add-role`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw Error(`Role Update failed with status: ${response.status}`);
}
const data: DiscordRoleUpdatedList = await response.json();
return data;
} catch (error) {
console.error('Error while updating discord user roles');
throw error;
}
};
26 changes: 26 additions & 0 deletions src/services/rdsBackendService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import config from '../config/config';
import { DiscordUserIdList, env } from '../types/global.types';
import { generateJwt } from '../utils/generateJwt';

export const getMissedUpdatesUsers = async (env: env) => {
try {
const url = config(env).RDS_BASE_API_URL;
const token = await generateJwt(env);
const response = await fetch(`${url}/tasks/users/discord?q=status:missed-updates`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Fetch call to get user discord details failed with status: ${response.status}`);
}

const data: DiscordUserIdList = await response.json();
return data;
} catch (error) {
console.error('Error occurrent while fetching discord user details');
throw error;
}
};
34 changes: 34 additions & 0 deletions src/tests/fixtures/missedRoleHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const missedUpdatesUsersMock = {
usersToAddRole: ['user1', 'user2'],
tasks: 10,
missedUpdatesTasks: 5,
cursor: 'some-cursor',
};
export const missedUpdatesUsersMockWithNoUsers = {
usersToAddRole: [],
tasks: 10,
missedUpdatesTasks: 5,
};
export const missedUpdatesUsersMockWithoutCursor = {
usersToAddRole: ['user1', 'user2'],
tasks: 10,
missedUpdatesTasks: 5,
};

export const updateRolesResponseMock = {
userid: 'user1',
roleid: '1',
success: true,
};
export const discordUserRoleMock = [
{ userid: 'user1', roleid: '1' },
{ userid: 'user2', roleid: '2' },
];

export const discordRoleUpdateResult = [
{
userid: '1',
roleid: '2',
success: true,
},
];
67 changes: 67 additions & 0 deletions src/tests/handlers/missedRoleHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { addMissedUpdatesRole } from '../../handlers/scheduledEventHandler';
import { updateUserRoles } from '../../services/discordBotServices';
import { getMissedUpdatesUsers } from '../../services/rdsBackendService';
import {
missedUpdatesUsersMock,
missedUpdatesUsersMockWithNoUsers,
missedUpdatesUsersMockWithoutCursor,
} from '../fixtures/missedRoleHandler';

jest.mock('.../../../../services/rdsBackendService', () => ({
getMissedUpdatesUsers: jest.fn(),
}));
jest.mock('.../../../../services/discordBotServices', () => ({
updateUserRoles: jest.fn(),
}));
describe('addMissedUpdatesRole', () => {
beforeEach(() => {
jest.resetAllMocks();
});
afterEach(() => {
jest.clearAllMocks();
});

it('should call getMissedUpdatesUsers and updateUserRoles when there are users to add role', async () => {
(getMissedUpdatesUsers as jest.Mock)
.mockResolvedValueOnce(missedUpdatesUsersMock)
.mockResolvedValueOnce(missedUpdatesUsersMockWithoutCursor);
await addMissedUpdatesRole({});
expect(getMissedUpdatesUsers).toHaveBeenCalledTimes(2);
expect(updateUserRoles).toHaveBeenCalledTimes(2);
});

it('should not call updateUserRoles when there are no users to add role', async () => {
(getMissedUpdatesUsers as jest.Mock).mockResolvedValueOnce(missedUpdatesUsersMockWithNoUsers);

await addMissedUpdatesRole({});
expect(getMissedUpdatesUsers).toHaveBeenCalledTimes(1);
expect(updateUserRoles).toHaveBeenCalledTimes(0);
});

it('should create chunks of userId and update roles multiple times when count is greater than 25', async () => {
const mockValue: any = { ...missedUpdatesUsersMockWithoutCursor, usersToAddRole: new Array(75).fill('id') };
(getMissedUpdatesUsers as jest.Mock).mockResolvedValueOnce(mockValue);

await addMissedUpdatesRole({});
expect(getMissedUpdatesUsers).toHaveBeenCalledTimes(1);
expect(updateUserRoles).toHaveBeenCalledTimes(3);
});

it('should handle errors', async () => {
(getMissedUpdatesUsers as jest.Mock).mockRejectedValueOnce(new Error('Error fetching missed updates users'));
const consoleSpy = jest.spyOn(console, 'error');
await addMissedUpdatesRole({});
expect(consoleSpy).toHaveBeenCalledWith('Error while adding missed updates roles');
});

it('should continue updating user roles even when a call fails', async () => {
(updateUserRoles as jest.Mock).mockRejectedValueOnce(new Error('Error occurred'));
const consoleSpy = jest.spyOn(console, 'error');
const mockValue: any = { ...missedUpdatesUsersMockWithoutCursor, usersToAddRole: new Array(75).fill('id') };
(getMissedUpdatesUsers as jest.Mock).mockResolvedValueOnce(mockValue);
await addMissedUpdatesRole({});
expect(getMissedUpdatesUsers).toHaveBeenCalledTimes(1);
expect(consoleSpy).toHaveBeenCalledTimes(1);
expect(updateUserRoles).toHaveBeenCalledTimes(3);
});
});
49 changes: 49 additions & 0 deletions src/tests/services/discordBotService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import config from '../../config/config';
import { updateUserRoles } from '../../services/discordBotServices';
import { discordRoleUpdateResult, discordUserRoleMock } from '../fixtures/missedRoleHandler';

jest.mock('../../utils/generateJwt', () => ({
generateDiscordBotJwt: jest.fn().mockResolvedValueOnce('mocked-jwt-token'),
}));
describe('discordBotService', () => {
describe('updateUserRoles', () => {
let fetchSpy: jest.Mock<any, any, any>;
beforeEach(() => {
fetchSpy = jest.fn();
jest.clearAllMocks();
});

it('should make a successful API call and return the expected data', async () => {
fetchSpy.mockResolvedValueOnce({
ok: true,
status: 200,
json: jest.fn().mockResolvedValueOnce(discordRoleUpdateResult),
} as unknown as Response);
const result = await updateUserRoles({ DISCORD_BOT: { fetch: fetchSpy } }, discordUserRoleMock);

expect(fetchSpy).toHaveBeenCalledWith(`${config({}).DISCORD_BOT_API_URL}/roles?action=add-role`, {
method: 'POST',
headers: {
Authorization: 'Bearer mocked-jwt-token',
'Content-Type': 'application/json',
},
body: JSON.stringify(discordUserRoleMock),
});
expect(result).toEqual([...discordRoleUpdateResult]);
});

it('should throw error when api call fails', async () => {
fetchSpy.mockResolvedValueOnce({
ok: false,
status: 400,
} as unknown as Response);
await expect(updateUserRoles({ DISCORD_BOT: { fetch: fetchSpy } }, [])).rejects.toThrow('Role Update failed with status: 400');
});
it('should handle unknown errors', async () => {
const consoleSpy = jest.spyOn(console, 'error');
fetchSpy.mockRejectedValueOnce(new Error('Error occurred'));
await expect(updateUserRoles({ DISCORD_BOT: { fetch: fetchSpy } }, [])).rejects.toThrow('Error occurred');
expect(consoleSpy).toHaveBeenCalledWith('Error while updating discord user roles');
});
});
});
48 changes: 48 additions & 0 deletions src/tests/services/rdsBackendService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import config from '../../config/config';
import { getMissedUpdatesUsers } from '../../services/rdsBackendService';
import { missedUpdatesUsersMock } from '../fixtures/missedRoleHandler';

jest.mock('../../utils/generateJwt', () => ({
generateJwt: jest.fn().mockResolvedValueOnce('mocked-jwt-token'),
}));

describe('rdsBackendService', () => {
describe('updateUserRoles', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should make a successful API call and return the expected data', async () => {
jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: true,
status: 200,
json: jest.fn().mockResolvedValueOnce(missedUpdatesUsersMock),
} as unknown as Response);
const result = await getMissedUpdatesUsers({});

expect(fetch).toHaveBeenCalledWith(`${config({}).RDS_BASE_API_URL}/tasks/users/discord?q=status:missed-updates`, {
method: 'GET',
headers: {
Authorization: 'Bearer mocked-jwt-token',
'Content-Type': 'application/json',
},
});
expect(result).toEqual({ ...missedUpdatesUsersMock });
});

it('should throw error when api call fails', async () => {
jest.spyOn(global, 'fetch').mockResolvedValueOnce({
ok: false,
status: 400,
} as unknown as Response);
await expect(getMissedUpdatesUsers({})).rejects.toThrow('Fetch call to get user discord details failed with status: 400');
});

it('should handle unknown errors', async () => {
const consoleSpy = jest.spyOn(console, 'error');
jest.spyOn(global, 'fetch').mockRejectedValueOnce(new Error('Error occurred'));
await expect(getMissedUpdatesUsers({})).rejects.toThrow('Error occurred');
expect(consoleSpy).toHaveBeenCalledWith('Error occurrent while fetching discord user details');
});
});
});
6 changes: 5 additions & 1 deletion src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { callDiscordNicknameBatchUpdate, ping } from './handlers/scheduledEventHandler';
import { addMissedUpdatesRole, callDiscordNicknameBatchUpdate, ping } from './handlers/scheduledEventHandler';
import { env } from './types/global.types';

const EVERY_4_HOURS = '0 */4 * * *';
const EVERY_6_HOURS = '0 */6 * * *';
const EVERY_12_HOURS = '0 */12 * * *';

export default {
async scheduled(req: ScheduledController, env: env, ctx: ExecutionContext) {
Expand All @@ -12,6 +13,9 @@ export default {
break;
case EVERY_6_HOURS:
return await callDiscordNicknameBatchUpdate(env);
case EVERY_12_HOURS: {
return await addMissedUpdatesRole(env);
}
default:
console.error('Unknown Trigger Value!');
}
Expand Down

0 comments on commit 847c72b

Please sign in to comment.