From 3d00c59fd4148c87faa96e13aaecc9a3322e1631 Mon Sep 17 00:00:00 2001 From: Vadim Vergasov Date: Tue, 16 Mar 2021 19:12:42 +0300 Subject: [PATCH] Added pytest and small fixes --- .github/workflows/codestyle.yml | 7 + .github/workflows/publish.yml | 1 - .github/workflows/test.yml | 36 +++ .gitignore | 17 +- codeforces_api/api_request_maker.py | 2 +- codeforces_api/api_requests.py | 5 +- codeforces_api/types.py | 4 +- codeforces_api/version.py | 2 +- requirements.txt | 6 + tests/conf.py.template | 2 - tests/conftest.py | 29 +++ tests/main.py | 40 ---- tests/test_api.py | 352 ++++++++++++++++++++++++++++ 13 files changed, 449 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 tests/conf.py.template create mode 100644 tests/conftest.py delete mode 100644 tests/main.py create mode 100644 tests/test_api.py diff --git a/.github/workflows/codestyle.yml b/.github/workflows/codestyle.yml index b5eee17..9aaf516 100644 --- a/.github/workflows/codestyle.yml +++ b/.github/workflows/codestyle.yml @@ -17,6 +17,13 @@ jobs: uses: actions/setup-python@v2 with: python-version: "3.9" + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b3524ab..20f82ab 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,6 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip- - ${{ runner.os }}- - name: Install twine run: >- pip install -r requirements.txt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7029368 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: Python package + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Test with pytest + run: | + pytest tests/test_api.py --api_key ${{ secrets.codeforces_api_key }} --api_secret ${{ secrets.codeforces_api_secret }} diff --git a/.gitignore b/.gitignore index 2e868e3..dd3d4f3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,19 @@ !/LICENSE !/*.md !/requirements.txt + !tests/ tests/* -!tests/main.py -!tests/conf.py.template -!/codeforces_api -/codeforces_api/ +!tests/test_api.py +!tests/conftest.py + +!codeforces_api/ +codeforces_api/* +!codeforces_api/__init__.py +!codeforces_api/api_request_maker.py +!codeforces_api/api_requests.py +!codeforces_api/parse_methods.py +!codeforces_api/types.py +!codeforces_api/version.py + !/.github diff --git a/codeforces_api/api_request_maker.py b/codeforces_api/api_request_maker.py index 2ce7256..aa188f2 100644 --- a/codeforces_api/api_request_maker.py +++ b/codeforces_api/api_request_maker.py @@ -86,7 +86,7 @@ def generate_request(self, method_name, **fields): def check_return_code(self, response): """ Checks if a returned response is OK. - + If not OK Exception will be raised will additional info. """ if response["status"] != "OK": diff --git a/codeforces_api/api_requests.py b/codeforces_api/api_requests.py index a6cf857..ba476e7 100644 --- a/codeforces_api/api_requests.py +++ b/codeforces_api/api_requests.py @@ -309,7 +309,7 @@ def user_info(self, handles): Get user.info. handles should be a list of users, up to 10000. - + Returns parsed response from codeforces.com. """ if not isinstance(handles, list): @@ -342,7 +342,7 @@ def user_rated_list(self, active_only=False): def user_rating(self, handle): """ Get user.rating. - + handle should be a string. Returns parsed response from codeforces.com. @@ -377,4 +377,3 @@ def user_status(self, handle, start=-1, count=-1): Submission.de_json(submission) for submission in self._make_request("user.status", **parameters) ] - diff --git a/codeforces_api/types.py b/codeforces_api/types.py index fe511ed..de2ddeb 100644 --- a/codeforces_api/types.py +++ b/codeforces_api/types.py @@ -166,7 +166,7 @@ def __init__( def to_dict(self): return { - "hand;e": self.handle, + "handle": self.handle, "email": self.email, "vk_id": self.vk_id, "open_id": self.open_id, @@ -352,7 +352,7 @@ def de_json(cls, json_string): contest_name = obj["contestName"] handle = obj["handle"] rank = obj["rank"] - rating_update_time_seconds = ["ratingUpdateTimeSeconds"] + rating_update_time_seconds = obj["ratingUpdateTimeSeconds"] old_rating = obj["oldRating"] new_rating = obj["newRating"] return cls( diff --git a/codeforces_api/version.py b/codeforces_api/version.py index 5fa9130..f6bb6f4 100644 --- a/codeforces_api/version.py +++ b/codeforces_api/version.py @@ -1 +1 @@ -__version__ = "2.0.3" +__version__ = "2.0.4" diff --git a/requirements.txt b/requirements.txt index 03929fd..d8e0116 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,7 @@ appdirs==1.4.4 astroid==2.5.1 +atomicwrites==1.4.0 +attrs==20.3.0 black==20.8b1 bleach==3.3.0 certifi==2020.12.5 @@ -9,6 +11,7 @@ colorama==0.4.4 docutils==0.16 idna==2.10 importlib-metadata==3.7.3 +iniconfig==1.1.1 isort==5.7.0 keyring==23.0.0 lazy-object-proxy==1.5.2 @@ -18,9 +21,12 @@ mypy-extensions==0.4.3 packaging==20.9 pathspec==0.8.1 pkginfo==1.7.0 +pluggy==0.13.1 +py==1.10.0 Pygments==2.8.1 pylint==2.7.2 pyparsing==2.4.7 +pytest==6.2.2 pywin32-ctypes==0.2.0 readme-renderer==29.0 regex==2020.11.13 diff --git a/tests/conf.py.template b/tests/conf.py.template deleted file mode 100644 index 1d9686b..0000000 --- a/tests/conf.py.template +++ /dev/null @@ -1,2 +0,0 @@ -API_KEY = "{str}" -API_SECRET = "{str}" diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..57ba6c2 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,29 @@ +""" +Config file for tests +""" +import pytest + + +def pytest_addoption(parser): + parser.addoption( + "--api_key", + action="store", + default="api_key", + help="API key for tests", + ) + parser.addoption( + "--api_secret", + action="store", + default="api_secret", + help="API secret for tests", + ) + + +@pytest.fixture +def api_key(request): + return request.config.getoption("--api_key") + + +@pytest.fixture +def api_secret(request): + return request.config.getoption("--api_secret") diff --git a/tests/main.py b/tests/main.py deleted file mode 100644 index bc4a995..0000000 --- a/tests/main.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Testing module work. -""" - -import time - -import codeforces_api - -import conf - -MAIN = codeforces_api.CodeforcesApi(conf.API_KEY, conf.API_SECRET) - -ANONIM_API = codeforces_api.CodeforcesApi() - -PARSER = codeforces_api.CodeforcesParser() - -COMMENTS = MAIN.blog_entry_comments(74185) -VIEW = MAIN.blog_entry_view(74185) -HACKS = MAIN.contest_hacks(1311) -LIST = MAIN.contest_list() -RATING_CHANGES = MAIN.contest_rating_changes(1311) -time.sleep(1) -STANDINGS = MAIN.contest_standings(1311, handles=["tourist", "VadVergasov"]) -STATUS = MAIN.contest_status(1311) -PROBLEMS = MAIN.problemset_problems() -RECENT_STATUS = MAIN.problemset_recent_status(10) -RECENT_ACTIONS = MAIN.recent_actions() -time.sleep(1) -USER_ENTRIES = MAIN.user_blog_entries("VadVergasov") -FRIENDS = MAIN.user_friends(True) -INFO = MAIN.user_info(["tourist", "VadVergasov"]) -RATINGS = MAIN.user_rated_list(True) -USER_RATING = MAIN.user_rating("VadVergasov") -time.sleep(1) -USER_STATUS = MAIN.user_status("VadVergasov") - -SOLUTION = PARSER.get_solution(1322, 72628149) -TAGS = PARSER.get_tags(1322, "D") - -print("No errors found!") diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..82bde26 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,352 @@ +""" +Testing requests to api. +""" +from codeforces_api.types import BlogEntry, Comment, Party, Problem +from codeforces_api import CodeforcesApi + + +def test_blog_entry_comments(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + comments = api.blog_entry_comments(74291) + for comment in comments: + if comment.id == 584151: + assert comment.creation_time_seconds == 1582795345 + + +def test_blog_entry_view(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + blog_entry = api.blog_entry_view(74291) + assert blog_entry.author_handle == "VadVergasov" + assert blog_entry.original_locale == "ru" + assert blog_entry.id == 74291 + assert blog_entry.title == "

Codeforces API python

" + assert blog_entry.allow_view_history == True + assert blog_entry.tags == [ + "#api", + "api", + "#codeforces", + "#python", + "#python 3", + "python 3", + ] + assert blog_entry.content is None + + +def test_contest_hacks(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + hacks = api.contest_hacks(1311) + for hack in hacks: + if hack.id == 615666: + assert hack.creation_time_seconds == 1582562199 + assert hack.verdict == "INVALID_INPUT" + assert hack.judge_protocol == { + "protocol": "Validator 'validator.exe' returns exit code 3 [FAIL Integer parameter [name=t] equals to 1000, violates the range [1, 100] (stdin, line 1)]", + "manual": "false", + "verdict": "Invalid input", + } + assert hack.hacker.participant_type == "PRACTICE" + assert hack.hacker.ghost == False + assert hack.hacker.members[0].handle == "VietCT" + assert hack.hacker.team_id is None + assert hack.hacker.contest_id == 1311 + assert hack.hacker.room is None + assert hack.hacker.start_time_seconds == 1582554900 + assert hack.defender.members[0].handle == "UMR" + assert hack.defender.participant_type == "OUT_OF_COMPETITION" + assert hack.defender.ghost == False + assert hack.defender.team_id is None + assert hack.defender.contest_id == 1311 + assert hack.defender.room is None + assert hack.defender.start_time_seconds == 1582554900 + assert hack.problem.index == "D" + assert hack.problem.name == "Three Integers" + assert hack.problem.problem_type == "PROGRAMMING" + assert hack.problem.contest_id == 1311 + assert hack.problem.problemset_name is None + assert hack.problem.points is None + assert hack.problem.rating == 2000 + assert hack.problem.tags == ["brute force", "math"] + assert hack.test is None + break + + +def test_contest_list(): + api = CodeforcesApi() + list = api.contest_list() + for contest in list: + if contest.id == 1496: + assert contest.name == "Codeforces Round #706 (Div. 2)" + assert contest.contest_type == "CF" + assert contest.phase == "FINISHED" + assert contest.frozen == False + assert contest.duration_seconds == 7200 + assert contest.start_time_seconds == 1615377900 + assert contest.prepared_by is None + assert contest.website_url is None + assert contest.description is None + assert contest.difficulty is None + assert contest.kind is None + assert contest.icpc_region is None + assert contest.country is None + assert contest.city is None + assert contest.season is None + break + + +def test_contest_rating_changes(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + changes = api.contest_rating_changes(1313) + for change in changes: + if change.handle == "VadVergasov": + assert change.contest_id == 1313 + assert change.contest_name == "Codeforces Round #622 (Div. 2)" + assert change.rank == 2303 + assert change.rating_update_time_seconds == 1582455900 + assert change.old_rating == 1330 + assert change.new_rating == 1381 + break + + +def test_contest_standings(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + standings = api.contest_standings(1313, handles=["VadVergasov"]) + assert standings["contest"].id == 1313 + assert standings["contest"].name == "Codeforces Round #622 (Div. 2)" + assert standings["contest"].contest_type == "CF" + assert standings["contest"].phase == "FINISHED" + assert standings["contest"].frozen == False + assert standings["contest"].duration_seconds == 7200 + assert standings["contest"].start_time_seconds == 1582448700 + assert standings["contest"].prepared_by is None + assert standings["contest"].website_url is None + assert standings["contest"].description is None + assert standings["contest"].difficulty is None + assert standings["contest"].kind is None + assert standings["contest"].icpc_region is None + assert standings["contest"].country is None + assert standings["contest"].city is None + assert standings["contest"].season is None + for problem in standings["problems"]: + if problem.index == "A": + assert problem.name == "Fast Food Restaurant" + assert problem.problem_type == "PROGRAMMING" + assert problem.contest_id == 1313 + assert problem.problemset_name is None + assert problem.points == 500.0 + assert problem.rating == 900 + assert problem.tags == ["brute force", "greedy", "implementation"] + break + for row in standings["rows"]: + if row.party.members[0].handle == "VadVergasov": + assert row.rank == 2303 + assert row.party.participant_type == "CONTESTANT" + assert row.party.ghost == False + assert row.party.team_id is None + assert row.party.contest_id == 1313 + assert row.party.room == 10 + assert row.party.start_time_seconds == 1582448700 + assert row.points == 1124.0 + assert row.penalty == 0 + assert row.successful_hack_count == 0 + assert row.unsuccessful_hack_count == 0 + for problem in [0, 1, 3, 4, 5]: + assert row.problem_results[problem].penalty is None + assert row.problem_results[problem].problem_type == "FINAL" + assert row.problem_results[problem].rejected_attempt_count == 0 + assert row.last_submission_time_seconds is None + assert row.problem_results[0].points == 452.0 + assert row.problem_results[0].best_submission_time_seconds == 1466 + assert row.problem_results[2].points == 672.0 + assert row.problem_results[2].rejected_attempt_count == 2 + assert row.problem_results[2].best_submission_time_seconds == 3439 + for problem in [1, 3, 4, 5]: + assert row.problem_results[problem].points == 0.0 + assert row.problem_results[problem].best_submission_time_seconds is None + assert row.last_submission_time_seconds is None + break + + +def test_contest_status(): + api = CodeforcesApi() + status = api.contest_status(1313) + for row in status: + if row.id == 71660372: + assert row.creation_time_seconds == 1582450166 + assert row.relative_time_seconds == 1466 + assert row.problem.index == "A" + assert row.problem.name == "Fast Food Restaurant" + assert row.problem.problem_type == "PROGRAMMING" + assert row.problem.contest_id == 1313 + assert row.problem.points == 500.0 + assert row.problem.rating == 900 + assert row.problem.tags == ["brute force", "greedy", "implementation"] + assert row.author.members[0].handle == "VadVergasov" + assert row.author.participant_type == "CONTESTANT" + assert row.author.ghost == False + assert row.author.team_id is None + assert row.author.contest_id == 1313 + assert row.author.room == 10 + assert row.author.start_time_seconds == 1582448700 + assert row.programming_language == "GNU C++17" + assert row.testset == "TESTS" + assert row.passed_test_count == 5 + assert row.time_consumed_millis == 171 + assert row.memory_consumed_bytes == 0 + assert row.contest_id == 1313 + assert row.verdict == "OK" + assert row.points is None + + +def test_problemset_problems(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + problemset = api.problemset_problems() + for problem in problemset["problems"]: + if problem.name == "Single Push": + assert problem.index == "A" + assert problem.problem_type == "PROGRAMMING" + assert problem.contest_id == 1253 + assert problem.problemset_name is None + assert problem.points == 500.0 + assert problem.rating == 1000 + assert problem.tags == ["implementation"] + for statistic in problemset["problem_statistics"]: + assert type(statistic.index) is str + assert type(statistic.solved_count) is int + assert type(statistic.contest_id) is int + + +def test_recent_status(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + status = api.problemset_recent_status(1)[0] + assert type(status.id) is int + assert type(status.creation_time_seconds) is int + assert type(status.relative_time_seconds) is int + assert type(status.problem) is Problem + assert type(status.author) is Party + assert type(status.programming_language) is str + assert type(status.testset) is str + assert type(status.passed_test_count) is int + assert type(status.time_consumed_millis) is int + assert type(status.memory_consumed_bytes) is int + assert type(status.contest_id) is int + assert status.verdict is None or type(status.verdict) is str + assert status.points is None or type(status.points) is float + + +def test_recent_actions(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + action = api.recent_actions()[0] + assert type(action.time_seconds) is int + if hasattr(action, "blog_entry"): + assert type(action.blog_entry) is BlogEntry + if hasattr(action, "comment"): + assert type(action.comment) is Comment + + +def test_user_blog_entries(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + entries = api.user_blog_entries("VadVergasov") + for entry in entries: + if entry.id == 74291: + assert entry.author_handle == "VadVergasov" + assert entry.original_locale == "ru" + assert entry.id == 74291 + assert entry.title == "

Codeforces API python

" + assert entry.allow_view_history == True + assert entry.tags == [ + "#api", + "api", + "#codeforces", + "#python", + "#python 3", + "python 3", + ] + assert entry.content is None + + +def test_user_friends(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + friends = api.user_friends() + assert "aropan" in friends or "gepardo" in friends + + +def test_user_info(api_key, api_secret): + api = CodeforcesApi(api_key, api_secret) + info = api.user_info(["VadVergasov", "tourist"]) + for user in info: + assert user.email is None or type(user.email) is str + assert user.open_id is None or type(user.open_id) is str + assert user.first_name is None or type(user.first_name) is str + assert user.last_name is None or type(user.last_name) is str + assert user.country is None or type(user.country) is str + assert user.vk_id is None or type(user.vk_id) is str + assert user.country is None or type(user.country) is str + assert user.city is None or type(user.city) is str + assert user.organization is None or type(user.organization) is str + assert type(user.contribution) is int + assert user.rank is None or type(user.rank) is str + assert user.rating is None or type(user.rating) is int + assert user.max_rank is None or type(user.max_rank) is str + assert user.max_rating is None or type(user.max_rating) is int + assert type(user.last_online) is int + assert type(user.registration_time_seconds) is int + assert type(user.friend_of_count) is int + assert type(user.avatar) is str + assert type(user.title_photo) is str + + +def test_user_rated_list(): + api = CodeforcesApi() + users = api.user_rated_list(True) + for user in users: + assert user.email is None or type(user.email) is str + assert user.open_id is None or type(user.open_id) is str + assert user.first_name is None or type(user.first_name) is str + assert user.last_name is None or type(user.last_name) is str + assert user.country is None or type(user.country) is str + assert user.vk_id is None or type(user.vk_id) is str + assert user.country is None or type(user.country) is str + assert user.city is None or type(user.city) is str + assert user.organization is None or type(user.organization) is str + assert type(user.contribution) is int + assert user.rank is None or type(user.rank) is str + assert user.rating is None or type(user.rating) is int + assert user.max_rank is None or type(user.max_rank) is str + assert user.max_rating is None or type(user.max_rating) is int + assert type(user.last_online) is int + assert type(user.registration_time_seconds) is int + assert type(user.friend_of_count) is int + assert type(user.avatar) is str + assert type(user.title_photo) is str + + +def test_user_rating(): + api = CodeforcesApi() + ratings = api.user_rating("VadVergasov") + for rating in ratings: + assert type(rating.contest_id) is int + assert type(rating.contest_name) is str + assert type(rating.handle) is str + assert type(rating.rank) is int + assert type(rating.rating_update_time_seconds) is int + assert type(rating.old_rating) is int + assert type(rating.new_rating) is int + + +def test_user_status(): + api = CodeforcesApi() + status = api.user_status("VadVergasov") + for row in status: + assert type(row.id) is int + assert type(row.creation_time_seconds) is int + assert type(row.relative_time_seconds) is int + assert type(row.problem) is Problem + assert type(row.author) is Party + assert type(row.programming_language) is str + assert type(row.testset) is str + assert type(row.passed_test_count) is int + assert type(row.time_consumed_millis) is int + assert type(row.memory_consumed_bytes) is int + assert row.contest_id is None or type(row.contest_id) is int + assert row.verdict is None or type(row.verdict) is str + assert row.points is None or type(row.points) is float