Skip to content

Commit

Permalink
Implement get_completed_items
Browse files Browse the repository at this point in the history
  • Loading branch information
jkawamoto committed Aug 10, 2023
1 parent bc8304b commit 5a37ce4
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 2 deletions.
12 changes: 12 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DEFAULT_COLLABORATORS_RESPONSE,
DEFAULT_COMMENT_RESPONSE,
DEFAULT_COMMENTS_RESPONSE,
DEFAULT_COMPLETED_ITEMS_RESPONSE,
DEFAULT_LABEL_RESPONSE,
DEFAULT_LABELS_RESPONSE,
DEFAULT_PROJECT_RESPONSE,
Expand All @@ -25,6 +26,7 @@
AuthResult,
Collaborator,
Comment,
CompletedItems,
Label,
Project,
QuickAddResult,
Expand Down Expand Up @@ -177,3 +179,13 @@ def default_auth_response() -> Dict[str, Any]:
@pytest.fixture()
def default_auth_result() -> AuthResult:
return AuthResult.from_dict(DEFAULT_AUTH_RESPONSE)


@pytest.fixture()
def default_completed_items_response() -> dict[str, Any]:
return DEFAULT_COMPLETED_ITEMS_RESPONSE


@pytest.fixture()
def default_completed_items() -> CompletedItems:
return CompletedItems.from_dict(DEFAULT_COMPLETED_ITEMS_RESPONSE)
30 changes: 30 additions & 0 deletions tests/data/test_defaults.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from __future__ import annotations

from typing import Any

REST_API_BASE_URL = "https://api.todoist.com/rest/v2"
Expand Down Expand Up @@ -148,3 +149,32 @@
"access_token": "1234",
"state": "somestate",
}

DEFAULT_ITEM_RESPONSE = {
"id": "2995104339",
"user_id": "2671355",
"project_id": "2203306141",
"content": "Buy Milk",
"description": "",
"priority": 1,
"due": DEFAULT_DUE_RESPONSE,
"child_order": 1,
"day_order": -1,
"collapsed": False,
"labels": ["Food", "Shopping"],
"added_by_uid": "2671355",
"assigned_by_uid": "2671355",
"checked": False,
"is_deleted": False,
"added_at": "2014-09-26T08:25:05.000000Z",
}

DEFAULT_ITEM_COMPLETED_INFO_RESPONSE = {"item_id": "2995104339", "completed_items": 12}

DEFAULT_COMPLETED_ITEMS_RESPONSE = {
"items": [DEFAULT_ITEM_RESPONSE],
"completed_info": [DEFAULT_ITEM_COMPLETED_INFO_RESPONSE],
"total": 22,
"next_cursor": "k85gVI5ZAs8AAAABFoOzAQ",
"has_more": True,
}
64 changes: 64 additions & 0 deletions tests/test_api_items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import Any
from urllib.parse import parse_qs, urlparse

import pytest
import responses

from tests.data.test_defaults import SYNC_API_BASE_URL
from tests.utils.test_utils import assert_auth_header
from todoist_api_python.api import TodoistAPI
from todoist_api_python.api_async import TodoistAPIAsync
from todoist_api_python.endpoints import COMPLETED_ITEMS_ENDPOINT
from todoist_api_python.models import CompletedItems


@pytest.mark.asyncio
async def test_get_completed_items(
todoist_api: TodoistAPI,
todoist_api_async: TodoistAPIAsync,
requests_mock: responses.RequestsMock,
default_completed_items_response: dict[str, Any],
default_completed_items: CompletedItems,
) -> None:
project_id = "1234"
section_id = "5678"
item_id = "90ab"
last_seen_id = "cdef"
limit = 30
cursor = "ghij"

def assert_query(url):
queries = parse_qs(urlparse(url).query)
assert queries.get("project_id") == [project_id]
assert queries.get("section_id") == [section_id]
assert queries.get("item_id") == [item_id]
assert queries.get("last_seen_id") == [last_seen_id]
assert queries.get("limit") == [str(limit)]
assert queries.get("cursor") == [cursor]

