Skip to content

Commit

Permalink
test restructuring via pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
sigma67 committed Jan 2, 2024
1 parent d1ac87c commit 69dd4a9
Show file tree
Hide file tree
Showing 19 changed files with 858 additions and 619 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,26 @@ jobs:
uses: actions/setup-python@master
with:
python-version: 3.x
- name: Setup PDM
uses: pdm-project/setup-pdm@v3
- name: create-json
uses: jsdaniell/[email protected]
with:
name: "oauth.json"
dir: "tests/"
json: ${{ secrets.OAUTH_JSON }}
- name: Install dependencies
run: pdm install
- name: Generate coverage report
env:
HEADERS_AUTH: ${{ secrets.HEADERS_AUTH }}
TEST_CFG: ${{ secrets.TEST_CFG }}
run: |
pip install -e .
pip install coverage
curl -o tests/test.mp3 https://www.kozco.com/tech/piano2-CoolEdit.mp3
cat <<< "$HEADERS_AUTH" > tests/browser.json
cat <<< "$TEST_CFG" > tests/test.cfg
coverage run
coverage xml
pdm run pytest
pdm run coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ venv*/
build
dist
.pdm-python
.venv
136 changes: 135 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,17 @@ include-package-data=false
[tool.setuptools.package-data]
"*" = ["**.rst", "**.py", "**.mo"]

###############
# DEVELOPMENT #
###############

[tool.pytest.ini_options]
python_functions = "test_*"
testpaths = ["tests"]
addopts = "--verbose --cov"

[tool.coverage.run]
command_line = "-m unittest discover tests"
source = ["ytmusicapi"]

[tool.ruff]
line-length = 110
Expand All @@ -59,4 +68,6 @@ dev = [
'sphinx-rtd-theme',
"ruff>=0.1.9",
"mypy>=1.8.0",
"pytest>=7.4.4",
"pytest-cov>=4.1.0",
]
5 changes: 2 additions & 3 deletions tests/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ Make sure you installed the dev requirements as explained in `CONTRIBUTING.rst <

.. code-block:: bash
cd tests
coverage run -m unittest test.py
pdm run pytest
to generate a coverage report.
to generate a coverage report.
15 changes: 15 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from importlib.metadata import PackageNotFoundError, version

from ytmusicapi.setup import setup, setup_oauth
from ytmusicapi.ytmusic import YTMusic

try:
__version__ = version("ytmusicapi")
except PackageNotFoundError:
# package is not installed
pass

__copyright__ = "Copyright 2023 sigma67"
__license__ = "MIT"
__title__ = "ytmusicapi"
__all__ = ["YTMusic", "setup_oauth", "setup"]
Empty file added tests/auth/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions tests/auth/test_browser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from unittest import mock

import ytmusicapi.setup
from ytmusicapi.setup import main


class TestBrowser:
def test_setup_browser(self, config, browser_filepath: str):
headers = ytmusicapi.setup(browser_filepath, config["auth"]["headers_raw"])
assert len(headers) >= 2
headers_raw = config["auth"]["headers_raw"].split("\n")
with (
mock.patch("sys.argv", ["ytmusicapi", "browser", "--file", browser_filepath]),
mock.patch("builtins.input", side_effect=(headers_raw + [EOFError()])),
):
headers = main()
assert len(headers) >= 2
101 changes: 101 additions & 0 deletions tests/auth/test_oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import json
import time
from pathlib import Path
from typing import Any, Dict
from unittest import mock

import pytest
from requests import Response

from ytmusicapi.auth.types import AuthType
from ytmusicapi.constants import OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET
from ytmusicapi.setup import main
from ytmusicapi.ytmusic import OAuthCredentials, YTMusic


@pytest.fixture(name="blank_code")
def fixture_blank_code() -> Dict[str, Any]:
return {
"device_code": "",
"user_code": "",
"expires_in": 1800,
"interval": 5,
"verification_url": "https://www.google.com/device",
}


@pytest.fixture(name="alt_oauth_credentials")
def fixture_alt_oauth_credentials() -> OAuthCredentials:
return OAuthCredentials(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET)


@pytest.fixture(name="yt_alt_oauth")
def fixture_yt_alt_oauth(browser_filepath: str, alt_oauth_credentials: OAuthCredentials) -> YTMusic:
return YTMusic(browser_filepath, oauth_credentials=alt_oauth_credentials)


class TestOAuth:
@mock.patch("requests.Response.json")
@mock.patch("requests.Session.post")
def test_setup_oauth(self, session_mock, json_mock, oauth_filepath, blank_code, yt_oauth):
session_mock.return_value = Response()
fresh_token = yt_oauth._token.as_dict()
json_mock.side_effect = [blank_code, fresh_token]
with mock.patch("builtins.input", return_value="y"), mock.patch(
"sys.argv", ["ytmusicapi", "oauth", "--file", oauth_filepath]
):
main()
assert Path(oauth_filepath).exists()

json_mock.side_effect = None
with open(oauth_filepath, mode="r", encoding="utf8") as oauth_file:
string_oauth_token = oauth_file.read()

YTMusic(string_oauth_token)

def test_oauth_tokens(self, oauth_filepath: str, yt_oauth: YTMusic):
# ensure instance initialized token
assert yt_oauth._token is not None

# set reference file
with open(oauth_filepath, "r") as f:
first_json = json.load(f)

# pull reference values from underlying token
first_token = yt_oauth._token.access_token
first_expire = yt_oauth._token.expires_at
# make token expire
yt_oauth._token.expires_at = int(time.time())
# check
assert yt_oauth._token.is_expiring
# pull new values, assuming token will be refreshed on access
second_token = yt_oauth._token.access_token
second_expire = yt_oauth._token.expires_at
second_token_inner = yt_oauth._token.access_token
# check it was refreshed
assert first_token != second_token
# check expiration timestamps to confirm
assert second_expire != first_expire
assert second_expire > time.time() + 60
# check token is propagating properly
assert second_token == second_token_inner

with open(oauth_filepath, "r") as f2:
second_json = json.load(f2)

# ensure token is updating local file
assert first_json != second_json

def test_oauth_custom_client(
self, alt_oauth_credentials: OAuthCredentials, oauth_filepath: str, yt_alt_oauth: YTMusic
):
# ensure client works/ignores alt if browser credentials passed as auth
assert yt_alt_oauth.auth_type != AuthType.OAUTH_CUSTOM_CLIENT
with open(oauth_filepath, "r") as f:
token_dict = json.load(f)
# oauth token dict entry and alt
yt_alt_oauth = YTMusic(token_dict, oauth_credentials=alt_oauth_credentials)
assert yt_alt_oauth.auth_type == AuthType.OAUTH_CUSTOM_CLIENT

def test_alt_oauth_request(self, yt_alt_oauth: YTMusic, sample_video):
yt_alt_oauth.get_watch_playlist(sample_video)
Loading

0 comments on commit 69dd4a9

Please sign in to comment.