2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-10-03 15:52:51 +00:00

move tests to api mocking to remove jitter in ci (#10447)

closes #10446
This commit is contained in:
Matthias Mair
2025-10-02 01:54:31 +02:00
committed by GitHub
parent 40700dfbcf
commit f01455411a
5 changed files with 90 additions and 51 deletions

View File

@@ -1,11 +1,11 @@
"""Unit tests for base mixins for plugins."""
import os
from django.conf import settings
from django.test import TestCase
from django.urls import include, path, re_path
import requests_mock
from InvenTree.unit_test import InvenTreeTestCase
from plugin import InvenTreePlugin
from plugin.helpers import MixinNotImplementedError
@@ -205,12 +205,12 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
'API_TOKEN': {
'name': 'API Token',
'protected': True,
'default': 'reqres-free-v1',
'default': 'sample-free-v1',
},
'API_URL': {
'name': 'External URL',
'description': 'Where is your API located?',
'default': 'https://api.github.com',
'default': 'https://api.example.com',
},
}
@@ -221,7 +221,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
@property
def api_url(self):
"""Override API URL for this test."""
return 'https://api.github.com'
return 'https://api.example.com'
def get_external_url(self, simple: bool = True):
"""Returns data from the sample endpoint."""
@@ -229,13 +229,6 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
self.mixin = MixinCls()
# If running in github workflow, make use of GITHUB_TOKEN
if settings.TESTING:
token = os.getenv('GITHUB_TOKEN', None)
if token:
self.mixin.set_setting('API_TOKEN', token)
class WrongCLS(APICallMixin, InvenTreePlugin):
pass
@@ -251,7 +244,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
# check init
self.assertTrue(self.mixin.has_api_call)
# api_url
self.assertEqual('https://api.github.com', self.mixin.api_url)
self.assertEqual('https://api.example.com', self.mixin.api_url)
# api_headers
headers = self.mixin.api_headers
@@ -273,9 +266,34 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
result = self.mixin.api_build_url_args({'a': 'b', 'c': ['d', 'efgh', 1337]})
self.assertEqual(result, '?a=b&c=d,efgh,1337')
def test_api_call(self):
@requests_mock.Mocker()
def test_api_call(self, m: requests_mock.Mocker):
"""Test that api calls work."""
import time
# Set up mock responses
m.get(
'https://api.example.com/orgs/inventree',
json={
'login': 'inventree',
'email': 'inventree',
'name': 'InvenTree',
'twitter_username': 'inventree',
},
status_code=200,
)
m.post(
'https://api.example.com/users/',
json={'name': 'morpheus', 'job': 'leader'},
status_code=201,
headers={
'Authorization': 'x-api-key sample-free-v1',
'Content-Type': 'application/json',
},
)
m.get(
'https://api.example.com/repos/inventree/InvenTree/stargazers?page=2',
json={'sample': True},
status_code=200,
)
# api_call
result = self.mixin.get_external_url()
@@ -287,27 +305,17 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
# api_call without json conversion
result = self.mixin.get_external_url(False)
self.assertTrue(result)
self.assertEqual(result.reason, 'OK')
self.assertTrue(result.ok)
# Set API TOKEN
self.mixin.set_setting('API_TOKEN', 'reqres-free-v1')
self.mixin.set_setting('API_TOKEN', 'sample-free-v1')
# api_call with post and data
# Try multiple times, account for the rate limit
result = None
for _ in range(5):
try:
result = self.mixin.api_call(
'https://reqres.in/api/users/',
json={'name': 'morpheus', 'job': 'leader'},
method='POST',
endpoint_is_url=True,
timeout=5000,
)
break
except Exception:
time.sleep(1)
result = self.mixin.api_call(
'https://api.example.com/users/',
json={'name': 'morpheus', 'job': 'leader'},
method='POST',
endpoint_is_url=True,
)
self.assertTrue(result)
self.assertNotIn('error', result)
@@ -317,16 +325,21 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
# api_call with endpoint with leading slash
result = self.mixin.api_call('/orgs/inventree', simple_response=False)
self.assertTrue(result)
self.assertEqual(result.reason, 'OK')
self.assertTrue(result.ok)
# api_call with filter
# api_call with filter - this errors out the mocker if not created correctly
result = self.mixin.api_call(
'repos/inventree/InvenTree/stargazers', url_args={'page': '2'}
)
self.assertTrue(result)
def test_function_errors(self):
@requests_mock.Mocker()
def test_function_errors(self, m: requests_mock.Mocker):
"""Test function errors."""
# Set up mock responses
m.get('https://api.example.com/orgs/inventree', status_code=404)
m.post('https://api.example.com/api/users/', status_code=400)
# wrongly defined plugins should not load
with self.assertRaises(MixinNotImplementedError):
self.mixin_wrong.has_api_call()
@@ -338,12 +351,12 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
# Too many data arguments
with self.assertRaises(ValueError):
self.mixin.api_call(
'https://reqres.in/api/users/', json={'a': 1}, data={'a': 1}
'https://api.example.com/api/users/', json={'a': 1}, data={'a': 1}
)
# Sending a request with a wrong data format should result in 40
# Sending a request with a wrong data format should result in 400
result = self.mixin.api_call(
'https://reqres.in/api/users/',
'https://api.example.com/api/users/',
data={'name': 'morpheus', 'job': 'leader'},
method='POST',
endpoint_is_url=True,

View File

@@ -18,7 +18,7 @@ class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
'API_URL': {
'name': 'External URL',
'description': 'Where is your API located?',
'default': 'reqres.in',
'default': 'api.example.com',
},
}
API_URL_SETTING = 'API_URL'

