Skip to content

Commit

Permalink
Replace mocks with VCR casettes for client testing
Browse files Browse the repository at this point in the history
  • Loading branch information
pi-sigma committed Jul 31, 2024
1 parent 224368f commit 3f9f943
Show file tree
Hide file tree
Showing 8 changed files with 905 additions and 95 deletions.
671 changes: 671 additions & 0 deletions fixtures/cassettes/forms.yaml

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions fixtures/cassettes/invalid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- python-requests/2.32.3
method: GET
uri: https://open-forms.test.maykin.opengem.nl/api/v2/public/forms
response:
body:
string: '{"type":"https://open-forms.test.maykin.opengem.nl/fouten/AuthenticationFailed/","code":"authentication_failed","title":"Ongeldige
authenticatiegegevens.","status":401,"detail":"Ongeldige token.","instance":"urn:uuid:b3f40157-9442-486c-aaba-037f9342c703"}'
headers:
Allow:
- GET, HEAD, OPTIONS
Cache-Control:
- max-age=0, no-cache, no-store, must-revalidate, private
Connection:
- keep-alive
Content-Language:
- nl
Content-Length:
- '255'
Content-Type:
- application/json
Cross-Origin-Opener-Policy:
- same-origin
Date:
- Wed, 31 Jul 2024 08:05:44 GMT
Expires:
- Wed, 31 Jul 2024 08:05:44 GMT
Pragma:
- no-cache
Referrer-Policy:
- same-origin
Set-Cookie:
- csrftoken=rDnuLzLrAUGpIgp2gYxelwj7UGZEqCV6; expires=Wed, 30 Jul 2025 08:05:44
GMT; Max-Age=31449600; Path=/; SameSite=None; Secure
- openforms_sessionid=8ev8ldwpo3vi2qsnw0l176htbcrcbg54; expires=Wed, 31 Jul
2024 08:10:44 GMT; HttpOnly; Max-Age=300; Path=/; SameSite=None; Secure
Strict-Transport-Security:
- max-age=63072000
Vary:
- Cookie, Origin, Accept-Language
WWW-Authenticate:
- Token
X-CSRFToken:
- 5dZg56rok3RheSgkWWqoIhnpkBNVnweDmGcAGv2FKNnwMYvc2KNsTDwm47CpDYZz
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Session-Expires-In:
- '300'
status:
code: 401
message: Unauthorized
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- python-requests/2.32.3
method: GET
uri: https://open-forms.test.maykin.opengem.nl/api/v2/forms/9c65ab49-2635-4c8d-ac22-aaca67044a6c
response:
body:
string: '{"type":"https://open-forms.test.maykin.opengem.nl/fouten/AuthenticationFailed/","code":"authentication_failed","title":"Ongeldige
authenticatiegegevens.","status":401,"detail":"Ongeldige token.","instance":"urn:uuid:d545fdc9-8186-456a-999b-c0c388fe922a"}'
headers:
Allow:
- GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Cache-Control:
- max-age=0, no-cache, no-store, must-revalidate, private
Connection:
- keep-alive
Content-Language:
- nl
Content-Length:
- '255'
Content-Type:
- application/json
Cross-Origin-Opener-Policy:
- same-origin
Date:
- Wed, 31 Jul 2024 08:05:45 GMT
Expires:
- Wed, 31 Jul 2024 08:05:45 GMT
Pragma:
- no-cache
Referrer-Policy:
- same-origin
Set-Cookie:
- csrftoken=erCNNjLn8PKuLjVPK1ZDw3a4Rv7PUQS9; expires=Wed, 30 Jul 2025 08:05:45
GMT; Max-Age=31449600; Path=/; SameSite=None; Secure
- openforms_sessionid=qm2mjv9kxo2xcckzvb0flhy5nt5d3zbz; expires=Wed, 31 Jul
2024 08:10:45 GMT; HttpOnly; Max-Age=300; Path=/; SameSite=None; Secure
Strict-Transport-Security:
- max-age=63072000
Vary:
- Cookie, Origin, Accept-Language
WWW-Authenticate:
- Token
X-CSRFToken:
- SDRW8cYDR5y2Ti1Ihv4D8Uns3RlWJ44eWUjzLlzQPK8murMnRmT6uNnmKciBtKMd
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- DENY
X-Session-Expires-In:
- '300'
status:
code: 401
message: Unauthorized
version: 1
27 changes: 17 additions & 10 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ install_requires =
django-solo
requests
tests_require =
requests-mock
pytest
pytest-django
tox
isort
black
flake8
isort
pytest
pytest-django
python-decouple
pyyaml
requests
requests-mock
time-machine
tox
vcrpy

[options.packages.find]
include =
Expand All @@ -49,14 +53,17 @@ include =

[options.extras_require]
tests =
requests-mock
pytest
pytest-django
tox
isort
black
flake8
isort
pytest
pytest-django
python-decouple
pyyaml
requests-mock
time-machine
tox
vcrpy
pep8 = flake8
coverage = pytest-cov
docs =
Expand Down
17 changes: 17 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest
from decouple import config

from openformsclient.client import Client

API_ROOT = config("OFC_API_ROOT", "https://open-forms.test.maykin.opengem.nl/api/v2/")
API_TOKEN = config("OFC_API_TOKEN", "hush-hush")


@pytest.fixture
def client():
return Client(api_root=API_ROOT, api_token=API_TOKEN, client_timeout=2)


