Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/TodoistApi.tasks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
ENDPOINT_REST_TASK_REOPEN,
ENDPOINT_REST_TASKS,
ENDPOINT_REST_TASKS_FILTER,
ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE,
ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE,
ENDPOINT_SYNC_QUICK_ADD,
} from './consts/endpoints'
import { setupRestClientMock } from './testUtils/mocks'
Expand Down Expand Up @@ -330,4 +332,102 @@ describe('TodoistApi task endpoints', () => {
await expect(api.getTasksByFilter(DEFAULT_GET_TASKS_BY_FILTER_ARGS)).rejects.toThrow()
})
})

describe('getCompletedTasksByCompletionDate', () => {
const DEFAULT_GET_COMPLETED_TASKS_ARGS = {
since: '2025-01-01T00:00:00Z',
until: '2025-12-31T23:59:59Z',
workspaceId: null,
cursor: null,
limit: 10,
}

test('calls get request with expected url', async () => {
const requestMock = setupRestClientMock({ items: [DEFAULT_TASK], nextCursor: null })
const api = getTarget()

await api.getCompletedTasksByCompletionDate(DEFAULT_GET_COMPLETED_TASKS_ARGS)

expect(requestMock).toBeCalledTimes(1)
expect(requestMock).toBeCalledWith(
'GET',
getSyncBaseUri(),
ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE,
DEFAULT_AUTH_TOKEN,
DEFAULT_GET_COMPLETED_TASKS_ARGS,
)
})

test('returns result from rest client', async () => {
setupRestClientMock({ items: [DEFAULT_TASK], nextCursor: '123' })
const api = getTarget()

const response = await api.getCompletedTasksByCompletionDate(
DEFAULT_GET_COMPLETED_TASKS_ARGS,
)

expect(response).toEqual({
items: [DEFAULT_TASK],
nextCursor: '123',
})
})

test('validates task array in response', async () => {
const invalidTask = { ...DEFAULT_TASK, due: '2020-01-31' }
setupRestClientMock({ items: [invalidTask], nextCursor: null })
const api = getTarget()

await expect(
api.getCompletedTasksByCompletionDate(DEFAULT_GET_COMPLETED_TASKS_ARGS),
).rejects.toThrow()
})
})

describe('getCompletedTasksByDueDate', () => {
const DEFAULT_GET_COMPLETED_TASKS_ARGS = {
since: '2025-01-01T00:00:00Z',
until: '2025-12-31T23:59:59Z',
workspaceId: null,
cursor: null,
limit: 10,
}

test('calls get request with expected url', async () => {
const requestMock = setupRestClientMock({ items: [DEFAULT_TASK], nextCursor: null })
const api = getTarget()

await api.getCompletedTasksByDueDate(DEFAULT_GET_COMPLETED_TASKS_ARGS)

expect(requestMock).toBeCalledTimes(1)
expect(requestMock).toBeCalledWith(
'GET',
getSyncBaseUri(),
ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE,
DEFAULT_AUTH_TOKEN,
DEFAULT_GET_COMPLETED_TASKS_ARGS,
)
})

test('returns result from rest client', async () => {
setupRestClientMock({ items: [DEFAULT_TASK], nextCursor: '456' })
const api = getTarget()

const response = await api.getCompletedTasksByDueDate(DEFAULT_GET_COMPLETED_TASKS_ARGS)

expect(response).toEqual({
items: [DEFAULT_TASK],
nextCursor: '456',
})
})

test('validates task array in response', async () => {
const invalidTask = { ...DEFAULT_TASK, due: '2020-01-31' }
setupRestClientMock({ items: [invalidTask], nextCursor: null })
const api = getTarget()

await expect(
api.getCompletedTasksByDueDate(DEFAULT_GET_COMPLETED_TASKS_ARGS),
).rejects.toThrow()
})
})
})
55 changes: 55 additions & 0 deletions src/TodoistApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,17 @@ import {
GetSharedLabelsResponse,
GetCommentsResponse,
type MoveTaskArgs,
GetCompletedTasksByCompletionDateArgs,
GetCompletedTasksByDueDateArgs,
GetCompletedTasksResponse,
} from './types/requests'
import { request, isSuccess } from './restClient'
import {
getSyncBaseUri,
ENDPOINT_REST_TASKS,
ENDPOINT_REST_TASKS_FILTER,
ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE,
ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE,
ENDPOINT_REST_PROJECTS,
ENDPOINT_SYNC_QUICK_ADD,
ENDPOINT_REST_TASK_CLOSE,
Expand Down Expand Up @@ -182,6 +187,56 @@ export class TodoistApi {
}
}