View File

@@ -2,15 +2,19 @@
from django.test import TestCase
import requests_mock
from plugin import registry
class SampleApiCallerPluginTests(TestCase):
"""Tests for SampleApiCallerPluginTests."""
def test_return(self):
@requests_mock.Mocker()
def test_return(self, m):
"""Check if the external api call works."""
import time
# Set up mock responses
m.get('https://api.example.com/api/users/2', json={'data': 'sample'})
# The plugin should be defined
self.assertIn('sample-api-caller', registry.plugins)
@@ -18,15 +22,7 @@ class SampleApiCallerPluginTests(TestCase):
self.assertTrue(plg)
# do an api call
# Note: rate limits may apply in CI
result = False
for _i in range(5):
result = plg.get_external_url()
if result:
break
else:
time.sleep(1)
result = plg.get_external_url()
self.assertTrue(result)
self.assertIn('data', result)

View File

@@ -12,3 +12,4 @@ pdfminer.six # PDF validation
ty # type checking
django-types # typing
django-stubs # typing
requests-mock # Mock requests for unit tests

View File

@@ -11,6 +11,12 @@ build==1.3.0 \
--hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \
--hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4
# via pip-tools
certifi==2025.8.3 \
--hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \
--hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5
# via
# -c src/backend/requirements.txt
# requests
cffi==1.17.1 \
--hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
@@ -182,6 +188,7 @@ charset-normalizer==3.4.2 \
# via
# -c src/backend/requirements.txt
# pdfminer-six
# requests
click==8.1.8 \
--hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
--hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
@@ -359,6 +366,12 @@ identify==2.6.12 \
--hash=sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2 \
--hash=sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6
# via pre-commit
idna==3.10 \
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
# via
# -c src/backend/requirements.txt
# requests
importlib-metadata==8.7.0 \
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
@@ -470,6 +483,16 @@ pyyaml==6.0.2 \
# via
# -c src/backend/requirements.txt
# pre-commit
requests==2.32.5 \
--hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \
--hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf
# via
# -c src/backend/requirements.txt
# requests-mock
requests-mock==1.12.1 \
--hash=sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563 \
--hash=sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401
# via -r src/backend/requirements-dev.in
setuptools==80.9.0 \
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
@@ -559,6 +582,12 @@ typing-extensions==4.14.1 \
# django-stubs
# django-stubs-ext
# django-test-migrations
urllib3==1.26.20 \
--hash=sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e \
--hash=sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32
# via
# -c src/backend/requirements.txt
# requests
virtualenv==20.33.1 \
--hash=sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67 \
--hash=sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8