Skip to content

Commit

Permalink
Merge pull request #4 from Vendic/fix/PIPE-268-fix-comments-with-wron…
Browse files Browse the repository at this point in the history
…g-extracted-tickets

PIPE-268 - Don't fail on clickup API call exceptions, and expose the tasks for which it failed.
  • Loading branch information
Ryangr0 authored Oct 16, 2024
2 parents ac0aad1 + 734fa76 commit a26f020
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 230 deletions.
234 changes: 177 additions & 57 deletions __tests__/get_tasks.test.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,179 @@
import * as core from '@actions/core'
import * as fs from "fs";
import nock from "nock";
import {expect, test} from '@jest/globals'
import get_tasks from '../src/get_tasks'

test('Test get 2 tasks from Clickup API', async () => {
// Mocks
const failed_mock = jest.spyOn(core, 'setFailed')
const output_mock = jest.spyOn(core, 'setOutput')

const get_reply = JSON.parse(fs.readFileSync(__dirname + '/' + 'get_response.json', 'utf-8'))
nock('https://api.clickup.com')
.persist()
.get('/api/v2/task/ABC-185/?custom_task_ids=true&team_id=123')
.reply(200, get_reply)
nock('https://api.clickup.com')
.persist()
.get('/api/v2/task/DEF-186/?custom_task_ids=true&team_id=123')
.reply(200, get_reply)

await get_tasks()

const expected_output = JSON.stringify([
{
"custom_id": "TEST123",
"description": "Price isn't correct "displayed" in the rich snippets",
"priority": {
"color": "#d8d8d8",
"id": "4",
"orderindex": "4",
"priority": "low"
},
"url": "https://app.clickup.com/t/9hx"
import * as core from '@actions/core';
import { expect, describe, it, beforeEach, jest } from '@jest/globals';
import get_tasks from '../src/get_tasks';
import { Task } from '../src/types';
import {getTask} from "../src/api_calls";

jest.mock('@actions/core');
jest.mock('../src/api_calls');

const mockedCore = core as jest.Mocked<typeof core>;
const mockedGetTask = getTask as jest.MockedFunction<typeof getTask>;

describe('get_tasks', () => {
const defaultTaskIds = ['TASK-1', 'TASK-2', 'INVALID-TASK'];
const defaultResponseFields = ['url', 'description', 'custom_id', 'priority'];
const task1: Task = {
url: 'https://app.clickup.com/t/TASK-1',
description: 'Task "One" description',
custom_id: 'TASK-1',
priority: {
color: '#ff0000',
id: '1',
orderindex: '1',
priority: 'High',
},
{
"custom_id": "TEST123",
"description": "Price isn&#39;t correct &quot;displayed&quot; in the rich snippets",
"priority": {
"color": "#d8d8d8",
"id": "4",
"orderindex": "4",
"priority": "low"
};
const task2: Task = {
url: 'https://app.clickup.com/t/TASK-2',
description: "It's the second task",
custom_id: 'TASK-2',
priority: {
color: '#00ff00',
id: '2',
orderindex: '2',
priority: 'Normal',
},
};

beforeEach(() => {
jest.resetAllMocks();

// Mock core inputs
mockedCore.getInput.mockImplementation((name: string) => {
const inputs: Record<string, string> = {
clickup_token: 'test_token',
clickup_team_id: 'test_team_id',
};
return inputs[name] || '';
});

mockedCore.getMultilineInput.mockImplementation((name: string) => {
const multilineInputs: Record<string, string[]> = {
clickup_custom_task_ids: defaultTaskIds,
response_fields: defaultResponseFields,
};
return multilineInputs[name] || [];
});

mockedCore.getBooleanInput.mockImplementation((name: string) => {
const booleanInputs: Record<string, boolean> = {
convert_quotes: true,
};
return booleanInputs[name] ?? true;
});

// Mock core output functions
jest.spyOn(mockedCore, 'setOutput');
jest.spyOn(mockedCore, 'setFailed');
jest.spyOn(mockedCore, 'info');
jest.spyOn(mockedCore, 'debug');
});

afterEach(() => {
jest.clearAllMocks();
});

it('retrieves tasks and converts quotes when convert_quotes is true', async () => {
// Arrange
mockedGetTask.mockImplementation(async (taskId: string) => {
if (taskId === 'TASK-1') return { ...task1 };
if (taskId === 'TASK-2') return { ...task2 };
const error = new Error('Task not found');
(error as any).response = { status: 404 };
throw error;
});

const expectedTasks = [
{
...task1,
description: 'Task &quot;One&quot; description',
},
{
...task2,
description: 'It&#39;s the second task',
},
"url": "https://app.clickup.com/t/9hx"
}
]);

expect(failed_mock).toHaveBeenCalledTimes(0)
expect(output_mock).toHaveBeenCalledWith('clickup_tasks', expected_output)
})

beforeEach(() => {
process.env['INPUT_CLICKUP_TOKEN'] = 'pk_123'
process.env['INPUT_RESPONSE_FIELDS'] = 'url\ndescription\ncustom_id\npriority'
process.env['INPUT_CLICKUP_CUSTOM_TASK_IDS'] = 'ABC-185\nDEF-186'
process.env['INPUT_CLICKUP_TEAM_ID'] = '123'
process.env['INPUT_CONVERT_QUOTES'] = 'true'
})
];

// Act
await get_tasks();

// Assert
expect(mockedGetTask).toHaveBeenCalledTimes(3);
expect(mockedCore.setOutput).toHaveBeenCalledWith('clickup_tasks', JSON.stringify(expectedTasks));
expect(mockedCore.setOutput).toHaveBeenCalledWith('invalid_tasks', JSON.stringify(['INVALID-TASK']));
expect(mockedCore.setFailed).not.toHaveBeenCalled();
});

it('does not convert quotes when convert_quotes is false', async () => {
// Arrange
mockedCore.getBooleanInput.mockReturnValue(false);

mockedGetTask.mockImplementation(async (taskId: string) => {
if (taskId === 'TASK-1') return { ...task1 };
if (taskId === 'TASK-2') return { ...task2 };
const error = new Error('Task not found');
(error as any).response = { status: 404 };
throw error;
});

const expectedTasks = [task1, task2];

// Act
await get_tasks();

// Assert
expect(mockedGetTask).toHaveBeenCalledTimes(3);
expect(mockedCore.setOutput).toHaveBeenCalledWith('clickup_tasks', JSON.stringify(expectedTasks));
expect(mockedCore.setOutput).toHaveBeenCalledWith('invalid_tasks', JSON.stringify(['INVALID-TASK']));
expect(mockedCore.setFailed).not.toHaveBeenCalled();
});

it('handles empty task IDs gracefully', async () => {
// Arrange
mockedCore.getMultilineInput.mockImplementation((name: string) => {
if (name === 'clickup_custom_task_ids') return [];
return defaultResponseFields;
});

// Act
await get_tasks();

// Assert
expect(mockedGetTask).not.toHaveBeenCalled();
expect(mockedCore.setOutput).toHaveBeenCalledWith('clickup_tasks', JSON.stringify([]));
expect(mockedCore.setOutput).toHaveBeenCalledWith('invalid_tasks', JSON.stringify([]));
expect(mockedCore.setFailed).not.toHaveBeenCalled();
});

it('collects invalid task IDs when getTask throws errors', async () => {
// Arrange
mockedGetTask.mockRejectedValue(new Error('Network Error'));

// Act
await get_tasks();

// Assert
expect(mockedGetTask).toHaveBeenCalledTimes(3);
expect(mockedCore.setOutput).toHaveBeenCalledWith('clickup_tasks', JSON.stringify([]));
expect(mockedCore.setOutput).toHaveBeenCalledWith('invalid_tasks', JSON.stringify(defaultTaskIds));
expect(mockedCore.setFailed).not.toHaveBeenCalled();
});

it('fails the action if an error occurs outside the loop', async () => {
// Arrange
mockedCore.getInput.mockImplementation((name: string) => {
if (name === 'clickup_token') throw new Error('Input required and not supplied: clickup_token');
return '';
});

// Act
await get_tasks();

// Assert
expect(mockedCore.setFailed).toHaveBeenCalledWith(
'Action failed: Error: Input required and not supplied: clickup_token'
);
expect(mockedCore.setOutput).not.toHaveBeenCalled();
});
});
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ inputs:
outputs:
clickup_tasks:
description: JSON object containing the clickup task info
invalid_tasks:
description: JSON object containing the clickup tasks that could not be found
Loading

0 comments on commit a26f020

Please sign in to comment.