expected_endpoint = f"{SYNC_API_BASE_URL}/{COMPLETED_ITEMS_ENDPOINT}"

requests_mock.add(
responses.GET,
expected_endpoint,
json=default_completed_items_response,
status=200,
)

completed_items = todoist_api.get_completed_items(
project_id, section_id, item_id, last_seen_id, limit, cursor
)

assert len(requests_mock.calls) == 1
assert_auth_header(requests_mock.calls[0].request)
assert_query(requests_mock.calls[0].request.url)
assert completed_items == default_completed_items

completed_items = await todoist_api_async.get_completed_items(
project_id, section_id, item_id, last_seen_id, limit, cursor
)

assert len(requests_mock.calls) == 2
assert_auth_header(requests_mock.calls[1].request)
assert_query(requests_mock.calls[1].request.url)
assert completed_items == default_completed_items
90 changes: 90 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
DEFAULT_ATTACHMENT_RESPONSE,
DEFAULT_COLLABORATOR_RESPONSE,
DEFAULT_COMMENT_RESPONSE,
DEFAULT_COMPLETED_ITEMS_RESPONSE,
DEFAULT_DUE_RESPONSE,
DEFAULT_ITEM_COMPLETED_INFO_RESPONSE,
DEFAULT_ITEM_RESPONSE,
DEFAULT_LABEL_RESPONSE,
DEFAULT_PROJECT_RESPONSE,
DEFAULT_SECTION_RESPONSE,
Expand All @@ -17,7 +20,10 @@
AuthResult,
Collaborator,
Comment,
CompletedItems,
Due,
Item,
ItemCompletedInfo,
Label,
Project,
QuickAddResult,
Expand Down Expand Up @@ -280,3 +286,87 @@ def test_auth_result_from_dict():

assert auth_result.access_token == token
assert auth_result.state == state


def test_item_from_dict():
sample_data = dict(DEFAULT_ITEM_RESPONSE)
sample_data.update(unexpected_data)

item = Item.from_dict(sample_data)

assert item.id == "2995104339"
assert item.user_id == "2671355"
assert item.project_id == "2203306141"
assert item.content == "Buy Milk"
assert item.description == ""
assert item.priority == 1
assert item.due.date == DEFAULT_DUE_RESPONSE["date"]
assert item.due.is_recurring == DEFAULT_DUE_RESPONSE["is_recurring"]
assert item.due.string == DEFAULT_DUE_RESPONSE["string"]
assert item.due.datetime == DEFAULT_DUE_RESPONSE["datetime"]
assert item.due.timezone == DEFAULT_DUE_RESPONSE["timezone"]
assert item.parent_id is None
assert item.child_order == 1
assert item.section_id is None
assert item.day_order == -1
assert item.collapsed is False
assert item.labels == ["Food", "Shopping"]
assert item.added_by_uid == "2671355"
assert item.assigned_by_uid == "2671355"
assert item.responsible_uid is None
assert item.checked is False
assert item.is_deleted is False
assert item.sync_id is None
assert item.added_at == "2014-09-26T08:25:05.000000Z"


def test_item_completed_info_from_dict():
sample_data = dict(DEFAULT_ITEM_COMPLETED_INFO_RESPONSE)
sample_data.update(unexpected_data)

info = ItemCompletedInfo.from_dict(sample_data)

assert info.item_id == "2995104339"
assert info.completed_items == 12


def test_completed_items_from_dict():
sample_data = dict(DEFAULT_COMPLETED_ITEMS_RESPONSE)
sample_data.update(unexpected_data)

completed_items = CompletedItems.from_dict(sample_data)

