mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-16 07:19:13 +08:00
Most of the time we just want an OK status code. 202, 204, 200 should all be OK and some of this depends on the Galaxy instance config, so this should be more robust to future changes.
454 lines
20 KiB
Python
454 lines
20 KiB
Python
from datetime import (
|
|
datetime,
|
|
timedelta,
|
|
)
|
|
from typing import (
|
|
Any,
|
|
Optional,
|
|
)
|
|
from uuid import uuid4
|
|
|
|
from galaxy_test.base.populators import (
|
|
DatasetPopulator,
|
|
WorkflowPopulator,
|
|
)
|
|
from galaxy_test.driver.integration_util import IntegrationTestCase
|
|
|
|
|
|
def notification_test_data(subject: Optional[str] = None, message: Optional[str] = None):
|
|
return {
|
|
"source": "integration_tests",
|
|
"variant": "info",
|
|
"category": "message",
|
|
"content": {
|
|
"category": "message",
|
|
"subject": subject or "Testing Subject",
|
|
"message": message or "Testing Message",
|
|
},
|
|
}
|
|
|
|
|
|
def notification_broadcast_test_data(subject: Optional[str] = None, message: Optional[str] = None):
|
|
return {
|
|
"source": "integration_tests",
|
|
"variant": "info",
|
|
"category": "broadcast",
|
|
"content": {
|
|
"category": "broadcast",
|
|
"subject": subject or "Testing Broadcast Subject",
|
|
"message": message or "Testing Broadcast Message",
|
|
},
|
|
}
|
|
|
|
|
|
class NotificationsIntegrationBase(IntegrationTestCase):
|
|
dataset_populator: DatasetPopulator
|
|
task_based = False
|
|
framework_tool_and_types = False
|
|
|
|
@classmethod
|
|
def handle_galaxy_config_kwds(cls, config):
|
|
super().handle_galaxy_config_kwds(config)
|
|
config["enable_celery_tasks"] = False
|
|
config["enable_notification_system"] = True
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.dataset_populator = DatasetPopulator(self.galaxy_interactor)
|
|
self.workflow_populator = WorkflowPopulator(self.galaxy_interactor)
|
|
|
|
def test_notification_status(self):
|
|
user1 = self._create_test_user()
|
|
user2 = self._create_test_user()
|
|
|
|
before_creating_notifications = datetime.utcnow()
|
|
|
|
# Only user1 will receive this notification
|
|
subject1 = f"notification_{uuid4()}"
|
|
created_response_1 = self._send_test_notification_to(
|
|
[user1["id"]], subject=subject1, message="test_notification_status 1"
|
|
)
|
|
self._assert_notifications_sent(created_response_1, expected_count=1)
|
|
|
|
# Both user1 and user2 will receive this notification
|
|
subject2 = f"notification_{uuid4()}"
|
|
created_response_2 = self._send_test_notification_to(
|
|
[user1["id"], user2["id"]], subject=subject2, message="test_notification_status 2"
|
|
)
|
|
self._assert_notifications_sent(created_response_2, expected_count=2)
|
|
|
|
# All users will receive this broadcasted notification
|
|
created_response_3 = self._send_broadcast_notification("test_notification_status 3")
|
|
assert created_response_3["total_notifications_sent"] == 1
|
|
|
|
after_creating_notifications = datetime.utcnow()
|
|
|
|
# The default user should have received only the broadcasted notifications
|
|
status = self._get_notifications_status_since(before_creating_notifications)
|
|
assert status["total_unread_count"] == 0
|
|
assert len(status["notifications"]) == 0
|
|
assert len(status["broadcasts"]) == 1
|
|
|
|
with self._different_user(user1["email"]):
|
|
status = self._get_notifications_status_since(before_creating_notifications)
|
|
assert status["total_unread_count"] == 2
|
|
assert len(status["notifications"]) == 2
|
|
assert len(status["broadcasts"]) == 1
|
|
# Mark one unseen notification as seen
|
|
unseen_notification = status["notifications"][0]["id"]
|
|
self._update_notification(unseen_notification, update_state={"seen": True})
|
|
status = self._get_notifications_status_since(before_creating_notifications)
|
|
# The total unread count should be updated
|
|
assert status["total_unread_count"] == 1
|
|
assert len(status["notifications"]) == 2
|
|
assert len(status["broadcasts"]) == 1
|
|
|
|
with self._different_user(user2["email"]):
|
|
status = self._get_notifications_status_since(before_creating_notifications)
|
|
assert status["total_unread_count"] == 1
|
|
assert len(status["notifications"]) == 1
|
|
assert len(status["broadcasts"]) == 1
|
|
|
|
# Getting the notifications status since a posterior date does not return notifications
|
|
# but the total number of unread notifications is maintained
|
|
status = self._get_notifications_status_since(after_creating_notifications)
|
|
assert status["total_unread_count"] == 1
|
|
assert len(status["notifications"]) == 0
|
|
assert len(status["broadcasts"]) == 0
|
|
|
|
# Updating a notification association value should return that notification in the next request
|
|
notification_id = self._get_notification_id_by_subject(subject2)
|
|
assert notification_id is not None
|
|
self._update_notification(notification_id, update_state={"seen": True})
|
|
status = self._get_notifications_status_since(after_creating_notifications)
|
|
assert status["total_unread_count"] == 0
|
|
assert len(status["notifications"]) == 1
|
|
assert len(status["broadcasts"]) == 0
|
|
|
|
with self._different_user(anon=True):
|
|
# Anonymous users can access broadcasted notifications
|
|
status = self._get_notifications_status_since(before_creating_notifications)
|
|
assert status["total_unread_count"] == 0
|
|
assert len(status["notifications"]) == 0
|
|
assert len(status["broadcasts"]) == 1
|
|
|
|
def test_user_cannot_access_other_users_notifications(self):
|
|
user1 = self._create_test_user()
|
|
user2 = self._create_test_user()
|
|
|
|
subject = f"notification_{uuid4()}"
|
|
created_response = self._send_test_notification_to(
|
|
[user1["id"]], subject=subject, message="test_user_cannot_access_other_users_notifications"
|
|
)
|
|
self._assert_notifications_sent(created_response, expected_count=1)
|
|
notification_id = None
|
|
|
|
with self._different_user(user1["email"]):
|
|
notification_id = self._get_notification_id_by_subject(subject)
|
|
assert notification_id is not None
|
|
response = self._get(f"notifications/{notification_id}")
|
|
self._assert_status_code_is_ok(response)
|
|
|
|
with self._different_user(user2["email"]):
|
|
assert notification_id is not None
|
|
response = self._get(f"notifications/{notification_id}")
|
|
self._assert_status_code_is(response, 404)
|
|
|
|
def test_delete_notification_by_user(self):
|
|
user1 = self._create_test_user()
|
|
user2 = self._create_test_user()
|
|
|
|
before_creating_notifications = datetime.utcnow()
|
|
|
|
subject = f"notification_{uuid4()}"
|
|
created_response = self._send_test_notification_to(
|
|
[user1["id"], user2["id"]], subject=subject, message="test_delete_notification_by_user"
|
|
)
|
|
self._assert_notifications_sent(created_response, expected_count=2)
|
|
|
|
with self._different_user(user1["email"]):
|
|
notification_id = self._get_notification_id_by_subject(subject)
|
|
assert notification_id is not None
|
|
response = self._get(f"notifications/{notification_id}")
|
|
self._assert_status_code_is_ok(response)
|
|
self._delete(f"notifications/{notification_id}")
|
|
# After delete, it is no longer available for this user
|
|
response = self._get(f"notifications/{notification_id}")
|
|
self._assert_status_code_is(response, 404)
|
|
status = self._get_notifications_status_since(before_creating_notifications)
|
|
assert status["total_unread_count"] == 0
|
|
assert len(status["notifications"]) == 0
|
|
assert len(status["broadcasts"]) == 0
|
|
|
|
with self._different_user(user2["email"]):
|
|
assert notification_id is not None
|
|
response = self._get(f"notifications/{notification_id}")
|
|
self._assert_status_code_is_ok(response)
|
|
|
|
def test_non_admin_users_cannot_create_notifications(self):
|
|
recipient_user = self._create_test_user()
|
|
notification_request = {
|
|
"recipients": {"user_ids": [recipient_user["id"]]},
|
|
"notification": notification_test_data(),
|
|
}
|
|
response = self._post("notifications", data=notification_request, json=True)
|
|
self._assert_status_code_is(response, 403)
|
|
|
|
broadcast_notification_data = notification_broadcast_test_data()
|
|
|
|
response = self._post("notifications/broadcast", data=broadcast_notification_data, json=True)
|
|
self._assert_status_code_is(response, 403)
|
|
|
|
with self._different_user(anon=True):
|
|
response = self._post("notifications", data=notification_request, json=True)
|
|
self._assert_status_code_is(response, 403)
|
|
|
|
response = self._post("notifications/broadcast", data=broadcast_notification_data, json=True)
|
|
self._assert_status_code_is(response, 403)
|
|
|
|
def test_update_notifications(self):
|
|
recipient_user = self._create_test_user()
|
|
subject = f"User Notification to update {uuid4()}"
|
|
created_user_notification_response = self._send_test_notification_to([recipient_user["id"]], subject=subject)
|
|
self._assert_notifications_sent(created_user_notification_response, expected_count=1)
|
|
|
|
with self._different_user(recipient_user["email"]):
|
|
user_notification_id = self._get_notification_id_by_subject(subject)
|
|
|
|
assert user_notification_id is not None
|
|
|
|
created_broadcast_notification_response = self._send_broadcast_notification(
|
|
subject="Broadcasted Notification to update"
|
|
)
|
|
assert created_broadcast_notification_response["total_notifications_sent"] == 1
|
|
broadcasted_notification_id = created_broadcast_notification_response["notification"]["id"]
|
|
|
|
update_core_value_payload = {"source": "updated_source"} # Core values are part of the notification itself
|
|
update_user_value_payload = {"seen": True} # User values are from the association (seen, deleted, etc)
|
|
|
|
# Regular users cannot update core notification values
|
|
update_response = self._put(f"notifications/{user_notification_id}", data=update_core_value_payload, json=True)
|
|
self._assert_status_code_is(update_response, 400)
|
|
|
|
update_response = self._put(
|
|
f"notifications/broadcast/{broadcasted_notification_id}", data=update_core_value_payload, json=True
|
|
)
|
|
self._assert_status_code_is(update_response, 403)
|
|
|
|
# Regular users cannot update associated values from other users (they don't exists for them)
|
|
update_response = self._put(f"notifications/{user_notification_id}", data=update_user_value_payload, json=True)
|
|
self._assert_status_code_is(update_response, 404)
|
|
|
|
# Recipient users can only update their associated values
|
|
with self._different_user(recipient_user["email"]):
|
|
update_response = self._put(
|
|
f"notifications/{user_notification_id}", data=update_core_value_payload, json=True
|
|
)
|
|
self._assert_status_code_is(update_response, 400)
|
|
|
|
update_response = self._put(
|
|
f"notifications/{user_notification_id}", data=update_user_value_payload, json=True
|
|
)
|
|
self._assert_status_code_is_ok(update_response)
|
|
updated_response = self._get(f"notifications/{user_notification_id}").json()
|
|
assert updated_response["seen_time"] is not None
|
|
|
|
# Admin users can update core values of broadcasted notifications
|
|
update_response = self._put(
|
|
f"notifications/broadcast/{broadcasted_notification_id}",
|
|
data=update_core_value_payload,
|
|
json=True,
|
|
admin=True,
|
|
)
|
|
self._assert_status_code_is_ok(update_response)
|
|
updated_response = self._get(f"notifications/broadcast/{broadcasted_notification_id}").json()
|
|
assert updated_response["source"] == "updated_source"
|
|
|
|
def test_admins_get_all_broadcasted_even_inactive(self):
|
|
tomorrow = datetime.utcnow() + timedelta(days=1)
|
|
yesterday = datetime.utcnow() - timedelta(days=1)
|
|
self._send_broadcast_notification(subject="Active")
|
|
self._send_broadcast_notification(subject="Scheduled", publication_time=tomorrow)
|
|
self._send_broadcast_notification(subject="Expired", expiration_time=yesterday)
|
|
|
|
# Regular users will only get active (published and non-expired) broadcasted notifications
|
|
response_from_user = self._get("notifications/broadcast").json()
|
|
subjects = [notification["content"]["subject"] for notification in response_from_user]
|
|
assert "Active" in subjects
|
|
assert "Scheduled" not in subjects
|
|
assert "Expired" not in subjects
|
|
|
|
# Admins will get all broadcasted notifications even inactive ones
|
|
response_from_admin = self._get("notifications/broadcast", admin=True).json()
|
|
subjects = [notification["content"]["subject"] for notification in response_from_admin]
|
|
assert "Active" in subjects
|
|
assert "Scheduled" in subjects
|
|
assert "Expired" in subjects
|
|
|
|
def test_notification_input_dates_consider_timezone(self):
|
|
payload = notification_broadcast_test_data(subject="Test", message="Test")
|
|
payload["publication_time"] = "2021-01-01T12:00:00+02:00"
|
|
payload["expiration_time"] = "2021-01-01T12:00:00Z"
|
|
response = self._post("notifications/broadcast", data=payload, admin=True, json=True)
|
|
self._assert_status_code_is_ok(response)
|
|
notification = response.json()["notification"]
|
|
assert notification["publication_time"] == "2021-01-01T10:00:00"
|
|
assert notification["expiration_time"] == "2021-01-01T12:00:00"
|
|
|
|
def test_broadcast_notification_action_links(self):
|
|
# Broadcast notifications can have relative and absolute links
|
|
response = self._send_broadcast_notification(
|
|
action_links=[("View Workflows", "/workflows/list"), ("Go to GTN", "https://training.galaxyproject.org")]
|
|
)
|
|
notification_id = response["notification"]["id"]
|
|
response = self._get(f"notifications/broadcast/{notification_id}")
|
|
self._assert_status_code_is_ok(response)
|
|
notification = response.json()
|
|
assert "content" in notification
|
|
assert "action_links" in notification["content"]
|
|
assert len(notification["content"]["action_links"]) == 2
|
|
action_link = notification["content"]["action_links"][0]
|
|
assert action_link["action_name"] == "View Workflows"
|
|
assert action_link["link"] == "/workflows/list"
|
|
action_link = notification["content"]["action_links"][1]
|
|
assert action_link["action_name"] == "Go to GTN"
|
|
assert action_link["link"] == "https://training.galaxyproject.org"
|
|
|
|
def test_sharing_items_creates_notifications_when_expected(self):
|
|
user1 = self._create_test_user()
|
|
user2 = self._create_test_user()
|
|
user_ids = [user1["id"], user2["id"]]
|
|
|
|
# User2 doesn't want to receive shared item notifications
|
|
with self._different_user(user2["email"]):
|
|
update_request = {
|
|
"preferences": {"new_shared_item": {"enabled": False, "channels": {"push": False}}},
|
|
}
|
|
update_response = self._put("notifications/preferences", data=update_request, json=True)
|
|
self._assert_status_code_is_ok(update_response)
|
|
|
|
# Share History with both users
|
|
history_id = self.dataset_populator.new_history("Notification test history")
|
|
self.dataset_populator.new_dataset(history_id)
|
|
payload = {"user_ids": user_ids, "share_option": "make_accessible_to_shared"}
|
|
sharing_response = self._put(f"histories/{history_id}/share_with_users", data=payload, json=True)
|
|
self._assert_status_code_is_ok(sharing_response)
|
|
|
|
# Share Workflow with both users
|
|
workflow_id = self.workflow_populator.simple_workflow("Notification test workflow")
|
|
payload = {"user_ids": user_ids}
|
|
sharing_response = self._put(f"workflows/{workflow_id}/share_with_users", data=payload, json=True)
|
|
self._assert_status_code_is_ok(sharing_response)
|
|
|
|
# Share Page with both users
|
|
page_id = self.dataset_populator.new_page(slug="notification-test-page")["id"]
|
|
payload = {"user_ids": user_ids}
|
|
sharing_response = self._put(f"pages/{page_id}/share_with_users", data=payload, json=True)
|
|
self._assert_status_code_is_ok(sharing_response)
|
|
|
|
# Share Visualization with both users
|
|
create_payload = {
|
|
"title": "Notification test visualization",
|
|
"slug": "notification-test-viz",
|
|
"type": "example",
|
|
"dbkey": "hg17",
|
|
}
|
|
response = self._post("visualizations", data=create_payload, json=True).json()
|
|
visualization_id = response["id"]
|
|
payload = {"user_ids": user_ids}
|
|
sharing_response = self._put(f"visualizations/{visualization_id}/share_with_users", data=payload, json=True)
|
|
self._assert_status_code_is_ok(sharing_response)
|
|
|
|
with self._different_user(user1["email"]):
|
|
notifications = self._get("notifications").json()
|
|
assert len(notifications) == 4
|
|
|
|
with self._different_user(user2["email"]):
|
|
notifications = self._get("notifications").json()
|
|
assert len(notifications) == 0
|
|
|
|
def _create_test_user(self):
|
|
user = self._setup_user(f"{uuid4()}@galaxy.test")
|
|
return user
|
|
|
|
def _get_notifications_status_since(self, since: datetime):
|
|
status_response = self._get(f"notifications/status?since={since}")
|
|
self._assert_status_code_is_ok(status_response)
|
|
status = status_response.json()
|
|
return status
|
|
|
|
def _send_test_notification_to(
|
|
self, user_ids: list[str], subject: Optional[str] = None, message: Optional[str] = None
|
|
):
|
|
request = {
|
|
"recipients": {"user_ids": user_ids},
|
|
"notification": notification_test_data(subject, message),
|
|
}
|
|
response = self._post("notifications", data=request, admin=True, json=True)
|
|
self._assert_status_code_is_ok(response)
|
|
created_response = response.json()
|
|
return created_response
|
|
|
|
def _send_broadcast_notification(
|
|
self,
|
|
subject: Optional[str] = None,
|
|
message: Optional[str] = None,
|
|
publication_time: Optional[datetime] = None,
|
|
expiration_time: Optional[datetime] = None,
|
|
action_links: Optional[list[tuple[str, str]]] = None,
|
|
):
|
|
payload = notification_broadcast_test_data()
|
|
if subject is not None:
|
|
payload["content"]["subject"] = subject
|
|
if message is not None:
|
|
payload["content"]["message"] = message
|
|
if action_links is not None:
|
|
payload["content"]["action_links"] = [
|
|
{"action_name": action_name, "link": link} for action_name, link in action_links
|
|
]
|
|
if publication_time is not None:
|
|
payload["publication_time"] = publication_time.isoformat()
|
|
if expiration_time is not None:
|
|
payload["expiration_time"] = expiration_time.isoformat()
|
|
response = self._post("notifications/broadcast", data=payload, admin=True, json=True)
|
|
self._assert_status_code_is_ok(response)
|
|
notifications_status = response.json()
|
|
return notifications_status
|
|
|
|
def _update_notification(self, notification_id: str, update_state: dict[str, Any]):
|
|
update_response = self._put(f"notifications/{notification_id}", data=update_state, json=True)
|
|
self._assert_status_code_is_ok(update_response)
|
|
|
|
def _assert_notifications_sent(self, response, expected_count: int = 0):
|
|
if self.task_based:
|
|
task_id = response["id"]
|
|
assert task_id is not None
|
|
self.dataset_populator.wait_on_task_id(task_id)
|
|
else:
|
|
assert response["total_notifications_sent"] == expected_count
|
|
|
|
def _get_notification_id_by_subject(self, subject: str) -> Optional[str]:
|
|
notifications = self._get("notifications").json()
|
|
for notification in notifications:
|
|
if notification["content"]["subject"] == subject:
|
|
return notification["id"]
|
|
return None
|
|
|
|
|
|
class TestNotificationsIntegration(NotificationsIntegrationBase):
|
|
task_based = False
|
|
|
|
@classmethod
|
|
def handle_galaxy_config_kwds(cls, config):
|
|
super().handle_galaxy_config_kwds(config)
|
|
config["enable_celery_tasks"] = False
|
|
|
|
|
|
class TestNotificationsIntegrationTaskBased(NotificationsIntegrationBase):
|
|
task_based = True
|
|
|
|
@classmethod
|
|
def handle_galaxy_config_kwds(cls, config):
|
|
super().handle_galaxy_config_kwds(config)
|
|
config["enable_celery_tasks"] = True
|