Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monthly webtests #116

Merged
merged 20 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0304bb9
added yaml file for monthly live test gh action
charlottekostelic Nov 7, 2024
affb3f4
added additional webtest for WorldcatAccessToken
charlottekostelic Nov 7, 2024
6a5137d
updated contributing.md with info about live tests
charlottekostelic Nov 7, 2024
b6f70d2
added fixtures for live tests and monthly checks
charlottekostelic Nov 7, 2024
88031ad
separated live tests into two classes
charlottekostelic Nov 7, 2024
2c9b9fc
renamed, simplified endpoint_params fixture
charlottekostelic Nov 7, 2024
e441d3e
reviewing changes to MetadataSession webtests
charlottekostelic Nov 7, 2024
6f36e5f
moved webtests to separate files and directory
charlottekostelic Nov 7, 2024
c69ed24
created separate conftest for webtests
charlottekostelic Nov 7, 2024
e74743a
fixed typos in conftest docstrings
charlottekostelic Nov 7, 2024
598694e
separated webtests into different classes
charlottekostelic Nov 7, 2024
a5b0fff
updated monthly test schedule
charlottekostelic Nov 7, 2024
78068fe
added additional type annotations to conftest.py
charlottekostelic Nov 7, 2024
d840dee
updated live_keys fixture for posix and windows
charlottekostelic Nov 7, 2024
59cf58a
updated triggers in monthly-api-tests.yaml
charlottekostelic Nov 8, 2024
d2bac7e
moved live_keys fixture to webtests/conftest.py
charlottekostelic Nov 8, 2024
4fa0d63
changed triggers for webtest workflow
charlottekostelic Nov 13, 2024
edea62b
fixed typo in conditionals
charlottekostelic Nov 13, 2024
4501873
changed live_token fixture scope to reduce calls
charlottekostelic Nov 14, 2024
931f3d0
added automatic retries to holdings tests
charlottekostelic Nov 14, 2024
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
44 changes: 44 additions & 0 deletions .github/workflows/monthly-api-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: monthly API response check
on:
pull_request:
branches:
- main
- "releases/**"
schedule:
- cron: '0 5 15 * *'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
WCKey: ${{ secrets.WC_KEY }}
WCSecret: ${{ secrets.WC_SECRET }}
WCScopes: "WorldCatMetadataAPI"
jobs:
webtests:
name: Run live webtests
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- name: Check if PR is from fork
if: ${{ github.event_name == 'pull_request' }}
run: |
if [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.event.pull_request.base.repo.full_name }}" ]; then
echo "is_fork=true" >> $GITHUB_ENV
else
echo "is_fork=false" >> $GITHUB_ENV
fi
- name: Set up Python ${{ matrix.python-version}}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version}}
- name: Install dependencies
if: ${{ env.is_fork == 'false' || github.event_name == 'schedule' }}
run: |
python -m pip install --upgrade pip
python -m pip install -r dev-requirements.txt
- name: Run monthly live tests
if: ${{ env.is_fork == 'false' || github.event_name == 'schedule' }}
run: |
pytest -m "webtest"
4 changes: 2 additions & 2 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ poetry install
```

#### Run tests
Run tests before making changes on your fork.
Run tests before making changes on your fork. Live tests will be run on a monthly basis using credentials saved in the repository's secrets.
??? info
Our live tests are designed to look for API credentials in a specific file/directory in a Windows environment. We will need to refactor the live tests to allow contributors to run live tests with their own API credentials and run live tests in a macOS environment.
Our live tests are designed to look for API credentials in a specific file/directory in a Windows environment. We will need to refactor the `live_keys` fixture to allow contributors to run live tests locally with their own API credentials and to run live tests in a macOS environment.

```py
# basic usage without webtests
Expand Down
31 changes: 9 additions & 22 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
# -*- coding: utf-8 -*-

import datetime
import json
import os
from typing import Dict, Generator, Union
import pytest
import requests

from bookops_worldcat import WorldcatAccessToken, MetadataSession


@pytest.fixture
def live_keys():
if os.name == "nt" and not os.getenv("GITHUB_ACTIONS"):
fh = os.path.join(os.environ["USERPROFILE"], ".oclc/nyp_wc_test.json")
with open(fh, "r") as file:
data = json.load(file)
os.environ["WCKey"] = data["key"]
os.environ["WCSecret"] = data["secret"]
os.environ["WCScopes"] = data["scopes"]


@pytest.fixture
def stub_marc_xml() -> str:
stub_marc_xml = "<record><leader>00000nam a2200000 a 4500</leader><controlfield tag='008'>120827s2012 nyua 000 0 eng d</controlfield><datafield tag='010' ind1=' ' ind2=' '><subfield code='a'> 63011276 </subfield></datafield><datafield tag='035' ind1=' ' ind2=' '><subfield code='a'>ocn850940548</subfield></datafield><datafield tag='040' ind1=' ' ind2=' '><subfield code='a'>OCWMS</subfield><subfield code='b'>eng</subfield><subfield code='c'>OCWMS</subfield></datafield><datafield tag='100' ind1='0' ind2=' '><subfield code='a'>OCLC Developer Network</subfield></datafield><datafield tag='245' ind1='1' ind2='0'><subfield code='a'>Test Record</subfield></datafield><datafield tag='500' ind1=' ' ind2=' '><subfield code='a'>FOR OCLC DEVELOPER NETWORK DOCUMENTATION</subfield></datafield></record>"
Expand All @@ -40,12 +27,12 @@ def stub_marc21() -> bytes:

