From ecf7bb1fef4667b00b749f78b55595584bc1dc96 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 29 Jul 2021 17:50:23 +0200 Subject: [PATCH] Various event AP converter changes and add tests Signed-off-by: Thomas Citharel --- .../activity_stream/converter/event.ex | 9 +- lib/mobilizon/events/events.ex | 22 +- .../activity_pub/types/events_test.exs | 270 ++++++++++++++++++ 3 files changed, 290 insertions(+), 11 deletions(-) create mode 100644 test/federation/activity_pub/types/events_test.exs diff --git a/lib/federation/activity_stream/converter/event.ex b/lib/federation/activity_stream/converter/event.ex index bd7dace4e..99e814eca 100644 --- a/lib/federation/activity_stream/converter/event.ex +++ b/lib/federation/activity_stream/converter/event.ex @@ -37,6 +37,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do @online_address_name "Website" @banner_picture_name "Banner" + @ap_public "https://www.w3.org/ns/activitystreams#Public" @doc """ Converts an AP object data to our internal data structure. @@ -92,15 +93,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do @impl Converter @spec model_to_as(EventModel.t()) :: map def model_to_as(%EventModel{} = event) do - to = + {to, cc} = if event.visibility == :public, - do: ["https://www.w3.org/ns/activitystreams#Public"], - else: [attributed_to_or_default(event).followers_url] + do: {[@ap_public], []}, + else: {[attributed_to_or_default(event).followers_url], [@ap_public]} %{ "type" => "Event", "to" => to, - "cc" => [], + "cc" => cc, "attributedTo" => attributed_to_or_default(event).url, "name" => event.title, "actor" => diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index c58ba3e7a..af3bb0f25 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -88,6 +88,8 @@ defmodule Mobilizon.Events do :media ] + @participant_preloads [:event, :actor] + @doc """ Gets a single event. """ @@ -307,8 +309,9 @@ defmodule Mobilizon.Events do """ @spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()} def update_event(%Event{draft: old_draft} = old_event, attrs) do - with %Changeset{changes: changes} = changeset <- - Event.update_changeset(Repo.preload(old_event, [:tags, :media]), attrs), + with %Event{} = old_event <- Repo.preload(old_event, @event_preloads), + %Changeset{changes: changes} = changeset <- + Event.update_changeset(old_event, attrs), {:ok, %{update: %Event{} = new_event}} <- Multi.new() |> Multi.update(:update, changeset) @@ -329,7 +332,8 @@ defmodule Mobilizon.Events do err -> err end end) - |> Repo.transaction() do + |> Repo.transaction(), + %Event{} = new_event <- Repo.preload(new_event, @event_preloads, force: true) do Cachex.del(:ics, "event_#{new_event.uuid}") Email.Event.calculate_event_diff_and_send_notifications( @@ -341,7 +345,7 @@ defmodule Mobilizon.Events do unless new_event.draft, do: Workers.BuildSearch.enqueue(:update_search_event, %{"event_id" => new_event.id}) - {:ok, Repo.preload(new_event, @event_preloads)} + {:ok, new_event} end end @@ -728,7 +732,7 @@ defmodule Mobilizon.Events do def get_participant(participant_id) do Participant |> where([p], p.id == ^participant_id) - |> preload([p], [:event, :actor]) + |> preload([p], ^@participant_preloads) |> Repo.one() end @@ -744,6 +748,7 @@ defmodule Mobilizon.Events do case Participant |> where([p], event_id: ^event_id, actor_id: ^actor_id) |> where([p], fragment("? ->>'email' = ?", p.metadata, ^email)) + |> preload([p], ^@participant_preloads) |> Repo.one() do %Participant{} = participant -> {:ok, participant} @@ -758,6 +763,7 @@ defmodule Mobilizon.Events do case Participant |> where([p], event_id: ^event_id, actor_id: ^actor_id) |> where([p], fragment("? ->>'cancellation_token' = ?", p.metadata, ^cancellation_token)) + |> preload([p], ^@participant_preloads) |> Repo.one() do %Participant{} = participant -> {:ok, participant} @@ -768,7 +774,9 @@ defmodule Mobilizon.Events do end def get_participant(event_id, actor_id, %{}) do - case Repo.get_by(Participant, event_id: event_id, actor_id: actor_id) do + case Participant + |> Repo.get_by(event_id: event_id, actor_id: actor_id) + |> Repo.preload(@participant_preloads) do %Participant{} = participant -> {:ok, participant} @@ -781,7 +789,7 @@ defmodule Mobilizon.Events do def get_participant_by_confirmation_token(confirmation_token) do Participant |> where([p], fragment("? ->>'confirmation_token' = ?", p.metadata, ^confirmation_token)) - |> preload([p], [:actor, :event]) + |> preload([p], ^@participant_preloads) |> Repo.one() end diff --git a/test/federation/activity_pub/types/events_test.exs b/test/federation/activity_pub/types/events_test.exs new file mode 100644 index 000000000..f2f934461 --- /dev/null +++ b/test/federation/activity_pub/types/events_test.exs @@ -0,0 +1,270 @@ +defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do + use Mobilizon.DataCase + + import Mobilizon.Factory + + alias Mobilizon.Actors.Actor + alias Mobilizon.Events + alias Mobilizon.Events.Event + alias Mobilizon.Federation.ActivityPub.Types.Events + + @ap_public "https://www.w3.org/ns/activitystreams#Public" + + describe "test event creation" do + @event_begins_on "2021-07-28T15:04:22Z" + @event_title "hey" + @event_data %{title: @event_title, begins_on: @event_begins_on} + + test "from a simple profile" do + %Actor{id: organizer_actor_id, url: actor_url, followers_url: followers_url} = + insert(:actor) + + assert {:ok, %Event{}, data} = + Events.create( + Map.merge(@event_data, %{organizer_actor_id: organizer_actor_id}), + %{} + ) + + assert match?( + %{ + "actor" => ^actor_url, + "attributedTo" => ^actor_url, + "cc" => [^followers_url], + "object" => %{ + "actor" => ^actor_url, + "anonymousParticipationEnabled" => false, + "attachment" => [], + "attributedTo" => ^actor_url, + "category" => nil, + "cc" => [], + "commentsEnabled" => false, + "content" => nil, + "draft" => false, + "endTime" => nil, + "ical:status" => "CONFIRMED", + "joinMode" => "free", + "maximumAttendeeCapacity" => nil, + "mediaType" => "text/html", + "name" => @event_title, + "repliesModerationOption" => nil, + "startTime" => @event_begins_on, + "tag" => [], + "to" => [@ap_public], + "type" => "Event" + }, + "to" => [@ap_public], + "type" => "Create" + }, + data + ) + end + + test "an unlisted event" do + %Actor{id: organizer_actor_id, url: actor_url, followers_url: followers_url} = + insert(:actor) + + assert {:ok, %Event{}, data} = + Events.create( + Map.merge(@event_data, %{ + organizer_actor_id: organizer_actor_id, + visibility: :unlisted + }), + %{} + ) + + assert match?( + %{ + "actor" => ^actor_url, + "attributedTo" => ^actor_url, + "cc" => [@ap_public], + "object" => %{ + "actor" => ^actor_url, + "anonymousParticipationEnabled" => false, + "attachment" => [], + "attributedTo" => ^actor_url, + "category" => nil, + "cc" => [@ap_public], + "commentsEnabled" => false, + "content" => nil, + "draft" => false, + "endTime" => nil, + "ical:status" => "CONFIRMED", + "joinMode" => "free", + "maximumAttendeeCapacity" => nil, + "mediaType" => "text/html", + "name" => @event_title, + "repliesModerationOption" => nil, + "startTime" => @event_begins_on, + "tag" => [], + "to" => [^followers_url], + "type" => "Event" + }, + "to" => [^followers_url], + "type" => "Create" + }, + data + ) + end + + test "from a group member" do + %Actor{id: organizer_actor_id, url: actor_url} = actor = insert(:actor) + + %Actor{ + id: attributed_to_id, + url: group_url, + followers_url: followers_url, + members_url: members_url + } = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone") + + insert(:member, parent: group, actor: actor, role: :moderator) + + assert {:ok, %Event{}, data} = + Events.create( + Map.merge(@event_data, %{ + organizer_actor_id: organizer_actor_id, + attributed_to_id: attributed_to_id + }), + %{} + ) + + assert match?( + %{ + "actor" => ^actor_url, + "attributedTo" => ^group_url, + "cc" => [^members_url, ^followers_url], + "object" => %{ + "actor" => ^actor_url, + "anonymousParticipationEnabled" => false, + "attachment" => [], + "attributedTo" => ^group_url, + "category" => nil, + "cc" => [], + "commentsEnabled" => false, + "content" => nil, + "draft" => false, + "endTime" => nil, + "ical:status" => "CONFIRMED", + "joinMode" => "free", + "maximumAttendeeCapacity" => nil, + "mediaType" => "text/html", + "name" => @event_title, + "repliesModerationOption" => nil, + "startTime" => @event_begins_on, + "tag" => [], + "to" => [@ap_public], + "type" => "Event" + }, + "to" => [@ap_public], + "type" => "Create" + }, + data + ) + end + end + + @event_updated_title "my event updated" + @event_update_data %{title: @event_updated_title} + + describe "test event update" do + test "from a simple profile" do + %Actor{url: actor_url, followers_url: followers_url} = actor = insert(:actor) + + {:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on) + %Event{} = event = insert(:event, organizer_actor: actor, begins_on: begins_on) + + assert {:ok, %Event{}, data} = + Events.update( + event, + @event_update_data, + %{} + ) + + assert match?( + %{ + "actor" => ^actor_url, + "attributedTo" => ^actor_url, + "cc" => [^followers_url], + "object" => %{ + "actor" => ^actor_url, + "anonymousParticipationEnabled" => false, + "attributedTo" => ^actor_url, + "cc" => [], + "commentsEnabled" => false, + "draft" => false, + "ical:status" => "CONFIRMED", + "joinMode" => "free", + "maximumAttendeeCapacity" => nil, + "mediaType" => "text/html", + "name" => @event_updated_title, + "repliesModerationOption" => nil, + "startTime" => @event_begins_on, + "tag" => [], + "to" => [@ap_public], + "type" => "Event" + }, + "to" => [@ap_public], + "type" => "Update" + }, + data + ) + end + + test "from a group member" do + %Actor{} = actor_1 = insert(:actor) + %Actor{id: organizer_actor_2_id, url: actor_2_url} = actor_2 = insert(:actor) + + %Actor{ + url: group_url, + followers_url: followers_url, + members_url: members_url + } = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone") + + insert(:member, parent: group, actor: actor_1, role: :moderator) + insert(:member, parent: group, actor: actor_2, role: :moderator) + + {:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on) + + %Event{} = + event = + insert(:event, organizer_actor: actor_1, begins_on: begins_on, attributed_to: group) + + assert {:ok, %Event{}, data} = + Events.update( + event, + Map.merge(@event_update_data, %{ + organizer_actor_id: organizer_actor_2_id + }), + %{} + ) + + assert match?( + %{ + "actor" => ^actor_2_url, + "attributedTo" => ^group_url, + "cc" => [^members_url, ^followers_url], + "object" => %{ + "actor" => ^actor_2_url, + "anonymousParticipationEnabled" => false, + "attributedTo" => ^group_url, + "cc" => [], + "commentsEnabled" => false, + "draft" => false, + "ical:status" => "CONFIRMED", + "joinMode" => "free", + "maximumAttendeeCapacity" => nil, + "mediaType" => "text/html", + "name" => @event_updated_title, + "repliesModerationOption" => nil, + "startTime" => @event_begins_on, + "tag" => [], + "to" => [@ap_public], + "type" => "Event" + }, + "to" => [@ap_public], + "type" => "Update" + }, + data + ) + end + end +end