186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from django.contrib.auth.models import Permission
|
|
from django.urls import reverse
|
|
|
|
from commerce.integrations.moysklad import MoySkladError
|
|
from commerce.models import IntegrationConnection, IntegrationSyncRun, SourceSystem
|
|
from commerce.services.credentials import decrypt_credentials, encrypt_credentials
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
@pytest.fixture
|
|
def admin_client(client, user_factory):
|
|
client.force_login(user_factory(staff=True, superuser=True))
|
|
return client
|
|
|
|
|
|
@pytest.fixture
|
|
def connection():
|
|
source = SourceSystem.objects.create(code="moysklad", name="МойСклад")
|
|
return IntegrationConnection.objects.create(
|
|
source=source,
|
|
provider="moysklad",
|
|
auth_type="token",
|
|
status="ready",
|
|
encrypted_credentials=encrypt_credentials({"token": "existing-test-token"}),
|
|
)
|
|
|
|
|
|
def connection_data(**overrides):
|
|
return {
|
|
"provider": "moysklad",
|
|
"auth_type": "token",
|
|
"auto_sync": "on",
|
|
"sync_interval_minutes": "15",
|
|
**overrides,
|
|
}
|
|
|
|
|
|
def action(client, connection, name):
|
|
return client.post(
|
|
reverse("admin:commerce_integrationconnection_changelist"),
|
|
{
|
|
"action": name,
|
|
"_selected_action": [str(connection.pk)],
|
|
},
|
|
)
|
|
|
|
|
|
def test_admin_creates_encrypted_connection_and_preserves_blank_credentials(
|
|
admin_client,
|
|
):
|
|
response = admin_client.post(
|
|
reverse("admin:commerce_integrationconnection_add"),
|
|
connection_data(token="admin-test-token"),
|
|
)
|
|
assert response.status_code == 302
|
|
connection = IntegrationConnection.objects.get()
|
|
assert connection.source.code == "moysklad"
|
|
assert connection.created_by.is_superuser
|
|
assert "admin-test-token" not in connection.encrypted_credentials
|
|
assert decrypt_credentials(connection) == {"token": "admin-test-token"}
|
|
encrypted = connection.encrypted_credentials
|
|
url = reverse("admin:commerce_integrationconnection_change", args=[connection.pk])
|
|
page = admin_client.get(url)
|
|
assert page.status_code == 200
|
|
assert b"admin-test-token" not in page.content
|
|
assert encrypted.encode() not in page.content
|
|
response = admin_client.post(url, connection_data(sync_interval_minutes="30"))
|
|
assert response.status_code == 302
|
|
connection.refresh_from_db()
|
|
assert connection.encrypted_credentials == encrypted
|
|
assert connection.sync_interval_minutes == 30
|
|
assert connection.next_sync_at is not None
|
|
|
|
|
|
def test_admin_requires_complete_new_auth_and_preserves_password_spaces(
|
|
admin_client, connection
|
|
):
|
|
url = reverse("admin:commerce_integrationconnection_change", args=[connection.pk])
|
|
response = admin_client.post(url, connection_data(auth_type="basic"))
|
|
assert response.status_code == 200
|
|
assert {"login", "password"} <= response.context["adminform"].form.errors.keys()
|
|
connection.refresh_from_db()
|
|
assert connection.auth_type == "token"
|
|
response = admin_client.post(
|
|
url,
|
|
connection_data(
|
|
auth_type="basic",
|
|
login="admin@test",
|
|
password=" password with spaces ",
|
|
),
|
|
)
|
|
assert response.status_code == 302
|
|
connection.refresh_from_db()
|
|
assert decrypt_credentials(connection) == {
|
|
"login": "admin@test",
|
|
"password": " password with spaces ",
|
|
}
|
|
assert connection.status == "disconnected"
|
|
|
|
|
|
def test_admin_rejects_basic_auth_for_wildberries(admin_client):
|
|
response = admin_client.post(
|
|
reverse("admin:commerce_integrationconnection_add"),
|
|
connection_data(
|
|
provider="wildberries", auth_type="basic", login="test", password="test"
|
|
),
|
|
)
|
|
assert response.status_code == 200
|
|
assert "auth_type" in response.context["adminform"].form.errors
|
|
assert not IntegrationConnection.objects.exists()
|
|
|
|
|
|
def test_admin_test_action_updates_state_and_reports_failure(admin_client, connection):
|
|
with patch(
|
|
"commerce.admin.test_moysklad_connection",
|
|
return_value={
|
|
"account_name": "Test company",
|
|
"account_id": "test-account",
|
|
},
|
|
):
|
|
assert action(admin_client, connection, "test_connections").status_code == 302
|
|
connection.refresh_from_db()
|
|
assert connection.status == "ready"
|
|
assert connection.account_name == "Test company"
|
|
assert connection.last_tested_at is not None
|
|
assert connection.source.metadata["account_id"] == "test-account"
|
|
with patch(
|
|
"commerce.admin.test_moysklad_connection",
|
|
side_effect=MoySkladError("Test failure"),
|
|
):
|
|
assert action(admin_client, connection, "test_connections").status_code == 302
|
|
connection.refresh_from_db()
|
|
assert connection.status == "error"
|
|
assert connection.last_error == "Test failure"
|
|
|
|
|
|
def test_admin_sync_enqueues_once_and_blocks_editing_active_connection(
|
|
admin_client,
|
|
connection,
|
|
django_capture_on_commit_callbacks,
|
|
):
|
|
with (
|
|
patch("commerce.tasks.sync_connection_run.apply_async") as publish,
|
|
django_capture_on_commit_callbacks(execute=True),
|
|
):
|
|
assert action(admin_client, connection, "sync_connections").status_code == 302
|
|
assert action(admin_client, connection, "sync_connections").status_code == 302
|
|
publish.assert_called_once()
|
|
assert IntegrationSyncRun.objects.filter(connection=connection).count() == 1
|
|
connection.refresh_from_db()
|
|
assert connection.status == "queued"
|
|
url = reverse("admin:commerce_integrationconnection_change", args=[connection.pk])
|
|
response = admin_client.post(url, connection_data(token="replacement"))
|
|
assert response.status_code == 200
|
|
assert response.context["adminform"].form.non_field_errors()
|
|
connection.refresh_from_db()
|
|
assert decrypt_credentials(connection) == {"token": "existing-test-token"}
|
|
|
|
|
|
def test_view_only_staff_cannot_run_admin_actions(client, user_factory, connection):
|
|
user = user_factory(staff=True)
|
|
user.user_permissions.add(
|
|
Permission.objects.get(
|
|
content_type__app_label="commerce",
|
|
codename="view_integrationconnection",
|
|
)
|
|
)
|
|
client.force_login(user)
|
|
assert (
|
|
client.get(
|
|
reverse(
|
|
"admin:commerce_integrationconnection_change",
|
|
args=[connection.pk],
|
|
)
|
|
).status_code
|
|
== 200
|
|
)
|
|
with patch("commerce.admin.enqueue_connection_sync") as enqueue:
|
|
action(client, connection, "sync_connections")
|
|
enqueue.assert_not_called()
|
|
assert not IntegrationSyncRun.objects.exists()
|