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:
@@ -1,11 +1,11 @@
|
|||||||
"""Unit tests for base mixins for plugins."""
|
"""Unit tests for base mixins for plugins."""
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import include, path, re_path
|
from django.urls import include, path, re_path
|
||||||
|
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
from InvenTree.unit_test import InvenTreeTestCase
|
from InvenTree.unit_test import InvenTreeTestCase
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.helpers import MixinNotImplementedError
|
from plugin.helpers import MixinNotImplementedError
|
||||||
@@ -205,12 +205,12 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
'API_TOKEN': {
|
'API_TOKEN': {
|
||||||
'name': 'API Token',
|
'name': 'API Token',
|
||||||
'protected': True,
|
'protected': True,
|
||||||
'default': 'reqres-free-v1',
|
'default': 'sample-free-v1',
|
||||||
},
|
},
|
||||||
'API_URL': {
|
'API_URL': {
|
||||||
'name': 'External URL',
|
'name': 'External URL',
|
||||||
'description': 'Where is your API located?',
|
'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
|
@property
|
||||||
def api_url(self):
|
def api_url(self):
|
||||||
"""Override API URL for this test."""
|
"""Override API URL for this test."""
|
||||||
return 'https://api.github.com'
|
return 'https://api.example.com'
|
||||||
|
|
||||||
def get_external_url(self, simple: bool = True):
|
def get_external_url(self, simple: bool = True):
|
||||||
"""Returns data from the sample endpoint."""
|
"""Returns data from the sample endpoint."""
|
||||||
@@ -229,13 +229,6 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
|
|
||||||
self.mixin = MixinCls()
|
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):
|
class WrongCLS(APICallMixin, InvenTreePlugin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -251,7 +244,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
# check init
|
# check init
|
||||||
self.assertTrue(self.mixin.has_api_call)
|
self.assertTrue(self.mixin.has_api_call)
|
||||||
# api_url
|
# api_url
|
||||||
self.assertEqual('https://api.github.com', self.mixin.api_url)
|
self.assertEqual('https://api.example.com', self.mixin.api_url)
|
||||||
|
|
||||||
# api_headers
|
# api_headers
|
||||||
headers = self.mixin.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]})
|
result = self.mixin.api_build_url_args({'a': 'b', 'c': ['d', 'efgh', 1337]})
|
||||||
self.assertEqual(result, '?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."""
|
"""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
|
# api_call
|
||||||
result = self.mixin.get_external_url()
|
result = self.mixin.get_external_url()
|
||||||
@@ -287,27 +305,17 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
# api_call without json conversion
|
# api_call without json conversion
|
||||||
result = self.mixin.get_external_url(False)
|
result = self.mixin.get_external_url(False)
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.assertEqual(result.reason, 'OK')
|
self.assertTrue(result.ok)
|
||||||
|
|
||||||
# Set API TOKEN
|
# 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
|
# api_call with post and data
|
||||||
|
result = self.mixin.api_call(
|
||||||
# Try multiple times, account for the rate limit
|
'https://api.example.com/users/',
|
||||||
result = None
|
json={'name': 'morpheus', 'job': 'leader'},
|
||||||
|
method='POST',
|
||||||
for _ in range(5):
|
endpoint_is_url=True,
|
||||||
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)
|
|
||||||
|
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.assertNotIn('error', result)
|
self.assertNotIn('error', result)
|
||||||
@@ -317,16 +325,21 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
# api_call with endpoint with leading slash
|
# api_call with endpoint with leading slash
|
||||||
result = self.mixin.api_call('/orgs/inventree', simple_response=False)
|
result = self.mixin.api_call('/orgs/inventree', simple_response=False)
|
||||||
self.assertTrue(result)
|
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(
|
result = self.mixin.api_call(
|
||||||
'repos/inventree/InvenTree/stargazers', url_args={'page': '2'}
|
'repos/inventree/InvenTree/stargazers', url_args={'page': '2'}
|
||||||
)
|
)
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
|
|
||||||
def test_function_errors(self):
|
@requests_mock.Mocker()
|
||||||
|
def test_function_errors(self, m: requests_mock.Mocker):
|
||||||
"""Test function errors."""
|
"""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
|
# wrongly defined plugins should not load
|
||||||
with self.assertRaises(MixinNotImplementedError):
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
self.mixin_wrong.has_api_call()
|
self.mixin_wrong.has_api_call()
|
||||||
@@ -338,12 +351,12 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
# Too many data arguments
|
# Too many data arguments
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
self.mixin.api_call(
|
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(
|
result = self.mixin.api_call(
|
||||||
'https://reqres.in/api/users/',
|
'https://api.example.com/api/users/',
|
||||||
data={'name': 'morpheus', 'job': 'leader'},
|
data={'name': 'morpheus', 'job': 'leader'},
|
||||||
method='POST',
|
method='POST',
|
||||||
endpoint_is_url=True,
|
endpoint_is_url=True,
|
||||||
|
@@ -18,7 +18,7 @@ class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
'API_URL': {
|
'API_URL': {
|
||||||
'name': 'External URL',
|
'name': 'External URL',
|
||||||
'description': 'Where is your API located?',
|
'description': 'Where is your API located?',
|
||||||
'default': 'reqres.in',
|
'default': 'api.example.com',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
API_URL_SETTING = 'API_URL'
|
API_URL_SETTING = 'API_URL'
|
||||||
|
@@ -2,15 +2,19 @@
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
from plugin import registry
|
from plugin import registry
|
||||||
|
|
||||||
|
|
||||||
class SampleApiCallerPluginTests(TestCase):
|
class SampleApiCallerPluginTests(TestCase):
|
||||||
"""Tests for SampleApiCallerPluginTests."""
|
"""Tests for SampleApiCallerPluginTests."""
|
||||||
|
|
||||||
def test_return(self):
|
@requests_mock.Mocker()
|
||||||
|
def test_return(self, m):
|
||||||
"""Check if the external api call works."""
|
"""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
|
# The plugin should be defined
|
||||||
self.assertIn('sample-api-caller', registry.plugins)
|
self.assertIn('sample-api-caller', registry.plugins)
|
||||||
@@ -18,15 +22,7 @@ class SampleApiCallerPluginTests(TestCase):
|
|||||||
self.assertTrue(plg)
|
self.assertTrue(plg)
|
||||||
|
|
||||||
# do an api call
|
# do an api call
|
||||||
# Note: rate limits may apply in CI
|
result = plg.get_external_url()
|
||||||
result = False
|
|
||||||
|
|
||||||
for _i in range(5):
|
|
||||||
result = plg.get_external_url()
|
|
||||||
if result:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.assertIn('data', result)
|
self.assertIn('data', result)
|
||||||
|
@@ -12,3 +12,4 @@ pdfminer.six # PDF validation
|
|||||||
ty # type checking
|
ty # type checking
|
||||||
django-types # typing
|
django-types # typing
|
||||||
django-stubs # typing
|
django-stubs # typing
|
||||||
|
requests-mock # Mock requests for unit tests
|
||||||
|
@@ -11,6 +11,12 @@ build==1.3.0 \
|
|||||||
--hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \
|
--hash=sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397 \
|
||||||
--hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4
|
--hash=sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4
|
||||||
# via pip-tools
|
# via pip-tools
|
||||||
|
certifi==2025.8.3 \
|
||||||
|
--hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \
|
||||||
|
--hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5
|
||||||
|
# via
|
||||||
|
# -c src/backend/requirements.txt
|
||||||
|
# requests
|
||||||
cffi==1.17.1 \
|
cffi==1.17.1 \
|
||||||
--hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
|
--hash=sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8 \
|
||||||
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
|
--hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \
|
||||||
@@ -182,6 +188,7 @@ charset-normalizer==3.4.2 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# pdfminer-six
|
# pdfminer-six
|
||||||
|
# requests
|
||||||
click==8.1.8 \
|
click==8.1.8 \
|
||||||
--hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
|
--hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \
|
||||||
--hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
|
--hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
|
||||||
@@ -359,6 +366,12 @@ identify==2.6.12 \
|
|||||||
--hash=sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2 \
|
--hash=sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2 \
|
||||||
--hash=sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6
|
--hash=sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6
|
||||||
# via pre-commit
|
# via pre-commit
|
||||||
|
idna==3.10 \
|
||||||
|
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
|
||||||
|
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
|
||||||
|
# via
|
||||||
|
# -c src/backend/requirements.txt
|
||||||
|
# requests
|
||||||
importlib-metadata==8.7.0 \
|
importlib-metadata==8.7.0 \
|
||||||
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
|
--hash=sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000 \
|
||||||
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
|
--hash=sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd
|
||||||
@@ -470,6 +483,16 @@ pyyaml==6.0.2 \
|
|||||||
# via
|
# via
|
||||||
# -c src/backend/requirements.txt
|
# -c src/backend/requirements.txt
|
||||||
# pre-commit
|
# 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 \
|
setuptools==80.9.0 \
|
||||||
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
|
--hash=sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922 \
|
||||||
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
|
--hash=sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c
|
||||||
@@ -559,6 +582,12 @@ typing-extensions==4.14.1 \
|
|||||||
# django-stubs
|
# django-stubs
|
||||||
# django-stubs-ext
|
# django-stubs-ext
|
||||||
# django-test-migrations
|
# django-test-migrations
|
||||||
|
urllib3==1.26.20 \
|
||||||
|
--hash=sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e \
|
||||||
|
--hash=sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32
|
||||||
|
# via
|
||||||
|
# -c src/backend/requirements.txt
|
||||||
|
# requests
|
||||||
virtualenv==20.33.1 \
|
virtualenv==20.33.1 \
|
||||||
--hash=sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67 \
|
--hash=sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67 \
|
||||||
--hash=sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8
|
--hash=sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8
|
||||||
|
Reference in New Issue
Block a user