2024-06-11 10:53:17 -04:00
|
|
|
from unittest import mock
|
2024-09-18 07:16:41 +08:00
|
|
|
from unittest.mock import Mock, PropertyMock, patch
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
import pytest
|
2023-02-21 09:47:52 +01:00
|
|
|
from django.core.cache import cache
|
2022-06-03 08:09:47 -06:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
2023-02-21 20:17:06 +01:00
|
|
|
from django.test import override_settings
|
2022-06-03 08:09:47 -06:00
|
|
|
from django.urls import reverse
|
|
|
|
|
from django.utils import timezone
|
|
|
|
|
from rest_framework import status
|
|
|
|
|
from rest_framework.response import Response
|
|
|
|
|
from rest_framework.test import APIClient
|
|
|
|
|
|
2023-06-16 12:00:14 +02:00
|
|
|
from apps.api.permissions import GrafanaAPIPermission, LegacyAccessControlRole, RBACPermission
|
2024-01-31 10:13:08 -03:00
|
|
|
from apps.api.serializers.user import UserHiddenFieldsSerializer
|
2024-02-09 17:36:12 -03:00
|
|
|
from apps.api.views.user import UPCOMING_SHIFTS_DEFAULT_DAYS
|
2022-06-03 08:09:47 -06:00
|
|
|
from apps.base.models import UserNotificationPolicy
|
2023-05-24 14:27:48 +08:00
|
|
|
from apps.phone_notifications.exceptions import FailedToFinishVerification
|
2023-04-05 17:19:02 -03:00
|
|
|
from apps.schedules.models import CustomOnCallShift, OnCallScheduleWeb
|
2022-07-11 13:16:56 +01:00
|
|
|
from apps.user_management.models.user import default_working_hours
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
2023-02-21 09:47:52 +01:00
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
|
def clear_cache():
|
|
|
|
|
# Ratelimit keys are stored in cache, clean to prevent ratelimits
|
|
|
|
|
cache.clear()
|
|
|
|
|
|
|
|
|
|
|
2023-07-21 17:38:58 +02:00
|
|
|
@pytest.mark.django_db
|
2024-01-29 14:41:20 -03:00
|
|
|
def test_current_user(
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
make_on_call_shift,
|
|
|
|
|
):
|
2023-07-21 17:38:58 +02:00
|
|
|
organization, user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:api-user")
|
|
|
|
|
|
|
|
|
|
expected_response = {
|
|
|
|
|
"pk": user.public_primary_key,
|
|
|
|
|
"organization": {"pk": organization.public_primary_key, "name": organization.org_title},
|
|
|
|
|
"current_team": None,
|
|
|
|
|
"email": user.email,
|
|
|
|
|
"hide_phone_number": False,
|
|
|
|
|
"username": user.username,
|
|
|
|
|
"name": user.name,
|
|
|
|
|
"role": user.role,
|
|
|
|
|
"rbac_permissions": user.permissions,
|
|
|
|
|
"timezone": None,
|
|
|
|
|
"working_hours": default_working_hours(),
|
2024-01-29 14:41:20 -03:00
|
|
|
"is_currently_oncall": False,
|
2023-07-21 17:38:58 +02:00
|
|
|
"unverified_phone_number": None,
|
|
|
|
|
"verified_phone_number": None,
|
|
|
|
|
"telegram_configuration": None,
|
|
|
|
|
"messaging_backends": {
|
|
|
|
|
"TESTONLY": {
|
|
|
|
|
"user": user.username,
|
|
|
|
|
}
|
|
|
|
|
},
|
2024-06-11 10:53:17 -04:00
|
|
|
"cloud_connection_status": mock.ANY,
|
2023-07-21 17:38:58 +02:00
|
|
|
"notification_chain_verbal": {"default": "", "important": ""},
|
|
|
|
|
"slack_user_identity": None,
|
|
|
|
|
"avatar": user.avatar_url,
|
2024-08-15 16:20:55 +02:00
|
|
|
"avatar_full": user.avatar_full_url(organization),
|
2024-04-02 14:59:03 -04:00
|
|
|
"has_google_oauth2_connected": False,
|
|
|
|
|
"google_calendar_settings": None,
|
2024-08-14 18:02:34 -04:00
|
|
|
"google_oauth2_token_is_missing_scopes": False,
|
2023-07-21 17:38:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json() == expected_response
|
|
|
|
|
|
2024-01-29 14:41:20 -03:00
|
|
|
# current user is on-call
|
|
|
|
|
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
schedule = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
|
|
|
|
on_call_shift = make_on_call_shift(
|
|
|
|
|
organization=organization,
|
|
|
|
|
shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
|
|
|
|
|
start=today,
|
|
|
|
|
rotation_start=today,
|
|
|
|
|
duration=timezone.timedelta(seconds=24 * 60 * 60),
|
|
|
|
|
priority_level=1,
|
|
|
|
|
frequency=CustomOnCallShift.FREQUENCY_DAILY,
|
|
|
|
|
schedule=schedule,
|
|
|
|
|
)
|
|
|
|
|
on_call_shift.add_rolling_users([[user]])
|
|
|
|
|
schedule.refresh_ical_file()
|
|
|
|
|
schedule.refresh_ical_final_schedule()
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
expected_response["is_currently_oncall"] = True
|
|
|
|
|
assert response.json() == expected_response
|
|
|
|
|
|
2023-07-21 17:38:58 +02:00
|
|
|
data_to_update = {"hide_phone_number": True}
|
|
|
|
|
|
|
|
|
|
response = client.put(url, data=data_to_update, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json() == expected_response | data_to_update
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_update_user(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_team,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
team = make_team(organization)
|
|
|
|
|
team.users.add(admin)
|
|
|
|
|
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
data = {
|
|
|
|
|
"unverified_phone_number": "+79123456789",
|
|
|
|
|
"current_team": team.public_primary_key,
|
|
|
|
|
}
|
|
|
|
|
response = client.put(url, data, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json()["unverified_phone_number"] == data["unverified_phone_number"]
|
|
|
|
|
assert response.json()["current_team"] == data["current_team"]
|
|
|
|
|
|
|
|
|
|
|
2024-04-02 14:59:03 -04:00
|
|
|
@pytest.mark.parametrize("oncall_schedules_to_consider_for_shift_swaps", [True, False])
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_update_user_google_calendar_settings(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
oncall_schedules_to_consider_for_shift_swaps,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
schedule1 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
|
|
|
|
schedule2 = make_schedule(organization, schedule_class=OnCallScheduleWeb)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
|
|
|
|
|
schedule_public_primary_keys = (
|
|
|
|
|
[schedule1.public_primary_key, schedule2.public_primary_key]
|
|
|
|
|
if oncall_schedules_to_consider_for_shift_swaps
|
|
|
|
|
else []
|
|
|
|
|
)
|
|
|
|
|
data = {
|
|
|
|
|
"google_calendar_settings": {
|
|
|
|
|
"oncall_schedules_to_consider_for_shift_swaps": schedule_public_primary_keys,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.put(url, data, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json()["google_calendar_settings"] == {
|
|
|
|
|
"oncall_schedules_to_consider_for_shift_swaps": schedule_public_primary_keys,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
Fix warnings when running backend tests (#2079)
# What this PR does
- update `make test` to always use `settings.ci-test`. Right now it will
use whatever the value of `DJANGO_SETTINGS_MODULE` is in
`./dev/.env.dev`, which causes ~45 tests to fail
- Fix several Python warnings that we see when running the tests
```bash
RemovedInDjango40Warning: The providing_args argument is deprecated. As it is purely documentational, it has no replacement. If you rely on this argument as documentation, you can move the text to a code comment or docstring.
alert_create_signal = django.dispatch.Signal(
```
```bash
PytestCollectionWarning: cannot collect test class 'TestOnlyBackend' because it has a __init__ constructor (from: apps/api/tests/test_alert_receive_channel_template.py)
class TestOnlyBackend(BaseMessagingBackend):
```
```bash
DeprecationWarning: The parameter 'use_aliases' in emoji.emojize() is deprecated and will be removed in version 2.0.0. Use language='alias' instead.
To hide this warning, pin/downgrade the package to 'emoji~=1.6.3'
return emoji.emojize(self.verbal_name, use_aliases=True)
```
```bash
DateTimeField CustomOnCallShift.start received a naive datetime (2023-06-01 12:53:12) while time zone support is active.
warnings.warn("DateTimeField %s received a naive datetime (%s)"
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
/etc/app/apps/twilioapp/tests/test_phone_calls.py:173: DeprecationWarning: The 'text' argument to find()-type methods is deprecated. Use 'string' instead.
content = BeautifulSoup(content, features="html.parser").findAll(text=True)
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
apps/twilioapp/tests/test_phone_calls.py::test_wrong_pressed_digit
/usr/local/lib/python3.11/site-packages/bs4/builder/__init__.py:545: XMLParsedAsHTMLWarning: It looks like you're parsing an XML document using an HTML parser. If this really is an HTML document (maybe it's XHTML?), you can ignore or filter this warning. If it's XML, you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the lxml package installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_forbidden_requests
/usr/local/lib/python3.11/site-packages/social_django/urls.py:15: RemovedInDjango40Warning: django.conf.urls.url() is deprecated in favor of django.urls.re_path().
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
```
```bash
apps/twilioapp/tests/test_phone_calls.py: 66 warnings
/usr/local/lib/python3.11/site-packages/debug_toolbar/utils.py:255: DeprecationWarning: currentThread() is deprecated, use current_thread() instead
thread = threading.currentThread()
```
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-06 20:38:00 +02:00
|
|
|
@override_settings(GRAFANA_CLOUD_NOTIFICATIONS_ENABLED=False)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_update_user_cant_change_email_and_username(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
phone_number = "+79123456789"
|
|
|
|
|
data = {
|
|
|
|
|
"unverified_phone_number": phone_number,
|
|
|
|
|
"email": "test@amixr.io",
|
|
|
|
|
"username": "bob",
|
|
|
|
|
}
|
|
|
|
|
expected_response = {
|
|
|
|
|
"pk": admin.public_primary_key,
|
|
|
|
|
"organization": {"pk": organization.public_primary_key, "name": organization.org_title},
|
|
|
|
|
"current_team": None,
|
|
|
|
|
"email": admin.email,
|
2022-09-09 12:42:40 +05:00
|
|
|
"hide_phone_number": False,
|
2022-06-03 08:09:47 -06:00
|
|
|
"username": admin.username,
|
2022-11-28 15:52:31 +00:00
|
|
|
"name": admin.name,
|
2022-06-03 08:09:47 -06:00
|
|
|
"role": admin.role,
|
2022-07-11 13:16:56 +01:00
|
|
|
"timezone": None,
|
|
|
|
|
"working_hours": default_working_hours(),
|
2024-01-29 14:41:20 -03:00
|
|
|
"is_currently_oncall": False,
|
2022-06-03 08:09:47 -06:00
|
|
|
"unverified_phone_number": phone_number,
|
|
|
|
|
"verified_phone_number": None,
|
|
|
|
|
"telegram_configuration": None,
|
|
|
|
|
"messaging_backends": {
|
|
|
|
|
"TESTONLY": {
|
|
|
|
|
"user": admin.username,
|
|
|
|
|
}
|
|
|
|
|
},
|
Fix warnings when running backend tests (#2079)
# What this PR does
- update `make test` to always use `settings.ci-test`. Right now it will
use whatever the value of `DJANGO_SETTINGS_MODULE` is in
`./dev/.env.dev`, which causes ~45 tests to fail
- Fix several Python warnings that we see when running the tests
```bash
RemovedInDjango40Warning: The providing_args argument is deprecated. As it is purely documentational, it has no replacement. If you rely on this argument as documentation, you can move the text to a code comment or docstring.
alert_create_signal = django.dispatch.Signal(
```
```bash
PytestCollectionWarning: cannot collect test class 'TestOnlyBackend' because it has a __init__ constructor (from: apps/api/tests/test_alert_receive_channel_template.py)
class TestOnlyBackend(BaseMessagingBackend):
```
```bash
DeprecationWarning: The parameter 'use_aliases' in emoji.emojize() is deprecated and will be removed in version 2.0.0. Use language='alias' instead.
To hide this warning, pin/downgrade the package to 'emoji~=1.6.3'
return emoji.emojize(self.verbal_name, use_aliases=True)
```
```bash
DateTimeField CustomOnCallShift.start received a naive datetime (2023-06-01 12:53:12) while time zone support is active.
warnings.warn("DateTimeField %s received a naive datetime (%s)"
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
/etc/app/apps/twilioapp/tests/test_phone_calls.py:173: DeprecationWarning: The 'text' argument to find()-type methods is deprecated. Use 'string' instead.
content = BeautifulSoup(content, features="html.parser").findAll(text=True)
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
apps/twilioapp/tests/test_phone_calls.py::test_wrong_pressed_digit
/usr/local/lib/python3.11/site-packages/bs4/builder/__init__.py:545: XMLParsedAsHTMLWarning: It looks like you're parsing an XML document using an HTML parser. If this really is an HTML document (maybe it's XHTML?), you can ignore or filter this warning. If it's XML, you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the lxml package installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_forbidden_requests
/usr/local/lib/python3.11/site-packages/social_django/urls.py:15: RemovedInDjango40Warning: django.conf.urls.url() is deprecated in favor of django.urls.re_path().
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
```
```bash
apps/twilioapp/tests/test_phone_calls.py: 66 warnings
/usr/local/lib/python3.11/site-packages/debug_toolbar/utils.py:255: DeprecationWarning: currentThread() is deprecated, use current_thread() instead
thread = threading.currentThread()
```
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-06 20:38:00 +02:00
|
|
|
"cloud_connection_status": None,
|
2022-06-03 08:09:47 -06:00
|
|
|
"notification_chain_verbal": {"default": "", "important": ""},
|
|
|
|
|
"slack_user_identity": None,
|
|
|
|
|
"avatar": admin.avatar_url,
|
2024-08-15 16:20:55 +02:00
|
|
|
"avatar_full": admin.avatar_full_url(organization),
|
2024-04-02 14:59:03 -04:00
|
|
|
"has_google_oauth2_connected": False,
|
|
|
|
|
"google_calendar_settings": None,
|
2022-06-03 08:09:47 -06:00
|
|
|
}
|
|
|
|
|
response = client.put(url, data, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json() == expected_response
|
|
|
|
|
|
|
|
|
|
|
Fix warnings when running backend tests (#2079)
# What this PR does
- update `make test` to always use `settings.ci-test`. Right now it will
use whatever the value of `DJANGO_SETTINGS_MODULE` is in
`./dev/.env.dev`, which causes ~45 tests to fail
- Fix several Python warnings that we see when running the tests
```bash
RemovedInDjango40Warning: The providing_args argument is deprecated. As it is purely documentational, it has no replacement. If you rely on this argument as documentation, you can move the text to a code comment or docstring.
alert_create_signal = django.dispatch.Signal(
```
```bash
PytestCollectionWarning: cannot collect test class 'TestOnlyBackend' because it has a __init__ constructor (from: apps/api/tests/test_alert_receive_channel_template.py)
class TestOnlyBackend(BaseMessagingBackend):
```
```bash
DeprecationWarning: The parameter 'use_aliases' in emoji.emojize() is deprecated and will be removed in version 2.0.0. Use language='alias' instead.
To hide this warning, pin/downgrade the package to 'emoji~=1.6.3'
return emoji.emojize(self.verbal_name, use_aliases=True)
```
```bash
DateTimeField CustomOnCallShift.start received a naive datetime (2023-06-01 12:53:12) while time zone support is active.
warnings.warn("DateTimeField %s received a naive datetime (%s)"
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
/etc/app/apps/twilioapp/tests/test_phone_calls.py:173: DeprecationWarning: The 'text' argument to find()-type methods is deprecated. Use 'string' instead.
content = BeautifulSoup(content, features="html.parser").findAll(text=True)
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
apps/twilioapp/tests/test_phone_calls.py::test_wrong_pressed_digit
/usr/local/lib/python3.11/site-packages/bs4/builder/__init__.py:545: XMLParsedAsHTMLWarning: It looks like you're parsing an XML document using an HTML parser. If this really is an HTML document (maybe it's XHTML?), you can ignore or filter this warning. If it's XML, you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the lxml package installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_forbidden_requests
/usr/local/lib/python3.11/site-packages/social_django/urls.py:15: RemovedInDjango40Warning: django.conf.urls.url() is deprecated in favor of django.urls.re_path().
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
```
```bash
apps/twilioapp/tests/test_phone_calls.py: 66 warnings
/usr/local/lib/python3.11/site-packages/debug_toolbar/utils.py:255: DeprecationWarning: currentThread() is deprecated, use current_thread() instead
thread = threading.currentThread()
```
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-06 20:38:00 +02:00
|
|
|
@override_settings(GRAFANA_CLOUD_NOTIFICATIONS_ENABLED=False)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_list_users(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
2024-01-31 10:13:08 -03:00
|
|
|
admin = make_user_for_organization(organization, _verified_phone_number="1234567890")
|
2022-11-29 09:41:56 +01:00
|
|
|
editor = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
|
|
|
|
|
expected_payload = {
|
|
|
|
|
"count": 2,
|
|
|
|
|
"next": None,
|
|
|
|
|
"previous": None,
|
|
|
|
|
"results": [
|
|
|
|
|
{
|
|
|
|
|
"pk": admin.public_primary_key,
|
|
|
|
|
"organization": {"pk": organization.public_primary_key, "name": organization.org_title},
|
|
|
|
|
"current_team": None,
|
|
|
|
|
"email": admin.email,
|
2022-09-09 12:42:40 +05:00
|
|
|
"hide_phone_number": False,
|
2022-06-03 08:09:47 -06:00
|
|
|
"username": admin.username,
|
2022-11-28 15:52:31 +00:00
|
|
|
"name": admin.name,
|
2022-06-03 08:09:47 -06:00
|
|
|
"role": admin.role,
|
2022-07-11 13:16:56 +01:00
|
|
|
"timezone": None,
|
|
|
|
|
"working_hours": default_working_hours(),
|
2022-06-03 08:09:47 -06:00
|
|
|
"unverified_phone_number": None,
|
2024-01-31 10:13:08 -03:00
|
|
|
"verified_phone_number": admin.verified_phone_number,
|
2022-06-03 08:09:47 -06:00
|
|
|
"telegram_configuration": None,
|
|
|
|
|
"messaging_backends": {
|
|
|
|
|
"TESTONLY": {
|
|
|
|
|
"user": admin.username,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"notification_chain_verbal": {"default": "", "important": ""},
|
|
|
|
|
"slack_user_identity": None,
|
|
|
|
|
"avatar": admin.avatar_url,
|
2024-08-15 16:20:55 +02:00
|
|
|
"avatar_full": admin.avatar_full_url(organization),
|
Fix warnings when running backend tests (#2079)
# What this PR does
- update `make test` to always use `settings.ci-test`. Right now it will
use whatever the value of `DJANGO_SETTINGS_MODULE` is in
`./dev/.env.dev`, which causes ~45 tests to fail
- Fix several Python warnings that we see when running the tests
```bash
RemovedInDjango40Warning: The providing_args argument is deprecated. As it is purely documentational, it has no replacement. If you rely on this argument as documentation, you can move the text to a code comment or docstring.
alert_create_signal = django.dispatch.Signal(
```
```bash
PytestCollectionWarning: cannot collect test class 'TestOnlyBackend' because it has a __init__ constructor (from: apps/api/tests/test_alert_receive_channel_template.py)
class TestOnlyBackend(BaseMessagingBackend):
```
```bash
DeprecationWarning: The parameter 'use_aliases' in emoji.emojize() is deprecated and will be removed in version 2.0.0. Use language='alias' instead.
To hide this warning, pin/downgrade the package to 'emoji~=1.6.3'
return emoji.emojize(self.verbal_name, use_aliases=True)
```
```bash
DateTimeField CustomOnCallShift.start received a naive datetime (2023-06-01 12:53:12) while time zone support is active.
warnings.warn("DateTimeField %s received a naive datetime (%s)"
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
/etc/app/apps/twilioapp/tests/test_phone_calls.py:173: DeprecationWarning: The 'text' argument to find()-type methods is deprecated. Use 'string' instead.
content = BeautifulSoup(content, features="html.parser").findAll(text=True)
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
apps/twilioapp/tests/test_phone_calls.py::test_wrong_pressed_digit
/usr/local/lib/python3.11/site-packages/bs4/builder/__init__.py:545: XMLParsedAsHTMLWarning: It looks like you're parsing an XML document using an HTML parser. If this really is an HTML document (maybe it's XHTML?), you can ignore or filter this warning. If it's XML, you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the lxml package installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_forbidden_requests
/usr/local/lib/python3.11/site-packages/social_django/urls.py:15: RemovedInDjango40Warning: django.conf.urls.url() is deprecated in favor of django.urls.re_path().
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
```
```bash
apps/twilioapp/tests/test_phone_calls.py: 66 warnings
/usr/local/lib/python3.11/site-packages/debug_toolbar/utils.py:255: DeprecationWarning: currentThread() is deprecated, use current_thread() instead
thread = threading.currentThread()
```
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-06 20:38:00 +02:00
|
|
|
"cloud_connection_status": None,
|
2024-04-02 14:59:03 -04:00
|
|
|
"has_google_oauth2_connected": False,
|
2022-06-03 08:09:47 -06:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"pk": editor.public_primary_key,
|
|
|
|
|
"organization": {"pk": organization.public_primary_key, "name": organization.org_title},
|
|
|
|
|
"current_team": None,
|
|
|
|
|
"email": editor.email,
|
2022-09-09 12:42:40 +05:00
|
|
|
"hide_phone_number": False,
|
2022-06-03 08:09:47 -06:00
|
|
|
"username": editor.username,
|
2022-11-28 15:52:31 +00:00
|
|
|
"name": editor.name,
|
2022-06-03 08:09:47 -06:00
|
|
|
"role": editor.role,
|
2022-07-11 13:16:56 +01:00
|
|
|
"timezone": None,
|
|
|
|
|
"working_hours": default_working_hours(),
|
2022-06-03 08:09:47 -06:00
|
|
|
"unverified_phone_number": None,
|
|
|
|
|
"verified_phone_number": None,
|
|
|
|
|
"telegram_configuration": None,
|
|
|
|
|
"messaging_backends": {
|
|
|
|
|
"TESTONLY": {
|
|
|
|
|
"user": editor.username,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"notification_chain_verbal": {"default": "", "important": ""},
|
|
|
|
|
"slack_user_identity": None,
|
|
|
|
|
"avatar": editor.avatar_url,
|
2024-08-15 16:20:55 +02:00
|
|
|
"avatar_full": editor.avatar_full_url(organization),
|
Fix warnings when running backend tests (#2079)
# What this PR does
- update `make test` to always use `settings.ci-test`. Right now it will
use whatever the value of `DJANGO_SETTINGS_MODULE` is in
`./dev/.env.dev`, which causes ~45 tests to fail
- Fix several Python warnings that we see when running the tests
```bash
RemovedInDjango40Warning: The providing_args argument is deprecated. As it is purely documentational, it has no replacement. If you rely on this argument as documentation, you can move the text to a code comment or docstring.
alert_create_signal = django.dispatch.Signal(
```
```bash
PytestCollectionWarning: cannot collect test class 'TestOnlyBackend' because it has a __init__ constructor (from: apps/api/tests/test_alert_receive_channel_template.py)
class TestOnlyBackend(BaseMessagingBackend):
```
```bash
DeprecationWarning: The parameter 'use_aliases' in emoji.emojize() is deprecated and will be removed in version 2.0.0. Use language='alias' instead.
To hide this warning, pin/downgrade the package to 'emoji~=1.6.3'
return emoji.emojize(self.verbal_name, use_aliases=True)
```
```bash
DateTimeField CustomOnCallShift.start received a naive datetime (2023-06-01 12:53:12) while time zone support is active.
warnings.warn("DateTimeField %s received a naive datetime (%s)"
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
/etc/app/apps/twilioapp/tests/test_phone_calls.py:173: DeprecationWarning: The 'text' argument to find()-type methods is deprecated. Use 'string' instead.
content = BeautifulSoup(content, features="html.parser").findAll(text=True)
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_resolve_by_phone
apps/twilioapp/tests/test_phone_calls.py::test_wrong_pressed_digit
/usr/local/lib/python3.11/site-packages/bs4/builder/__init__.py:545: XMLParsedAsHTMLWarning: It looks like you're parsing an XML document using an HTML parser. If this really is an HTML document (maybe it's XHTML?), you can ignore or filter this warning. If it's XML, you should know that using an XML parser will be more reliable. To parse this document as XML, make sure you have the lxml package installed, and pass the keyword argument `features="xml"` into the BeautifulSoup constructor.
```
```bash
apps/twilioapp/tests/test_phone_calls.py::test_forbidden_requests
/usr/local/lib/python3.11/site-packages/social_django/urls.py:15: RemovedInDjango40Warning: django.conf.urls.url() is deprecated in favor of django.urls.re_path().
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
```
```bash
apps/twilioapp/tests/test_phone_calls.py: 66 warnings
/usr/local/lib/python3.11/site-packages/debug_toolbar/utils.py:255: DeprecationWarning: currentThread() is deprecated, use current_thread() instead
thread = threading.currentThread()
```
## Checklist
- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-06-06 20:38:00 +02:00
|
|
|
"cloud_connection_status": None,
|
2024-04-02 14:59:03 -04:00
|
|
|
"has_google_oauth2_connected": False,
|
2022-06-03 08:09:47 -06:00
|
|
|
},
|
|
|
|
|
],
|
augment API response pagination attributes (#2471)
# What this PR does
This PR:
- adds a few attributes to paginated API responses
- removes channel filter "send demo alert" internal API endpoint + tests
(this endpoint was marked as deprecated + not consumed by the web UI)
With the new paginated API response schema, the web UI will no longer
need to:
- hardcode `ITEMS_PER_PAGE` for each table
- manually calculate total number of pages
(these two things ☝️ will be done in
https://github.com/grafana/oncall/issues/2476)
For `GET /api/internal/v1/alertgroups` the response will now look like
this:
```diff
{
"next": <url> | None,
"previous": <url> | None,
"results": [],
++ "page_size": <int>
}
```
For all other paginated API responses, the response will now look like:
```diff
{
"count": <int>,
"next": <url> | None,
"previous": <url> | None,
"results": [],
++ "page_size": <int>,
++ "current_page_number": <int>,
++ "total_pages": <int>
}
```
## TODO
- [x] update public API docs to include these new attributes
## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-07-14 17:19:40 +02:00
|
|
|
"current_page_number": 1,
|
|
|
|
|
"page_size": 100,
|
|
|
|
|
"total_pages": 1,
|
2022-06-03 08:09:47 -06:00
|
|
|
}
|
|
|
|
|
|
2024-01-31 10:13:08 -03:00
|
|
|
# as admin
|
2022-06-03 08:09:47 -06:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
|
2024-01-31 10:13:08 -03:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json() == expected_payload
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2024-01-31 10:13:08 -03:00
|
|
|
# as editor
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(editor, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2024-01-31 10:13:08 -03:00
|
|
|
admin_user_data = expected_payload["results"][0]
|
|
|
|
|
for f_name in admin_user_data:
|
|
|
|
|
if f_name not in UserHiddenFieldsSerializer.fields_available_for_all_users:
|
|
|
|
|
admin_user_data[f_name] = "******"
|
|
|
|
|
admin_user_data["hidden_fields"] = True
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.json() == expected_payload
|
|
|
|
|
|
|
|
|
|
|
2023-05-02 08:19:34 -04:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_list_users_filtered_by_granted_permission(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
perm_to_filter_on = RBACPermission.Permissions.NOTIFICATIONS_READ.value
|
|
|
|
|
perms_to_grant = [GrafanaAPIPermission(action=perm_to_filter_on)]
|
|
|
|
|
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin_user = make_user_for_organization(organization)
|
|
|
|
|
user1 = make_user_for_organization(organization, permissions=perms_to_grant)
|
|
|
|
|
user2 = make_user_for_organization(organization, permissions=perms_to_grant)
|
|
|
|
|
user3 = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
|
|
|
|
|
response = client.get(
|
|
|
|
|
f"{url}?permission={perm_to_filter_on}", format="json", **make_user_auth_headers(admin_user, token)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
returned_user_pks = [u["pk"] for u in response.json()["results"]]
|
|
|
|
|
|
|
|
|
|
assert admin_user.public_primary_key in returned_user_pks
|
|
|
|
|
assert user1.public_primary_key in returned_user_pks
|
|
|
|
|
assert user2.public_primary_key in returned_user_pks
|
|
|
|
|
assert user3.public_primary_key not in returned_user_pks
|
|
|
|
|
|
|
|
|
|
|
2023-11-14 09:56:58 -03:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_list_users_filtered_by_public_primary_key(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin_user = make_user_for_organization(organization)
|
|
|
|
|
user1 = make_user_for_organization(organization)
|
|
|
|
|
make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
|
|
|
|
|
response = client.get(
|
|
|
|
|
f"{url}?search={user1.public_primary_key}", format="json", **make_user_auth_headers(admin_user, token)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
returned_user_pks = [u["pk"] for u in response.json()["results"]]
|
|
|
|
|
assert returned_user_pks == [user1.public_primary_key]
|
|
|
|
|
|
|
|
|
|
|
2024-01-11 15:03:54 +00:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_list_users_filtered_by_team(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_team,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user1 = make_user_for_organization(organization)
|
|
|
|
|
user2 = make_user_for_organization(organization)
|
2024-01-17 12:18:08 -03:00
|
|
|
user3 = make_user_for_organization(organization)
|
2024-01-11 15:03:54 +00:00
|
|
|
|
|
|
|
|
team1 = make_team(organization)
|
|
|
|
|
team2 = make_team(organization)
|
|
|
|
|
team3 = make_team(organization)
|
|
|
|
|
|
|
|
|
|
user1.teams.add(team1)
|
|
|
|
|
user1.teams.add(team2)
|
|
|
|
|
user2.teams.add(team2)
|
|
|
|
|
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
|
|
|
|
|
def _get_user_pks(teams):
|
|
|
|
|
response = client.get(
|
|
|
|
|
url,
|
2024-01-17 12:18:08 -03:00
|
|
|
data={"team": [team.public_primary_key if team else "null" for team in teams]}, # these are query params
|
2024-01-11 15:03:54 +00:00
|
|
|
**make_user_auth_headers(user1, token),
|
|
|
|
|
)
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
return [u["pk"] for u in response.json()["results"]]
|
|
|
|
|
|
|
|
|
|
assert _get_user_pks([team1]) == [user1.public_primary_key]
|
|
|
|
|
assert _get_user_pks([team1, team2]) == [user1.public_primary_key, user2.public_primary_key]
|
|
|
|
|
assert _get_user_pks([team3]) == []
|
2024-01-17 12:18:08 -03:00
|
|
|
assert _get_user_pks([team1, None]) == [user1.public_primary_key, user3.public_primary_key]
|
|
|
|
|
assert _get_user_pks([None]) == [user3.public_primary_key]
|
2024-01-11 15:03:54 +00:00
|
|
|
|
|
|
|
|
# check non-existent team returns bad request
|
2024-01-17 12:18:08 -03:00
|
|
|
response = client.get(f"{url}?team=non-existing", **make_user_auth_headers(user1, token))
|
2024-01-11 15:03:54 +00:00
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_notification_chain_verbal(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_user_notification_policy,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
data_for_creation = [
|
|
|
|
|
{"step": UserNotificationPolicy.Step.NOTIFY, "notify_by": UserNotificationPolicy.NotificationChannel.SLACK},
|
|
|
|
|
{"step": UserNotificationPolicy.Step.WAIT, "wait_delay": timezone.timedelta(minutes=5)},
|
|
|
|
|
{
|
|
|
|
|
"step": UserNotificationPolicy.Step.NOTIFY,
|
|
|
|
|
"notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL,
|
|
|
|
|
},
|
|
|
|
|
{"step": UserNotificationPolicy.Step.WAIT, "wait_delay": None},
|
|
|
|
|
{"step": UserNotificationPolicy.Step.NOTIFY, "notify_by": UserNotificationPolicy.NotificationChannel.TELEGRAM},
|
|
|
|
|
{"step": None},
|
|
|
|
|
{
|
|
|
|
|
"step": UserNotificationPolicy.Step.NOTIFY,
|
|
|
|
|
"notify_by": UserNotificationPolicy.NotificationChannel.SLACK,
|
|
|
|
|
"important": True,
|
|
|
|
|
},
|
|
|
|
|
{"step": UserNotificationPolicy.Step.WAIT, "wait_delay": timezone.timedelta(minutes=5), "important": True},
|
|
|
|
|
{
|
|
|
|
|
"step": UserNotificationPolicy.Step.NOTIFY,
|
|
|
|
|
"notify_by": UserNotificationPolicy.NotificationChannel.PHONE_CALL,
|
|
|
|
|
"important": True,
|
|
|
|
|
},
|
|
|
|
|
{"step": UserNotificationPolicy.Step.WAIT, "wait_delay": None, "important": True},
|
|
|
|
|
{
|
|
|
|
|
"step": UserNotificationPolicy.Step.NOTIFY,
|
|
|
|
|
"notify_by": UserNotificationPolicy.NotificationChannel.TELEGRAM,
|
|
|
|
|
"important": True,
|
|
|
|
|
},
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for data in data_for_creation:
|
|
|
|
|
make_user_notification_policy(admin, **data)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
|
|
|
|
|
expected_notification_chain = {
|
|
|
|
|
"default": "Slack - 5 min - \U0000260E - Telegram",
|
|
|
|
|
"important": "Slack - 5 min - \U0000260E - Telegram",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json()["notification_chain_verbal"] == expected_notification_chain
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_update_self_permissions(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": tester.public_primary_key})
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.api.views.user.UserView.update",
|
|
|
|
|
return_value=Response(
|
|
|
|
|
status=status.HTTP_200_OK,
|
|
|
|
|
),
|
|
|
|
|
):
|
|
|
|
|
response = client.put(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_update_other_permissions(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
data = {"unverified_phone_number": "+79123456789"}
|
|
|
|
|
|
|
|
|
|
response = client.put(url, data, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
2023-11-23 13:41:27 -07:00
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_list_permissions(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.api.views.user.UserView.list",
|
|
|
|
|
return_value=Response(
|
|
|
|
|
status=status.HTTP_200_OK,
|
|
|
|
|
),
|
|
|
|
|
):
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_detail_self_permissions(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": tester.public_primary_key})
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.api.views.user.UserView.list",
|
|
|
|
|
return_value=Response(
|
|
|
|
|
status=status.HTTP_200_OK,
|
|
|
|
|
),
|
|
|
|
|
):
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
2024-02-16 13:48:24 -03:00
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_detail_other_permissions(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
2024-02-16 13:48:24 -03:00
|
|
|
# hidden information for editor/viewer
|
2024-02-19 11:27:13 -03:00
|
|
|
available_fields = UserHiddenFieldsSerializer.fields_available_for_all_users + ["hidden_fields"]
|
2024-02-16 13:48:24 -03:00
|
|
|
if role in (LegacyAccessControlRole.EDITOR, LegacyAccessControlRole.VIEWER):
|
|
|
|
|
user_details = response.json()
|
|
|
|
|
for f_name in user_details:
|
2024-02-19 11:27:13 -03:00
|
|
|
if f_name not in available_fields:
|
|
|
|
|
assert user_details[f_name] == "******"
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_get_own_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": tester.public_primary_key})
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.api.views.user.UserView.get_verification_code",
|
|
|
|
|
return_value=Response(
|
|
|
|
|
status=status.HTTP_200_OK,
|
|
|
|
|
),
|
|
|
|
|
):
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_get_other_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": admin.public_primary_key})
|
2023-05-24 14:27:48 +08:00
|
|
|
with patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock()):
|
2022-06-03 08:09:47 -06:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
2023-03-06 16:59:48 +08:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_validation_of_verification_code(
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization, user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
with patch(
|
2023-05-24 14:27:48 +08:00
|
|
|
"apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True
|
2023-03-06 16:59:48 +08:00
|
|
|
) as verify_phone_number:
|
|
|
|
|
url_with_token = f"{url}?token=some_token"
|
|
|
|
|
r = client.put(url_with_token, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert r.status_code == 200
|
|
|
|
|
assert verify_phone_number.call_count == 1
|
|
|
|
|
|
|
|
|
|
url_without_token = f"{url}"
|
|
|
|
|
r = client.put(url_without_token, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert r.status_code == 400
|
|
|
|
|
assert verify_phone_number.call_count == 1
|
|
|
|
|
|
|
|
|
|
url_with_empty_token = f"{url}?token="
|
|
|
|
|
r = client.put(url_with_empty_token, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert r.status_code == 400
|
|
|
|
|
assert verify_phone_number.call_count == 1
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_verification_code_provider_exception(
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization, user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number",
|
|
|
|
|
side_effect=FailedToFinishVerification,
|
|
|
|
|
) as verify_phone_number:
|
|
|
|
|
url_with_token = f"{url}?token=some_token"
|
|
|
|
|
r = client.put(url_with_token, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert r.status_code == 503
|
|
|
|
|
assert verify_phone_number.call_count == 1
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_verify_own_phone(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": tester.public_primary_key})
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.api.views.user.UserView.verify_number",
|
|
|
|
|
return_value=Response(
|
|
|
|
|
status=status.HTTP_200_OK,
|
|
|
|
|
),
|
|
|
|
|
):
|
|
|
|
|
response = client.put(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
2024-01-31 15:23:11 +01:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
|
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_400_BAD_REQUEST),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_400_BAD_REQUEST),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
|
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_get_own_telegram_verification_code_with_telegram_connected(
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_telegram_user_connector,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
|
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
make_telegram_user_connector(tester)
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": tester.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
"""
|
|
|
|
|
Tests below are outdated
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_verify_another_phone(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, tester, token = make_organization_and_user_with_plugin_token(role)
|
|
|
|
|
other_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": other_user.public_primary_key})
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
with patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True):
|
2022-06-03 08:09:47 -06:00
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_get_own_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, tester, token = make_organization_and_user_with_plugin_token(role)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": tester.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
|
2023-10-19 14:39:08 -03:00
|
|
|
(LegacyAccessControlRole.NONE, status.HTTP_403_FORBIDDEN),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_user_get_another_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, tester, token = make_organization_and_user_with_plugin_token(role)
|
|
|
|
|
other_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": other_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(tester, token))
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_update_user(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@amixr.io",
|
|
|
|
|
"username": "updated_test_username",
|
|
|
|
|
"unverified_phone_number": "+1234567890",
|
|
|
|
|
"slack_login": "",
|
|
|
|
|
}
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
response = client.put(url, format="json", data=data, **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_admin_can_update_himself(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@amixr.io",
|
|
|
|
|
"username": "updated_test_username",
|
|
|
|
|
"unverified_phone_number": "+1234567890",
|
|
|
|
|
"slack_login": "",
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
response = client.put(url, format="json", data=data, **make_user_auth_headers(user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_admin_can_list_users(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-list")
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_detail_users(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_get_own_verification_code(
|
|
|
|
|
mock_verification_start,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_get_another_user_verification_code(
|
|
|
|
|
mock_verification_start,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_verify_own_phone(
|
|
|
|
|
mocked_verification_check,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_verify_another_user_phone(
|
|
|
|
|
mocked_verification_check,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": first_user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_get_own_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_get_another_user_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": first_user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_get_another_user_backend_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = (
|
2022-11-29 09:41:56 +01:00
|
|
|
reverse("api-internal:user-get-backend-verification-code", kwargs={"pk": first_user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
+ "?backend=TESTONLY"
|
|
|
|
|
)
|
|
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_unlink_another_user_backend_account(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = (
|
|
|
|
|
reverse("api-internal:user-unlink-backend", kwargs={"pk": first_user.public_primary_key}) + "?backend=TESTONLY"
|
|
|
|
|
)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(second_user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2022-08-29 14:07:15 -03:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_admin_can_unlink_another_user_slack_account(
|
|
|
|
|
make_organization_with_slack_team_identity,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_with_slack_user_identity,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
2022-11-29 09:41:56 +01:00
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
user, _ = make_user_with_slack_user_identity(
|
|
|
|
|
slack_team_identity, organization, slack_id="user_2", role=LegacyAccessControlRole.ADMIN
|
2022-08-29 14:07:15 -03:00
|
|
|
)
|
2022-11-29 09:41:56 +01:00
|
|
|
other_user = make_user_for_organization(organization)
|
2022-08-29 14:07:15 -03:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-unlink-slack", kwargs={"pk": other_user.public_primary_key})
|
2022-08-29 14:07:15 -03:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(user, token))
|
2022-08-29 14:07:15 -03:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2022-11-29 09:41:56 +01:00
|
|
|
other_user.refresh_from_db()
|
|
|
|
|
assert other_user.slack_user_identity is None
|
2022-08-29 14:07:15 -03:00
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
"""Test user permissions"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_cant_update_user(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@amixr.io",
|
|
|
|
|
"username": "updated_test_username",
|
|
|
|
|
"unverified_phone_number": "+1234567890",
|
|
|
|
|
"slack_login": "",
|
|
|
|
|
}
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
response = client.put(url, format="json", data=data, **make_user_auth_headers(second_user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_user_can_update_themself(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@amixr.io",
|
|
|
|
|
"username": "updated_test_username",
|
|
|
|
|
"unverified_phone_number": "+1234567890",
|
|
|
|
|
"slack_login": "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
response = client.put(url, format="json", data=data, **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_user_can_list_users(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-list")
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_can_detail_users(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": first_user.public_primary_key})
|
2022-06-03 08:09:47 -06:00
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
2024-02-16 13:48:24 -03:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
# hidden information though
|
|
|
|
|
user_details = response.json()
|
2024-02-19 11:27:13 -03:00
|
|
|
available_fields = UserHiddenFieldsSerializer.fields_available_for_all_users + ["hidden_fields"]
|
2024-02-16 13:48:24 -03:00
|
|
|
for f_name in user_details:
|
2024-02-19 11:27:13 -03:00
|
|
|
if f_name not in available_fields:
|
|
|
|
|
assert user_details[f_name] == "******"
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_can_get_own_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
mock_verification_start, make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_cant_get_another_user_verification_code(
|
|
|
|
|
mock_verification_start,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_can_verify_own_phone(
|
2022-11-29 09:41:56 +01:00
|
|
|
mocked_verification_check, make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_cant_verify_another_user_phone(
|
|
|
|
|
mocked_verification_check,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_can_get_own_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_cant_get_another_user_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_can_get_own_backend_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = (
|
|
|
|
|
reverse("api-internal:user-get-backend-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
+ "?backend=TESTONLY"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
with patch(
|
|
|
|
|
"apps.base.tests.messaging_backend.TestOnlyBackend.generate_user_verification_code",
|
|
|
|
|
return_value="the-code",
|
|
|
|
|
) as mock_generate_code:
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.json() == "the-code"
|
|
|
|
|
mock_generate_code.assert_called_once_with(user)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_cant_get_another_user_backend_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = (
|
|
|
|
|
reverse("api-internal:user-get-backend-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
+ "?backend=TESTONLY"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
2022-08-29 14:07:15 -03:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_can_unlink_own_slack_account(
|
|
|
|
|
make_organization_with_slack_team_identity,
|
|
|
|
|
make_user_with_slack_user_identity,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
2022-11-29 09:41:56 +01:00
|
|
|
user, _ = make_user_with_slack_user_identity(
|
|
|
|
|
slack_team_identity, organization, slack_id="user_2", role=LegacyAccessControlRole.EDITOR
|
2022-08-29 14:07:15 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-unlink-slack", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
user.refresh_from_db()
|
|
|
|
|
assert user.slack_user_identity is None
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_user_can_unlink_backend_own_account(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-unlink-backend", kwargs={"pk": user.public_primary_key}) + "?backend=TESTONLY"
|
|
|
|
|
|
|
|
|
|
response = client.post(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_user_unlink_backend_invalid_backend_id(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-unlink-backend", kwargs={"pk": user.public_primary_key}) + "?backend=INVALID"
|
|
|
|
|
|
|
|
|
|
response = client.post(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_unlink_backend_backend_account_not_found(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-unlink-backend", kwargs={"pk": user.public_primary_key}) + "?backend=TESTONLY"
|
|
|
|
|
with patch("apps.base.tests.messaging_backend.TestOnlyBackend.unlink_user", side_effect=ObjectDoesNotExist):
|
|
|
|
|
response = client.post(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
2022-08-29 14:07:15 -03:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_user_cant_unlink_slack_another_user(
|
|
|
|
|
make_organization_with_slack_team_identity,
|
|
|
|
|
make_user_with_slack_user_identity,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
organization, slack_team_identity = make_organization_with_slack_team_identity()
|
2022-11-29 09:41:56 +01:00
|
|
|
|
|
|
|
|
first_user, _ = make_user_with_slack_user_identity(
|
|
|
|
|
slack_team_identity, organization, slack_id="user_1", role=LegacyAccessControlRole.EDITOR
|
2022-08-29 14:07:15 -03:00
|
|
|
)
|
2022-11-29 09:41:56 +01:00
|
|
|
second_user, _ = make_user_with_slack_user_identity(
|
|
|
|
|
slack_team_identity, organization, slack_id="user_2", role=LegacyAccessControlRole.EDITOR
|
2022-08-29 14:07:15 -03:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-unlink-slack", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
first_user.refresh_from_db()
|
|
|
|
|
assert first_user.slack_user_identity is not None
|
|
|
|
|
|
|
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
2023-06-13 20:54:52 +02:00
|
|
|
def test_user_cant_unlink_backend_another_user(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = (
|
|
|
|
|
reverse("api-internal:user-unlink-backend", kwargs={"pk": first_user.public_primary_key}) + "?backend=TESTONLY"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = client.post(f"{url}", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Test stakeholder permissions"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_update_user(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.VIEWER)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@amixr.io",
|
2022-11-29 09:41:56 +01:00
|
|
|
"role": LegacyAccessControlRole.EDITOR,
|
2022-06-03 08:09:47 -06:00
|
|
|
"username": "updated_test_username",
|
|
|
|
|
"unverified_phone_number": "+1234567890",
|
|
|
|
|
"slack_login": "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
response = client.put(url, format="json", data=data, **make_user_auth_headers(second_user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_viewer_cant_update_himself(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
"email": "test@amixr.io",
|
2022-11-29 09:41:56 +01:00
|
|
|
"role": LegacyAccessControlRole.VIEWER,
|
2022-06-03 08:09:47 -06:00
|
|
|
"username": "updated_test_username",
|
|
|
|
|
"unverified_phone_number": "+1234567890",
|
|
|
|
|
"slack_login": "",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
response = client.put(url, format="json", data=data, **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2023-11-23 13:41:27 -07:00
|
|
|
def test_viewer_can_list_users(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
2023-11-23 13:41:27 -07:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_get_own_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
mock_verification_start, make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_get_another_user_verification_code(
|
|
|
|
|
mock_verification_start,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_verify_own_phone(
|
2022-11-29 09:41:56 +01:00
|
|
|
mocked_verification_check, make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2022-06-03 08:09:47 -06:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_verify_another_user_phone(
|
|
|
|
|
mocked_verification_check,
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_get_own_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_get_another_user_telegram_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-telegram-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status,initial_unverified_number,initial_verified_number",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK, "+1234567890", None),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK, "+1234567890", None),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN, "+1234567890", None),
|
|
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK, None, "+1234567890"),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK, None, "+1234567890"),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN, None, "+1234567890"),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_forget_own_number(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token,
|
2022-06-03 08:09:47 -06:00
|
|
|
make_user_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
initial_unverified_number,
|
|
|
|
|
initial_verified_number,
|
|
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, admin, token = make_organization_and_user_with_plugin_token()
|
2022-06-03 08:09:47 -06:00
|
|
|
user = make_user_for_organization(
|
|
|
|
|
organization,
|
|
|
|
|
role=role,
|
|
|
|
|
unverified_phone_number=initial_unverified_number,
|
|
|
|
|
_verified_phone_number=initial_verified_number,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-forget-number", kwargs={"pk": user.public_primary_key})
|
2023-05-24 14:27:48 +08:00
|
|
|
with patch("apps.phone_notifications.phone_backend.PhoneBackend._notify_disconnected_number", return_value=None):
|
2022-06-03 08:09:47 -06:00
|
|
|
response = client.put(url, None, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
|
|
|
|
user_detail_url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
response = client.get(user_detail_url, None, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
if expected_status == status.HTTP_200_OK:
|
|
|
|
|
assert not response.json()["unverified_phone_number"]
|
|
|
|
|
assert not response.json()["verified_phone_number"]
|
|
|
|
|
else:
|
|
|
|
|
assert response.json()["unverified_phone_number"] == initial_unverified_number
|
|
|
|
|
assert response.json()["verified_phone_number"] == initial_verified_number
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"role,expected_status,initial_unverified_number,initial_verified_number",
|
|
|
|
|
[
|
2022-11-29 09:41:56 +01:00
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK, "+1234567890", None),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN, "+1234567890", None),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN, "+1234567890", None),
|
|
|
|
|
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK, None, "+1234567890"),
|
|
|
|
|
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN, None, "+1234567890"),
|
|
|
|
|
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN, None, "+1234567890"),
|
2022-06-03 08:09:47 -06:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_forget_other_number(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
role,
|
|
|
|
|
expected_status,
|
|
|
|
|
initial_unverified_number,
|
|
|
|
|
initial_verified_number,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
2022-11-29 09:41:56 +01:00
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
admin = make_user_for_organization(
|
|
|
|
|
organization, unverified_phone_number=initial_unverified_number, _verified_phone_number=initial_verified_number
|
2022-06-03 08:09:47 -06:00
|
|
|
)
|
|
|
|
|
other_user = make_user_for_organization(organization, role=role)
|
2022-11-29 09:41:56 +01:00
|
|
|
admin_primary_key = admin.public_primary_key
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
2022-11-29 09:41:56 +01:00
|
|
|
url = reverse("api-internal:user-forget-number", kwargs={"pk": admin_primary_key})
|
2023-05-24 14:27:48 +08:00
|
|
|
with patch("apps.phone_notifications.phone_backend.PhoneBackend._notify_disconnected_number", return_value=None):
|
2022-06-03 08:09:47 -06:00
|
|
|
response = client.put(url, None, format="json", **make_user_auth_headers(other_user, token))
|
|
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
|
2022-11-29 09:41:56 +01:00
|
|
|
user_detail_url = reverse("api-internal:user-detail", kwargs={"pk": admin_primary_key})
|
|
|
|
|
response = client.get(user_detail_url, None, format="json", **make_user_auth_headers(admin, token))
|
2022-06-03 08:09:47 -06:00
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2022-11-29 09:41:56 +01:00
|
|
|
|
2022-06-03 08:09:47 -06:00
|
|
|
if expected_status == status.HTTP_200_OK:
|
|
|
|
|
assert not response.json()["unverified_phone_number"]
|
|
|
|
|
assert not response.json()["verified_phone_number"]
|
|
|
|
|
else:
|
|
|
|
|
assert response.json()["unverified_phone_number"] == initial_unverified_number
|
|
|
|
|
assert response.json()["verified_phone_number"] == initial_verified_number
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_get_another_user_backend_verification_code(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = (
|
|
|
|
|
reverse("api-internal:user-get-backend-verification-code", kwargs={"pk": first_user.public_primary_key})
|
|
|
|
|
+ "?backend=TESTONLY"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_viewer_cant_unlink_backend_another_user(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_auth_headers
|
2022-06-03 08:09:47 -06:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
organization, first_user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
|
|
|
|
second_user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
|
2022-06-03 08:09:47 -06:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = (
|
|
|
|
|
reverse("api-internal:user-unlink-backend", kwargs={"pk": first_user.public_primary_key}) + "?backend=TESTONLY"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
response = client.post(url, format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
2022-07-11 13:16:56 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_change_timezone(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-07-11 13:16:56 +01:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
data = {"timezone": "Europe/London"}
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}", data, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert "timezone" in response.json()
|
|
|
|
|
assert response.json()["timezone"] == "Europe/London"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize("timezone", ["", 1, "NotATimezone"])
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_invalid_timezone(make_organization_and_user_with_plugin_token, make_user_auth_headers, timezone):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-07-11 13:16:56 +01:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
data = {"timezone": timezone}
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}", data, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2022-11-29 09:41:56 +01:00
|
|
|
def test_change_working_hours(make_organization_and_user_with_plugin_token, make_user_auth_headers):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-07-11 13:16:56 +01:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
periods = [{"start": "05:00:00", "end": "23:00:00"}]
|
|
|
|
|
working_hours = {
|
|
|
|
|
day: periods for day in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data = {"working_hours": working_hours}
|
|
|
|
|
|
|
|
|
|
response = client.put(f"{url}", data, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert "working_hours" in response.json()
|
|
|
|
|
assert response.json()["working_hours"] == working_hours
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"working_hours_extra",
|
|
|
|
|
[
|
|
|
|
|
{},
|
|
|
|
|
{"sunday": 1},
|
|
|
|
|
{"sunday": ""},
|
|
|
|
|
{"sunday": {"start": "18:00:00"}},
|
|
|
|
|
{"sunday": {"start": "", "end": ""}},
|
|
|
|
|
{"sunday": {"start": "18:00:00", "end": None}},
|
|
|
|
|
{"sunday": {"start": "18:00:00", "end": "18:00:00"}},
|
|
|
|
|
{"sunday": {"start": "18:00:00", "end": "9:00:00"}},
|
|
|
|
|
{"sunday": {"start": "18:00:00", "end": "9:00:00", "extra": 1}},
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_invalid_working_hours(
|
2022-11-29 09:41:56 +01:00
|
|
|
make_organization_and_user_with_plugin_token, make_user_auth_headers, working_hours_extra
|
2022-07-11 13:16:56 +01:00
|
|
|
):
|
2022-11-29 09:41:56 +01:00
|
|
|
_, user, token = make_organization_and_user_with_plugin_token(role=LegacyAccessControlRole.EDITOR)
|
2022-07-11 13:16:56 +01:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
periods = [{"start": "05:00:00", "end": "23:00:00"}]
|
|
|
|
|
working_hours = {day: periods for day in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]}
|
|
|
|
|
working_hours.update(working_hours_extra)
|
|
|
|
|
|
|
|
|
|
data = {"working_hours": working_hours}
|
|
|
|
|
response = client.put(f"{url}", data, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
2023-01-17 12:19:08 -03:00
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
|
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2023-02-21 09:47:52 +01:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_phone_number_verification_flow_ratelimit_per_user(
|
|
|
|
|
mock_verification_start,
|
|
|
|
|
mocked_verification_check,
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
with patch(
|
|
|
|
|
"apps.api.throttlers.GetPhoneVerificationCodeThrottlerPerUser.rate",
|
|
|
|
|
new_callable=PropertyMock,
|
|
|
|
|
) as mocked_rate:
|
|
|
|
|
mocked_rate.return_value = "1/10m"
|
|
|
|
|
# first get_verification_code request is succesfull
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2023-02-21 09:47:52 +01:00
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
# second get_verification_code request is ratelimited
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
|
2023-02-21 09:47:52 +01:00
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
with patch(
|
|
|
|
|
"apps.api.throttlers.VerifyPhoneNumberThrottlerPerUser.rate",
|
|
|
|
|
new_callable=PropertyMock,
|
|
|
|
|
) as mocked_rate:
|
|
|
|
|
mocked_rate.return_value = "1/10m"
|
2023-02-21 09:47:52 +01:00
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
# first verify_number request is succesfull, because it uses different ratelimit scope
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2023-02-21 09:47:52 +01:00
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
|
|
|
|
|
# second verify_number request is succesfull, because it ratelimited
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
|
2023-02-21 09:47:52 +01:00
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
|
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
|
2023-02-21 09:47:52 +01:00
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_phone_number_verification_flow_ratelimit_per_org(
|
|
|
|
|
mock_verification_start,
|
|
|
|
|
mocked_verification_check,
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
This test is checks per-org ratelimits for phone verification flow.
|
|
|
|
|
It makes two get_verification_code and two verify_number requests from different users and expect that second call will be ratelimited.
|
|
|
|
|
"""
|
|
|
|
|
org, user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
second_user = make_user_for_organization(org)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
with patch(
|
|
|
|
|
"apps.api.throttlers.GetPhoneVerificationCodeThrottlerPerOrg.rate",
|
|
|
|
|
new_callable=PropertyMock,
|
|
|
|
|
) as mocked_rate:
|
|
|
|
|
mocked_rate.return_value = "1/10m"
|
2023-02-21 09:47:52 +01:00
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2023-02-21 09:47:52 +01:00
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": second_user.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
|
2023-02-21 09:47:52 +01:00
|
|
|
|
2024-09-18 07:16:41 +08:00
|
|
|
with patch(
|
|
|
|
|
"apps.api.throttlers.VerifyPhoneNumberThrottlerPerOrg.rate",
|
|
|
|
|
new_callable=PropertyMock,
|
|
|
|
|
) as mocked_rate:
|
|
|
|
|
mocked_rate.return_value = "1/10m"
|
|
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-verify-number", kwargs={"pk": second_user.public_primary_key})
|
|
|
|
|
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(second_user, token))
|
|
|
|
|
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
|
2023-02-21 20:17:06 +01:00
|
|
|
|
|
|
|
|
|
2023-05-24 14:27:48 +08:00
|
|
|
@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
|
2023-02-21 20:17:06 +01:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"recaptcha_testing_pass,expected_status",
|
|
|
|
|
[
|
|
|
|
|
(True, status.HTTP_200_OK),
|
|
|
|
|
(False, status.HTTP_400_BAD_REQUEST),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
@pytest.mark.django_db
|
2023-03-06 16:59:48 +08:00
|
|
|
@override_settings(RECAPTCHA_V3_ENABLED=True)
|
2023-02-21 20:17:06 +01:00
|
|
|
def test_phone_number_verification_recaptcha(
|
|
|
|
|
mock_verification_start,
|
|
|
|
|
make_organization_and_user_with_plugin_token,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
recaptcha_testing_pass,
|
|
|
|
|
expected_status,
|
|
|
|
|
):
|
|
|
|
|
_, user, token = make_organization_and_user_with_plugin_token()
|
|
|
|
|
|
|
|
|
|
recaptcha_token = "asdasdfasdf"
|
|
|
|
|
client = APIClient()
|
|
|
|
|
request_headers = {"HTTP_X-OnCall-Recaptcha": recaptcha_token, **make_user_auth_headers(user, token)}
|
|
|
|
|
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
|
2023-03-06 16:59:48 +08:00
|
|
|
with patch("apps.api.views.user.check_recaptcha_internal_api", return_value=recaptcha_testing_pass):
|
2023-02-21 20:17:06 +01:00
|
|
|
response = client.get(url, format="json", **request_headers)
|
2023-03-06 16:59:48 +08:00
|
|
|
assert response.status_code == expected_status
|
|
|
|
|
if expected_status == status.HTTP_200_OK:
|
2023-05-24 14:27:48 +08:00
|
|
|
mock_verification_start.assert_called_once_with(user)
|
2023-03-06 16:59:48 +08:00
|
|
|
else:
|
|
|
|
|
mock_verification_start.assert_not_called()
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
2023-05-08 16:01:24 -03:00
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"days",
|
|
|
|
|
["invalid", 75, -2, 0],
|
|
|
|
|
)
|
2023-04-05 17:19:02 -03:00
|
|
|
def test_upcoming_shifts_invalid_days(
|
2023-05-08 16:01:24 -03:00
|
|
|
make_organization, make_user_for_organization, make_token_for_organization, make_user_auth_headers, days
|
2023-04-05 17:19:02 -03:00
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
2023-05-08 16:01:24 -03:00
|
|
|
url = reverse("api-internal:user-upcoming-shifts", kwargs={"pk": admin.public_primary_key}) + "?days={}".format(
|
|
|
|
|
days
|
|
|
|
|
)
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_upcoming_shifts_oncall(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
make_on_call_shift,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
other_user = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
schedule = make_schedule(
|
|
|
|
|
organization,
|
|
|
|
|
schedule_class=OnCallScheduleWeb,
|
|
|
|
|
)
|
|
|
|
|
shifts = (
|
|
|
|
|
# user, priority, start time (h), duration (seconds)
|
|
|
|
|
(admin, 1, 0, (24 * 60 * 60) - 1), # r1-1: 0-23:59:59
|
|
|
|
|
)
|
|
|
|
|
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
for user, priority, start_h, duration in shifts:
|
|
|
|
|
data = {
|
|
|
|
|
"start": today + timezone.timedelta(hours=start_h),
|
|
|
|
|
"rotation_start": today + timezone.timedelta(hours=start_h),
|
|
|
|
|
"duration": timezone.timedelta(seconds=duration),
|
|
|
|
|
"priority_level": priority,
|
|
|
|
|
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
|
|
|
|
|
"schedule": schedule,
|
|
|
|
|
}
|
|
|
|
|
on_call_shift = make_on_call_shift(
|
|
|
|
|
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
|
|
|
|
|
)
|
|
|
|
|
on_call_shift.add_rolling_users([[user]])
|
|
|
|
|
schedule.refresh_ical_file()
|
2023-05-08 16:01:24 -03:00
|
|
|
schedule.refresh_ical_final_schedule()
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
|
|
|
|
|
url = reverse("api-internal:user-upcoming-shifts", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2023-04-18 13:42:04 -03:00
|
|
|
returned_data = response.data[0]
|
|
|
|
|
assert returned_data["schedule_id"] == schedule.public_primary_key
|
|
|
|
|
assert returned_data["schedule_name"] == schedule.name
|
|
|
|
|
assert returned_data["is_oncall"]
|
|
|
|
|
assert returned_data["current_shift"]["start"] == on_call_shift.start
|
2023-04-05 17:19:02 -03:00
|
|
|
next_shift_start = on_call_shift.start + timezone.timedelta(days=1)
|
2023-04-18 13:42:04 -03:00
|
|
|
assert returned_data["next_shift"]["start"] == next_shift_start
|
2024-02-09 17:36:12 -03:00
|
|
|
assert len(returned_data["upcoming_shifts"]) == UPCOMING_SHIFTS_DEFAULT_DAYS
|
|
|
|
|
for i, shift in enumerate(returned_data["upcoming_shifts"], start=1):
|
|
|
|
|
assert shift["start"] == on_call_shift.start + timezone.timedelta(days=i)
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
# empty response for other user
|
|
|
|
|
url = reverse("api-internal:user-upcoming-shifts", kwargs={"pk": other_user.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2023-04-18 13:42:04 -03:00
|
|
|
assert response.data == []
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_upcoming_shifts_override(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
make_on_call_shift,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
schedule = make_schedule(
|
|
|
|
|
organization,
|
|
|
|
|
schedule_class=OnCallScheduleWeb,
|
|
|
|
|
)
|
2023-04-18 22:11:07 -03:00
|
|
|
tomorrow = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + timezone.timedelta(days=1)
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
override_data = {
|
2023-04-18 22:11:07 -03:00
|
|
|
"start": tomorrow + timezone.timedelta(hours=22),
|
|
|
|
|
"rotation_start": tomorrow + timezone.timedelta(hours=22),
|
2023-04-05 17:19:02 -03:00
|
|
|
"duration": timezone.timedelta(hours=1),
|
|
|
|
|
"schedule": schedule,
|
|
|
|
|
}
|
|
|
|
|
override = make_on_call_shift(
|
|
|
|
|
organization=organization, shift_type=CustomOnCallShift.TYPE_OVERRIDE, **override_data
|
|
|
|
|
)
|
|
|
|
|
override.add_rolling_users([[admin]])
|
|
|
|
|
schedule.refresh_ical_file()
|
2023-05-08 16:01:24 -03:00
|
|
|
schedule.refresh_ical_final_schedule()
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-upcoming-shifts", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(admin, token))
|
|
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2023-04-18 13:42:04 -03:00
|
|
|
returned_data = response.data[0]
|
|
|
|
|
assert returned_data["schedule_id"] == schedule.public_primary_key
|
|
|
|
|
assert returned_data["schedule_name"] == schedule.name
|
|
|
|
|
assert returned_data["is_oncall"] is False
|
|
|
|
|
assert returned_data["current_shift"] is None
|
|
|
|
|
assert returned_data["next_shift"]["start"] == override.start
|
2024-02-09 17:36:12 -03:00
|
|
|
assert returned_data["upcoming_shifts"] == [returned_data["next_shift"]]
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_upcoming_shifts_multiple_schedules(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
make_on_call_shift,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
admin = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
schedules = []
|
2023-04-18 13:42:04 -03:00
|
|
|
# create schedules in a reversed order to check the output is sorted later
|
|
|
|
|
for i in range(2, -1, -1):
|
2023-04-05 17:19:02 -03:00
|
|
|
schedule = make_schedule(
|
|
|
|
|
organization,
|
|
|
|
|
schedule_class=OnCallScheduleWeb,
|
|
|
|
|
)
|
|
|
|
|
shifts = (
|
|
|
|
|
# user, priority, start time (h), duration (seconds)
|
|
|
|
|
(admin, 1, 0, (24 * 60 * 60) - 1), # r1-1: 0-23:59:59
|
|
|
|
|
)
|
|
|
|
|
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
for user, priority, start_h, duration in shifts:
|
|
|
|
|
data = {
|
|
|
|
|
"start": today + timezone.timedelta(hours=start_h) + timezone.timedelta(days=i),
|
|
|
|
|
"rotation_start": today + timezone.timedelta(hours=start_h) + timezone.timedelta(days=i),
|
|
|
|
|
"duration": timezone.timedelta(seconds=duration),
|
|
|
|
|
"priority_level": priority,
|
|
|
|
|
"frequency": CustomOnCallShift.FREQUENCY_DAILY,
|
|
|
|
|
"schedule": schedule,
|
|
|
|
|
}
|
|
|
|
|
on_call_shift = make_on_call_shift(
|
|
|
|
|
organization=organization, shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT, **data
|
|
|
|
|
)
|
|
|
|
|
on_call_shift.add_rolling_users([[user]])
|
|
|
|
|
schedule.refresh_ical_file()
|
2023-05-08 16:01:24 -03:00
|
|
|
schedule.refresh_ical_final_schedule()
|
2023-04-05 17:19:02 -03:00
|
|
|
schedules.append(schedule)
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-upcoming-shifts", kwargs={"pk": admin.public_primary_key})
|
|
|
|
|
|
2024-02-09 17:36:12 -03:00
|
|
|
num_days = 2
|
|
|
|
|
response = client.get(url + f"?days={num_days}", format="json", **make_user_auth_headers(admin, token))
|
2023-04-05 17:19:02 -03:00
|
|
|
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
returned_data = response.data
|
2023-04-18 13:42:04 -03:00
|
|
|
for i, schedule in enumerate(reversed(schedules)):
|
|
|
|
|
assert returned_data[i]["schedule_name"] == schedule.name
|
2023-04-05 17:19:02 -03:00
|
|
|
expected_start = today + timezone.timedelta(hours=start_h) + timezone.timedelta(days=i)
|
|
|
|
|
if i == 0:
|
2023-04-18 13:42:04 -03:00
|
|
|
assert returned_data[i]["is_oncall"]
|
|
|
|
|
assert returned_data[i]["current_shift"]["start"] == expected_start
|
|
|
|
|
assert returned_data[i]["next_shift"]["start"] == expected_start + timezone.timedelta(days=1)
|
2024-02-09 17:36:12 -03:00
|
|
|
assert len(returned_data[i]["upcoming_shifts"]) == 2
|
|
|
|
|
for delta, shift in enumerate(returned_data[i]["upcoming_shifts"], start=1):
|
|
|
|
|
assert shift["start"] == expected_start + timezone.timedelta(days=delta)
|
2023-04-05 17:19:02 -03:00
|
|
|
else:
|
2023-04-18 13:42:04 -03:00
|
|
|
assert returned_data[i]["is_oncall"] is False
|
|
|
|
|
assert returned_data[i]["current_shift"] is None
|
|
|
|
|
assert returned_data[i]["next_shift"]["start"] == expected_start
|
2024-02-09 17:36:12 -03:00
|
|
|
assert len(returned_data[i]["upcoming_shifts"]) == num_days - i + 1
|
|
|
|
|
for delta, shift in enumerate(returned_data[i]["upcoming_shifts"], start=0):
|
|
|
|
|
assert shift["start"] == expected_start + timezone.timedelta(days=delta)
|
Add responders improvements (#3128)
# What this PR does
https://www.loom.com/share/c5e10b5ec51343d0954c6f41cfd6a5fb
## Summary of backend changes
- Add `AlertReceiveChannel.get_orgs_direct_paging_integrations` method
and `AlertReceiveChannel.is_contactable` property. These are needed to
be able to (optionally) filter down teams, in the `GET /teams` internal
API endpoint
([here](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R63)),
to just teams that have a "contactable" Direct Paging integration
- `engine/apps/alerts/paging.py`
- update these functions to support new UX. In short `direct_paging` no
longer takes a list of `ScheduleNotifications` or an `EscalationChain`
object
- add `user_is_oncall` helper function
- add `_construct_title` helper function. In short if no `title` is
provided, which is the case for Direct Pages originating from OnCall
(either UI or Slack), then the format is `f"{from_user.username} is
paging <team.name (if team is specified> <comma separated list of
user.usernames> to join escalation"`
- `engine/apps/api/serializers/team.py` - add
`number_of_users_currently_oncall` attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-26af48f796c9e987a76447586dd0f92349783d6ea6a0b6039a2f0f28bd58c2ebR45-R52))
- `engine/apps/api/serializers/user.py` - add `is_currently_oncall`
attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-6744b5544ebb120437af98a996da5ad7d48ee1139a6112c7e3904010ab98f232R157-R162))
- `engine/apps/api/views/team.py` - add support for two new optional
query params `only_include_notifiable_teams` and `include_no_team`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R70))
- `engine/apps/api/views/user.py`
- in the `GET /users` internal API endpoint, when specifying the
`search` query param now also search on `teams__name`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R223);
this is a new UX requirement)
- add support for a new optional query param, `is_currently_oncall`, to
allow filtering users based on.. whether they are currently on call or
not
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R272-R282))
- remove `check_availability` endpoint (no longer used with new UX; also
removed references in frontend code)
- `engine/apps/slack/scenarios/paging.py` and
`engine/apps/slack/scenarios/manage_responders.py` - update Slack
workflows to support new UX. Schedules are no longer a concept here.
When creating a new alert group via `/escalate` the user either
specifies a team and/or user(s) (they must specify at least one of the
two and validation is done here to check this). When adding responders
to an existing alert group it's simply a list of users that they can
add, no more schedules.
- add `Organization.slack_is_configured` and
`Organization.telegram_is_configured` properties. These are needed to
support [this new functionality
](https://github.com/grafana/oncall/pull/3128/files#diff-9d96504027309f2bd1e95352bac1433b09b60eb4fafb611b52a6c15ed16cbc48R271-R272)
in the `AlertReceiveChannel` model.
## Summary of frontend changes
- Refactor/rename `EscalationVariants` component to `AddResponders` +
remove `grafana-plugin/src/containers/UserWarningModal` (no longer
needed with new UX)
- Remove `grafana-plugin/src/models/user.ts` as it seemed to be a
duplicate of `grafana-plugin/src/models/user/user.types.ts`
Related to https://github.com/grafana/incident/issues/4278
- Closes #3115
- Closes #3116
- Closes #3117
- Closes #3118
- Closes #3177
## TODO
- [x] make frontend changes
- [x] update Slack backend functionality
- [x] update public documentation
- [x] add/update e2e tests
## Post-deploy To-dos
- [ ] update dev/ops/production Slack bots to update `/escalate` command
description (should now say "Direct page a team or user(s)")
## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-10-27 12:12:07 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_users_is_currently_oncall_attribute_works_properly(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
make_on_call_shift,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user1 = make_user_for_organization(organization)
|
|
|
|
|
user2 = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
schedule = make_schedule(
|
|
|
|
|
organization,
|
|
|
|
|
schedule_class=OnCallScheduleWeb,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
|
|
|
|
|
on_call_shift = make_on_call_shift(
|
|
|
|
|
organization=organization,
|
|
|
|
|
shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
|
|
|
|
|
start=today,
|
|
|
|
|
rotation_start=today,
|
|
|
|
|
duration=timezone.timedelta(seconds=24 * 60 * 60),
|
|
|
|
|
priority_level=1,
|
|
|
|
|
frequency=CustomOnCallShift.FREQUENCY_DAILY,
|
|
|
|
|
schedule=schedule,
|
|
|
|
|
)
|
|
|
|
|
on_call_shift.add_rolling_users([[user1]])
|
|
|
|
|
schedule.refresh_ical_file()
|
|
|
|
|
schedule.refresh_ical_final_schedule()
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
2023-11-03 12:40:54 -04:00
|
|
|
url = f"{reverse('api-internal:user-list')}?is_currently_oncall=all"
|
Add responders improvements (#3128)
# What this PR does
https://www.loom.com/share/c5e10b5ec51343d0954c6f41cfd6a5fb
## Summary of backend changes
- Add `AlertReceiveChannel.get_orgs_direct_paging_integrations` method
and `AlertReceiveChannel.is_contactable` property. These are needed to
be able to (optionally) filter down teams, in the `GET /teams` internal
API endpoint
([here](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R63)),
to just teams that have a "contactable" Direct Paging integration
- `engine/apps/alerts/paging.py`
- update these functions to support new UX. In short `direct_paging` no
longer takes a list of `ScheduleNotifications` or an `EscalationChain`
object
- add `user_is_oncall` helper function
- add `_construct_title` helper function. In short if no `title` is
provided, which is the case for Direct Pages originating from OnCall
(either UI or Slack), then the format is `f"{from_user.username} is
paging <team.name (if team is specified> <comma separated list of
user.usernames> to join escalation"`
- `engine/apps/api/serializers/team.py` - add
`number_of_users_currently_oncall` attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-26af48f796c9e987a76447586dd0f92349783d6ea6a0b6039a2f0f28bd58c2ebR45-R52))
- `engine/apps/api/serializers/user.py` - add `is_currently_oncall`
attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-6744b5544ebb120437af98a996da5ad7d48ee1139a6112c7e3904010ab98f232R157-R162))
- `engine/apps/api/views/team.py` - add support for two new optional
query params `only_include_notifiable_teams` and `include_no_team`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R70))
- `engine/apps/api/views/user.py`
- in the `GET /users` internal API endpoint, when specifying the
`search` query param now also search on `teams__name`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R223);
this is a new UX requirement)
- add support for a new optional query param, `is_currently_oncall`, to
allow filtering users based on.. whether they are currently on call or
not
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R272-R282))
- remove `check_availability` endpoint (no longer used with new UX; also
removed references in frontend code)
- `engine/apps/slack/scenarios/paging.py` and
`engine/apps/slack/scenarios/manage_responders.py` - update Slack
workflows to support new UX. Schedules are no longer a concept here.
When creating a new alert group via `/escalate` the user either
specifies a team and/or user(s) (they must specify at least one of the
two and validation is done here to check this). When adding responders
to an existing alert group it's simply a list of users that they can
add, no more schedules.
- add `Organization.slack_is_configured` and
`Organization.telegram_is_configured` properties. These are needed to
support [this new functionality
](https://github.com/grafana/oncall/pull/3128/files#diff-9d96504027309f2bd1e95352bac1433b09b60eb4fafb611b52a6c15ed16cbc48R271-R272)
in the `AlertReceiveChannel` model.
## Summary of frontend changes
- Refactor/rename `EscalationVariants` component to `AddResponders` +
remove `grafana-plugin/src/containers/UserWarningModal` (no longer
needed with new UX)
- Remove `grafana-plugin/src/models/user.ts` as it seemed to be a
duplicate of `grafana-plugin/src/models/user/user.types.ts`
Related to https://github.com/grafana/incident/issues/4278
- Closes #3115
- Closes #3116
- Closes #3117
- Closes #3118
- Closes #3177
## TODO
- [x] make frontend changes
- [x] update Slack backend functionality
- [x] update public documentation
- [x] add/update e2e tests
## Post-deploy To-dos
- [ ] update dev/ops/production Slack bots to update `/escalate` command
description (should now say "Direct page a team or user(s)")
## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-10-27 12:12:07 -04:00
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user1, token))
|
|
|
|
|
|
|
|
|
|
oncall_statuses = {
|
|
|
|
|
user1.public_primary_key: True,
|
|
|
|
|
user2.public_primary_key: False,
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-06 15:30:32 -05:00
|
|
|
for user in response.json():
|
Add responders improvements (#3128)
# What this PR does
https://www.loom.com/share/c5e10b5ec51343d0954c6f41cfd6a5fb
## Summary of backend changes
- Add `AlertReceiveChannel.get_orgs_direct_paging_integrations` method
and `AlertReceiveChannel.is_contactable` property. These are needed to
be able to (optionally) filter down teams, in the `GET /teams` internal
API endpoint
([here](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R63)),
to just teams that have a "contactable" Direct Paging integration
- `engine/apps/alerts/paging.py`
- update these functions to support new UX. In short `direct_paging` no
longer takes a list of `ScheduleNotifications` or an `EscalationChain`
object
- add `user_is_oncall` helper function
- add `_construct_title` helper function. In short if no `title` is
provided, which is the case for Direct Pages originating from OnCall
(either UI or Slack), then the format is `f"{from_user.username} is
paging <team.name (if team is specified> <comma separated list of
user.usernames> to join escalation"`
- `engine/apps/api/serializers/team.py` - add
`number_of_users_currently_oncall` attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-26af48f796c9e987a76447586dd0f92349783d6ea6a0b6039a2f0f28bd58c2ebR45-R52))
- `engine/apps/api/serializers/user.py` - add `is_currently_oncall`
attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-6744b5544ebb120437af98a996da5ad7d48ee1139a6112c7e3904010ab98f232R157-R162))
- `engine/apps/api/views/team.py` - add support for two new optional
query params `only_include_notifiable_teams` and `include_no_team`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R70))
- `engine/apps/api/views/user.py`
- in the `GET /users` internal API endpoint, when specifying the
`search` query param now also search on `teams__name`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R223);
this is a new UX requirement)
- add support for a new optional query param, `is_currently_oncall`, to
allow filtering users based on.. whether they are currently on call or
not
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R272-R282))
- remove `check_availability` endpoint (no longer used with new UX; also
removed references in frontend code)
- `engine/apps/slack/scenarios/paging.py` and
`engine/apps/slack/scenarios/manage_responders.py` - update Slack
workflows to support new UX. Schedules are no longer a concept here.
When creating a new alert group via `/escalate` the user either
specifies a team and/or user(s) (they must specify at least one of the
two and validation is done here to check this). When adding responders
to an existing alert group it's simply a list of users that they can
add, no more schedules.
- add `Organization.slack_is_configured` and
`Organization.telegram_is_configured` properties. These are needed to
support [this new functionality
](https://github.com/grafana/oncall/pull/3128/files#diff-9d96504027309f2bd1e95352bac1433b09b60eb4fafb611b52a6c15ed16cbc48R271-R272)
in the `AlertReceiveChannel` model.
## Summary of frontend changes
- Refactor/rename `EscalationVariants` component to `AddResponders` +
remove `grafana-plugin/src/containers/UserWarningModal` (no longer
needed with new UX)
- Remove `grafana-plugin/src/models/user.ts` as it seemed to be a
duplicate of `grafana-plugin/src/models/user/user.types.ts`
Related to https://github.com/grafana/incident/issues/4278
- Closes #3115
- Closes #3116
- Closes #3117
- Closes #3118
- Closes #3177
## TODO
- [x] make frontend changes
- [x] update Slack backend functionality
- [x] update public documentation
- [x] add/update e2e tests
## Post-deploy To-dos
- [ ] update dev/ops/production Slack bots to update `/escalate` command
description (should now say "Direct page a team or user(s)")
## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-10-27 12:12:07 -04:00
|
|
|
assert user["teams"] == []
|
|
|
|
|
assert user["is_currently_oncall"] == oncall_statuses[user["pk"]]
|
|
|
|
|
|
2024-01-29 14:41:20 -03:00
|
|
|
# getting specific user details include currently on-call info
|
|
|
|
|
url = reverse("api-internal:user-detail", kwargs={"pk": user1.public_primary_key})
|
|
|
|
|
response = client.get(url, format="json", **make_user_auth_headers(user1, token))
|
|
|
|
|
|
|
|
|
|
assert response.json()["is_currently_oncall"]
|
|
|
|
|
|
Add responders improvements (#3128)
# What this PR does
https://www.loom.com/share/c5e10b5ec51343d0954c6f41cfd6a5fb
## Summary of backend changes
- Add `AlertReceiveChannel.get_orgs_direct_paging_integrations` method
and `AlertReceiveChannel.is_contactable` property. These are needed to
be able to (optionally) filter down teams, in the `GET /teams` internal
API endpoint
([here](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R63)),
to just teams that have a "contactable" Direct Paging integration
- `engine/apps/alerts/paging.py`
- update these functions to support new UX. In short `direct_paging` no
longer takes a list of `ScheduleNotifications` or an `EscalationChain`
object
- add `user_is_oncall` helper function
- add `_construct_title` helper function. In short if no `title` is
provided, which is the case for Direct Pages originating from OnCall
(either UI or Slack), then the format is `f"{from_user.username} is
paging <team.name (if team is specified> <comma separated list of
user.usernames> to join escalation"`
- `engine/apps/api/serializers/team.py` - add
`number_of_users_currently_oncall` attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-26af48f796c9e987a76447586dd0f92349783d6ea6a0b6039a2f0f28bd58c2ebR45-R52))
- `engine/apps/api/serializers/user.py` - add `is_currently_oncall`
attribute to response schema
([code](https://github.com/grafana/oncall/pull/3128/files#diff-6744b5544ebb120437af98a996da5ad7d48ee1139a6112c7e3904010ab98f232R157-R162))
- `engine/apps/api/views/team.py` - add support for two new optional
query params `only_include_notifiable_teams` and `include_no_team`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-a4bd76e557f7e11dafb28a52c1034c075028c693b3c12d702d53c07fc6f24c05R55-R70))
- `engine/apps/api/views/user.py`
- in the `GET /users` internal API endpoint, when specifying the
`search` query param now also search on `teams__name`
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R223);
this is a new UX requirement)
- add support for a new optional query param, `is_currently_oncall`, to
allow filtering users based on.. whether they are currently on call or
not
([code](https://github.com/grafana/oncall/pull/3128/files#diff-30309629484ad28e6fe09816e1bd226226d652ea977b6f3b6775976c729bf4b5R272-R282))
- remove `check_availability` endpoint (no longer used with new UX; also
removed references in frontend code)
- `engine/apps/slack/scenarios/paging.py` and
`engine/apps/slack/scenarios/manage_responders.py` - update Slack
workflows to support new UX. Schedules are no longer a concept here.
When creating a new alert group via `/escalate` the user either
specifies a team and/or user(s) (they must specify at least one of the
two and validation is done here to check this). When adding responders
to an existing alert group it's simply a list of users that they can
add, no more schedules.
- add `Organization.slack_is_configured` and
`Organization.telegram_is_configured` properties. These are needed to
support [this new functionality
](https://github.com/grafana/oncall/pull/3128/files#diff-9d96504027309f2bd1e95352bac1433b09b60eb4fafb611b52a6c15ed16cbc48R271-R272)
in the `AlertReceiveChannel` model.
## Summary of frontend changes
- Refactor/rename `EscalationVariants` component to `AddResponders` +
remove `grafana-plugin/src/containers/UserWarningModal` (no longer
needed with new UX)
- Remove `grafana-plugin/src/models/user.ts` as it seemed to be a
duplicate of `grafana-plugin/src/models/user/user.types.ts`
Related to https://github.com/grafana/incident/issues/4278
- Closes #3115
- Closes #3116
- Closes #3117
- Closes #3118
- Closes #3177
## TODO
- [x] make frontend changes
- [x] update Slack backend functionality
- [x] update public documentation
- [x] add/update e2e tests
## Post-deploy To-dos
- [ ] update dev/ops/production Slack bots to update `/escalate` command
description (should now say "Direct page a team or user(s)")
## 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] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-10-27 12:12:07 -04:00
|
|
|
|
|
|
|
|
@pytest.mark.django_db
|
|
|
|
|
def test_list_users_filtered_by_is_currently_oncall(
|
|
|
|
|
make_organization,
|
|
|
|
|
make_user_for_organization,
|
|
|
|
|
make_token_for_organization,
|
|
|
|
|
make_user_auth_headers,
|
|
|
|
|
make_schedule,
|
|
|
|
|
make_on_call_shift,
|
|
|
|
|
):
|
|
|
|
|
organization = make_organization()
|
|
|
|
|
user1 = make_user_for_organization(organization)
|
|
|
|
|
user2 = make_user_for_organization(organization)
|
|
|
|
|
_, token = make_token_for_organization(organization)
|
|
|
|
|
|
|
|
|
|
schedule = make_schedule(
|
|
|
|
|
organization,
|
|
|
|
|
schedule_class=OnCallScheduleWeb,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
today = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
|
|
|
|
|
|
on_call_shift = make_on_call_shift(
|
|
|
|
|
organization=organization,
|
|
|
|
|
shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
|
|
|
|
|
start=today,
|
|
|
|
|
rotation_start=today,
|
|
|
|
|
duration=timezone.timedelta(seconds=24 * 60 * 60),
|
|
|
|
|
priority_level=1,
|
|
|
|
|
frequency=CustomOnCallShift.FREQUENCY_DAILY,
|
|
|
|
|
schedule=schedule,
|
|
|
|
|
)
|
|
|
|
|
on_call_shift.add_rolling_users([[user1]])
|
|
|
|
|
schedule.refresh_ical_file()
|
|
|
|
|
schedule.refresh_ical_final_schedule()
|
|
|
|
|
|
|
|
|
|
client = APIClient()
|
|
|
|
|
url = reverse("api-internal:user-list")
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}?is_currently_oncall=true", format="json", **make_user_auth_headers(user1, token))
|
|
|
|
|
|
|
|
|
|
response = response.json()["results"]
|
|
|
|
|
assert len(response) == 1
|
|
|
|
|
assert response[0]["pk"] == user1.public_primary_key
|
|
|
|
|
|
|
|
|
|
response = client.get(f"{url}?is_currently_oncall=false", format="json", **make_user_auth_headers(user1, token))
|
|
|
|
|
|
|
|
|
|
response = response.json()["results"]
|
|
|
|
|
user = response[0]
|
|
|
|
|
|
|
|
|
|
assert len(response) == 1
|
|
|
|
|
assert user["pk"] == user2.public_primary_key
|
|
|
|
|
assert user["teams"] == []
|