import pytest from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.urls import reverse from wagtail.models import ReferenceIndex from events.merge_organizers import CUSTOM_VALUE, merge_event_organizers from events.models import EventOrganizer, EventOrganizerLink, EventPage from tests.conftest import AssociationPageFactory, EventPageFactory pytestmark = pytest.mark.django_db @pytest.fixture def organizers(db): keeper = EventOrganizer.objects.create(name="Kulturutvalget", slug="kulturutvalget") goner = EventOrganizer.objects.create(name="KU", slug="ku") return keeper, goner @pytest.fixture def editor_client(client, django_user_model): user = django_user_model.objects.create_user(username="editor", password="pw") user.user_permissions.add(Permission.objects.get(codename="access_admin")) client.force_login(user) return client def test_merge_repoints_event_links_and_deletes_loser(event_index, organizers): keeper, goner = organizers event = EventPageFactory(parent=event_index) EventOrganizerLink.objects.create(event=event, organizer=goner) merged = merge_event_organizers(keeper, goner) assert merged.pk == keeper.pk assert not EventOrganizer.objects.filter(pk=goner.pk).exists() assert list(event.organizer_links.values_list("organizer_id", flat=True)) == [keeper.pk] def test_lowest_pk_survives_regardless_of_argument_order(organizers): keeper, goner = organizers merged = merge_event_organizers(goner, keeper) assert merged.pk == keeper.pk assert not EventOrganizer.objects.filter(pk=goner.pk).exists() def test_merge_applies_chosen_name_and_slug(organizers): keeper, goner = organizers merged = merge_event_organizers(keeper, goner, name="KU", slug="ku") assert merged.name == "KU" assert merged.slug == "ku" def test_merge_keeps_survivor_name_and_slug_by_default(organizers): keeper, goner = organizers merged = merge_event_organizers(keeper, goner) assert merged.name == "Kulturutvalget" assert merged.slug == "kulturutvalget" def test_merge_collapses_duplicate_links_on_same_event(event_index, organizers): keeper, goner = organizers event = EventPageFactory(parent=event_index) EventOrganizerLink.objects.create(event=event, organizer=keeper) EventOrganizerLink.objects.create(event=event, organizer=goner) merge_event_organizers(keeper, goner) assert list(event.organizer_links.values_list("organizer_id", flat=True)) == [keeper.pk] def test_merge_fills_blank_fields_from_loser(association_index, organizers): keeper, goner = organizers association = AssociationPageFactory(parent=association_index) goner.association = association goner.external_url = "https://example.com" goner.save() merged = merge_event_organizers(keeper, goner) assert merged.association_id == association.pk assert merged.external_url == "https://example.com" def test_merge_keeps_survivor_fields_when_set(organizers): keeper, goner = organizers keeper.external_url = "https://keeper.example.com" keeper.save() goner.external_url = "https://goner.example.com" goner.save() merged = merge_event_organizers(keeper, goner) assert merged.external_url == "https://keeper.example.com" def test_merge_rewrites_event_page_revisions(event_index, organizers): keeper, goner = organizers event = EventPageFactory(parent=event_index) EventOrganizerLink.objects.create(event=event, organizer=goner) revision = EventPage.objects.get(pk=event.pk).save_revision() assert [link["organizer"] for link in revision.content["organizer_links"]] == [goner.pk] merge_event_organizers(keeper, goner) revision.refresh_from_db() assert [link["organizer"] for link in revision.content["organizer_links"]] == [keeper.pk] def test_merge_dedupes_organizers_within_revisions(event_index, organizers): keeper, goner = organizers event = EventPageFactory(parent=event_index) EventOrganizerLink.objects.create(event=event, organizer=keeper) EventOrganizerLink.objects.create(event=event, organizer=goner) revision = EventPage.objects.get(pk=event.pk).save_revision() merge_event_organizers(keeper, goner) revision.refresh_from_db() assert [link["organizer"] for link in revision.content["organizer_links"]] == [keeper.pk] def test_merge_updates_reference_index(event_index, organizers): keeper, goner = organizers event = EventPageFactory(parent=event_index) EventOrganizerLink.objects.create(event=event, organizer=goner) ReferenceIndex.create_or_update_for_object(EventPage.objects.get(pk=event.pk)) organizer_ct = ContentType.objects.get_for_model(EventOrganizer) assert ReferenceIndex.objects.filter( to_content_type=organizer_ct, to_object_id=str(goner.pk) ).exists() merge_event_organizers(keeper, goner) assert not ReferenceIndex.objects.filter( to_content_type=organizer_ct, to_object_id=str(goner.pk) ).exists() assert ReferenceIndex.objects.filter( to_content_type=organizer_ct, to_object_id=str(keeper.pk) ).exists() def test_merge_refuses_same_organizer(organizers): keeper, _ = organizers with pytest.raises(ValueError): merge_event_organizers(keeper, keeper) def test_merge_views_require_superuser(editor_client, organizers): keeper, goner = organizers confirm_url = reverse("events_merge_organizers_confirm") urls = [ reverse("events_merge_organizers"), f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}", ] for url in urls: response = editor_client.get(url) assert response.status_code == 302 assert response.url == reverse("wagtailadmin_home") assert EventOrganizer.objects.filter(pk=goner.pk).exists() def test_select_view_redirects_to_confirm(admin_client, organizers): keeper, goner = organizers response = admin_client.post( reverse("events_merge_organizers"), {"organizer_a": keeper.pk, "organizer_b": goner.pk}, ) assert response.status_code == 302 confirm_url = reverse("events_merge_organizers_confirm") assert response.url == f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}" def test_select_view_rejects_same_organizer_twice(admin_client, organizers): keeper, _ = organizers response = admin_client.post( reverse("events_merge_organizers"), {"organizer_a": keeper.pk, "organizer_b": keeper.pk}, ) assert response.status_code == 200 assert "Pick two different organizers." in response.content.decode() def test_confirm_view_shows_both_organizers(admin_client, organizers): keeper, goner = organizers confirm_url = reverse("events_merge_organizers_confirm") response = admin_client.get(f"{confirm_url}?organizer_a={goner.pk}&organizer_b={keeper.pk}") content = response.content.decode() assert response.status_code == 200 assert "Kulturutvalget" in content assert "KU" in content def test_confirm_view_rejects_missing_or_equal_organizers(admin_client, organizers): keeper, _ = organizers confirm_url = reverse("events_merge_organizers_confirm") queries = [ "", f"?organizer_a={keeper.pk}&organizer_b={keeper.pk}", f"?organizer_a=abc&organizer_b={keeper.pk}", f"?organizer_a={keeper.pk}&organizer_b=999999", ] for query in queries: response = admin_client.get(confirm_url + query) assert response.status_code == 302 assert response.url == reverse("events_merge_organizers") def test_confirm_view_merges_with_picked_values(admin_client, event_index, organizers): keeper, goner = organizers event = EventPageFactory(parent=event_index) EventOrganizerLink.objects.create(event=event, organizer=goner) confirm_url = reverse("events_merge_organizers_confirm") response = admin_client.post( f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}", {"name": goner.name, "name_custom": "", "slug": goner.slug, "slug_custom": ""}, ) assert response.status_code == 302 assert response.url == reverse("wagtailsnippets_events_eventorganizer:list") keeper.refresh_from_db() assert keeper.name == "KU" assert keeper.slug == "ku" assert not EventOrganizer.objects.filter(pk=goner.pk).exists() assert list(event.organizer_links.values_list("organizer_id", flat=True)) == [keeper.pk] def test_confirm_view_merges_with_custom_values(admin_client, organizers): keeper, goner = organizers confirm_url = reverse("events_merge_organizers_confirm") response = admin_client.post( f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}", { "name": CUSTOM_VALUE, "name_custom": "Kulturutvalget (KU)", "slug": CUSTOM_VALUE, "slug_custom": "Kulturutvalget KU", }, ) assert response.status_code == 302 keeper.refresh_from_db() assert keeper.name == "Kulturutvalget (KU)" assert keeper.slug == "kulturutvalget-ku" def test_confirm_view_requires_custom_value_when_picked(admin_client, organizers): keeper, goner = organizers confirm_url = reverse("events_merge_organizers_confirm") response = admin_client.post( f"{confirm_url}?organizer_a={keeper.pk}&organizer_b={goner.pk}", {"name": CUSTOM_VALUE, "name_custom": "", "slug": keeper.slug, "slug_custom": ""}, ) assert response.status_code == 200 assert EventOrganizer.objects.filter(pk=goner.pk).exists()