/**
* Retrieves completed tasks by completion date.
*
* @param args - Parameters for filtering, including required since, until.
* @returns A promise that resolves to a paginated response of completed tasks.
*/
async getCompletedTasksByCompletionDate(
args: GetCompletedTasksByCompletionDateArgs,
): Promise<GetCompletedTasksResponse> {
const {
data: { items, nextCursor },
} = await request<GetCompletedTasksResponse>(
'GET',
this.syncApiBase,
ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE,
this.authToken,
args,
)

return {
items: validateTaskArray(items),
nextCursor,
}
}

/**
* Retrieves completed tasks by due date.
*
* @param args - Parameters for filtering, including required since, until.
* @returns A promise that resolves to a paginated response of completed tasks.
*/
async getCompletedTasksByDueDate(
args: GetCompletedTasksByDueDateArgs,
): Promise<GetCompletedTasksResponse> {
const {
data: { items, nextCursor },
} = await request<GetCompletedTasksResponse>(
'GET',
this.syncApiBase,
ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE,
this.authToken,
args,
)

return {
items: validateTaskArray(items),
nextCursor,
}
}

/**
* Creates a new task with the provided parameters.
*
Expand Down
4 changes: 4 additions & 0 deletions src/consts/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export function getAuthBaseUri(domainBase: string = TODOIST_URI): string {

export const ENDPOINT_REST_TASKS = 'tasks'
export const ENDPOINT_REST_TASKS_FILTER = ENDPOINT_REST_TASKS + '/filter'
export const ENDPOINT_REST_TASKS_COMPLETED_BY_COMPLETION_DATE =
ENDPOINT_REST_TASKS + '/completed/by_completion_date'
export const ENDPOINT_REST_TASKS_COMPLETED_BY_DUE_DATE =
ENDPOINT_REST_TASKS + '/completed/by_due_date'
export const ENDPOINT_REST_PROJECTS = 'projects'
export const ENDPOINT_REST_SECTIONS = 'sections'
export const ENDPOINT_REST_LABELS = 'labels'
Expand Down
44 changes: 44 additions & 0 deletions src/types/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,41 @@ export type GetTasksByFilterArgs = {
limit?: number
}

/**
* Arguments for retrieving completed tasks by completion date.
* @see https://todoist.com/api/v1/docs#tag/Tasks/operation/tasks_completed_by_completion_date_api_v1_tasks_completed_by_completion_date_get
*/
export type GetCompletedTasksByCompletionDateArgs = {
since: string
until: string
workspaceId?: string | null
projectId?: string | null
sectionId?: string | null
parentId?: string | null
filterQuery?: string | null
filterLang?: string | null
cursor?: string | null
limit?: number
publicKey?: string | null
}

/**
* Arguments for retrieving completed tasks by due date.
* @see https://todoist.com/api/v1/docs#tag/Tasks/operation/tasks_completed_by_due_date_api_v1_tasks_completed_by_due_date_get
*/
export type GetCompletedTasksByDueDateArgs = {
since: string
until: string
workspaceId?: string | null
projectId?: string | null
sectionId?: string | null
parentId?: string | null
filterQuery?: string | null
filterLang?: string | null
cursor?: string | null
limit?: number
}

/**
* @see https://todoist.com/api/v1/docs#tag/Tasks/operation/get_tasks_api_v1_tasks_get
*/
Expand All @@ -71,6 +106,15 @@ export type GetTasksResponse = {
nextCursor: string | null
}

/**
* @see https://todoist.com/api/v1/docs#tag/Tasks/operation/tasks_completed_by_due_date_api_v1_tasks_completed_by_due_date_get
* @see https://todoist.com/api/v1/docs#tag/Tasks/operation/tasks_completed_by_completion_date_api_v1_tasks_completed_by_completion_date_get
*/
export type GetCompletedTasksResponse = {
items: Task[]
nextCursor: string | null
}

/**
* Arguments for updating a task.
* @see https://todoist.com/api/v1/docs#tag/Tasks/operation/update_task_api_v1_tasks__task_id__post
Expand Down