assert completed_items.total == 22
assert completed_items.next_cursor == "k85gVI5ZAs8AAAABFoOzAQ"
assert completed_items.has_more is True
assert len(completed_items.items) == 1
assert completed_items.items[0].id == "2995104339"
assert completed_items.items[0].user_id == "2671355"
assert completed_items.items[0].project_id == "2203306141"
assert completed_items.items[0].content == "Buy Milk"
assert completed_items.items[0].description == ""
assert completed_items.items[0].priority == 1
assert completed_items.items[0].due.date == DEFAULT_DUE_RESPONSE["date"]
assert (
completed_items.items[0].due.is_recurring
== DEFAULT_DUE_RESPONSE["is_recurring"]
)
assert completed_items.items[0].due.string == DEFAULT_DUE_RESPONSE["string"]
assert completed_items.items[0].due.datetime == DEFAULT_DUE_RESPONSE["datetime"]
assert completed_items.items[0].due.timezone == DEFAULT_DUE_RESPONSE["timezone"]
assert completed_items.items[0].parent_id is None
assert completed_items.items[0].child_order == 1
assert completed_items.items[0].section_id is None
assert completed_items.items[0].day_order == -1
assert completed_items.items[0].collapsed is False
assert completed_items.items[0].labels == ["Food", "Shopping"]
assert completed_items.items[0].added_by_uid == "2671355"
assert completed_items.items[0].assigned_by_uid == "2671355"
assert completed_items.items[0].responsible_uid is None
assert completed_items.items[0].checked is False
assert completed_items.items[0].is_deleted is False
assert completed_items.items[0].sync_id is None
assert completed_items.items[0].added_at == "2014-09-26T08:25:05.000000Z"
assert len(completed_items.completed_info) == 1
assert completed_items.completed_info[0].item_id == "2995104339"
assert completed_items.completed_info[0].completed_items == 12
27 changes: 27 additions & 0 deletions todoist_api_python/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from todoist_api_python.endpoints import (
COLLABORATORS_ENDPOINT,
COMMENTS_ENDPOINT,
COMPLETED_ITEMS_ENDPOINT,
LABELS_ENDPOINT,
PROJECTS_ENDPOINT,
QUICK_ADD_ENDPOINT,
Expand All @@ -22,6 +23,7 @@
from todoist_api_python.models import (
Collaborator,
Comment,
CompletedItems,
Label,
Project,
QuickAddResult,
Expand Down Expand Up @@ -207,3 +209,28 @@ def remove_shared_label(self, name: str) -> bool:
endpoint = get_rest_url(SHARED_LABELS_REMOVE_ENDPOINT)
data = {"name": name}
return post(self._session, endpoint, self._token, data=data)

def get_completed_items(
self,
project_id: str | None = None,
section_id: str | None = None,
item_id: str | None = None,
last_seen_id: str | None = None,
limit: int | None = None,
cursor: str | None = None,
) -> CompletedItems:
endpoint = get_sync_url(COMPLETED_ITEMS_ENDPOINT)
completed_items = get(
self._session,
endpoint,
self._token,
{
"project_id": project_id,
"section_id": section_id,
"item_id": item_id,
"last_seen_id": last_seen_id,
"limit": limit,
"cursor": cursor,
},
)
return CompletedItems.from_dict(completed_items)
16 changes: 16 additions & 0 deletions todoist_api_python/api_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from todoist_api_python.models import (
Collaborator,
Comment,
CompletedItems,
Label,
Project,
QuickAddResult,
Expand Down Expand Up @@ -120,3 +121,18 @@ async def rename_shared_label(self, name: str, new_name: str) -> bool:

async def remove_shared_label(self, name: str) -> bool:
return await run_async(lambda: self._api.remove_shared_label(name))

async def get_completed_items(
self,
project_id: str | None = None,
section_id: str | None = None,
item_id: str | None = None,
last_seen_id: str | None = None,
limit: int | None = None,
cursor: str | None = None,
) -> CompletedItems:
return await run_async(
lambda: self._api.get_completed_items(
project_id, section_id, item_id, last_seen_id, limit, cursor
)
)
2 changes: 2 additions & 0 deletions todoist_api_python/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
TOKEN_ENDPOINT = "oauth/access_token"
REVOKE_TOKEN_ENDPOINT = "access_tokens/revoke"

COMPLETED_ITEMS_ENDPOINT = "archive/items"


def get_rest_url(relative_path: str) -> str:
return urljoin(REST_API, relative_path)
Expand Down
Loading

0 comments on commit 5a37ce4

Please sign in to comment.