diff --git a/AUTHORS.rst b/AUTHORS.rst index 134648c..efe8bc2 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -11,3 +11,4 @@ Contributors ------------ * Chris Pruitt, advice and feedback +* Marcus Rönnbäck \ No newline at end of file diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index d6cb732..413a4d4 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -30,3 +30,5 @@ Use these subsections to denote what chages are made. Delete any sections that a ### Security - in case of vulnerabilities. + + diff --git a/letterboxd/letterboxd.py b/letterboxd/letterboxd.py index 1ee5382..db9264a 100644 --- a/letterboxd/letterboxd.py +++ b/letterboxd/letterboxd.py @@ -14,11 +14,13 @@ from .services.member import Member from .services.search import Search from .services.list import Lists +from .services.log_entry import LogEntry +from .services.comment import Comment # TODO: Write these modules # from .services.comment import Comment -# from .services.log_entry import LogEntry + # from .services.news import News @@ -175,3 +177,53 @@ def create_list(self, list_creation_request): list_creation_request=list_creation_request ) return list_create_response + + def entries(self, log_entry_request): + """ + :param log_entry_request: dict - LogEntriesRequest + :return: services.log_entry.LogEntry object + """ + log_entry = LogEntry(log_entry_request=log_entry_request, api=self.api) + return log_entry + + def entry(self, entry_id): + """ + :param entry_id: string - id + + :return: services.log_entry.LogEntry object + """ + log_entry = LogEntry(entry_id=entry_id, api=self.api) + return log_entry + + def update_entry(self, entry_id, log_entry_request): + """ + :param entry_id: string - id + :param log_entry_request: dict - request + + :return: services.log_entry.LogEntry object + """ + log_entry = LogEntry( + entry_id=entry_id, log_entry_request=log_entry_request, api=self.api + ) + return log_entry + + def update_comment(self, comment_id, comment_request): + """ + :param comment_id: string - id + :param comment_request: dict - request + + :return: services.comment.Comment object + """ + comment = Comment( + comment_id=comment_id, comment_request=comment_request, api=self.api + ) + return comment + + def delete_comment(self, comment_id): + """ + :param comment_id: string - id + + :return: services.comment.Comment object + """ + comment = Comment(comment_id=comment_id, api=self.api) + return comment diff --git a/letterboxd/services/comment.py b/letterboxd/services/comment.py new file mode 100644 index 0000000..bd85732 --- /dev/null +++ b/letterboxd/services/comment.py @@ -0,0 +1,88 @@ +import logging + +logging.getLogger(__name__) + + +class Comment(object): + """ + /comment for the Letterboxd API + """ + + def __init__(self, api, comment_id=None, comment_request=None): + self._api = api + self._comment_id = comment_id + self._comment_request = comment_request + + def update(self, comment_id=None, comment_update_request=None): + """ + [PATCH]/comment/{id} + + Update the message portion of a comment. + Calls to this endpoint must include the access token for an authenticated member. + Comments may only be edited by their owner. + + :param comment_id: string - id + :param comment_update_request: dict - CommentUpdateRequest + + :return: comment_update_response - dict - CommentUpdateResponse + """ + if comment_id is None: + comment_id = self._comment_id + if comment_update_request is None: + comment_update_request = self._comment_request + response = self._api.api_call( + path=f"comment/{comment_id}", method="PATCH", params=comment_update_request, + ) + comment_update_response = response.json() + return comment_update_response + + def delete(self, comment_id=None): + """ + [DELETE]/comment/{id} + + Delete a comment. + Calls to this endpoint must include the access token for an authenticated member. + Comments may be deleted by their owner, or by the owner of the thread to which they are posted. + + :param comment_id: string - id + + :return: bool - Success + """ + if comment_id is None: + comment_id = self._comment_id + response = self._api.api_call( + path=f"comment/{comment_id}", method="DELETE", params={} + ) + if response.status_code == 204: + # 204: Success + return True + else: + return False + + def report(self, comment_id=None, report_comment_request=None): + """ + [PATCH]/comment/{id} + + Update the message portion of a comment. + Calls to this endpoint must include the access token for an authenticated member. + Comments may only be edited by their owner. + + :param comment_id: string - id + :param report_comment_request: dict - ReportCommentRequest + + :return: comment_update_response - dict - CommentUpdateResponse + """ + if comment_id is None: + comment_id = self._comment_id + if report_comment_request is None: + report_comment_request = self._comment_request + response = self._api.api_call( + path=f"comment/{comment_id}/report", + method="PATCH", + params=report_comment_request, + ) + if response.status_code == 204: + # 204: Success + return True + else: + return False diff --git a/letterboxd/services/log_entry.py b/letterboxd/services/log_entry.py new file mode 100644 index 0000000..1c16afe --- /dev/null +++ b/letterboxd/services/log_entry.py @@ -0,0 +1,244 @@ +import logging + +logging.getLogger(__name__) + + +class LogEntry(object): + """ + /log-entries for the Letterboxd API + """ + + def __init__(self, api, entry_id=None, log_entry_request=None): + self._api = api + self._entry_id = entry_id + self._log_entry_request = log_entry_request + + def get(self, log_entry_request=None): + """ + [GET]/log-entries + + A cursored window over the log entries for a film or member. + + :param log_entry_request: dict - LogEntriesRequest + + :return: log_entries_response - dict - LogEntriesResponse + """ + if log_entry_request is None: + log_entry_request = self._log_entry_request + response = self._api.api_call(path="log-entries", params=log_entry_request) + log_entries_response = response.json() + return log_entries_response + + def post(self, log_entry_request=None): + """ + [POST]/log-entries + + Create a log entry. A log entry is either a diary entry (must have a date) or a review (must have review text). + Log entries can be both a diary entry and a review if they satisfy both criteria. + + :param log_entry_request: dict - LogEntryCreationRequest + + :return: log_entry_response - dict - LogEntry + """ + if log_entry_request is None: + log_entry_request = self._log_entry_request + + response = self._api.api_call( + path="log-entries", method="POST", params=log_entry_request + ) + log_entry_response = response.json() + return log_entry_response + + def get_id(self, log_entry_id=None): + """ + [GET]/log-entry/{id} + + Get details about a log entry by ID. + + :param log_entry_id: string - id + + :return: log_entry_response - dict - LogEntry + """ + if log_entry_id is None: + log_entry_id = self._entry_id + response = self._api.api_call(path=f"log-entry/{log_entry_id}") + log_entry_response = response.json() + return log_entry_response + + def update(self, entry_id=None, log_entry_update_request=None): + """ + [PATCH]/log-entry/{id} + + Update a log entry by ID. + Calls to this endpoint must include the access token for an authenticated member + + :param entry_id: string - id + :param log_entry_update_request: dict - LogEntryUpdateRequest + + :return: review_update_response - dict - ReviewUpdateResponse + """ + if entry_id is None: + entry_id = self._entry_id + if log_entry_update_request is None: + log_entry_update_request = self._log_entry_request + response = self._api.api_call( + path=f"log-entry/{entry_id}", + method="PATCH", + params=log_entry_update_request, + ) + log_entry_response = response.json() + return log_entry_response + + def delete(self, entry_id=None): + """ + [DELETE]/log-entry/{id} + + Delete a log entry by ID. + Calls to this endpoint must include the access token for an authenticated member + + :param entry_id: string - id + + :return: bool - Success + """ + if entry_id is None: + entry_id = self._entry_id + response = self._api.api_call( + path=f"log-entry/{entry_id}", method="DELETE", params={} + ) + if response.status_code == 204: + # 204: Success + return True + else: + return False + + def comments(self, entry_id=None, comments_request=None): + """ + [GET]/log-entry/{id}/comments + + A cursored window over the comments for a log entry’s review. + Use the ‘next’ cursor to move through the comments. + + :param entry_id: string - id + :param comments_request: dict - CommentsRequest + + :return: review_comments_response - dict - ReviewCommentsResponse + """ + if entry_id is None: + entry_id = self._entry_id + if comments_request is None: + comments_request = self._log_entry_request + response = self._api.api_call( + path=f"log-entry/{entry_id}/comments", params=comments_request + ) + review_comments_response = response.json() + return review_comments_response + + def create_comment(self, entry_id=None, comment_creation_request=None): + """ + [POST]/log-entry/{id}/comments + + Create a comment on a review. + Calls to this endpoint must include the access token for an authenticated member + + :param entry_id: string - id + :param comment_creation_request: dict - CommentCreationRequest + + :return: review_comments_response - dict - ReviewComment + """ + if entry_id is None: + entry_id = self._entry_id + if comment_creation_request is None: + comment_creation_request = self._log_entry_request + response = self._api.api_call( + path=f"log-entry/{entry_id}/comments", + params=comment_creation_request, + method="POST", + ) + review_comments_response = response.json() + return review_comments_response + + def get_relationship(self, entry_id=None): + """ + [GET]/log-entry/{id}/me + + Get details of the authenticated member’s relationship with a log entry’s review by ID. + Calls to this endpoint must include the access token for an authenticated member + + :param entry_id: string - id + + :return: review_relationship - dict - ReviewRelationship + """ + if entry_id is None: + entry_id = self._entry_id + response = self._api.api_call(path=f"log-entry/{entry_id}/me") + review_relationship = response.json() + return review_relationship + + def update_relationship( + self, entry_id=None, review_relationship_update_request=None + ): + """ + [PATCH]/log-entry/{id}/me + + Update the authenticated member’s relationship with a log entry’s review by ID. + Calls to this endpoint must include the access token for an authenticated member + + :param review_relationship_update_request: dict - ReviewRelationshipUpdateRequest + :param entry_id: string - id + + :return: review_relationship - dict - ReviewRelationshipUpdateResponse + """ + if entry_id is None: + entry_id = self._entry_id + if review_relationship_update_request is None: + review_relationship_update_request = self._log_entry_request + response = self._api.api_call( + path=f"log-entry/{entry_id}/me", + params=review_relationship_update_request, + method="PATCH", + ) + review_relationship = response.json() + return review_relationship + + def report(self, entry_id=None, report_review_request=None): + """ + [POST]/log-entry/{id}/report + + Report a log entry’s review by ID. + Calls to this endpoint must include the access token for an authenticated member + + :param report_review_request: dict - ReportReviewRequest + :param entry_id: string - id + + :return: :return: bool - Success + """ + if entry_id is None: + entry_id = self._entry_id + if report_review_request is None: + report_review_request = self._log_entry_request + response = self._api.api_call( + path=f"log-entry/{entry_id}/report", + params=report_review_request, + method="POST", + ) + if response.status_code == 204: + # 204: Success + return True + else: + return False + + def statistics(self, entry_id=None): + """ + [GET]/log-entry/{id}/statistics + + Get statistical data about a log-entry’s review by ID. + + :param entry_id: string - id + + :return: review_statistics_response - dict - ReviewStatistics + """ + if entry_id is None: + entry_id = self._entry_id + response = self._api.api_call(path=f"log-entry/{entry_id}/statistics") + review_statistics_response = response.json() + return review_statistics_response diff --git a/requirements.txt b/requirements.txt index b450057..65d7ffc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests==2.23.0 +requests>=2.25.1 diff --git a/requirements_dev.txt b/requirements_dev.txt index 501db8b..05f6016 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,13 +1,13 @@ -pip==20.1 -black==19.10b0 +pip==21.0.1 +black==20.8b1 bumpversion==0.5.3 -coverage==5.1 -tox==3.15.0 -wheel==0.34.2 -watchdog==0.10.2 -flake8==3.7.9 -Sphinx==3.0.3 -twine==3.1.1 +coverage==5.5 +tox==3.23.0 +wheel==0.36.2 +watchdog==2.0.2 +flake8==3.9.0 +Sphinx==3.5.3 +twine==3.4.1 -pytest==3.6.3 -pytest-runner==5.2 +pytest==6.2.3 +pytest-runner==5.3.0 diff --git a/tests/letterboxd_definitions.py b/tests/letterboxd_definitions.py index 7c53255..30fd8ef 100644 --- a/tests/letterboxd_definitions.py +++ b/tests/letterboxd_definitions.py @@ -5,7 +5,6 @@ from _pytest.fixtures import fixture - # ------------------------- # Film-collection # ------------------------- @@ -254,7 +253,7 @@ def list_comment_keys(): """ ListComment definition - Optional keys: + Optional keys: """ return [ "id", @@ -323,7 +322,67 @@ def list_summary_keys(): """ return ["id", "name", "filmCount", "published", "ranked", "owner", "previewEntries"] - +@fixture def list_create_response_keys(): """Returns list of keys in ListCreateResponse""" return ["data", "messages"] + +# ------------------------- +# Log-Entries / Log-entry +# ------------------------- + +@fixture +def log_entries_response_keys(): + """Returns list of keys in LogEntriesResponse + Optional keys: "next" - cursor + """ + return ["items"] + +@fixture +def log_entry_keys(): + """Returns list of keys in LogEntry""" + return ["id", "name", "owner", "film", "diaryDetails", "review", "tags", "tags2", "whenCreated", "whenUpdated", "like", "commentable", "links"] + +@fixture +def review_update_response_keys(): + """Returns list of keys in ReviewUpdateResponse""" + return ["data", "messages"] + +@fixture +def review_comments_response_keys(): + """Returns list of keys in ReviewCommentsResponse + Optional keys: "next" - cursor + """ + return ["items"] + +@fixture +def review_comment_keys(): + """Returns list of keys in ReviewComment""" + return ["id", "member", "whenCreated", "whenUpdated", "commentLbml", "removedByAdmin", "removedByContentOwner", "deleted", "blocked", "blockedByOwner", "editableWindowExpiresIn", "review", "comment"] + +@fixture +def review_relationship_keys(): + """Returns list of keys in ReviewRelationship""" + return ["liked", "subscribed", "subscriptionState", "commentThreadState"] + +@fixture +def review_relationship_update_response_keys(): + """Returns list of keys in ReviewRelationshipUpdateResponse""" + return ["data", "messages"] + +@fixture +def review_statistics_keys(): + """Returns list of keys in ReviewStatistics""" + return ["logEntry", "counts"] + +# ------------------------- +# Comment +# ------------------------- + +@fixture +def comment_update_response_keys(): + """Returns list of keys in CommentUpdateResponse""" + return ["data", "messages"] + + + diff --git a/tests/test_auth.py b/tests/test_auth.py index e8cf9b3..377635e 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -5,7 +5,8 @@ import letterboxd from letterboxd.services.auth import Authentication - +from tests.letterboxd_definitions import * +from tests.test_letterboxd import load_user_pass logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) @@ -15,7 +16,7 @@ def test_forgotten_password_request(): status_code = Authentication.forgotten_password_request( api=lbxd.api, forgotten_password_request={"emailAddress": "user@example.com"} ) - assert status_code is 204 + assert status_code == 204 def test_username_check(): diff --git a/tests/test_comment.py b/tests/test_comment.py new file mode 100644 index 0000000..63695ce --- /dev/null +++ b/tests/test_comment.py @@ -0,0 +1,72 @@ +import logging + + +import letterboxd +from tests.letterboxd_definitions import * +from tests.test_letterboxd import load_user_pass +logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) + + +def test_create_edit_delete_comment(load_user_pass, review_comment_keys, comment_update_response_keys): + # commenting requires authentication + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + + # create a entry to test comments on + log_entry_creation_request = { + "filmId": "29ho", # Cars + "diaryDetails": { + "diaryDate": "2020-07-20" + }, + "review": { + "text": "API_TEST - IGNORE", + "containsSpoilers": False + } + } + entry_instance = lbxd.entries(log_entry_creation_request) + log_entry = entry_instance.post() # Not testing this as it is not the meaning of this test + + # set entry_id to refer to this entry so that we only work inside it. + entry_id = log_entry["id"] + + # post comment on entry + comment_creation_request = { + "comment": "This is just an API-test, no need to get exited!122!!!" + } + create_comment_instance = lbxd.update_entry(entry_id=entry_id, log_entry_request=comment_creation_request) + review_comment = create_comment_instance.create_comment() + assert isinstance(review_comment, dict) + logging.debug(f"review_comment: {review_comment}") + logging.debug(f"review_comment.keys(): {review_comment.keys()}") + assert set(review_comment_keys).issubset(review_comment.keys()), "All keys should be in Keys." + + # set comment_id to refer to this entry so that we only work on it. + comment_id = review_comment["id"] + + # edit the text of the comment + comment_update_request = { + "comment": "This comment was updated using the API" + } + comment_update_instance = lbxd.update_comment(comment_id=comment_id, comment_request=comment_update_request) + comment_update_response = comment_update_instance.update() + assert isinstance(comment_update_response, dict) + logging.debug(f"comment_update_response: {comment_update_response}") + logging.debug(f"comment_update_response.keys(): {comment_update_response.keys()}") + assert set(comment_update_response_keys).issubset(comment_update_response.keys()), "All keys should be in Keys." + + # delete the comment + comment_deletion_instance = lbxd.delete_comment(comment_id=comment_id) + success = comment_deletion_instance.delete() + logging.debug(f"success: {success}") + assert success is True + + # delete the entry + entry_instance = lbxd.entry(entry_id=entry_id) + delete_log_entry = entry_instance.delete() # Not testing this as it is not the meaning of this test + logging.debug(f"success: {delete_log_entry}") + + + + diff --git a/tests/test_film.py b/tests/test_film.py index 8216083..b254419 100644 --- a/tests/test_film.py +++ b/tests/test_film.py @@ -6,7 +6,6 @@ from letterboxd.services.film import Film from tests.letterboxd_definitions import * from tests.test_letterboxd import load_user_pass - logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) @@ -16,7 +15,7 @@ # ------------------------- -def test_film_details(): +def test_film_details(film_keys): """Tests API call to get a film's details""" # Assume use of environment variables for api key and secret @@ -27,7 +26,7 @@ def test_film_details(): logging.debug(f"film: {film}") assert isinstance(film, dict) assert film["id"] == "2bbs", "The ID should be in the response" - assert set(film_keys()).issubset(film.keys()), "All keys should be in Film" + assert set(film_keys).issubset(film.keys()), "All keys should be in Film" def test_film_availability(): @@ -47,8 +46,8 @@ def test_film_availability(): # ), "All keys should be in FilmAvailability" -def test_film_me(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_film_me(load_user_pass, film_relationship_keys): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() # login lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) @@ -56,7 +55,7 @@ def test_film_me(): film_relationship = film_instance.me() logging.debug(f"film_relationship 1: {film_relationship}") assert isinstance(film_relationship, dict) - assert set(film_relationship_keys()).issubset( + assert set(film_relationship_keys).issubset( film_relationship.keys() ), "All keys should be in FilmRelationship, against film with relationship" @@ -64,13 +63,13 @@ def test_film_me(): film_instance = lbxd.film(film_id="Xwa") # Shark Attack 2 film_relationship = film_instance.me() logging.debug(f"film_relationship 2: {film_relationship}") - assert set(film_relationship_keys()).issubset( + assert set(film_relationship_keys).issubset( film_relationship.keys() ), "All keys should be in FilmRelationship, against film with no relationship" -def test_film_patch_me(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_film_patch_me(load_user_pass, film_relationship_update_response_keys, film_relationship_keys): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() # login lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) @@ -85,12 +84,12 @@ def test_film_patch_me(): f"film_relationship_update_response: {film_relationship_update_response}" ) assert isinstance(film_relationship_update_response, dict) - assert set(film_relationship_update_response_keys()).issubset( + assert set(film_relationship_update_response_keys).issubset( film_relationship_update_response.keys() ), "All keys should be in FilmRelationshipUpdateResponse" assert isinstance(film_relationship_update_response["data"], dict) film_relationship = film_relationship_update_response["data"] - assert set(film_relationship_keys()).issubset( + assert set(film_relationship_keys).issubset( film_relationship.keys() ), "All keys should be in FilmRelationship" assert isinstance(film_relationship_update_response["messages"], list) @@ -121,8 +120,8 @@ def test_film_patch_me(): assert isinstance(film_relationship_update_response, dict) -def test_film_members(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_film_members(load_user_pass, member_film_relationships_response_keys, member_summary_keys, film_relationship_keys): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() # login lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) @@ -144,7 +143,7 @@ def test_film_members(): f"member_film_relationships_response.keys(): {member_film_relationships_response.keys()}" ) assert isinstance(member_film_relationships_response, dict) - assert set(member_film_relationships_response_keys()).issubset( + assert set(member_film_relationships_response_keys).issubset( member_film_relationships_response.keys() ), "All keys should be in MemberFilmRelationshipsResponse" assert isinstance(member_film_relationships_response["items"], list) @@ -153,19 +152,19 @@ def test_film_members(): assert isinstance(member_film_relationship["member"], dict) member_summary = member_film_relationship["member"] logging.debug(f"member_summary: {member_summary}") - assert set(member_summary_keys()).issubset( + assert set(member_summary_keys).issubset( member_summary.keys() ), "All keys should be in MemberSummary" assert isinstance(member_film_relationship["relationship"], dict) film_relationship = member_film_relationship["relationship"] logging.debug(f"film_relationship: {film_relationship}") - assert set(film_relationship_keys()).issubset( + assert set(film_relationship_keys).issubset( film_relationship.keys() ), "All keys should be in FilmRelationship" -def test_film_report(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_film_report(load_user_pass): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() # login, even though we don't use this value lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) @@ -178,7 +177,7 @@ def test_film_report(): assert success is True -def test_film_statistics(): +def test_film_statistics(film_statistics_keys): """Tests API call to get a film's statistics""" # Assume use of environment variables for api key and secret @@ -188,13 +187,13 @@ def test_film_statistics(): film_statistics = film_instance.statistics() logging.debug(f"film_statistics: {film_statistics}") assert isinstance(film_statistics, dict) - assert set(film_statistics_keys()).issubset( + assert set(film_statistics_keys).issubset( film_statistics.keys() ), "All keys should be in FilmStatistics" assert film_statistics["film"]["id"] == "2bbs", "The ID should be in the response" -def test_films(): +def test_films(films_response_keys): """ Test API call to /films """ @@ -219,7 +218,7 @@ def test_films(): films_response = films.films(films_request=films_request) logging.debug(f"films_response: {films_response}") assert isinstance(films_response, dict) - assert set(films_response_keys()).issubset( + assert set(films_response_keys).issubset( films_response.keys() ), "All keys should be in FilmsResponse" # Debug print a simple list of the movies @@ -229,27 +228,27 @@ def test_films(): film_num += 1 -def test_films_services(): +def test_films_services(load_user_pass, film_services_response_keys, service_keys): """ Test API call to /films/film-services """ lbxd = Letterboxd() # login, so that we can see all of the services available to this member - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) films = lbxd.films() film_services_response = films.services() logging.debug(f"film_services_response: {film_services_response}") assert isinstance(film_services_response, dict) - assert set(film_services_response_keys()).issubset( + assert set(film_services_response_keys).issubset( film_services_response.keys() ), "All keys should be in FilmServicesResponse" assert isinstance(film_services_response["items"], list) service = film_services_response["items"][0] - assert set(service_keys()).issubset(service.keys()), "All keys should be in Service" + assert set(service_keys).issubset(service.keys()), "All keys should be in Service" -def test_films_genres(): +def test_films_genres(genres_response_keys, genre_keys): """ Test API call to /films/genres """ @@ -258,21 +257,21 @@ def test_films_genres(): genres_response = films.genres() logging.debug(f"genres_response: {genres_response}") assert isinstance(genres_response, dict) - assert set(genres_response_keys()).issubset( + assert set(genres_response_keys).issubset( genres_response.keys() ), "All keys should be in GenresResponse" genre = genres_response["items"][0] logging.debug(f"genre: {genre}") - assert set(genre_keys()).issubset(genre.keys()), "All keys should be in the Genre" + assert set(genre_keys).issubset(genre.keys()), "All keys should be in the Genre" -def test_film_collection(): +def test_film_collection(load_user_pass, film_summary_keys, link_keys): """ Test API call to /film-collection/{id} """ lbxd = letterboxd.new() # Log in as a user - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) film_collection_id = "Nb" # Indiana Jones film_collection_request = { @@ -296,10 +295,10 @@ def test_film_collection(): film_summary = film_collection["films"][0] logging.debug(f"film_summary: {film_summary}") assert isinstance(film_summary, dict) - assert set(film_summary_keys()).issubset( + assert set(film_summary_keys).issubset( film_summary.keys() ), "All keys should be in FilmSummary" link = film_collection["links"][0] logging.debug(f"link: {link}") assert isinstance(link, dict) - assert set(link_keys()).issubset(link.keys()), "All keys should be in Link" + assert set(link_keys).issubset(link.keys()), "All keys should be in Link" diff --git a/tests/test_letterboxd.py b/tests/test_letterboxd.py index b457976..817e6c8 100644 --- a/tests/test_letterboxd.py +++ b/tests/test_letterboxd.py @@ -4,6 +4,7 @@ from pytest import fixture from letterboxd.letterboxd import Letterboxd +from tests.letterboxd_definitions import * logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) @@ -18,7 +19,6 @@ def test_letterboxd(): # Letterboxd API Definitions # ------------------------- - @fixture def load_user_pass(): """ diff --git a/tests/test_list.py b/tests/test_list.py index 24d5ac1..f3bec4d 100644 --- a/tests/test_list.py +++ b/tests/test_list.py @@ -7,7 +7,6 @@ import letterboxd from tests.letterboxd_definitions import * from tests.test_letterboxd import load_user_pass - logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) @@ -16,7 +15,7 @@ # ------------------------- -def test_list_details(): +def test_list_details(list_keys): """ /list/{id} """ @@ -27,16 +26,16 @@ def test_list_details(): assert isinstance(list_details, dict) logging.debug(f"list_details: {list_details}") logging.debug(f"list_details.keys(): {list_details.keys()}") - assert set(list_keys()).issubset(list_details.keys()), "All keys should be in Keys." + assert set(list_keys).issubset(list_details.keys()), "All keys should be in Keys." -def test_list_update(): +def test_list_update(load_user_pass, list_update_response_keys): """ /list/{id} [PATCH] """ lbxd = letterboxd.new() # Login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) list_id = "1UxUo" # test_optical: "These are Twenty Films to Test With" rand_int_str = ( @@ -70,12 +69,12 @@ def test_list_update(): assert isinstance(list_update_response, dict) logging.debug(f"list_update_response.keys(): {list_update_response.keys()}") - assert set(list_update_response_keys()).issubset( + assert set(list_update_response_keys).issubset( list_update_response.keys() ), "All keys should be in ListUpdateResponse" -def test_list_comments(): +def test_list_comments(list_comments_response_keys, list_comment_keys): """ /list/{id}/comments """ @@ -86,7 +85,7 @@ def test_list_comments(): list_comments_response = list.comments(comments_request=comments_request) assert isinstance(list_comments_response, dict) logging.debug(f"list_comments_response: {list_comments_response}") - assert set(list_comments_response_keys()).issubset( + assert set(list_comments_response_keys).issubset( list_comments_response.keys() ), "All keys should be in ListCommentsResponse" @@ -94,15 +93,15 @@ def test_list_comments(): assert list_comment["list"]["id"] == list_id logging.debug(f"list_comment: {list_comment}") logging.debug(f"list_comment.keys(): {list_comment.keys()}") - assert set(list_comment_keys()).issubset( + assert set(list_comment_keys).issubset( list_comment.keys() ), "All keys should be in ListComment" -def test_list_create_comment(): +def test_list_create_comment(load_user_pass, list_comment_keys): lbxd = letterboxd.new() # Login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) comment_creation_request = { "comment": "API TEST - IGNORE\n\nHere's a comment with some strong language, emphasized, bold, italics, a link, and here's a quote:\n\n
We have nothing to fear but fear itself. And werewolves.\n\n—FDR, Werewolf Hunter
" @@ -116,13 +115,13 @@ def test_list_create_comment(): logging.debug(f"list_comment: {list_comment}") assert isinstance(list_comment, dict) - logging.debug(f"list_comment.keys(): {list_comment.keys()}") - assert set(list_comment_keys()).issubset( + logging.debug(f"list_comment.keys(): {list_comment.keys}") + assert set(list_comment_keys).issubset( list_comment.keys() ), "All keys should be in ListComment" -def test_list_entries(): +def test_list_entries(list_entries_response_keys): lbxd = letterboxd.new() list_id = "1UxUo" # test_optical: "These are Twenty Films to Test With" list_entries_request = { @@ -138,15 +137,15 @@ def test_list_entries(): assert isinstance(list_entries_response, dict) logging.debug(f"list_entries_response.keys(): {list_entries_response.keys()}") - assert set(list_entries_response_keys()).issubset( + assert set(list_entries_response_keys).issubset( list_entries_response.keys() ), "All keys should be in ListEntriesResponse." -def test_list_me(): +def test_list_me(load_user_pass, list_relationship_keys): lbxd = letterboxd.new() # Login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) list_id = "1UxUo" # test_optical: "These are Twenty Films to Test With" @@ -156,15 +155,15 @@ def test_list_me(): assert isinstance(list_relationship, dict) logging.debug(f"list_relationship.keys(): {list_relationship.keys()}") - assert set(list_relationship_keys()).issubset( + assert set(list_relationship_keys).issubset( list_relationship.keys() ), "All keys should be in ListRelationship." -def test_list_me_update(): +def test_list_me_update(load_user_pass, list_relationship_update_response_keys): lbxd = letterboxd.new() # Login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) list_id = "j4lQ" # bobtiki: "Personal Top 100" list_relationship_update_request = {"liked": True, "subscribed": True} @@ -181,13 +180,13 @@ def test_list_me_update(): logging.debug( f"list_relationship_update_response.keys(): {list_relationship_update_response.keys()}" ) - assert set(list_relationship_update_response_keys()).issubset( + assert set(list_relationship_update_response_keys).issubset( list_relationship_update_response.keys() ), "All keys should be in ListRelationshipUpdateResponse" -def test_list_report(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_list_report(load_user_pass): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = letterboxd.new() lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) list_id = "1UxUo" # test_optical: "These are Twenty Films to Test With" @@ -199,7 +198,7 @@ def test_list_report(): assert success is True -def test_list_statistics(): +def test_list_statistics(list_statistics_keys): lbxd = letterboxd.new() list_id = "1UxUo" # test_optical: "These are Twenty Films to Test With" @@ -209,7 +208,7 @@ def test_list_statistics(): assert isinstance(list_statistics, dict) logging.debug(f"list_statistics.keys(): {list_statistics.keys()}") - assert set(list_statistics_keys()).issubset( + assert set(list_statistics_keys).issubset( list_statistics.keys() ), "All keys should be in ListStatistics" assert list_statistics["list"]["id"] == list_id @@ -220,7 +219,7 @@ def test_list_statistics(): # ------------------------- -def test_lists(): +def test_lists(load_user_pass, lists_response_keys, list_summary_keys): """ /lists @@ -228,7 +227,7 @@ def test_lists(): """ lbxd = letterboxd.new() # Login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) lists_request = { "perPage": 20, @@ -248,7 +247,7 @@ def test_lists(): assert isinstance(lists, dict) # logging.debug("-------------------------\nlists:") # logging.debug(pprint.pformat(lists)) - assert set(lists_response_keys()).issubset( + assert set(lists_response_keys).issubset( lists.keys() ), "All keys should be in the lists_response." @@ -256,18 +255,18 @@ def test_lists(): assert isinstance(list_summary, dict) # logging.debug("-------------------------\nlist_summary:") # logging.debug(pprint.pformat(list_summary)) - assert set(list_summary_keys()).issubset( + assert set(list_summary_keys).issubset( list_summary.keys() ), "All keys should be in list_summary." -def test_create_and_delete_list(): +def test_create_and_delete_list(load_user_pass, list_create_response_keys): """ /lists [POST] """ lbxd = letterboxd.new() # Login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) list_creation_request = { "published": True, @@ -297,7 +296,7 @@ def test_create_and_delete_list(): logging.debug("-------------------------\nlist_create_response:") logging.debug(pprint.pformat(list_create_response)) logging.debug(f"list_create_response.keys() {list_create_response.keys()}") - assert set(list_create_response_keys()).issubset( + assert set(list_create_response_keys).issubset( list_create_response.keys() ), "All keys should be in the lists_response." diff --git a/tests/test_log_entry.py b/tests/test_log_entry.py new file mode 100644 index 0000000..ba40341 --- /dev/null +++ b/tests/test_log_entry.py @@ -0,0 +1,167 @@ +import logging +import letterboxd +from tests.letterboxd_definitions import * +from tests.test_letterboxd import load_user_pass +logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) + + +# I have combined some of the tests into one in order to be able to delete the entry after. +# This way there is no data left by the tests on letterboxd. + +def test_create_edit_comment_delete_entry(load_user_pass, log_entries_response_keys, log_entry_keys, + review_update_response_keys, review_comment_keys, review_comments_response_keys): + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + + # create a entry + log_entry_creation_request = { + "filmId": "H4Y", # Cars 2 + "diaryDetails": { + "diaryDate": "2020-07-20" + }, + "review": { + "text": "API_TEST - IGNORE", + "containsSpoilers": False + } + } + log_entries_instance = lbxd.entries(log_entry_creation_request) + log_entries_response = log_entries_instance.post(log_entry_creation_request) + assert isinstance(log_entries_response, dict) + logging.debug(f"create_post_response_details: {log_entries_response}") + logging.debug(f"create_post_response_details.keys(): {log_entries_response.keys()}") + assert set(log_entry_keys).issubset(log_entries_response.keys()), "All keys should be in Keys." + + # remember this log-entry + entry_id = log_entries_response["id"] + + # edit log entry + log_entry_update_request = { + "diaryDetails": { + "diaryDate": "2020-07-21", + "rewatch": True + }, + "review": { + "text": "API_TEST - IGNORE" + }, + "rating": 5.0 + } + update_log_entry_instance = lbxd.update_entry(entry_id=entry_id, log_entry_request=log_entry_update_request) + review_update_response = update_log_entry_instance.update() + assert isinstance(review_update_response, dict) + logging.debug(f"review_update_response: {review_update_response}") + logging.debug(f"review_update_response.keys(): {review_update_response.keys()}") + assert set(review_update_response_keys).issubset(review_update_response.keys()), "All keys should be in Keys." + + # comment on entry + comment_creation_request = { + "comment": "This is just an API-test, no need to get exited!122!!!" + } + comment_on_log_entry_instance = lbxd.update_entry(entry_id=entry_id, log_entry_request=comment_creation_request) + review_comment = comment_on_log_entry_instance.create_comment() + assert isinstance(review_comment, dict) + logging.debug(f"review_comment: {review_comment}") + logging.debug(f"review_comment.keys(): {review_comment.keys()}") + assert set(review_comment_keys).issubset(review_comment.keys()), "All keys should be in Keys." + + # get comments on entry + comments_request = { + "perPage": 1, + "includeDeletions": False + } + commments_on_entry_instance = lbxd.update_entry(entry_id=entry_id, log_entry_request=comments_request) + review_comments_response = commments_on_entry_instance.comments() + assert isinstance(review_comments_response, dict) + logging.debug(f"review_comments_response: {review_comments_response}") + logging.debug(f"review_comments_response.keys(): {review_comments_response.keys()}") + assert set(review_comments_response_keys).issubset(review_comments_response.keys()), "All keys should be in Keys." + + # get entry + get_log_entry_instance = lbxd.entry(entry_id=entry_id) + get_log_entry = get_log_entry_instance.get_id() + assert isinstance(get_log_entry, dict) + logging.debug(f"get_log_entry: {get_log_entry}") + logging.debug(f"get_log_entry.keys(): {get_log_entry.keys()}") + assert set(log_entry_keys).issubset(get_log_entry.keys()), "All keys should be in Keys." + + # delete entry + delete_log_entry_instance = lbxd.entry(entry_id=entry_id) + delete_log_entry = delete_log_entry_instance.delete() + logging.debug(f"success: {delete_log_entry}") + assert delete_log_entry is True + + +def test_get_entries(load_user_pass, log_entries_response_keys): + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + + log_entries_request = {} # get random entry + + log_entries_instance = lbxd.entries(log_entries_request) + log_entries_response = log_entries_instance.get() + assert isinstance(log_entries_response, dict) + logging.debug(f"log_entries_response: {log_entries_response}") + logging.debug(f"log_entries_response.keys(): {log_entries_response.keys()}") + assert set(log_entries_response_keys).issubset(log_entries_response.keys()), "All keys should be in Keys." + + +def test_relationship(load_user_pass, review_relationship_keys): + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + review_id = "1g6otb" + log_entry_instance = lbxd.entry(entry_id=review_id) + review_relationship = log_entry_instance.get_relationship() + assert isinstance(review_relationship, dict) + logging.debug(f"review_relationship: {review_relationship}") + logging.debug(f"review_relationship.keys(): {review_relationship.keys()}") + assert set(review_relationship_keys).issubset(review_relationship.keys()), "All keys should be in Keys." + + +def test_change_relationship(load_user_pass, review_relationship_update_response_keys): + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + review_id = "1g6otb" + review_relationship_update_request = { + "liked": False, + "subscribed": False + } + change_relationship_of_entry_instance = lbxd.update_entry(entry_id=review_id, + log_entry_request=review_relationship_update_request) + review_relationship_update_response = change_relationship_of_entry_instance.update_relationship() + assert isinstance(review_relationship_update_response, dict) + logging.debug(f"review_relationship_update_response: {review_relationship_update_response}") + logging.debug(f"review_relationship_update_response.keys(): {review_relationship_update_response.keys()}") + assert set(review_relationship_update_response_keys)\ + .issubset(review_relationship_update_response.keys()), "All keys should be in Keys." + + +def test_report_entry(load_user_pass): + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + review_id = "1g6otb" + report_review_request = {"reason": "Other", "message": "API TEST — IGNORE :)"} + report_entry_instance = lbxd.update_entry(entry_id=review_id, log_entry_request=report_review_request) + report_status = report_entry_instance.report() + assert report_status is True + + +def test_statistics(load_user_pass, review_statistics_keys): + lbxd = letterboxd.new() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass + lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) + review_id = "1g6otb" + statistics_entry_instance = lbxd.entry(entry_id=review_id) + review_statistics = statistics_entry_instance.statistics() + assert isinstance(review_statistics, dict) + logging.debug(f"review_statistics: {review_statistics}") + logging.debug(f"review_statistics.keys(): {review_statistics.keys()}") + assert set(review_statistics_keys).issubset( + review_statistics.keys()), "All keys should be in Keys." + + + diff --git a/tests/test_member.py b/tests/test_member.py index 97c282e..51b45aa 100644 --- a/tests/test_member.py +++ b/tests/test_member.py @@ -5,14 +5,13 @@ from letterboxd.services.member import Member from tests.letterboxd_definitions import * from tests.test_letterboxd import load_user_pass - logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) -def test_member_watchlist(): +def test_member_watchlist(load_user_pass, films_response_keys, film_summary_keys): # set up - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() test_user = lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) # get the watchlist @@ -27,7 +26,7 @@ def test_member_watchlist(): films_response = member.watchlist(watchlist_request=watchlist_request) logging.debug(f"films_response (watchlist): {films_response}") assert isinstance(films_response, dict) - assert set(films_response_keys()).issubset( + assert set(films_response_keys).issubset( films_response.keys() ), "All keys should be in the FilmsResponse" # Test the first movie in the watchlist @@ -35,6 +34,6 @@ def test_member_watchlist(): film_summary = films_response["items"][0] logging.debug(f"film_summary: {film_summary}") logging.debug(f"film_summary.keys(): {film_summary.keys()}") - assert set(film_summary_keys()).issubset( + assert set(film_summary_keys).issubset( film_summary.keys() ), "All keys should be in the FilmSummary" diff --git a/tests/test_search.py b/tests/test_search.py index 2ef1182..a6fddbd 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -4,13 +4,12 @@ from letterboxd.letterboxd import Letterboxd from tests.letterboxd_definitions import * from tests.test_letterboxd import load_user_pass - logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) -def test_search(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_search(load_user_pass, search_response_keys, abstract_search_item_keys): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() search_request = { "perPage": 5, @@ -24,11 +23,11 @@ def test_search(): assert isinstance(search_response, dict) # TODO: test returned keys - assert set(search_response_keys()).issubset( + assert set(search_response_keys).issubset( search_response.keys() ), "All keys should be in SearchResponse" abstract_search_item = search_response["items"][0] - assert set(abstract_search_item_keys()).issubset( + assert set(abstract_search_item_keys).issubset( abstract_search_item.keys() ), "All keys should be in the AbstractSearchItem" diff --git a/tests/test_user.py b/tests/test_user.py index 16bb015..d0351e4 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -5,13 +5,12 @@ from letterboxd.user import User from tests.letterboxd_definitions import * from tests.test_letterboxd import load_user_pass - logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG) -def test_user_auth(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_user_auth(load_user_pass): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() # make login test_user = lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) @@ -33,8 +32,8 @@ def test_user_auth_bad(): logging.error(e.args) -def test_user_me(): - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() +def test_user_me(load_user_pass): + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() # login test_user = lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) @@ -43,13 +42,13 @@ def test_user_me(): assert isinstance(me_dict, dict) -def test_user_me_update(): +def test_user_me_update(load_user_pass, member_settings_update_response_keys): """ :return: """ # login - LBXD_USERNAME, LBXD_PASSWORD = load_user_pass() + LBXD_USERNAME, LBXD_PASSWORD = load_user_pass lbxd = Letterboxd() test_user = lbxd.user(LBXD_USERNAME, LBXD_PASSWORD) # test @@ -64,7 +63,7 @@ def test_user_me_update(): ) logging.debug(f"member_settings_update_response: {member_settings_update_response}") assert isinstance(member_settings_update_response, dict) - assert set(member_settings_update_response_keys()).issubset( + assert set(member_settings_update_response_keys).issubset( member_settings_update_response.keys() ), "All keys should be in MemberSettingsUpdateResponse"