oncall-engine/engine/apps/grafana_plugin/tests/test_sync_v2.py

333 lines
12 KiB
Python
Raw Permalink Normal View History

import gzip
import json
from dataclasses import asdict
from unittest.mock import call, patch
import pytest
from django.urls import reverse
from rest_framework import status
from rest_framework.exceptions import ValidationError
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.grafana_plugin.serializers.sync_data import SyncOnCallSettingsSerializer, SyncTeamSerializer
from apps.grafana_plugin.sync_data import SyncData, SyncSettings, SyncUser
from apps.grafana_plugin.tasks.sync_v2 import start_sync_organizations_v2, sync_organizations_v2
from common.constants.plugin_ids import PluginID
@pytest.mark.django_db
def test_auth_success(make_organization_and_user_with_plugin_token, make_user_auth_headers):
organization, user, token = make_organization_and_user_with_plugin_token()
client = APIClient()
auth_headers = make_user_auth_headers(user, token)
del auth_headers["HTTP_X-Grafana-Context"]
with patch("apps.grafana_plugin.views.sync_v2.SyncV2View.do_sync", return_value=organization) as mock_sync:
response = client.post(reverse("grafana-plugin:sync-v2"), format="json", **auth_headers)
assert response.status_code == status.HTTP_200_OK
assert mock_sync.called
@pytest.mark.django_db
def test_invalid_auth(make_organization_and_user_with_plugin_token, make_user_auth_headers):
organization, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
client = APIClient()
auth_headers = make_user_auth_headers(user, "invalid-token")
with patch("apps.grafana_plugin.views.sync_v2.SyncV2View.do_sync", return_value=organization) as mock_sync:
response = client.post(reverse("grafana-plugin:sync-v2"), format="json", **auth_headers)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert not mock_sync.called
auth_headers = make_user_auth_headers(None, token, organization=organization)
del auth_headers["HTTP_X-Instance-Context"]
with patch("apps.grafana_plugin.views.sync_v2.SyncV2View.do_sync", return_value=organization) as mock_sync:
response = client.post(reverse("grafana-plugin:sync-v2"), format="json", **auth_headers)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert not mock_sync.called
@pytest.mark.parametrize(
"api_token, sync_called",
[
("", False),
("abc", False),
("glsa_abcdefghijklmnopqrstuvwxyz", True),
],
)
@pytest.mark.django_db
def test_skip_org_without_api_token(make_organization, api_token, sync_called):
make_organization(api_token=api_token)
with patch(
"apps.grafana_plugin.helpers.GrafanaAPIClient.sync",
return_value=(
None,
{
"url": "",
"connected": True,
"status_code": status.HTTP_200_OK,
"message": "",
},
),
):
with patch(
"apps.grafana_plugin.tasks.sync_v2.sync_organizations_v2.apply_async", return_value=None
) as mock_sync:
start_sync_organizations_v2()
assert mock_sync.called == sync_called
@pytest.mark.parametrize("format", [("json"), ("gzip")])
@pytest.mark.django_db
def test_sync_v2_content_encoding(
make_organization_and_user_with_plugin_token, make_user_auth_headers, settings, format
):
organization, user, token = make_organization_and_user_with_plugin_token()
settings.LICENSE = settings.CLOUD_LICENSE_NAME
client = APIClient()
headers = make_user_auth_headers(None, token, organization=organization)
data = SyncData(
users=[
SyncUser(
id=user.user_id,
name=user.username,
login=user.username,
email=user.email,
role="Admin",
avatar_url="",
permissions=[],
teams=[],
)
],
teams=[],
team_members={},
settings=SyncSettings(
stack_id=organization.stack_id,
org_id=organization.org_id,
license=settings.CLOUD_LICENSE_NAME,
oncall_api_url="http://localhost",
oncall_token="",
grafana_url="http://localhost",
grafana_token="fake_token",
rbac_enabled=False,
incident_enabled=False,
incident_backend_url="",
labels_enabled=False,
irm_enabled=False,
),
)
payload = asdict(data)
headers["HTTP_Content-Type"] = "application/json"
url = reverse("grafana-plugin:sync-v2")
with patch("apps.grafana_plugin.views.sync_v2.apply_sync_data") as mock_sync:
if format == "gzip":
headers["HTTP_Content-Encoding"] = "gzip"
json_data = json.dumps(payload)
payload = gzip.compress(json_data.encode("utf-8"))
response = client.generic("POST", url, data=payload, **headers)
else:
response = client.post(url, format=format, data=payload, **headers)
assert response.status_code == status.HTTP_200_OK
mock_sync.assert_called()
chore: add `pytest-socket` library + disable network calls in tests (#5315) # What this PR does Inspired by [this discussion](https://github.com/grafana/oncall/pull/5307#discussion_r1862449480). _tldr;_ ensures that if any of our tests try making an external network call, they will fail. Setup an example test: ```python def test_external_network_call(): import requests response = requests.get('https://www.example.com') assert response.status_code == 200 ``` and it worked (failed; [example CI test run](https://github.com/grafana/oncall/actions/runs/12106416991/job/33752144727?pr=5315#step:6:389)) as expected: ```bash __________________________ test_external_network_call __________________________ def test_external_network_call(): import requests > response = requests.get('https://www.example.com') requests = <module 'requests' from '/opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/__init__.py'> apps/test_joey.py:4: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/api.py:73: in get return request("get", url, params=params, **kwargs) kwargs = {} params = None url = 'https://www.example.com' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/api.py:59: in request return session.request(method=method, url=url, **kwargs) kwargs = {'params': None} method = 'get' session = <requests.sessions.Session object at 0x7f10ebaada90> url = 'https://www.example.com' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/sessions.py:589: in request resp = self.send(prep, **send_kwargs) allow_redirects = True auth = None cert = None cookies = None data = None files = None headers = None hooks = None json = None method = 'get' params = None prep = <PreparedRequest [GET]> proxies = {} req = <Request [GET]> self = <requests.sessions.Session object at 0x7f10ebaada90> send_kwargs = {'allow_redirects': True, 'cert': None, 'proxies': OrderedDict(), 'stream': False, ...} settings = {'cert': None, 'proxies': OrderedDict(), 'stream': False, 'verify': True} stream = None timeout = None url = 'https://www.example.com' verify = None /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/sessions.py:703: in send r = adapter.send(request, **kwargs) adapter = <requests.adapters.HTTPAdapter object at 0x7f10ebaada30> allow_redirects = True hooks = {'response': []} kwargs = {'cert': None, 'proxies': OrderedDict(), 'stream': False, 'timeout': None, ...} request = <PreparedRequest [GET]> self = <requests.sessions.Session object at 0x7f10ebaada90> start = 1733064371.649901 stream = False /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/adapters.py:667: in send resp = conn.urlopen( cert = None chunked = False conn = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> proxies = OrderedDict() request = <PreparedRequest [GET]> self = <requests.adapters.HTTPAdapter object at 0x7f10ebaada30> stream = False timeout = Timeout(connect=None, read=None, total=None) url = '/' verify = True /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connectionpool.py:715: in urlopen httplib_response = self._make_request( assert_same_host = False body = None body_pos = None chunked = False clean_exit = False conn = None destination_scheme = None err = None headers = {'User-Agent': 'python-requests/2.32.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'} http_tunnel_required = False is_new_proxy_conn = False method = 'GET' parsed_url = Url(scheme=None, auth=None, host=None, port=None, path='/', query=None, fragment=None) pool_timeout = None redirect = False release_conn = False release_this_conn = True response_kw = {'decode_content': False, 'preload_content': False} retries = Retry(total=0, connect=None, read=False, redirect=None, status=None) self = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> timeout = Timeout(connect=None, read=None, total=None) timeout_obj = Timeout(connect=None, read=None, total=None) url = '/' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connectionpool.py:404: in _make_request self._validate_conn(conn) chunked = False conn = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> httplib_request_kw = {'body': None, 'headers': {'User-Agent': 'python-requests/2.32.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}} method = 'GET' self = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> timeout = Timeout(connect=None, read=None, total=None) timeout_obj = Timeout(connect=None, read=None, total=None) url = '/' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connectionpool.py:1060: in _validate_conn conn.connect() __class__ = <class 'urllib3.connectionpool.HTTPSConnectionPool'> conn = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> self = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connection.py:363: in connect self.sock = conn = self._new_conn() self = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connection.py:174: in _new_conn conn = connection.create_connection( extra_kw = {'socket_options': [(6, 1, 1)]} self = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/util/connection.py:85: in create_connection sock.connect(sa) address = ('www.example.com', 443) af = <AddressFamily.AF_INET: 2> canonname = '' err = None family = <AddressFamily.AF_UNSPEC: 0> host = 'www.example.com' port = 443 proto = 6 res = (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('93.184.215.14', 443)) sa = ('93.184.215.14', 443) sock = <socket.socket fd=12, family=2, type=1, proto=6, laddr=('0.0.0.0', 0)> socket_options = [(6, 1, 1)] socktype = <SocketKind.SOCK_STREAM: 1> source_address = None timeout = None _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ inst = <socket.socket fd=12, family=2, type=1, proto=6, laddr=('0.0.0.0', 0)> args = (('93.184.215.14', 443),), host = '93.184.215.14' def guarded_connect(inst, *args): host = host_from_connect_args(args) if host in allowed_ip_hosts_and_hostnames or ( _is_unix_socket(inst.family) and allow_unix_socket ): return _true_connect(inst, *args) > raise SocketConnectBlockedError(allowed_list, host) E pytest_socket.SocketConnectBlockedError: A test tried to use socket.socket.connect() with host "93.184.215.14" (allowed: "calendar.google.com (142.251.167.100,142.251.167.101,142.251.167.102,142.251.167.113,142.251.167.138,142.251.167.139,2607:f8b0:4004:c09::65,2607:f8b0:4004:c09::66,2607:f8b0:4004:c09::71,2607:f8b0:4004:c09::8b),localhost (127.0.0.1,::1),oncall-dev-mariadb ()"). allow_unix_socket = False allowed_ip_hosts_and_hostnames = {'127.0.0.1', '142.251.167.100', '142.251.167.101', '142.251.167.102', '142.251.167.113', '142.251.167.138', ...} allowed_list = ['calendar.google.com (142.251.167.100,142.251.167.101,142.251.167.102,142.251.167.113,142.251.167.138,142.251.167.139...8b0:4004:c09::66,2607:f8b0:4004:c09::71,2607:f8b0:4004:c09::8b)', 'localhost (127.0.0.1,::1)', 'oncall-dev-mariadb ()'] args = (('93.184.215.14', 443),) host = '93.184.215.14' inst = <socket.socket fd=12, family=2, type=1, proto=6, laddr=('0.0.0.0', 0)> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/pytest_socket.py:252: SocketConnectBlockedError ``` ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
2024-12-02 10:53:18 -05:00
@patch("apps.grafana_plugin.helpers.client.GrafanaAPIClient.check_token", return_value=(None, {"connected": True}))
@pytest.mark.parametrize(
"irm_enabled,expected",
[
(True, True),
(False, False),
],
)
@pytest.mark.django_db
def test_sync_v2_irm_enabled(
chore: add `pytest-socket` library + disable network calls in tests (#5315) # What this PR does Inspired by [this discussion](https://github.com/grafana/oncall/pull/5307#discussion_r1862449480). _tldr;_ ensures that if any of our tests try making an external network call, they will fail. Setup an example test: ```python def test_external_network_call(): import requests response = requests.get('https://www.example.com') assert response.status_code == 200 ``` and it worked (failed; [example CI test run](https://github.com/grafana/oncall/actions/runs/12106416991/job/33752144727?pr=5315#step:6:389)) as expected: ```bash __________________________ test_external_network_call __________________________ def test_external_network_call(): import requests > response = requests.get('https://www.example.com') requests = <module 'requests' from '/opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/__init__.py'> apps/test_joey.py:4: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/api.py:73: in get return request("get", url, params=params, **kwargs) kwargs = {} params = None url = 'https://www.example.com' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/api.py:59: in request return session.request(method=method, url=url, **kwargs) kwargs = {'params': None} method = 'get' session = <requests.sessions.Session object at 0x7f10ebaada90> url = 'https://www.example.com' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/sessions.py:589: in request resp = self.send(prep, **send_kwargs) allow_redirects = True auth = None cert = None cookies = None data = None files = None headers = None hooks = None json = None method = 'get' params = None prep = <PreparedRequest [GET]> proxies = {} req = <Request [GET]> self = <requests.sessions.Session object at 0x7f10ebaada90> send_kwargs = {'allow_redirects': True, 'cert': None, 'proxies': OrderedDict(), 'stream': False, ...} settings = {'cert': None, 'proxies': OrderedDict(), 'stream': False, 'verify': True} stream = None timeout = None url = 'https://www.example.com' verify = None /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/sessions.py:703: in send r = adapter.send(request, **kwargs) adapter = <requests.adapters.HTTPAdapter object at 0x7f10ebaada30> allow_redirects = True hooks = {'response': []} kwargs = {'cert': None, 'proxies': OrderedDict(), 'stream': False, 'timeout': None, ...} request = <PreparedRequest [GET]> self = <requests.sessions.Session object at 0x7f10ebaada90> start = 1733064371.649901 stream = False /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/requests/adapters.py:667: in send resp = conn.urlopen( cert = None chunked = False conn = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> proxies = OrderedDict() request = <PreparedRequest [GET]> self = <requests.adapters.HTTPAdapter object at 0x7f10ebaada30> stream = False timeout = Timeout(connect=None, read=None, total=None) url = '/' verify = True /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connectionpool.py:715: in urlopen httplib_response = self._make_request( assert_same_host = False body = None body_pos = None chunked = False clean_exit = False conn = None destination_scheme = None err = None headers = {'User-Agent': 'python-requests/2.32.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'} http_tunnel_required = False is_new_proxy_conn = False method = 'GET' parsed_url = Url(scheme=None, auth=None, host=None, port=None, path='/', query=None, fragment=None) pool_timeout = None redirect = False release_conn = False release_this_conn = True response_kw = {'decode_content': False, 'preload_content': False} retries = Retry(total=0, connect=None, read=False, redirect=None, status=None) self = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> timeout = Timeout(connect=None, read=None, total=None) timeout_obj = Timeout(connect=None, read=None, total=None) url = '/' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connectionpool.py:404: in _make_request self._validate_conn(conn) chunked = False conn = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> httplib_request_kw = {'body': None, 'headers': {'User-Agent': 'python-requests/2.32.3', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}} method = 'GET' self = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> timeout = Timeout(connect=None, read=None, total=None) timeout_obj = Timeout(connect=None, read=None, total=None) url = '/' /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connectionpool.py:1060: in _validate_conn conn.connect() __class__ = <class 'urllib3.connectionpool.HTTPSConnectionPool'> conn = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> self = <urllib3.connectionpool.HTTPSConnectionPool object at 0x7f10ebaadd30> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connection.py:363: in connect self.sock = conn = self._new_conn() self = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/connection.py:174: in _new_conn conn = connection.create_connection( extra_kw = {'socket_options': [(6, 1, 1)]} self = <urllib3.connection.HTTPSConnection object at 0x7f10ebaadd60> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/urllib3/util/connection.py:85: in create_connection sock.connect(sa) address = ('www.example.com', 443) af = <AddressFamily.AF_INET: 2> canonname = '' err = None family = <AddressFamily.AF_UNSPEC: 0> host = 'www.example.com' port = 443 proto = 6 res = (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('93.184.215.14', 443)) sa = ('93.184.215.14', 443) sock = <socket.socket fd=12, family=2, type=1, proto=6, laddr=('0.0.0.0', 0)> socket_options = [(6, 1, 1)] socktype = <SocketKind.SOCK_STREAM: 1> source_address = None timeout = None _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ inst = <socket.socket fd=12, family=2, type=1, proto=6, laddr=('0.0.0.0', 0)> args = (('93.184.215.14', 443),), host = '93.184.215.14' def guarded_connect(inst, *args): host = host_from_connect_args(args) if host in allowed_ip_hosts_and_hostnames or ( _is_unix_socket(inst.family) and allow_unix_socket ): return _true_connect(inst, *args) > raise SocketConnectBlockedError(allowed_list, host) E pytest_socket.SocketConnectBlockedError: A test tried to use socket.socket.connect() with host "93.184.215.14" (allowed: "calendar.google.com (142.251.167.100,142.251.167.101,142.251.167.102,142.251.167.113,142.251.167.138,142.251.167.139,2607:f8b0:4004:c09::65,2607:f8b0:4004:c09::66,2607:f8b0:4004:c09::71,2607:f8b0:4004:c09::8b),localhost (127.0.0.1,::1),oncall-dev-mariadb ()"). allow_unix_socket = False allowed_ip_hosts_and_hostnames = {'127.0.0.1', '142.251.167.100', '142.251.167.101', '142.251.167.102', '142.251.167.113', '142.251.167.138', ...} allowed_list = ['calendar.google.com (142.251.167.100,142.251.167.101,142.251.167.102,142.251.167.113,142.251.167.138,142.251.167.139...8b0:4004:c09::66,2607:f8b0:4004:c09::71,2607:f8b0:4004:c09::8b)', 'localhost (127.0.0.1,::1)', 'oncall-dev-mariadb ()'] args = (('93.184.215.14', 443),) host = '93.184.215.14' inst = <socket.socket fd=12, family=2, type=1, proto=6, laddr=('0.0.0.0', 0)> /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/pytest_socket.py:252: SocketConnectBlockedError ``` ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
2024-12-02 10:53:18 -05:00
# mock this out so that we're not making a real network call, the sync v2 endpoint ends up calling
# user_management.sync._sync_organization which calls GrafanaApiClient.check_token
_mock_grafana_api_client_check_token,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
settings,
irm_enabled,
expected,
):
settings.LICENSE = settings.CLOUD_LICENSE_NAME
organization, _, token = make_organization_and_user_with_plugin_token()
assert organization.is_grafana_irm_enabled is False
client = APIClient()
headers = make_user_auth_headers(None, token, organization=organization)
url = reverse("grafana-plugin:sync-v2")
data = SyncData(
users=[],
teams=[],
team_members={},
settings=SyncSettings(
stack_id=organization.stack_id,
org_id=organization.org_id,
license=settings.CLOUD_LICENSE_NAME,
oncall_api_url="http://localhost",
oncall_token="",
grafana_url="http://localhost",
grafana_token="fake_token",
rbac_enabled=False,
incident_enabled=False,
incident_backend_url="",
labels_enabled=False,
irm_enabled=irm_enabled,
),
)
response = client.post(url, format="json", data=asdict(data), **headers)
assert response.status_code == status.HTTP_200_OK
organization.refresh_from_db()
assert organization.is_grafana_irm_enabled == expected
@patch("apps.grafana_plugin.helpers.client.GrafanaAPIClient.check_token", return_value=(None, {"connected": True}))
@pytest.mark.django_db
def test_sync_v2_none_values(
# mock this out so that we're not making a real network call, the sync v2 endpoint ends up calling
# user_management.sync._sync_organization which calls GrafanaApiClient.check_token
_mock_grafana_api_client_check_token,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
settings,
):
settings.LICENSE = settings.CLOUD_LICENSE_NAME
organization, _, token = make_organization_and_user_with_plugin_token()
client = APIClient()
headers = make_user_auth_headers(None, token, organization=organization)
url = reverse("grafana-plugin:sync-v2")
data = SyncData(
users=None,
teams=None,
team_members={},
settings=SyncSettings(
stack_id=organization.stack_id,
org_id=organization.org_id,
license=settings.CLOUD_LICENSE_NAME,
oncall_api_url="http://localhost",
oncall_token="",
grafana_url="http://localhost",
grafana_token="fake_token",
rbac_enabled=False,
incident_enabled=False,
incident_backend_url="",
labels_enabled=False,
irm_enabled=False,
),
)
response = client.post(url, format="json", data=asdict(data), **headers)
assert response.status_code == status.HTTP_200_OK
@pytest.mark.parametrize(
"test_team, validation_pass",
[
({"team_id": 1, "name": "Test Team", "email": "", "avatar_url": ""}, True),
({"team_id": 1, "name": "", "email": "", "avatar_url": ""}, False),
({"name": "ABC", "email": "", "avatar_url": ""}, False),
({"team_id": 1, "name": "ABC", "email": "test@example.com", "avatar_url": ""}, True),
({"team_id": 1, "name": "123", "email": "<invalid email>", "avatar_url": ""}, True),
],
)
@pytest.mark.django_db
def test_sync_team_serialization(test_team, validation_pass):
serializer = SyncTeamSerializer(data=test_team)
validation_error = None
try:
serializer.is_valid(raise_exception=True)
except ValidationError as e:
validation_error = e
assert (validation_error is None) == validation_pass
@pytest.mark.django_db
def test_sync_grafana_url_serialization():
data = {
"stack_id": 123,
"org_id": 321,
"license": "OSS",
"oncall_api_url": "http://localhost",
"oncall_token": "",
"grafana_url": "http://localhost/",
"grafana_token": "fake_token",
"rbac_enabled": False,
"incident_enabled": False,
"incident_backend_url": "",
"labels_enabled": False,
"irm_enabled": False,
}
serializer = SyncOnCallSettingsSerializer(data=data)
serializer.is_valid(raise_exception=True)
cleaned_data = serializer.save()
assert cleaned_data.grafana_url == "http://localhost"
@pytest.mark.django_db
def test_sync_batch_tasks(make_organization, settings):
settings.SYNC_V2_MAX_TASKS = 2
settings.SYNC_V2_PERIOD_SECONDS = 10
settings.SYNC_V2_BATCH_SIZE = 2
for _ in range(9):
make_organization(api_token="glsa_abcdefghijklmnopqrstuvwxyz")
expected_calls = [
call(size=2, countdown=0),
call(size=2, countdown=0),
call(size=2, countdown=10),
call(size=2, countdown=10),
call(size=1, countdown=20),
]
with patch("apps.grafana_plugin.tasks.sync_v2.sync_organizations_v2.apply_async", return_value=None) as mock_sync:
start_sync_organizations_v2()
def check_call(actual, expected):
return (
len(actual.args[0][0]) == expected.kwargs["size"]
and actual.kwargs["countdown"] == expected.kwargs["countdown"]
)
for actual_call, expected_call in zip(mock_sync.call_args_list, expected_calls):
assert check_call(actual_call, expected_call)
assert mock_sync.call_count == len(expected_calls)
@patch(
"apps.grafana_plugin.tasks.sync_v2.GrafanaAPIClient.api_post",
return_value=(None, {"status_code": status.HTTP_200_OK}),
)
@pytest.mark.parametrize(
"is_grafana_irm_enabled,expected",
[
(True, PluginID.IRM),
(False, PluginID.ONCALL),
],
)
@pytest.mark.django_db
def test_sync_organizations_v2_calls_right_backend_plugin_sync_endpoint(
mocked_grafana_api_client_api_post, make_organization, is_grafana_irm_enabled, expected
):
org = make_organization(is_grafana_irm_enabled=is_grafana_irm_enabled)
sync_organizations_v2(org_ids=[org.pk])
mocked_grafana_api_client_api_post.assert_called_once_with(f"api/plugins/{expected}/resources/plugin/sync")