mirror of
https://github.com/GSA/notifications-api.git
synced 2026-01-31 15:15:38 -05:00
Removed one more utils reference in the README and fixed directory name
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import io
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from notifications_utils.clients.antivirus.antivirus_client import (
|
||||
AntivirusClient,
|
||||
AntivirusError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def antivirus(app, mocker):
|
||||
client = AntivirusClient()
|
||||
app.config["ANTIVIRUS_API_HOST"] = "https://antivirus"
|
||||
app.config["ANTIVIRUS_API_KEY"] = "test-antivirus-key"
|
||||
client.init_app(app)
|
||||
return client
|
||||
|
||||
|
||||
def test_scan_document(antivirus, rmock):
|
||||
document = io.BytesIO(b"filecontents")
|
||||
rmock.request(
|
||||
"POST",
|
||||
"https://antivirus/scan",
|
||||
json={"ok": True},
|
||||
request_headers={
|
||||
"Authorization": "Bearer test-antivirus-key",
|
||||
},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
resp = antivirus.scan(document)
|
||||
|
||||
assert resp
|
||||
assert "filecontents" in rmock.last_request.text
|
||||
assert document.tell() == 0
|
||||
|
||||
|
||||
def test_should_raise_for_status(antivirus, rmock):
|
||||
with pytest.raises(AntivirusError) as excinfo:
|
||||
rmock.request(
|
||||
"POST",
|
||||
"https://antivirus/scan",
|
||||
json={"error": "Antivirus error"},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
antivirus.scan(io.BytesIO(b"document"))
|
||||
|
||||
assert excinfo.value.message == "Antivirus error"
|
||||
assert excinfo.value.status_code == 400
|
||||
|
||||
|
||||
def test_should_raise_for_connection_errors(antivirus, rmock):
|
||||
with pytest.raises(AntivirusError) as excinfo:
|
||||
rmock.request(
|
||||
"POST", "https://antivirus/scan", exc=requests.exceptions.ConnectTimeout
|
||||
)
|
||||
antivirus.scan(io.BytesIO(b"document"))
|
||||
|
||||
assert excinfo.value.message == "connection error"
|
||||
assert excinfo.value.status_code == 503
|
||||
@@ -0,0 +1,88 @@
|
||||
import pytest
|
||||
|
||||
from notifications_utils.clients.encryption.encryption_client import (
|
||||
Encryption,
|
||||
EncryptionError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def encryption_client(app):
|
||||
client = Encryption()
|
||||
|
||||
app.config["SECRET_KEY"] = "test-notify-secret-key"
|
||||
app.config["DANGEROUS_SALT"] = "test-notify-salt"
|
||||
|
||||
client.init_app(app)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def test_should_ensure_shared_salt_security(app):
|
||||
client = Encryption()
|
||||
app.config["SECRET_KEY"] = "test-notify-secret-key"
|
||||
app.config["DANGEROUS_SALT"] = "too-short"
|
||||
with pytest.raises(EncryptionError):
|
||||
client.init_app(app)
|
||||
|
||||
|
||||
def test_should_ensure_custom_salt_security(encryption_client):
|
||||
with pytest.raises(EncryptionError):
|
||||
encryption_client.encrypt("this", salt="too-short")
|
||||
|
||||
|
||||
def test_should_encrypt_strings(encryption_client):
|
||||
encrypted = encryption_client.encrypt("this")
|
||||
assert encrypted != "this"
|
||||
assert isinstance(encrypted, str)
|
||||
|
||||
|
||||
def test_should_encrypt_dicts(encryption_client):
|
||||
to_encrypt = {"hello": "world"}
|
||||
encrypted = encryption_client.encrypt(to_encrypt)
|
||||
assert encrypted != to_encrypt
|
||||
assert encryption_client.decrypt(encrypted) == to_encrypt
|
||||
|
||||
|
||||
def test_encryption_is_nondeterministic(encryption_client):
|
||||
first_run = encryption_client.encrypt("this")
|
||||
second_run = encryption_client.encrypt("this")
|
||||
assert first_run != second_run
|
||||
|
||||
|
||||
def test_should_decrypt_content(encryption_client):
|
||||
encrypted = encryption_client.encrypt("this")
|
||||
assert encryption_client.decrypt(encrypted) == "this"
|
||||
|
||||
|
||||
def test_should_decrypt_content_with_custom_salt(encryption_client):
|
||||
salt = "different-salt-value"
|
||||
encrypted = encryption_client.encrypt("this", salt=salt)
|
||||
assert encryption_client.decrypt(encrypted, salt=salt) == "this"
|
||||
|
||||
|
||||
def test_should_verify_decryption(encryption_client):
|
||||
encrypted = encryption_client.encrypt("this")
|
||||
with pytest.raises(EncryptionError):
|
||||
encryption_client.decrypt(encrypted, salt="different-salt-value")
|
||||
|
||||
|
||||
def test_should_sign_and_serialize_string(encryption_client):
|
||||
signed = encryption_client.sign("this")
|
||||
assert signed != "this"
|
||||
|
||||
|
||||
def test_should_verify_signature_and_deserialize_string(encryption_client):
|
||||
signed = encryption_client.sign("this")
|
||||
assert encryption_client.verify_signature(signed) == "this"
|
||||
|
||||
|
||||
def test_should_raise_encryption_error_on_bad_salt(encryption_client):
|
||||
signed = encryption_client.sign("this")
|
||||
with pytest.raises(EncryptionError):
|
||||
encryption_client.verify_signature(signed, salt="different-salt-value")
|
||||
|
||||
|
||||
def test_should_sign_and_serialize_json(encryption_client):
|
||||
signed = encryption_client.sign({"this": "that"})
|
||||
assert encryption_client.verify_signature(signed) == {"this": "that"}
|
||||
221
tests/notifications_utils/clients/redis/test_redis_client.py
Normal file
221
tests/notifications_utils/clients/redis/test_redis_client.py
Normal file
@@ -0,0 +1,221 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from unittest.mock import Mock, call
|
||||
|
||||
import pytest
|
||||
from freezegun import freeze_time
|
||||
|
||||
from notifications_utils.clients.redis.redis_client import RedisClient, prepare_value
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mocked_redis_pipeline():
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def delete_mock():
|
||||
return Mock(return_value=4)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mocked_redis_client(app, mocked_redis_pipeline, delete_mock, mocker):
|
||||
app.config["REDIS_ENABLED"] = True
|
||||
|
||||
redis_client = RedisClient()
|
||||
redis_client.init_app(app)
|
||||
|
||||
mocker.patch.object(redis_client.redis_store, "get", return_value=100)
|
||||
mocker.patch.object(redis_client.redis_store, "set")
|
||||
mocker.patch.object(redis_client.redis_store, "incr")
|
||||
mocker.patch.object(redis_client.redis_store, "delete")
|
||||
mocker.patch.object(
|
||||
redis_client.redis_store, "pipeline", return_value=mocked_redis_pipeline
|
||||
)
|
||||
|
||||
mocker.patch.object(
|
||||
redis_client, "scripts", {"delete-keys-by-pattern": delete_mock}
|
||||
)
|
||||
|
||||
mocker.patch.object(
|
||||
redis_client.redis_store,
|
||||
"hgetall",
|
||||
return_value={b"template-1111": b"8", b"template-2222": b"8"},
|
||||
)
|
||||
|
||||
return redis_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def failing_redis_client(mocked_redis_client, delete_mock):
|
||||
mocked_redis_client.redis_store.get.side_effect = Exception("get failed")
|
||||
mocked_redis_client.redis_store.set.side_effect = Exception("set failed")
|
||||
mocked_redis_client.redis_store.incr.side_effect = Exception("incr failed")
|
||||
mocked_redis_client.redis_store.pipeline.side_effect = Exception("pipeline failed")
|
||||
mocked_redis_client.redis_store.delete.side_effect = Exception("delete failed")
|
||||
delete_mock.side_effect = Exception("delete by pattern failed")
|
||||
return mocked_redis_client
|
||||
|
||||
|
||||
def test_should_not_raise_exception_if_raise_set_to_false(
|
||||
app, caplog, failing_redis_client, mocker
|
||||
):
|
||||
mock_logger = mocker.patch("flask.Flask.logger")
|
||||
|
||||
assert failing_redis_client.get("get_key") is None
|
||||
assert failing_redis_client.set("set_key", "set_value") is None
|
||||
assert failing_redis_client.incr("incr_key") is None
|
||||
assert failing_redis_client.exceeded_rate_limit("rate_limit_key", 100, 100) is False
|
||||
assert failing_redis_client.delete("delete_key") is None
|
||||
assert failing_redis_client.delete("a", "b", "c") is None
|
||||
assert failing_redis_client.delete_by_pattern("pattern") == 0
|
||||
|
||||
assert mock_logger.mock_calls == [
|
||||
call.exception("Redis error performing get on get_key"),
|
||||
call.exception("Redis error performing set on set_key"),
|
||||
call.exception("Redis error performing incr on incr_key"),
|
||||
call.exception("Redis error performing rate-limit-pipeline on rate_limit_key"),
|
||||
call.exception("Redis error performing delete on delete_key"),
|
||||
call.exception("Redis error performing delete on a, b, c"),
|
||||
call.exception("Redis error performing delete-by-pattern on pattern"),
|
||||
]
|
||||
|
||||
|
||||
def test_should_raise_exception_if_raise_set_to_true(
|
||||
app,
|
||||
failing_redis_client,
|
||||
):
|
||||
with pytest.raises(Exception) as e:
|
||||
failing_redis_client.get("test", raise_exception=True)
|
||||
assert str(e.value) == "get failed"
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
failing_redis_client.set("test", "test", raise_exception=True)
|
||||
assert str(e.value) == "set failed"
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
failing_redis_client.incr("test", raise_exception=True)
|
||||
assert str(e.value) == "incr failed"
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
failing_redis_client.exceeded_rate_limit("test", 100, 200, raise_exception=True)
|
||||
assert str(e.value) == "pipeline failed"
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
failing_redis_client.delete("test", raise_exception=True)
|
||||
assert str(e.value) == "delete failed"
|
||||
|
||||
with pytest.raises(Exception) as e:
|
||||
failing_redis_client.delete_by_pattern("pattern", raise_exception=True)
|
||||
assert str(e.value) == "delete by pattern failed"
|
||||
|
||||
|
||||
def test_should_not_call_if_not_enabled(mocked_redis_client, delete_mock):
|
||||
mocked_redis_client.active = False
|
||||
|
||||
assert mocked_redis_client.get("get_key") is None
|
||||
assert mocked_redis_client.set("set_key", "set_value") is None
|
||||
assert mocked_redis_client.incr("incr_key") is None
|
||||
assert mocked_redis_client.exceeded_rate_limit("rate_limit_key", 100, 100) is False
|
||||
assert mocked_redis_client.delete("delete_key") is None
|
||||
assert mocked_redis_client.delete_by_pattern("pattern") == 0
|
||||
|
||||
mocked_redis_client.redis_store.get.assert_not_called()
|
||||
mocked_redis_client.redis_store.set.assert_not_called()
|
||||
mocked_redis_client.redis_store.incr.assert_not_called()
|
||||
mocked_redis_client.redis_store.delete.assert_not_called()
|
||||
mocked_redis_client.redis_store.pipeline.assert_not_called()
|
||||
delete_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_should_call_set_if_enabled(mocked_redis_client):
|
||||
mocked_redis_client.set("key", "value")
|
||||
mocked_redis_client.redis_store.set.assert_called_with(
|
||||
"key", "value", None, None, False, False
|
||||
)
|
||||
|
||||
|
||||
def test_should_call_get_if_enabled(mocked_redis_client):
|
||||
assert mocked_redis_client.get("key") == 100
|
||||
mocked_redis_client.redis_store.get.assert_called_with("key")
|
||||
|
||||
|
||||
@freeze_time("2001-01-01 12:00:00.000000")
|
||||
def test_exceeded_rate_limit_should_add_correct_calls_to_the_pipe(
|
||||
mocked_redis_client, mocked_redis_pipeline
|
||||
):
|
||||
mocked_redis_client.exceeded_rate_limit("key", 100, 100)
|
||||
assert mocked_redis_client.redis_store.pipeline.called
|
||||
mocked_redis_pipeline.zadd.assert_called_with("key", {978350400.0: 978350400.0})
|
||||
mocked_redis_pipeline.zremrangebyscore.assert_called_with(
|
||||
"key", "-inf", 978350300.0
|
||||
)
|
||||
mocked_redis_pipeline.zcard.assert_called_with("key")
|
||||
mocked_redis_pipeline.expire.assert_called_with("key", 100)
|
||||
assert mocked_redis_pipeline.execute.called
|
||||
|
||||
|
||||
@freeze_time("2001-01-01 12:00:00.000000")
|
||||
def test_exceeded_rate_limit_should_fail_request_if_over_limit(
|
||||
mocked_redis_client, mocked_redis_pipeline
|
||||
):
|
||||
mocked_redis_pipeline.execute.return_value = [True, True, 100, True]
|
||||
assert mocked_redis_client.exceeded_rate_limit("key", 99, 100)
|
||||
|
||||
|
||||
@freeze_time("2001-01-01 12:00:00.000000")
|
||||
def test_exceeded_rate_limit_should_allow_request_if_not_over_limit(
|
||||
mocked_redis_client, mocked_redis_pipeline
|
||||
):
|
||||
mocked_redis_pipeline.execute.return_value = [True, True, 100, True]
|
||||
assert not mocked_redis_client.exceeded_rate_limit("key", 101, 100)
|
||||
|
||||
|
||||
@freeze_time("2001-01-01 12:00:00.000000")
|
||||
def test_exceeded_rate_limit_not_exceeded(mocked_redis_client, mocked_redis_pipeline):
|
||||
mocked_redis_pipeline.execute.return_value = [True, True, 80, True]
|
||||
assert not mocked_redis_client.exceeded_rate_limit("key", 90, 100)
|
||||
|
||||
|
||||
def test_exceeded_rate_limit_should_not_call_if_not_enabled(
|
||||
mocked_redis_client, mocked_redis_pipeline
|
||||
):
|
||||
mocked_redis_client.active = False
|
||||
|
||||
assert not mocked_redis_client.exceeded_rate_limit("key", 100, 100)
|
||||
assert not mocked_redis_client.redis_store.pipeline.called
|
||||
|
||||
|
||||
def test_delete(mocked_redis_client):
|
||||
key = "hash-key"
|
||||
mocked_redis_client.delete(key)
|
||||
mocked_redis_client.redis_store.delete.assert_called_with(key)
|
||||
|
||||
|
||||
def test_delete_multi(mocked_redis_client):
|
||||
mocked_redis_client.delete("a", "b", "c")
|
||||
mocked_redis_client.redis_store.delete.assert_called_with("a", "b", "c")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output",
|
||||
[
|
||||
(b"asdf", b"asdf"),
|
||||
("asdf", "asdf"),
|
||||
(0, 0),
|
||||
(1.2, 1.2),
|
||||
(uuid.UUID(int=0), "00000000-0000-0000-0000-000000000000"),
|
||||
pytest.param({"a": 1}, None, marks=pytest.mark.xfail(raises=ValueError)),
|
||||
pytest.param(
|
||||
datetime.utcnow(), None, marks=pytest.mark.xfail(raises=ValueError)
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_prepare_value(input, output):
|
||||
assert prepare_value(input) == output
|
||||
|
||||
|
||||
def test_delete_by_pattern(mocked_redis_client, delete_mock):
|
||||
ret = mocked_redis_client.delete_by_pattern("foo")
|
||||
assert ret == 4
|
||||
delete_mock.assert_called_once_with(args=["foo"])
|
||||
190
tests/notifications_utils/clients/redis/test_request_cache.py
Normal file
190
tests/notifications_utils/clients/redis/test_request_cache.py
Normal file
@@ -0,0 +1,190 @@
|
||||
import pytest
|
||||
|
||||
from notifications_utils.clients.redis import RequestCache
|
||||
from notifications_utils.clients.redis.redis_client import RedisClient
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mocked_redis_client(app):
|
||||
app.config["REDIS_ENABLED"] = True
|
||||
redis_client = RedisClient()
|
||||
redis_client.init_app(app)
|
||||
return redis_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cache(mocked_redis_client):
|
||||
return RequestCache(mocked_redis_client)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"args, kwargs, expected_cache_key",
|
||||
(
|
||||
([1, 2, 3], {}, "1-2-3-None-None-None"),
|
||||
([1, 2, 3, 4, 5, 6], {}, "1-2-3-4-5-6"),
|
||||
([1, 2, 3], {"x": 4, "y": 5, "z": 6}, "1-2-3-4-5-6"),
|
||||
([1, 2, 3, 4], {"y": 5}, "1-2-3-4-5-None"),
|
||||
),
|
||||
)
|
||||
def test_set(
|
||||
mocker,
|
||||
mocked_redis_client,
|
||||
cache,
|
||||
args,
|
||||
kwargs,
|
||||
expected_cache_key,
|
||||
):
|
||||
mock_redis_set = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"set",
|
||||
)
|
||||
mock_redis_get = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"get",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
@cache.set("{a}-{b}-{c}-{x}-{y}-{z}")
|
||||
def foo(a, b, c, x=None, y=None, z=None):
|
||||
return "bar"
|
||||
|
||||
assert foo(*args, **kwargs) == "bar"
|
||||
|
||||
mock_redis_get.assert_called_once_with(expected_cache_key)
|
||||
|
||||
mock_redis_set.assert_called_once_with(
|
||||
expected_cache_key,
|
||||
'"bar"',
|
||||
ex=604_800,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cache_set_call, expected_redis_client_ttl",
|
||||
(
|
||||
(0, 0),
|
||||
(1, 1),
|
||||
(1.111, 1),
|
||||
("2000", 2_000),
|
||||
),
|
||||
)
|
||||
def test_set_with_custom_ttl(
|
||||
mocker,
|
||||
mocked_redis_client,
|
||||
cache,
|
||||
cache_set_call,
|
||||
expected_redis_client_ttl,
|
||||
):
|
||||
mock_redis_set = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"set",
|
||||
)
|
||||
mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"get",
|
||||
return_value=None,
|
||||
)
|
||||
|
||||
@cache.set("foo", ttl_in_seconds=cache_set_call)
|
||||
def foo():
|
||||
return "bar"
|
||||
|
||||
foo()
|
||||
|
||||
mock_redis_set.assert_called_once_with(
|
||||
"foo",
|
||||
'"bar"',
|
||||
ex=expected_redis_client_ttl,
|
||||
)
|
||||
|
||||
|
||||
def test_raises_if_key_doesnt_match_arguments(cache):
|
||||
@cache.set("{baz}")
|
||||
def foo(bar):
|
||||
pass
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
foo(1)
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
foo()
|
||||
|
||||
|
||||
def test_get(mocker, mocked_redis_client, cache):
|
||||
mock_redis_get = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"get",
|
||||
return_value=b'"bar"',
|
||||
)
|
||||
|
||||
@cache.set("{a}-{b}-{c}")
|
||||
def foo(a, b, c):
|
||||
# This function should not be called because the cache has
|
||||
# returned a value
|
||||
raise RuntimeError
|
||||
|
||||
assert foo(1, 2, 3) == "bar"
|
||||
|
||||
mock_redis_get.assert_called_once_with("1-2-3")
|
||||
|
||||
|
||||
def test_delete(mocker, mocked_redis_client, cache):
|
||||
mock_redis_delete = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"delete",
|
||||
)
|
||||
|
||||
@cache.delete("{a}-{b}-{c}")
|
||||
def foo(a, b, c):
|
||||
return "bar"
|
||||
|
||||
assert foo(1, 2, 3) == "bar"
|
||||
|
||||
mock_redis_delete.assert_called_once_with("1-2-3")
|
||||
|
||||
|
||||
def test_delete_even_if_call_raises(mocker, mocked_redis_client, cache):
|
||||
mock_redis_delete = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"delete",
|
||||
)
|
||||
|
||||
@cache.delete("bar")
|
||||
def foo():
|
||||
raise RuntimeError
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
foo()
|
||||
|
||||
mock_redis_delete.assert_called_once_with("bar")
|
||||
|
||||
|
||||
def test_delete_by_pattern(mocker, mocked_redis_client, cache):
|
||||
mock_redis_delete = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"delete_by_pattern",
|
||||
)
|
||||
|
||||
@cache.delete_by_pattern("{a}-{b}-{c}-???")
|
||||
def foo(a, b, c):
|
||||
return "bar"
|
||||
|
||||
assert foo(1, 2, 3) == "bar"
|
||||
|
||||
mock_redis_delete.assert_called_once_with("1-2-3-???")
|
||||
|
||||
|
||||
def test_delete_by_pattern_even_if_call_raises(mocker, mocked_redis_client, cache):
|
||||
mock_redis_delete = mocker.patch.object(
|
||||
mocked_redis_client,
|
||||
"delete_by_pattern",
|
||||
)
|
||||
|
||||
@cache.delete_by_pattern("bar-???")
|
||||
def foo():
|
||||
raise RuntimeError
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
foo()
|
||||
|
||||
mock_redis_delete.assert_called_once_with("bar-???")
|
||||
7
tests/notifications_utils/clients/test_redis.py
Normal file
7
tests/notifications_utils/clients/test_redis.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from notifications_utils.clients.redis import rate_limit_cache_key
|
||||
|
||||
|
||||
def test_rate_limit_cache_key(sample_service):
|
||||
assert rate_limit_cache_key(sample_service.id, "TEST") == "{}-TEST".format(
|
||||
sample_service.id
|
||||
)
|
||||
220
tests/notifications_utils/clients/zendesk/test_zendesk_client.py
Normal file
220
tests/notifications_utils/clients/zendesk/test_zendesk_client.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from base64 import b64decode
|
||||
|
||||
import pytest
|
||||
|
||||
from notifications_utils.clients.zendesk.zendesk_client import (
|
||||
NotifySupportTicket,
|
||||
ZendeskClient,
|
||||
ZendeskError,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def zendesk_client(app):
|
||||
client = ZendeskClient()
|
||||
|
||||
app.config["ZENDESK_API_KEY"] = "testkey"
|
||||
|
||||
client.init_app(app)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
def test_zendesk_client_send_ticket_to_zendesk(zendesk_client, app, mocker, rmock):
|
||||
rmock.request(
|
||||
"POST",
|
||||
ZendeskClient.ZENDESK_TICKET_URL,
|
||||
status_code=201,
|
||||
json={
|
||||
"ticket": {
|
||||
"id": 12345,
|
||||
"subject": "Something is wrong",
|
||||
}
|
||||
},
|
||||
)
|
||||
mock_logger = mocker.patch.object(app.logger, "info")
|
||||
|
||||
ticket = NotifySupportTicket("subject", "message", "incident")
|
||||
zendesk_client.send_ticket_to_zendesk(ticket)
|
||||
|
||||
assert rmock.last_request.headers["Authorization"][:6] == "Basic "
|
||||
b64_auth = rmock.last_request.headers["Authorization"][6:]
|
||||
assert (
|
||||
b64decode(b64_auth.encode()).decode()
|
||||
== "zd-api-notify@digital.cabinet-office.gov.uk/token:testkey"
|
||||
)
|
||||
assert rmock.last_request.json() == ticket.request_data
|
||||
mock_logger.assert_called_once_with("Zendesk create ticket 12345 succeeded")
|
||||
|
||||
|
||||
def test_zendesk_client_send_ticket_to_zendesk_error(
|
||||
zendesk_client, app, mocker, rmock
|
||||
):
|
||||
rmock.request(
|
||||
"POST", ZendeskClient.ZENDESK_TICKET_URL, status_code=401, json={"foo": "bar"}
|
||||
)
|
||||
|
||||
mock_logger = mocker.patch.object(app.logger, "error")
|
||||
|
||||
ticket = NotifySupportTicket("subject", "message", "incident")
|
||||
|
||||
with pytest.raises(ZendeskError):
|
||||
zendesk_client.send_ticket_to_zendesk(ticket)
|
||||
|
||||
mock_logger.assert_called_with(
|
||||
"Zendesk create ticket request failed with 401 '{'foo': 'bar'}'"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"p1_arg, expected_tags, expected_priority",
|
||||
(
|
||||
(
|
||||
{},
|
||||
["govuk_notify_support"],
|
||||
"normal",
|
||||
),
|
||||
(
|
||||
{
|
||||
"p1": False,
|
||||
},
|
||||
["govuk_notify_support"],
|
||||
"normal",
|
||||
),
|
||||
(
|
||||
{
|
||||
"p1": True,
|
||||
},
|
||||
["govuk_notify_emergency"],
|
||||
"urgent",
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_notify_support_ticket_request_data(p1_arg, expected_tags, expected_priority):
|
||||
notify_ticket_form = NotifySupportTicket("subject", "message", "question", **p1_arg)
|
||||
|
||||
assert notify_ticket_form.request_data == {
|
||||
"ticket": {
|
||||
"subject": "subject",
|
||||
"comment": {
|
||||
"body": "message",
|
||||
"public": True,
|
||||
},
|
||||
"group_id": NotifySupportTicket.NOTIFY_GROUP_ID,
|
||||
"organization_id": NotifySupportTicket.NOTIFY_ORG_ID,
|
||||
"ticket_form_id": NotifySupportTicket.NOTIFY_TICKET_FORM_ID,
|
||||
"priority": expected_priority,
|
||||
"tags": expected_tags,
|
||||
"type": "question",
|
||||
"custom_fields": [
|
||||
{"id": "1900000744994", "value": "notify_ticket_type_non_technical"},
|
||||
{"id": "360022836500", "value": []},
|
||||
{"id": "360022943959", "value": None},
|
||||
{"id": "360022943979", "value": None},
|
||||
{"id": "1900000745014", "value": None},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_notify_support_ticket_request_data_with_message_hidden_from_requester():
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "problem", requester_sees_message_content=False
|
||||
)
|
||||
|
||||
assert notify_ticket_form.request_data["ticket"]["comment"]["public"] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name, zendesk_name", [("Name", "Name"), (None, "(no name supplied)")]
|
||||
)
|
||||
def test_notify_support_ticket_request_data_with_user_name_and_email(
|
||||
name, zendesk_name
|
||||
):
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "question", user_name=name, user_email="user@example.com"
|
||||
)
|
||||
|
||||
assert (
|
||||
notify_ticket_form.request_data["ticket"]["requester"]["email"]
|
||||
== "user@example.com"
|
||||
)
|
||||
assert (
|
||||
notify_ticket_form.request_data["ticket"]["requester"]["name"] == zendesk_name
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"custom_fields, tech_ticket_tag, categories, org_id, org_type, service_id",
|
||||
[
|
||||
(
|
||||
{"technical_ticket": True},
|
||||
"notify_ticket_type_technical",
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"technical_ticket": False},
|
||||
"notify_ticket_type_non_technical",
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"ticket_categories": ["notify_billing", "notify_bug"]},
|
||||
"notify_ticket_type_non_technical",
|
||||
["notify_billing", "notify_bug"],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"org_id": "1234", "org_type": "local"},
|
||||
"notify_ticket_type_non_technical",
|
||||
[],
|
||||
"1234",
|
||||
"notify_org_type_local",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{"service_id": "abcd", "org_type": "nhs"},
|
||||
"notify_ticket_type_non_technical",
|
||||
[],
|
||||
None,
|
||||
"notify_org_type_nhs",
|
||||
"abcd",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_notify_support_ticket_request_data_custom_fields(
|
||||
custom_fields,
|
||||
tech_ticket_tag,
|
||||
categories,
|
||||
org_id,
|
||||
org_type,
|
||||
service_id,
|
||||
):
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "question", **custom_fields
|
||||
)
|
||||
|
||||
assert notify_ticket_form.request_data["ticket"]["custom_fields"] == [
|
||||
{"id": "1900000744994", "value": tech_ticket_tag},
|
||||
{"id": "360022836500", "value": categories},
|
||||
{"id": "360022943959", "value": org_id},
|
||||
{"id": "360022943979", "value": org_type},
|
||||
{"id": "1900000745014", "value": service_id},
|
||||
]
|
||||
|
||||
|
||||
def test_notify_support_ticket_request_data_email_ccs():
|
||||
notify_ticket_form = NotifySupportTicket(
|
||||
"subject", "message", "question", email_ccs=["someone@example.com"]
|
||||
)
|
||||
|
||||
assert notify_ticket_form.request_data["ticket"]["email_ccs"] == [
|
||||
{"user_email": "someone@example.com", "action": "put"},
|
||||
]
|
||||
Reference in New Issue
Block a user