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

Refactor integration tests to have no side-effects #3544

Merged
merged 37 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9bd6af2
Remove unused caches and move config into separate file
dhruvkb Dec 16, 2023
bdddc14
Organise and deduplicate fixtures
dhruvkb Dec 16, 2023
0fdec47
Add factory for model `ContentProvider`
dhruvkb Dec 16, 2023
e94788a
Replace use of `cache` from `django.core.cache` with fixtures
dhruvkb Dec 16, 2023
9605a43
Make caching of filtered providers Redis-resilient
dhruvkb Dec 16, 2023
edff54b
Make caching of providers stats Redis-resilient
dhruvkb Dec 16, 2023
b4b2822
Make caching of URL liveness Redis-resilient
dhruvkb Dec 16, 2023
2c25742
Make tallying of providers Redis-resilient
dhruvkb Dec 16, 2023
2c6a131
Fix bad test for dead links
dhruvkb Dec 16, 2023
a3c4783
Drop concept of IP allowlist from throttling
dhruvkb Dec 16, 2023
32ec720
Make DRF throttling Redis-resilient
dhruvkb Dec 16, 2023
1aca113
Make caching of query mask Redis-resilient
dhruvkb Dec 16, 2023
6d7bf7e
Make checking of token stats Redis-resilient
dhruvkb Dec 16, 2023
f53310f
Use pipeline and make tallying of response codes Redis-resilient
dhruvkb Dec 16, 2023
cdc929d
Make caching of image extension Redis-resilient
dhruvkb Dec 16, 2023
0c652b1
Make tallying of thumbnail errors Redis-resilient
dhruvkb Dec 16, 2023
f0b9b81
Add blank lines to split long blocks into chunks
dhruvkb Dec 18, 2023
65f7dba
Make Redis request even if recently failed
dhruvkb Dec 18, 2023
08f7cc3
Delete dead code from tests
dhruvkb Dec 16, 2023
b36f94c
Move integration tests into the `integration/` subdirectory
dhruvkb Dec 16, 2023
75cde84
Drop top-level `conftest.py`
dhruvkb Dec 16, 2023
5d67f59
Parametrize tests for 301 redirects
dhruvkb Dec 17, 2023
55761eb
Move tests for endpoints returning 410 Gone
dhruvkb Dec 17, 2023
011a85a
Delete redundant and skipped tests
dhruvkb Dec 17, 2023
dd264a8
Merge common tests into one suite with parametrized fixtures
dhruvkb Dec 18, 2023
e0c5d14
Move smaller tests of the media models to unit tests
dhruvkb Dec 18, 2023
5f79ab8
Use client for testing dead link filtering
dhruvkb Dec 18, 2023
44f3015
Remove previously-existing stray comma
dhruvkb Dec 18, 2023
788cf62
Merge branch 'main' into redis_resilience
dhruvkb Dec 18, 2023
6c231e1
Merge branch 'redis_resilience' of https://github.com/WordPress/openv…
dhruvkb Dec 19, 2023
b8576ce
Merge branch 'main' of https://github.com/WordPress/openverse into in…
dhruvkb Dec 20, 2023
d1d370e
Fix lint issue with single-line docstring
dhruvkb Dec 20, 2023
b5eb751
Use api_client, move fixtures around to share basic defaults (#3560)
sarayourfriend Dec 20, 2023
b54d73a
Merge branch 'main' of https://github.com/WordPress/openverse into in…
dhruvkb Feb 6, 2024
170226c
Add note about running pytest inside the container
dhruvkb Feb 9, 2024
2a7dc28
Add note about location of common tests
dhruvkb Feb 9, 2024
4fadef5
Remove commented line
dhruvkb Feb 13, 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
473 changes: 0 additions & 473 deletions api/test/api_live_integration.py

This file was deleted.

43 changes: 0 additions & 43 deletions api/test/api_live_search_qa.py

This file was deleted.

5 changes: 5 additions & 0 deletions api/test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Fixtures usable by or necessary for both unit and integration tests."""

from test.fixtures.asynchronous import ensure_asgi_lifecycle, get_new_loop, session_loop
from test.fixtures.cache import (
django_cache,
redis,
unreachable_django_cache,
unreachable_redis,
)
from test.fixtures.rest_framework import api_client, request_factory


__all__ = [
Expand All @@ -15,4 +18,6 @@
"redis",
"unreachable_django_cache",
"unreachable_redis",
"api_client",
"request_factory",
]
15 changes: 15 additions & 0 deletions api/test/fixtures/rest_framework.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from rest_framework.test import APIClient, APIRequestFactory

import pytest


@pytest.fixture
def api_client():
return APIClient()


@pytest.fixture
def request_factory() -> APIRequestFactory():
request_factory = APIRequestFactory(defaults={"REMOTE_ADDR": "192.0.2.1"})

return request_factory
13 changes: 13 additions & 0 deletions api/test/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest


@pytest.fixture
def django_db_setup():
"""
We want the integration tests to use the real database so that we can test
the complete behaviour of the system. This fixture overrides the fixture
from ``pytest-django`` that sets up the tests database and because it's a
no-op, the tests will use the real database.
"""

pass
29 changes: 29 additions & 0 deletions api/test/integration/test_audio_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
End-to-end API tests for audio.

Can be used to verify a live deployment is functioning as designed.
Run with the `pytest -s` command from this directory, inside the Docker
container.

Tests common to all media types are in ``test_media_integration.py``.
"""

import pytest


pytestmark = pytest.mark.django_db


def test_audio_detail_without_thumb(api_client):
resp = api_client.get("/v1/audio/44540200-91eb-483d-9e99-38ce86a52fb6/")
assert resp.status_code == 200
parsed = resp.json()
assert parsed["thumbnail"] is None


def test_audio_search_without_thumb(api_client):
"""The first audio of this search should not have a thumbnail."""
resp = api_client.get("/v1/audio/?q=zaus")
assert resp.status_code == 200
parsed = resp.json()
assert parsed["results"][0]["thumbnail"] is None
99 changes: 45 additions & 54 deletions api/test/test_auth.py → api/test/integration/test_auth.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import time
import uuid
from unittest.mock import patch

from django.urls import reverse
from django.utils.http import urlencode

import pytest
from oauth2_provider.models import AccessToken
Expand Down Expand Up @@ -38,13 +36,13 @@ def unreachable_oauth_cache(unreachable_django_cache, monkeypatch):

@pytest.mark.django_db
@pytest.fixture
def test_auth_tokens_registration(client):
def test_auth_tokens_registration(api_client):
data = {
"name": f"INTEGRATION TEST APPLICATION {uuid.uuid4()}",
"description": "A key for testing the OAuth2 registration process.",
"email": "[email protected]",
}
res = client.post(
res = api_client.post(
"/v1/auth_tokens/register/",
data,
verify=False,
Expand All @@ -56,20 +54,19 @@ def test_auth_tokens_registration(client):

@pytest.mark.django_db
@pytest.fixture
def test_auth_token_exchange(client, test_auth_tokens_registration):
client_id = test_auth_tokens_registration["client_id"]
client_secret = test_auth_tokens_registration["client_secret"]
data = urlencode(
{
"client_id": client_id,
"client_secret": client_secret,
"grant_type": "client_credentials",
}
)
res = client.post(
def test_auth_token_exchange(api_client, test_auth_tokens_registration):
api_client_id = test_auth_tokens_registration["client_id"]
api_client_secret = test_auth_tokens_registration["client_secret"]
data = {
"client_id": api_client_id,
"client_secret": api_client_secret,
"grant_type": "client_credentials",
}

res = api_client.post(
"/v1/auth_tokens/token/",
data,
"application/x-www-form-urlencoded",
"multipart",
verify=False,
)
res_data = res.json()
Expand All @@ -78,20 +75,20 @@ def test_auth_token_exchange(client, test_auth_tokens_registration):


@pytest.mark.django_db
def test_auth_token_exchange_unsupported_method(client):
res = client.get(
def test_auth_token_exchange_unsupported_method(api_client):
res = api_client.get(
"/v1/auth_tokens/token/",
verify=False,
)
assert res.status_code == 405
assert res.json()["detail"] == 'Method "GET" not allowed.'


def _integration_verify_most_recent_token(client):
def _integration_verify_most_recent_token(api_client):
verify = OAuth2Verification.objects.last()
code = verify.code
path = reverse("verify-email", args=[code])
return client.get(path)
return api_client.get(path)


@pytest.mark.django_db
Expand All @@ -110,17 +107,17 @@ def _integration_verify_most_recent_token(client):
)
def test_auth_email_verification(
request,
client,
api_client,
is_cache_reachable,
cache_name,
rate_limit_model,
test_auth_token_exchange,
):
res = _integration_verify_most_recent_token(client)
res = _integration_verify_most_recent_token(api_client)
assert res.status_code == 200
test_auth_rate_limit_reporting(
request,
client,
api_client,
is_cache_reachable,
cache_name,
rate_limit_model,
Expand All @@ -137,7 +134,7 @@ def test_auth_email_verification(
@cache_availability_params
def test_auth_rate_limit_reporting(
request,
client,
api_client,
is_cache_reachable,
cache_name,
rate_limit_model,
Expand All @@ -153,7 +150,7 @@ def test_auth_rate_limit_reporting(
application = AccessToken.objects.get(token=token).application
application.rate_limit_model = rate_limit_model
application.save()
res = client.get("/v1/rate_limit/", HTTP_AUTHORIZATION=f"Bearer {token}")
res = api_client.get("/v1/rate_limit/", HTTP_AUTHORIZATION=f"Bearer {token}")
res_data = res.json()
if is_cache_reachable:
assert res.status_code == 200
Expand All @@ -176,14 +173,14 @@ def test_auth_rate_limit_reporting(
(True, False),
)
def test_auth_response_headers(
client, verified, test_auth_tokens_registration, test_auth_token_exchange
api_client, verified, test_auth_tokens_registration, test_auth_token_exchange
):
if verified:
_integration_verify_most_recent_token(client)
_integration_verify_most_recent_token(api_client)

token = test_auth_token_exchange["access_token"]

res = client.get("/v1/images/", HTTP_AUTHORIZATION=f"Bearer {token}")
res = api_client.get("/v1/images/", HTTP_AUTHORIZATION=f"Bearer {token}")

assert (
res.headers["x-ov-client-application-name"]
Expand All @@ -192,8 +189,8 @@ def test_auth_response_headers(
assert res.headers["x-ov-client-application-verified"] == str(verified)


def test_unauthed_response_headers(client):
res = client.get("/v1/images")
def test_unauthed_response_headers(api_client):
res = api_client.get("/v1/images")

assert "x-ov-client-application-name" not in res.headers
assert "x-ov-client-application-verified" not in res.headers
Expand All @@ -207,21 +204,16 @@ def test_unauthed_response_headers(client):
("asc", "2022-01-01"),
],
)
def test_sorting_authed(client, test_auth_token_exchange, sort_dir, exp_indexed_on):
def test_sorting_authed(api_client, test_auth_token_exchange, sort_dir, exp_indexed_on):
time.sleep(1)
token = test_auth_token_exchange["access_token"]
query_params = {
"unstable__sort_by": "indexed_on",
"unstable__sort_dir": sort_dir,
}
with patch(
"api.views.image_views.ImageViewSet.get_db_results"
) as mock_get_db_result:
mock_get_db_result.side_effect = lambda value: value

res = client.get(
"/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}"
)
res = api_client.get(
"/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}"
)
assert res.status_code == 200

res_data = res.json()
Expand All @@ -238,7 +230,7 @@ def test_sorting_authed(client, test_auth_token_exchange, sort_dir, exp_indexed_
],
)
def test_authority_authed(
client, test_auth_token_exchange, authority_boost, exp_source
api_client, test_auth_token_exchange, authority_boost, exp_source
):
time.sleep(1)
token = test_auth_token_exchange["access_token"]
Expand All @@ -247,14 +239,9 @@ def test_authority_authed(
"unstable__authority": "true",
"unstable__authority_boost": authority_boost,
}
with patch(
"api.views.image_views.ImageViewSet.get_db_results"
) as mock_get_db_result:
mock_get_db_result.side_effect = lambda value: value

res = client.get(
"/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}"
)
res = api_client.get(
"/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}"
)
assert res.status_code == 200

res_data = res.json()
Expand All @@ -263,23 +250,27 @@ def test_authority_authed(


@pytest.mark.django_db
def test_page_size_limit_unauthed(client):
def test_page_size_limit_unauthed(api_client):
query_params = {"page_size": 20}
res = client.get("/v1/images/", query_params)
res = api_client.get("/v1/images/", query_params)
assert res.status_code == 200
query_params["page_size"] = 21
res = client.get("/v1/images/", query_params)
res = api_client.get("/v1/images/", query_params)
assert res.status_code == 401


@pytest.mark.django_db
def test_page_size_limit_authed(client, test_auth_token_exchange):
def test_page_size_limit_authed(api_client, test_auth_token_exchange):
time.sleep(1)
token = test_auth_token_exchange["access_token"]
query_params = {"page_size": 21}
res = client.get("/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}")
res = api_client.get(
"/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}"
)
assert res.status_code == 200

query_params = {"page_size": 500}
res = client.get("/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}")
res = api_client.get(
"/v1/images/", query_params, HTTP_AUTHORIZATION=f"Bearer {token}"
)
assert res.status_code == 200
Loading
Loading