defmodule Mobilizon.Web.Resolvers.EventTest do use Mobilizon.Web.ConnCase use Bamboo.Test, shared: true use Oban.Testing, repo: Mobilizon.Storage.Repo import Mobilizon.Factory alias Mobilizon.Actors.Actor alias Mobilizon.{Events, Users} alias Mobilizon.Events.Event alias Mobilizon.Service.Workers alias Mobilizon.Users.User alias Mobilizon.GraphQL.AbsintheHelpers alias Mobilizon.Web.Email @event %{ description: "some body", title: "some title", begins_on: DateTime.utc_now() |> DateTime.truncate(:second), uuid: "b5126423-f1af-43e4-a923-002a03003ba4", url: "some url", category: "MEETING" } @find_event_query """ query Event($uuid: UUID!) { event(uuid: $uuid) { uuid, draft } } """ setup %{conn: conn} do user = insert(:user) actor = insert(:actor, user: user, preferred_username: "test") {:ok, conn: conn, actor: actor, user: user} end describe "Find an event" do test "find_event/3 returns an event", context do event = @event |> Map.put(:organizer_actor_id, context.actor.id) {:ok, event} = Events.create_event(event) res = context.conn |> AbsintheHelpers.graphql_query(query: @find_event_query, variables: %{uuid: event.uuid}) assert res["data"]["event"]["uuid"] == to_string(event.uuid) res = context.conn |> AbsintheHelpers.graphql_query(query: @find_event_query, variables: %{uuid: "bad uuid"}) assert [%{"message" => "Argument \"uuid\" has invalid value $uuid."}] = res["errors"] res = context.conn |> AbsintheHelpers.graphql_query( query: @find_event_query, variables: %{uuid: "b5126423-f1af-43e4-a923-002a03003ba5"} ) assert [%{"message" => "Event not found"}] = res["errors"] end end describe "create_event/3 for a regular profile" do @create_event_mutation """ mutation CreateEvent( $title: String!, $description: String!, $begins_on: DateTime!, $ends_on: DateTime, $status: EventStatus, $visibility: EventVisibility, $organizer_actor_id: ID!, $attributed_to_id: ID, $online_address: String, $options: EventOptionsInput, $draft: Boolean, $language: String $picture: MediaInput $tags: [String] $physicalAddress: AddressInput $category: EventCategory ) { createEvent( title: $title, description: $description, begins_on: $begins_on, ends_on: $ends_on, status: $status, visibility: $visibility, organizer_actor_id: $organizer_actor_id, attributed_to_id: $attributed_to_id, online_address: $online_address, options: $options, draft: $draft, picture: $picture language: $language physicalAddress: $physicalAddress category: $category tags: $tags ) { id, uuid, title, description, begins_on, ends_on, status, visibility, organizer_actor { id }, attributed_to { id }, physicalAddress { id, url, geom, street } online_address, phone_address, category, draft language options { maximumAttendeeCapacity, showRemainingAttendeeCapacity, showEndTime } picture { url name } tags { slug title } } } """ test "create_event/3 should check the organizer_actor_id is owned by the user", %{ conn: conn, user: user } do another_actor = insert(:actor) begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{begins_on}", organizer_actor_id: "#{another_actor.id}" } ) assert res["data"]["createEvent"] == nil assert hd(res["errors"])["message"] == "Organizer profile is not owned by the user" end test "create_event/3 should check that end time is after start time", %{ conn: conn, actor: actor, user: user } do begins_on = DateTime.utc_now() |> DateTime.truncate(:second) ends_on = DateTime.add(begins_on, -2 * 3600) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{begins_on}", ends_on: "#{ends_on}", organizer_actor_id: "#{actor.id}" } ) assert hd(res["errors"])["message"] == ["ends_on cannot be set before begins_on"] end test "create_event/3 creates an event", %{conn: conn, actor: actor, user: user} do begins_on = DateTime.utc_now() res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{DateTime.add(begins_on, 3600 * 24)}", organizer_actor_id: "#{actor.id}" } ) assert res["data"]["createEvent"]["title"] == "come to my event" id = String.to_integer(res["data"]["createEvent"]["id"]) uuid = res["data"]["createEvent"]["uuid"] assert_enqueued( worker: Workers.BuildSearch, args: %{event_id: id, op: :insert_search_event} ) assert_enqueued( worker: Workers.EventDelayedNotificationWorker, args: %{event_uuid: uuid, action: :notify_of_new_event}, scheduled_at: {DateTime.add(begins_on, 1800), delta: 5} ) end test "create_event/3 creates an event and escapes title", %{ conn: conn, actor: actor, user: user } do res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "My Event title ", description: "My description ", begins_on: DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601(), organizer_actor_id: "#{actor.id}" } ) assert res["errors"] == nil assert res["data"]["createEvent"]["title"] == "My Event title " assert res["data"]["createEvent"]["description"] == "My description " {id, ""} = res["data"]["createEvent"]["id"] |> Integer.parse() assert_enqueued( worker: Workers.BuildSearch, args: %{event_id: id, op: :insert_search_event} ) end test "create_event/3 creates an event as a draft", %{conn: conn, actor: actor, user: user} do res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{DateTime.utc_now()}", organizer_actor_id: "#{actor.id}", draft: true } ) assert res["data"]["createEvent"]["title"] == "come to my event" assert res["data"]["createEvent"]["draft"] == true event_uuid = res["data"]["createEvent"]["uuid"] event_id = res["data"]["createEvent"]["id"] {event_id_int, ""} = Integer.parse(event_id) refute_enqueued( worker: Workers.BuildSearch, args: %{event_id: event_id_int, op: :insert_search_event} ) res = conn |> AbsintheHelpers.graphql_query(query: @find_event_query, variables: %{uuid: event_uuid}) assert hd(res["errors"])["message"] =~ "not found" res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query(query: @find_event_query, variables: %{uuid: event_uuid}) assert res["errors"] == nil assert res["data"]["event"]["draft"] == true query = """ query Person($actor_id: ID!, $event_id: ID) { person(id: $actor_id) { id, participations(eventId: $event_id) { elements { id, role, actor { id }, event { id } } } } } """ res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: query, variables: %{actor_id: actor.id, event_id: event_id} ) assert res["errors"] == nil assert res["data"]["person"]["participations"]["elements"] == [] end test "create_event/3 creates an event as a draft for a group", %{ conn: conn, actor: actor, user: user } do %Actor{id: group_id} = group = insert(:group) insert(:member, parent: group, actor: actor, role: :moderator) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{DateTime.utc_now()}", organizer_actor_id: "#{actor.id}", attributed_to_id: group_id, draft: true } ) assert res["data"]["createEvent"]["title"] == "come to my event" assert res["data"]["createEvent"]["draft"] == true assert res["data"]["createEvent"]["attributed_to"]["id"] == to_string(group_id) event_uuid = res["data"]["createEvent"]["uuid"] event_id = res["data"]["createEvent"]["id"] refute_enqueued( worker: Workers.BuildSearch, args: %{event_id: String.to_integer(event_id), op: :insert_search_event} ) res = conn |> AbsintheHelpers.graphql_query(query: @find_event_query, variables: %{uuid: event_uuid}) assert hd(res["errors"])["message"] =~ "not found" res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query(query: @find_event_query, variables: %{uuid: event_uuid}) assert res["errors"] == nil assert res["data"]["event"]["draft"] == true end test "create_event/3 creates an event with options", %{conn: conn, actor: actor, user: user} do begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() ends_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{begins_on}", ends_on: "#{ends_on}", status: "TENTATIVE", visibility: "UNLISTED", organizer_actor_id: "#{actor.id}", online_address: "toto@example.com", options: %{ maximumAttendeeCapacity: 30, showRemainingAttendeeCapacity: true, showEndTime: false } } ) assert res["errors"] == nil event = res["data"]["createEvent"] assert event["title"] == "come to my event" assert event["description"] == "it will be fine" assert event["begins_on"] == begins_on assert event["ends_on"] == ends_on assert event["status"] == "TENTATIVE" assert event["visibility"] == "UNLISTED" assert event["organizer_actor"]["id"] == "#{actor.id}" assert event["online_address"] == "toto@example.com" assert event["options"]["maximumAttendeeCapacity"] == 30 assert event["options"]["showRemainingAttendeeCapacity"] == true assert event["options"]["showEndTime"] == false {event_id_int, ""} = Integer.parse(event["id"]) assert_enqueued( worker: Workers.BuildSearch, args: %{event_id: event_id_int, op: :insert_search_event} ) end test "create_event/3 creates an event an invalid options", %{ conn: conn, actor: actor, user: user } do begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{begins_on}", organizer_actor_id: "#{actor.id}", options: %{ maximumAttendeeCapacity: -5 } } ) assert hd(res["errors"])["message"] == %{ "maximum_attendee_capacity" => ["must be greater than or equal to 0"] } end test "create_event/3 creates an event with tags", %{conn: conn, actor: actor, user: user} do res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "my event is referenced", description: "with tags!", begins_on: "#{DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()}", organizer_actor_id: "#{actor.id}", category: "PARTY", tags: ["nicolas", "birthday", "bad tag"] } ) assert res["errors"] == nil assert res["data"]["createEvent"]["title"] == "my event is referenced" assert res["data"]["createEvent"]["tags"] == [ %{"slug" => "nicolas", "title" => "nicolas"}, %{"slug" => "birthday", "title" => "birthday"}, %{"slug" => "bad-tag", "title" => "bad tag"} ] assert res["data"]["createEvent"]["category"] == "PARTY" end test "create_event/3 creates an event with an address", %{ conn: conn, actor: actor, user: user } do address = %{street: "I am a street, please believe me", locality: "Where ever"} res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "my event is referenced", description: "with tags!", begins_on: "#{DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()}", organizer_actor_id: "#{actor.id}", category: "PARTY", physicalAddress: %{ street: "#{address.street}", locality: "#{address.locality}" } } ) assert res["errors"] == nil assert res["data"]["createEvent"]["title"] == "my event is referenced" assert res["data"]["createEvent"]["physicalAddress"]["street"] == address.street address_url = res["data"]["createEvent"]["physicalAddress"]["url"] address_id = res["data"]["createEvent"]["physicalAddress"]["id"] res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "my event is referenced", description: "with tags!", begins_on: "#{DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()}", organizer_actor_id: "#{actor.id}", category: "PARTY", physicalAddress: %{ id: "#{address_id}" } } ) assert res["errors"] == nil assert res["data"]["createEvent"]["title"] == "my event is referenced" assert res["data"]["createEvent"]["physicalAddress"]["street"] == address.street assert res["data"]["createEvent"]["physicalAddress"]["id"] == address_id assert res["data"]["createEvent"]["physicalAddress"]["url"] == address_url end test "create_event/3 creates an event with an attached picture", %{ conn: conn, actor: actor, user: user } do map = %{ "query" => @create_event_mutation, "variables" => %{ title: "come to my event", description: "it will be fine", begins_on: "#{DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()}", organizer_actor_id: "#{actor.id}", category: "PARTY", picture: %{ media: %{ name: "picture for my event", alt: "A very sunny landscape", file: "event.jpg", actor_id: actor.id } } }, "event.jpg" => %Plug.Upload{ path: "test/fixtures/picture.png", filename: "event.jpg" } } res = conn |> auth_conn(user) |> put_req_header("content-type", "multipart/form-data") |> post( "/api", map ) |> json_response(200) assert res["data"]["createEvent"]["title"] == "come to my event" assert res["data"]["createEvent"]["picture"]["name"] == "picture for my event" end @upload_media_mutation """ mutation UploadMedia($name: String!, $alt: String, $file: Upload!) { uploadMedia( name: $name alt: $alt file: $file ) { id url name content_type size } } """ test "create_event/3 creates an event with an picture ID", %{ conn: conn, actor: actor, user: user } do media = %{name: "my pic", alt: "represents something", file: "picture.png"} map = %{ "query" => @upload_media_mutation, "variables" => media, media.file => %Plug.Upload{ path: "test/fixtures/picture.png", filename: media.file } } res = conn |> auth_conn(user) |> put_req_header("content-type", "multipart/form-data") |> post( "/api", map ) |> json_response(200) assert res["errors"] == nil assert res["data"]["uploadMedia"]["name"] == media.name media_id = res["data"]["uploadMedia"]["id"] assert media_id !== "" and not is_nil(media_id) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "come to my event", description: "it will be fine", begins_on: "#{DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()}", organizer_actor_id: "#{actor.id}", category: "PARTY", picture: %{ media_id: "#{media_id}" } } ) assert res["errors"] == nil assert res["data"]["createEvent"]["title"] == "come to my event" assert res["data"]["createEvent"]["picture"]["name"] == media.name assert res["data"]["createEvent"]["picture"]["url"] end test "create_event/3 creates an event with detected language", %{ conn: conn, actor: %Actor{id: actor_id}, user: user } do res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "Come to my event", description: "This should be long enough to get detected", organizer_actor_id: actor_id, begins_on: "2021-07-26T09:00:00Z" } ) assert res["errors"] == nil assert res["data"]["createEvent"]["language"] == "en" end test "create_event/3 creates an event with manually set language", %{ conn: conn, actor: %Actor{id: actor_id}, user: user } do res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: %{ title: "Come to my event", description: "This should be long enough to get detected", organizer_actor_id: actor_id, begins_on: "2021-07-26T09:00:00Z", language: "it" } ) assert res["errors"] == nil assert res["data"]["createEvent"]["language"] == "it" end end describe "create_event/3 on behalf of a group" do @variables %{ title: "come to my event", description: "it will be fine", begins_on: "2021-07-26T09:00:00Z" } test "create_event/3 should check the member has permission to create a group event", %{ conn: conn } do %User{} = user = insert(:user) %Actor{id: group_id} = group = insert(:group) %Actor{id: member_not_approved_actor_id} = member_not_approved_actor = insert(:actor, user: user) insert(:member, parent: group, actor: member_not_approved_actor) %Actor{id: member_actor_id} = member_actor = insert(:actor, user: user) insert(:member, parent: group, actor: member_actor, role: :member) %Actor{id: moderator_actor_id} = moderator_actor = insert(:actor, user: user) insert(:member, parent: group, actor: moderator_actor, role: :moderator) %Actor{id: not_member_actor_id} = insert(:actor, user: user) variables = Map.put(@variables, :attributed_to_id, "#{group_id}") res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{member_not_approved_actor_id}") ) assert res["data"]["createEvent"] == nil assert hd(res["errors"])["message"] == "Organizer profile doesn't have permission to create an event on behalf of this group" res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{not_member_actor_id}") ) assert res["data"]["createEvent"] == nil assert hd(res["errors"])["message"] == "Organizer profile doesn't have permission to create an event on behalf of this group" res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{member_actor_id}") ) assert res["data"]["createEvent"] == nil assert hd(res["errors"])["message"] == "Organizer profile doesn't have permission to create an event on behalf of this group" res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @create_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{moderator_actor_id}") ) assert res["errors"] == nil assert res["data"]["createEvent"] != nil end end @update_event_mutation """ mutation updateEvent( $eventId: ID! $title: String $description: String $beginsOn: DateTime $endsOn: DateTime $status: EventStatus $visibility: EventVisibility $joinOptions: EventJoinOptions $draft: Boolean $tags: [String] $picture: MediaInput $onlineAddress: String $phoneAddress: String $organizerActorId: ID $attributedToId: ID $category: EventCategory $physicalAddress: AddressInput $options: EventOptionsInput $contacts: [Contact] ) { updateEvent( eventId: $eventId title: $title description: $description beginsOn: $beginsOn endsOn: $endsOn status: $status visibility: $visibility joinOptions: $joinOptions draft: $draft tags: $tags picture: $picture onlineAddress: $onlineAddress phoneAddress: $phoneAddress organizerActorId: $organizerActorId attributedToId: $attributedToId category: $category physicalAddress: $physicalAddress options: $options contacts: $contacts ) { id, uuid, url, title draft description beginsOn endsOn status tags { title, slug }, online_address, phone_address, category, options { maximumAttendeeCapacity, showRemainingAttendeeCapacity }, physicalAddress { url, geom, street } picture { name } } } """ describe "update_event/3" do test "update_event/3 should check the event exists", %{conn: conn, actor: _actor, user: user} do res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{eventId: 45, title: "my event updated"} ) assert hd(res["errors"])["message"] == "Event not found" end test "update_event/3 should check the user can change the organizer", %{ conn: conn, actor: actor, user: user } do event = insert(:event, organizer_actor: actor) actor2 = insert(:actor) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{eventId: event.id, title: "my event updated", organizerActorId: actor2.id} ) assert hd(res["errors"])["message"] == "You can't attribute this event to this profile." end test "update_event/3 should check the user is the organizer", %{ conn: conn, actor: _actor, user: user } do event = insert(:event) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{eventId: event.id, title: "my event updated"} ) assert hd(res["errors"])["message"] == "You can't edit this event." end test "update_event/3 should check the user is the organizer also when it's changed", %{ conn: conn, actor: actor, user: user } do event = insert(:event) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{eventId: event.id, title: "my event updated", organizerActorId: actor.id} ) assert hd(res["errors"])["message"] == "You can't edit this event." end test "update_event/3 should check end time is after the beginning time", %{ conn: conn, actor: actor, user: user } do event = insert(:event, organizer_actor: actor) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{ eventId: event.id, title: "my event updated", endsOn: event.begins_on |> DateTime.add(3600 * -2) |> DateTime.to_iso8601() } ) assert hd(res["errors"])["message"] == ["ends_on cannot be set before begins_on"] end test "updates an event", %{conn: conn, actor: actor, user: user} do %Event{uuid: event_uuid, title: event_title} = event = insert(:event, organizer_actor: actor) creator = insert(:participant, event: event, actor: actor, role: :creator) participant_user = insert(:user) participant_actor = insert(:actor, user: participant_user) participant = insert(:participant, event: event, actor: participant_actor, role: :participant) address = insert(:address) begins_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() ends_on = DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{ eventId: event.id, title: "my event updated", description: "description updated", beginsOn: "#{begins_on}", endsOn: "#{ends_on}", status: "TENTATIVE", tags: ["tag1_updated", "tag2_updated"], onlineAddress: "toto@example.com", phoneAddress: "0000000000", category: "PARTY", options: %{ maximumAttendeeCapacity: 30, showRemainingAttendeeCapacity: true }, physicalAddress: %{ street: "#{address.street}", locality: "#{address.locality}" } } ) assert res["errors"] == nil event_res = res["data"]["updateEvent"] assert event_res["title"] == "my event updated" assert event_res["description"] == "description updated" assert event_res["beginsOn"] == "#{begins_on}" assert event_res["endsOn"] == "#{ends_on}" assert event_res["status"] == "TENTATIVE" assert event_res["online_address"] == "toto@example.com" assert event_res["phone_address"] == "0000000000" assert event_res["category"] == "PARTY" assert event_res["options"]["maximumAttendeeCapacity"] == 30 assert event_res["options"]["showRemainingAttendeeCapacity"] == true assert event_res["uuid"] == event.uuid assert event_res["url"] == event.url assert event_res["physicalAddress"]["street"] == address.street refute event_res["physicalAddress"]["url"] == address.url assert event_res["tags"] == [ %{"slug" => "tag1-updated", "title" => "tag1_updated"}, %{"slug" => "tag2-updated", "title" => "tag2_updated"} ] {event_id_int, ""} = Integer.parse(event_res["id"]) assert_enqueued( worker: Workers.BuildSearch, args: %{event_id: event_id_int, op: :update_search_event} ) assert [ %{ args: %{ "event_uuid" => ^event_uuid, "action" => "notify_of_event_update", "old_event" => %{ "title" => ^event_title }, "changes" => %{ "description" => "description updated", "status" => "tentative", "title" => "my event updated" } } } ] = all_enqueued(worker: Workers.EventDelayedNotificationWorker) Oban.drain_queue(queue: :default, with_scheduled: true) {:ok, new_event} = Mobilizon.Events.get_event_with_preload(event.id) assert_delivered_email( Email.Event.event_updated( user.email, creator, actor, event, new_event, MapSet.new([:title, :begins_on, :ends_on, :status, :physical_address]), "Etc/UTC", "en" ) ) assert_delivered_email( Email.Event.event_updated( participant_user.email, participant, participant_actor, event, new_event, MapSet.new([:title, :begins_on, :ends_on, :status, :physical_address]), "Etc/UTC", "en" ) ) end test "update_event/3 updates an event with a new picture", %{ conn: conn, actor: actor, user: user } do event = insert(:event, organizer_actor: actor) begins_on = event.begins_on |> DateTime.add(3 * 3600) |> DateTime.truncate(:second) |> DateTime.to_iso8601() res = conn |> auth_conn(user) |> put_req_header("content-type", "multipart/form-data") |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{ eventId: event.id, title: "my event updated", description: "description updated", beginsOn: "#{begins_on}", category: "PARTY", picture: %{ media: %{ name: "picture for my event", alt: "A very sunny landscape", file: "event.jpg", actorId: "#{actor.id}" } } }, uploads: %{ "event.jpg" => %Plug.Upload{ path: "test/fixtures/picture.png", filename: "event.jpg" } } ) assert res["errors"] == nil assert res["data"]["updateEvent"]["title"] == "my event updated" assert res["data"]["updateEvent"]["uuid"] == event.uuid assert res["data"]["updateEvent"]["url"] == event.url assert res["data"]["updateEvent"]["beginsOn"] == event.begins_on |> DateTime.add(3 * 3600) |> DateTime.to_iso8601() assert res["data"]["updateEvent"]["picture"]["name"] == "picture for my event" end @person_participations_query """ query EventPersonParticipation($actorId: ID!, $eventId: ID!) { person(id: $actorId) { id participations(eventId: $eventId) { total elements { role actor { id } event { id } } } } } """ test "respects the draft status", %{conn: conn, actor: actor, user: user} do event = insert(:event, organizer_actor: actor, draft: true) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{ eventId: event.id, title: "my event updated but still draft", draft: true } ) assert res["data"]["updateEvent"]["draft"] == true res = conn |> AbsintheHelpers.graphql_query( query: @find_event_query, variables: %{ uuid: event.uuid } ) assert hd(res["errors"])["message"] =~ "not found" res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @find_event_query, variables: %{ uuid: event.uuid } ) assert res["errors"] == nil assert res["data"]["event"]["draft"] == true res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @person_participations_query, variables: %{ eventId: event.id, actorId: actor.id } ) assert res["errors"] == nil assert res["data"]["person"]["participations"]["elements"] == [] res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: %{ eventId: event.id, title: "my event updated and no longer draft", draft: false } ) assert res["data"]["updateEvent"]["draft"] == false res = conn |> AbsintheHelpers.graphql_query( query: @find_event_query, variables: %{ uuid: event.uuid } ) assert res["errors"] == nil assert res["data"]["event"]["draft"] == false res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @person_participations_query, variables: %{ eventId: event.id, actorId: actor.id } ) assert res["errors"] == nil assert res["data"]["person"]["participations"]["elements"] == [ %{ "actor" => %{"id" => to_string(actor.id)}, "event" => %{"id" => to_string(event.id)}, "role" => "CREATOR" } ] end end describe "update_event/3 on behalf of a group" do test "should check the member has permission to update a group event", %{ conn: conn } do %User{} = user = insert(:user) %Actor{id: group_id} = group = insert(:group) %Actor{id: member_not_approved_actor_id} = member_not_approved_actor = insert(:actor, user: user) insert(:member, parent: group, actor: member_not_approved_actor) %Actor{id: member_actor_id} = member_actor = insert(:actor, user: user) insert(:member, parent: group, actor: member_actor, role: :member) %Actor{id: moderator_actor_id} = moderator_actor = insert(:actor, user: user) insert(:member, parent: group, actor: moderator_actor, role: :moderator) %Actor{} = administrator_actor = insert(:actor, user: user) insert(:member, parent: group, actor: administrator_actor, role: :administrator) %Actor{id: not_member_actor_id} = not_member_actor = insert(:actor, user: user) %Event{} = event = insert(:event, attributed_to: group, organizer_actor: administrator_actor) variables = @variables |> Map.put(:attributed_to_id, "#{group_id}") |> Map.put(:eventId, to_string(event.id)) Users.update_user_default_actor(user, member_not_approved_actor) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{member_not_approved_actor_id}") ) assert res["data"]["updateEvent"] == nil assert hd(res["errors"])["message"] == "This profile doesn't have permission to update an event on behalf of this group" Users.update_user_default_actor(user, not_member_actor) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{not_member_actor_id}") ) assert res["data"]["updateEvent"] == nil assert hd(res["errors"])["message"] == "This profile doesn't have permission to update an event on behalf of this group" Users.update_user_default_actor(user, member_actor) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{member_actor_id}") ) assert res["data"]["updateEvent"] == nil assert hd(res["errors"])["message"] == "This profile doesn't have permission to update an event on behalf of this group" Users.update_user_default_actor(user, moderator_actor) res = conn |> auth_conn(user) |> AbsintheHelpers.graphql_query( query: @update_event_mutation, variables: Map.put(variables, :organizer_actor_id, "#{moderator_actor_id}") ) assert res["errors"] == nil assert res["data"]["updateEvent"] != nil end end describe "list_events/3" do @fetch_events_query """ query Events($page: Int, $limit: Int) { events(page: $page, limit: $limit) { total elements { uuid } } } """ test "list_events/3 returns events", %{conn: conn} do event = insert(:event) res = conn |> AbsintheHelpers.graphql_query(query: @fetch_events_query) assert res["data"]["events"]["elements"] |> Enum.map(& &1["uuid"]) == [ event.uuid ] insert(:event) insert(:event) res = conn |> AbsintheHelpers.graphql_query( query: @fetch_events_query, variables: %{page: 1, limit: 2} ) assert res["data"]["events"]["total"] == 3 assert res["data"]["events"]["elements"] |> length == 2 res = conn |> AbsintheHelpers.graphql_query( query: @fetch_events_query, variables: %{page: 2, limit: 2} ) assert res["data"]["events"]["total"] == 3 assert res["data"]["events"]["elements"] |> length == 1 res = conn |> AbsintheHelpers.graphql_query( query: @fetch_events_query, variables: %{page: 3, limit: 2} ) assert res["data"]["events"]["total"] == 3 assert res["data"]["events"]["elements"] |> length == 0 end test "list_events/3 doesn't list private events", %{conn: conn} do insert(:event, visibility: :private) insert(:event, visibility: :unlisted) insert(:event, visibility: :restricted) res = conn |> AbsintheHelpers.graphql_query(query: @fetch_events_query) assert res["data"]["events"]["total"] == 0 assert res["data"]["events"]["elements"] |> Enum.map(& &1["uuid"]) == [] end test "list_events/3 doesn't list draft events", %{conn: conn} do insert(:event, visibility: :public, draft: true) res = conn |> AbsintheHelpers.graphql_query(query: @fetch_events_query) assert res["data"]["events"]["total"] == 0 assert res["data"]["events"]["elements"] |> Enum.map(& &1["uuid"]) == [] end test "find_event/3 returns an unlisted event", context do event = insert(:event, visibility: :unlisted) query = """ { event(uuid: "#{event.uuid}") { uuid, } } """ res = context.conn |> get("/api", AbsintheHelpers.query_skeleton(query, "event")) assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid) end # test "find_event/3 doesn't return a private event", context do # event = insert(:event, visibility: :private) # # query = """ # { # event(uuid: "#{event.uuid}") { # uuid, # } # } # """ # # res = # context.conn # |> get("/api", AbsintheHelpers.query_skeleton(query, "event")) # # assert json_response(res, 200)["errors"] |> hd |> Map.get("message") == # "Event with UUID #{event.uuid} not found" # end end describe "delete_event/3" do test "delete_event/3 deletes an event", %{conn: conn, user: user, actor: actor} do event = insert(:event, organizer_actor: actor) mutation = """ mutation { deleteEvent( event_id: #{event.id} ) { id } } """ res = conn |> auth_conn(user) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) assert json_response(res, 200)["errors"] == nil assert json_response(res, 200)["data"]["deleteEvent"]["id"] == to_string(event.id) res = conn |> auth_conn(user) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) assert hd(json_response(res, 200)["errors"])["message"] =~ "not found" end test "delete_event/3 should check the user is authenticated", %{conn: conn, actor: actor} do event = insert(:event, organizer_actor: actor) mutation = """ mutation { deleteEvent( event_id: #{event.id} ) { id } } """ res = conn |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) assert hd(json_response(res, 200)["errors"])["message"] =~ "logged-in" end test "delete_event/3 should check the event can be deleted by the user", %{ conn: conn, user: user, actor: _actor } do actor2 = insert(:actor) event = insert(:event, organizer_actor: actor2) mutation = """ mutation { deleteEvent( event_id: #{event.id} ) { id } } """ res = conn |> auth_conn(user) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) assert hd(json_response(res, 200)["errors"])["message"] =~ "cannot delete" end test "delete_event/3 allows a event being deleted by a moderator and creates a entry in actionLogs", %{ conn: conn, user: _user, actor: _actor } do user_moderator = insert(:user, role: :moderator) actor_moderator = insert(:actor, user: user_moderator) actor2 = insert(:actor) event = insert(:event, organizer_actor: actor2) mutation = """ mutation { deleteEvent( event_id: #{event.id} ) { id } } """ res = conn |> auth_conn(user_moderator) |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) assert json_response(res, 200)["data"]["deleteEvent"]["id"] == to_string(event.id) query = """ { actionLogs { total elements { action, actor { preferredUsername }, object { ... on Report { id, status }, ... on ReportNote { content } ... on Event { id, title } } } } } """ res = conn |> auth_conn(user_moderator) |> get("/api", AbsintheHelpers.query_skeleton(query, "actionLogs")) assert hd(json_response(res, 200)["data"]["actionLogs"]["elements"]) == %{ "action" => "EVENT_DELETION", "actor" => %{"preferredUsername" => actor_moderator.preferred_username}, "object" => %{"title" => event.title, "id" => to_string(event.id)} } end end describe "list_related_events/3" do test "list_related_events/3 should give related events", %{ conn: conn, actor: actor } do tag1 = insert(:tag, title: "Elixir", slug: "elixir") tag2 = insert(:tag, title: "PostgreSQL", slug: "postgresql") event = insert(:event, title: "Initial event", organizer_actor: actor, tags: [tag1, tag2]) event2 = insert(:event, title: "Event from same actor", organizer_actor: actor, visibility: :public, begins_on: Timex.shift(DateTime.utc_now(), days: 3) ) event3 = insert(:event, title: "Event with same tags", tags: [tag1, tag2], visibility: :public, begins_on: Timex.shift(DateTime.utc_now(), days: 3) ) query = """ { event(uuid: "#{event.uuid}") { uuid, title, tags { id }, related_events { uuid, title, tags { id } } } } """ res = conn |> get("/api", AbsintheHelpers.query_skeleton(query, "event")) assert hd(json_response(res, 200)["data"]["event"]["related_events"])["uuid"] == event2.uuid assert hd(tl(json_response(res, 200)["data"]["event"]["related_events"]))["uuid"] == event3.uuid end end end