@pytest.fixture
def bogus_client():
return Client(api_root=API_ROOT, api_token="bogus", client_timeout=2)
Empty file added tests/data/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions tests/data/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
test_form = {
"uuid": "9c65ab49-2635-4c8d-ac22-aaca67044a6c",
"name": "Open Inwoner ZGW Dev",
"internalName": "",
"slug": "open-inwoner-zgw-dev",
}
152 changes: 67 additions & 85 deletions tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,91 +1,73 @@
from unittest.mock import patch
from urllib.parse import urljoin

from django.test import TestCase

import requests_mock
import pytest
import vcr
from requests.exceptions import HTTPError

from openformsclient.client import Client

from .data.forms import test_form

CASSETTE_PATH_FORMS = "fixtures/cassettes/forms.yaml"
CASSETTE_PATH_INVALID = "fixtures/cassettes/invalid.yaml"
VCR_DEFAULTS = {
"record_mode": "none", # use new_episodes to record casettes
"filter_headers": ["authorization"],
}


def test_client_has_config(client):
bogus_client = Client("", "", "")

assert client.has_config()
assert not bogus_client.has_config()


@vcr.use_cassette(CASSETTE_PATH_FORMS, **VCR_DEFAULTS)
def test_client_is_healthy(client):
health, msg = client.is_healthy()
assert health
assert msg == ""


@vcr.use_cassette(CASSETTE_PATH_FORMS, **VCR_DEFAULTS)
def test_get_forms(client):
results = client.get_forms()["results"]

assert test_form in results


@vcr.use_cassette(CASSETTE_PATH_INVALID, **VCR_DEFAULTS)
def test_get_forms_unauthorized(bogus_client):
url = urljoin(bogus_client.api_root, "public/forms")
msg = f"401 Client Error: Unauthorized for url: {url}"

with pytest.raises(HTTPError, match=msg):
bogus_client.get_forms()


@vcr.use_cassette(CASSETTE_PATH_FORMS, **VCR_DEFAULTS)
def test_get_single_form(client):
result = client.get_form(uuid_or_slug=test_form["uuid"])

assert result["uuid"] == test_form["uuid"]
assert result["name"] == test_form["name"]


@vcr.use_cassette(CASSETTE_PATH_FORMS, **VCR_DEFAULTS)
def test_get_single_form_not_found(client):
bogus_uuid = "aaaa-bbbb-cccc-dddd"
url = urljoin(client.api_root, f"forms/{bogus_uuid}")
msg = f"404 Client Error: Not Found for url: {url}"

with pytest.raises(HTTPError, match=msg):
client.get_form(uuid_or_slug=bogus_uuid)


@vcr.use_cassette(CASSETTE_PATH_INVALID, **VCR_DEFAULTS)
def test_get_single_form_unauthorized(bogus_client):
url = urljoin(bogus_client.api_root, f"forms/{test_form['uuid']}")
msg = f"401 Client Error: Unauthorized for url: {url}"

@requests_mock.Mocker()
class ClientTests(TestCase):
def setUp(self):
self.api_root = "https://example.com/api/v2/"
self.api_token = "token"
self.client_timeout = 2
self.client = Client(self.api_root, self.api_token, self.client_timeout)

def test_has_config(self, m):
self.assertTrue(self.client.has_config())
self.assertFalse(Client("", "", "").has_config())

def test_is_healthy(self, m):
m.head(
f"{self.api_root}public/forms",
request_headers={"Authorization": f"Token {self.api_token}"},
)

health, msg = self.client.is_healthy()
self.assertTrue(health)
self.assertEqual(msg, "")

def test_is_healthy_invalid_response(self, m):
m.head(f"{self.api_root}public/forms", status_code=500) # Doesn't really matter
m.get(f"{self.api_root}public/forms", text="Woops")

health, msg = self.client.is_healthy()
self.assertFalse(health)
self.assertEqual(msg, "Server did not return a valid response (HTTP 500).")

def test_is_healthy_invalid_token(self, m):
m.head(f"{self.api_root}public/forms", status_code=401)
m.get(
f"{self.api_root}public/forms",
json={
"type": "https://example.com/fouten/AuthenticationFailed/",
"code": "authentication_failed",
"title": "Ongeldige authenticatiegegevens.",
"status": 401,
"detail": "Ongeldige token.",
"instance": "urn:uuid:8dddcb04-a412-451a-a7c5-f77d1aef36f5",
},
)

health, msg = self.client.is_healthy()
self.assertFalse(health)
self.assertEqual(msg, "Ongeldige token.")

def test_get_forms(self, m):
m.get(f"{self.api_root}public/forms", json=[])

result = self.client.get_forms()
self.assertListEqual(result, [])

def test_get_forms_with_error(self, m):
m.get(f"{self.api_root}public/forms", status_code=401)

with self.assertRaises(HTTPError):
self.client.get_forms()

def test_get_form(self, m):
m.get(f"{self.api_root}forms/myform", json={})

result = self.client.get_form("myform")
self.assertDictEqual(result, {})

def test_get_form_with_error(self, m):
m.get(f"{self.api_root}forms/myform", status_code=401)

with self.assertRaises(HTTPError):
self.client.get_form("myform")

def test_request_uses_configured_timeout(self, m):
with patch("openformsclient.client.requests.request") as mock_request:
self.client.get_form("myform")
mock_request.assert_called_with(
"get",
"https://example.com/api/v2/forms/myform",
headers={"Authorization": "Token token"},
timeout=2,
)
with pytest.raises(HTTPError, match=msg):
bogus_client.get_form(uuid_or_slug=test_form["uuid"])
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ extras =
tests
coverage
deps =
setuptools==71.1.0
django32: Django~=3.2.0
django40: Django~=4.0.0
commands =
Expand Down

0 comments on commit 3f9f943

Please sign in to comment.