class FakeUtcNow(datetime.datetime):
@classmethod
def now(cls, tzinfo=datetime.timezone.utc):
def now(cls, tzinfo=datetime.timezone.utc) -> "FakeUtcNow":
return cls(2020, 1, 1, 17, 0, 0, 0, tzinfo=datetime.timezone.utc)


@pytest.fixture
def mock_now(monkeypatch):
def mock_now(monkeypatch) -> None:
monkeypatch.setattr(datetime, "datetime", FakeUtcNow)


Expand Down Expand Up @@ -130,7 +117,7 @@ def __init__(self, http_code) -> None:


@pytest.fixture
def mock_session_response(request, monkeypatch):
def mock_session_response(request, monkeypatch) -> None:
"""
Use together with `pytest.mark.http_code` marker to pass
specific HTTP code to be returned to simulate various
Expand Down Expand Up @@ -165,44 +152,44 @@ def mock_oauth_server_response(


@pytest.fixture
def mock_successful_post_token_response(mock_now, monkeypatch):
def mock_successful_post_token_response(mock_now, monkeypatch) -> None:
def mock_oauth_server_response(*args, **kwargs):
return MockAuthServerResponseSuccess()

monkeypatch.setattr(requests, "post", mock_oauth_server_response)


@pytest.fixture
def mock_failed_post_token_response(monkeypatch):
def mock_failed_post_token_response(monkeypatch) -> None:
def mock_oauth_server_response(*args, **kwargs):
return MockAuthServerResponseFailure()

monkeypatch.setattr(requests, "post", mock_oauth_server_response)


@pytest.fixture
def mock_unexpected_error(monkeypatch):
def mock_unexpected_error(monkeypatch) -> None:
monkeypatch.setattr("requests.post", MockUnexpectedException)
monkeypatch.setattr("requests.get", MockUnexpectedException)
monkeypatch.setattr("requests.Session.send", MockUnexpectedException)


@pytest.fixture
def mock_timeout(monkeypatch):
def mock_timeout(monkeypatch) -> None:
monkeypatch.setattr("requests.post", MockTimeout)
monkeypatch.setattr("requests.get", MockTimeout)
monkeypatch.setattr("requests.Session.send", MockTimeout)


@pytest.fixture
def mock_connection_error(monkeypatch):
def mock_connection_error(monkeypatch) -> None:
monkeypatch.setattr("requests.post", MockConnectionError)
monkeypatch.setattr("requests.get", MockConnectionError)
monkeypatch.setattr("requests.Session.send", MockConnectionError)


@pytest.fixture
def mock_retry_error(monkeypatch):
def mock_retry_error(monkeypatch) -> None:
monkeypatch.setattr("requests.post", MockRetryError)
monkeypatch.setattr("requests.get", MockRetryError)
monkeypatch.setattr("requests.Session.send", MockRetryError)
Expand Down
80 changes: 0 additions & 80 deletions tests/test_authorize.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-

import datetime
import os

import pytest

Expand Down Expand Up @@ -301,82 +300,3 @@ def test_token_repr(
str(mock_token)
== "access_token: 'tk_Yebz4BpEp9dAsghA7KpWx6dYD1OZKWBlHjqW', expires_at: '2020-01-01 17:19:58Z'"
)

@pytest.mark.webtest
def test_cred_in_env_variables(self, live_keys):
assert os.getenv("WCKey") is not None
assert os.getenv("WCSecret") is not None
assert os.getenv("WCScopes") == "WorldCatMetadataAPI"

@pytest.mark.webtest
def test_post_token_request_with_live_service(self, live_keys):
token = WorldcatAccessToken(
key=os.getenv("WCKey"),
secret=os.getenv("WCSecret"),
scopes=os.getenv("WCScopes"),
)

assert token.server_response.status_code == 200

# test presence of all returned parameters
response = token.server_response.json()
params = [
"access_token",
"expires_at",
"authenticating_institution_id",
"principalID",
"context_institution_id",
"scope",
"scopes",
"token_type",
"expires_in",
"principalIDNS",
]
for p in params:
assert p in response

# test if any new additions are present
assert sorted(params) == sorted(response.keys())

# test if token looks right
assert token.token_str.startswith("tk_")
assert response["scopes"] == response["scope"]
assert token.is_expired() is False
assert isinstance(token.token_expires_at, datetime.datetime)

@pytest.mark.webtest
def test_post_token_request_with_live_service_no_timeout(self, live_keys):
token = WorldcatAccessToken(
key=os.getenv("WCKey"),
secret=os.getenv("WCSecret"),
scopes=os.getenv("WCScopes"),
timeout=None,
)

assert token.server_response.status_code == 200

# test presence of all returned parameters
response = token.server_response.json()
params = [
"access_token",
"expires_at",
"authenticating_institution_id",
"principalID",
"context_institution_id",
"scope",
"scopes",
"token_type",
"expires_in",
"principalIDNS",
]
for p in params:
assert p in response

# test if any new additions are present
assert sorted(params) == sorted(response.keys())

# test if token looks right
assert token.token_str.startswith("tk_")
assert response["scopes"] == response["scope"]
assert token.is_expired() is False
assert isinstance(token.token_expires_at, datetime.datetime)
Loading