More tests

This commit is contained in:
Thomas Citharel 2018-08-24 11:34:00 +02:00
parent a3852f26c1
commit 686cf04787
29 changed files with 945 additions and 241 deletions

View file

@ -3,5 +3,5 @@ defmodule Eventos.Activity do
Represents an activity
"""
defstruct [:id, :data, :local, :actor, :recipients, :notifications]
defstruct [:id, :data, :local, :actor, :recipients, :notifications, :type]
end

View file

@ -173,23 +173,9 @@ defmodule Eventos.Actors.Actor do
|> put_change(:local, true)
end
def get_or_fetch_by_url(url) do
if user = Actors.get_actor_by_url(url) do
user
else
case ActivityPub.make_actor_from_url(url) do
{:ok, user} ->
user
_ ->
{:error, "Could not fetch by AP id"}
end
end
end
@spec get_public_key_for_url(String.t()) :: {:ok, String.t()}
def get_public_key_for_url(url) do
with %Actor{} = actor <- get_or_fetch_by_url(url) do
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(url) do
actor.keys
|> Eventos.Service.ActivityPub.Utils.pem_to_public_key()
else
@ -228,4 +214,44 @@ defmodule Eventos.Actors.Actor do
)
)
end
def get_groups_member_of(%Actor{id: actor_id}) do
Repo.all(
from(
a in Actor,
join: m in Member,
on: a.id == m.parent_id,
where: m.actor_id == ^actor_id
)
)
end
def get_members_for_group(%Actor{id: actor_id}) do
Repo.all(
from(
a in Actor,
join: m in Member,
on: a.id == m.actor_id,
where: m.parent_id == ^actor_id
)
)
end
def follow(%Actor{} = follower, %Actor{} = followed) do
# Check if actor is locked
# Check if followed has blocked follower
# Check if follower already follows followed
cond do
following?(follower, followed) ->
{:error,
"Could not follow actor: you are already following #{followed.preferred_username}"}
# true -> nil
# Follow the person
end
end
def following?(%Actor{} = follower, %Actor{followers: followers}) do
Enum.member?(followers, follower)
end
end

View file

@ -265,13 +265,11 @@ defmodule Eventos.Actors do
def get_or_fetch_by_url(url) do
if actor = get_actor_by_url(url) do
actor
{:ok, actor}
else
ap_try = ActivityPub.make_actor_from_url(url)
case ap_try do
case ActivityPub.make_actor_from_url(url) do
{:ok, actor} ->
actor
{:ok, actor}
_ ->
{:error, "Could not fetch by AP id"}
@ -299,7 +297,7 @@ defmodule Eventos.Actors do
@doc """
Find actors by their name or displayed name
"""
def find_actors_by_username(username) do
def find_actors_by_username_or_name(username) do
Repo.all(
from(
a in Actor,
@ -320,7 +318,7 @@ defmodule Eventos.Actors do
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
def search(name) do
# find already saved accounts
case find_actors_by_username(name) do
case find_actors_by_username_or_name(name) do
[] ->
# no accounts found, let's test if it's an username@domain.tld
with true <- Regex.match?(@email_regex, name),
@ -457,6 +455,24 @@ defmodule Eventos.Actors do
|> Repo.insert()
end
@doc """
Gets an user by it's email
## Examples
iex> get_user_by_email(user, email)
{:ok, %User{}}
iex> get_user_by_email(user, wrong_email)
{:error, nil}
"""
def get_user_by_email(email) do
case Repo.get_by(User, email: email) do
nil -> {:error, nil}
user -> {:ok, user}
end
end
@doc """
Updates a user.
@ -548,10 +564,12 @@ defmodule Eventos.Actors do
"""
def create_member(attrs \\ %{}) do
%Member{}
|> Member.changeset(attrs)
|> Repo.insert!()
|> Repo.preload([:actor, :parent])
with {:ok, %Member{} = member} <-
%Member{}
|> Member.changeset(attrs)
|> Repo.insert() do
{:ok, Repo.preload(member, [:actor, :parent])}
end
end
@doc """

View file

@ -7,7 +7,6 @@ defmodule Eventos.Actors.Member do
alias Eventos.Actors.Member
alias Eventos.Actors.Actor
@primary_key false
schema "members" do
field(:approved, :boolean, default: true)
# 0 : Member, 1 : Moderator, 2 : Admin
@ -23,5 +22,6 @@ defmodule Eventos.Actors.Member do
member
|> cast(attrs, [:role, :approved, :parent_id, :actor_id])
|> validate_required([:parent_id, :actor_id])
|> unique_constraint(:parent_id, name: :members_actor_parent_unique_index)
end
end

View file

@ -11,16 +11,18 @@ defmodule Eventos.Actors.Service.ResetPassword do
"""
@spec check_reset_password_token(String.t(), String.t()) :: tuple
def check_reset_password_token(password, token) do
with %User{} = user <- Repo.get_by(User, reset_password_token: token) do
Repo.update(
User.password_reset_changeset(user, %{
"password" => password,
"reset_password_sent_at" => nil,
"reset_password_token" => nil
})
)
with %User{} = user <- Repo.get_by(User, reset_password_token: token),
{:ok, %User{} = user} <-
Repo.update(
User.password_reset_changeset(user, %{
"password" => password,
"reset_password_sent_at" => nil,
"reset_password_token" => nil
})
) do
{:ok, Repo.preload(user, :actors)}
else
_err ->
err ->
{:error, :invalid_token}
end
end

View file

@ -21,17 +21,23 @@ defmodule Eventos.Events.Comment do
belongs_to(:in_reply_to_comment, Comment, foreign_key: :in_reply_to_comment_id)
belongs_to(:origin_comment, Comment, foreign_key: :origin_comment_id)
timestamps()
timestamps(type: :utc_datetime)
end
@doc false
def changeset(comment, attrs) do
uuid = Ecto.UUID.generate()
# TODO : really change me right away
url =
if Map.has_key?(attrs, "url"),
do: attrs["url"],
else: "#{EventosWeb.Endpoint.url()}/comments/#{uuid}"
comment
|> cast(attrs, [:url, :text, :actor_id, :event_id, :in_reply_to_comment_id, :attributed_to_id])
|> validate_required([:text, :actor_id])
|> put_change(:uuid, uuid)
|> put_change(:url, "#{EventosWeb.Endpoint.url()}/comments/#{uuid}")
|> put_change(:url, url)
|> validate_required([:text, :actor_id, :url])
end
end

View file

@ -18,8 +18,11 @@ defmodule Eventos.Events.Event do
field(:description, :string)
field(:ends_on, Timex.Ecto.DateTimeWithTimezone)
field(:title, :string)
# ???
field(:state, :integer, default: 0)
# Event status: TENTATIVE 1, CONFIRMED 2, CANCELLED 3
field(:status, :integer, default: 0)
# If the event is public or private
field(:public, :boolean, default: true)
field(:thumbnail, :string)
field(:large_image, :string)
@ -42,9 +45,7 @@ defmodule Eventos.Events.Event do
@doc false
def changeset(%Event{} = event, attrs) do
uuid = Ecto.UUID.generate()
# TODO : check what's the use here. Tests ?
# TODO : Change all of this
actor_url =
if Map.has_key?(attrs, :organizer_actor) do
attrs.organizer_actor.preferred_username
@ -52,6 +53,13 @@ defmodule Eventos.Events.Event do
""
end
uuid = Ecto.UUID.generate()
url =
if Map.has_key?(attrs, "url"),
do: attrs["url"],
else: "#{EventosWeb.Endpoint.url()}/@#{actor_url}/#{uuid}"
event
|> Ecto.Changeset.cast(attrs, [
:title,
@ -74,7 +82,7 @@ defmodule Eventos.Events.Event do
|> cast_assoc(:tags)
|> cast_assoc(:physical_address)
|> put_change(:uuid, uuid)
|> put_change(:url, "#{EventosWeb.Endpoint.url()}/@#{actor_url}/#{uuid}")
|> put_change(:url, url)
|> validate_required([
:title,
:begins_on,

View file

@ -110,10 +110,17 @@ defmodule Eventos.Events do
@doc """
Gets an event by it's URL
"""
def get_event_by_url!(url) do
def get_event_by_url(url) do
Repo.get_by(Event, url: url)
end
@doc """
Gets an event by it's URL
"""
def get_event_by_url!(url) do
Repo.get_by!(Event, url: url)
end
@doc """
Gets an event by it's UUID
"""
@ -175,7 +182,10 @@ defmodule Eventos.Events do
@doc """
Find events by name
"""
def find_events_by_name(name) when name == "", do: []
def find_events_by_name(name) do
name = String.trim(name)
events = Repo.all(from(a in Event, where: ilike(a.title, ^like_sanitize(name))))
Repo.preload(events, [:organizer_actor])
end
@ -780,6 +790,32 @@ defmodule Eventos.Events do
Repo.all(Comment)
end
def get_comments_for_actor(%Actor{id: actor_id}, page \\ 1, limit \\ 10) do
start = (page - 1) * limit
query =
from(
c in Comment,
where: c.actor_id == ^actor_id,
limit: ^limit,
order_by: [desc: :id],
offset: ^start,
preload: [
:actor,
:in_reply_to_comment,
:origin_comment,
:event
]
)
comments = Repo.all(query)
count_comments =
Repo.one(from(c in Comment, select: count(c.id), where: c.actor_id == ^actor_id))
{:ok, comments, count_comments}
end
@doc """
Gets a single comment.
@ -798,6 +834,16 @@ defmodule Eventos.Events do
def get_comment_with_uuid!(uuid), do: Repo.get_by!(Comment, uuid: uuid)
def get_comment_from_url(url), do: Repo.get_by(Comment, url: url)
def get_comment_from_url!(url), do: Repo.get_by!(Comment, url: url)
def get_comment_full_from_url!(url) do
with %Comment{} = comment <- Repo.get_by!(Comment, url: url) do
Repo.preload(comment, :actor)
end
end
@doc """
Creates a comment.

View file

@ -18,10 +18,16 @@ defmodule EventosWeb.ActivityPubController do
end
def event(conn, %{"uuid" => uuid}) do
with %Event{} = event <- Events.get_event_full_by_uuid(uuid) do
with %Event{} = event <- Events.get_event_full_by_uuid(uuid),
true <- event.public do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("event.json", %{event: event}))
else
false ->
conn
|> put_status(404)
|> json("Not found")
end
end

View file

@ -37,8 +37,12 @@ defmodule EventosWeb.ActorController do
end
def show(conn, %{"name" => name}) do
actor = Actors.get_actor_by_name_with_everything(name)
render(conn, "show.json", actor: actor)
with %Actor{} = actor <- Actors.get_actor_by_name_with_everything(name) do
render(conn, "show.json", actor: actor)
else
nil ->
send_resp(conn, :not_found, "")
end
end
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
@ -57,7 +61,7 @@ defmodule EventosWeb.ActorController do
actor = Actors.get_local_actor_by_name(name)
with {:ok, %Actor{} = actor} <- Actors.update_actor(actor, actor_params) do
render(conn, "show.json", actor: actor)
render(conn, "show_basic.json", actor: actor)
end
end

View file

@ -15,15 +15,13 @@ defmodule EventosWeb.GroupController do
end
def create(conn, %{"group" => group_params}) do
with {:ok, %Actor{} = group} <- Actors.create_group(group_params) do
%Member{} =
_member =
Actors.create_member(%{
"parent_id" => group.id,
"actor_id" => Actors.get_local_actor_by_name(group_params["actor_admin"]).id,
"role" => 2
})
with {:ok, %Actor{} = group} <- Actors.create_group(group_params),
{:ok, %Member{} = member} <-
Actors.create_member(%{
"parent_id" => group.id,
"actor_id" => Actors.get_local_actor_by_name(group_params["actor_admin"]).id,
"role" => 2
}) do
conn
|> put_status(:created)
|> put_resp_header("location", actor_path(conn, :show, group))
@ -34,7 +32,7 @@ defmodule EventosWeb.GroupController do
def join(conn, %{"name" => group_name, "actor_name" => actor_name}) do
with %Actor{} = group <- Actors.get_group_by_name(group_name),
%Actor{} = actor <- Actors.get_local_actor_by_name(actor_name),
%Member{} = member <-
{:ok, %Member{} = member} <-
Actors.create_member(%{"parent_id" => group.id, "actor_id" => actor.id}) do
conn
|> put_status(:created)

View file

@ -42,11 +42,15 @@ defmodule EventosWeb.UserController do
end
end
@time_before_resend 3600
def resend_confirmation(conn, %{"email" => email}) do
with {:ok, %User{} = user} <- Actors.find_by_email(email),
false <- is_nil(user.confirmation_token),
true <-
Timex.before?(Timex.shift(user.confirmation_sent_at, hours: 1), DateTime.utc_now()) do
Timex.before?(
Timex.shift(user.confirmation_sent_at, seconds: @time_before_resend),
DateTime.utc_now()
) do
Activation.resend_confirmation_email(user)
render(conn, "confirmation.json", %{user: user})
else
@ -58,7 +62,10 @@ defmodule EventosWeb.UserController do
_ ->
conn
|> put_status(:not_found)
|> json(%{"error" => "Unable to resend the validation token"})
|> json(%{
"error" =>
"Unable to resend the validation token. Please wait a while before you can ask for resending token"
})
end
end
@ -67,7 +74,7 @@ defmodule EventosWeb.UserController do
{:ok, _} <- ResetPassword.send_password_reset_email(user) do
render(conn, "password_reset.json", %{user: user})
else
{:error, :not_found} ->
{:error, nil} ->
conn
|> put_status(:not_found)
|> json(%{"errors" => "Unable to find an user with this email"})
@ -105,23 +112,23 @@ defmodule EventosWeb.UserController do
render(conn, "show_simple.json", user: user)
end
defp handle_changeset_errors(errors) do
errors
|> Enum.map(fn {field, detail} ->
"#{field} " <> render_detail(detail)
end)
|> Enum.join()
end
# defp handle_changeset_errors(errors) do
# errors
# |> Enum.map(fn {field, detail} ->
# "#{field} " <> render_detail(detail)
# end)
# |> Enum.join()
# end
defp render_detail({message, values}) do
Enum.reduce(values, message, fn {k, v}, acc ->
String.replace(acc, "%{#{k}}", to_string(v))
end)
end
# defp render_detail({message, values}) do
# Enum.reduce(values, message, fn {k, v}, acc ->
# String.replace(acc, "%{#{k}}", to_string(v))
# end)
# end
defp render_detail(message) do
message
end
# defp render_detail(message) do
# message
# end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Actors.get_user!(id)

View file

@ -122,6 +122,7 @@ defmodule EventosWeb.Router do
get("/@:name/following", ActivityPubController, :following)
get("/@:name/followers", ActivityPubController, :followers)
get("/events/:uuid", ActivityPubController, :event)
get("/comments/:uuid", ActivityPubController, :event)
post("/@:name/inbox", ActivityPubController, :inbox)
post("/inbox", ActivityPubController, :inbox)
end

View file

@ -134,10 +134,17 @@ defmodule EventosWeb.ActivityPub.ActorView do
else
"Announce"
end,
"actor" => activity.data.organizer_actor.url,
"actor" => activity.actor,
"published" => Timex.now(),
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => render_one(activity.data, ObjectView, "event.json", as: :event)
"object" =>
case activity.type do
:Event ->
render_one(activity.data, ObjectView, "event.json", as: :event)
:Comment ->
render_one(activity.data, ObjectView, "note.json", as: :note)
end
}
end

View file

@ -32,6 +32,19 @@ defmodule EventosWeb.ActivityPub.ObjectView do
Map.merge(event, @base)
end
def render("note.json", %{note: note}) do
event = %{
"type" => "Note",
"id" => note.url,
"content" => note.text,
"mediaType" => "text/markdown",
"published" => Timex.format!(note.inserted_at, "{ISO:Extended}"),
"updated" => Timex.format!(note.updated_at, "{ISO:Extended}")
}
Map.merge(event, @base)
end
def render("category.json", %{category: category}) do
%{"title" => category.title}
end

View file

@ -6,7 +6,7 @@ defmodule Eventos.Service.ActivityPub do
"""
alias Eventos.Events
alias Eventos.Events.{Event, Category}
alias Eventos.Events.{Event, Category, Comment}
alias Eventos.Service.ActivityPub.Transmogrifier
alias Eventos.Service.WebFinger
alias Eventos.Activity
@ -44,36 +44,65 @@ defmodule Eventos.Service.ActivityPub do
end
end
def fetch_event_from_url(url) do
if object = Events.get_event_by_url!(url) do
{:ok, object}
def fetch_object_from_url(url, :event), do: fetch_event_from_url(url)
def fetch_object_from_url(url, :note), do: fetch_note_from_url(url)
@spec fetch_object_from_url(String.t()) :: tuple()
def fetch_object_from_url(url) do
with true <- String.starts_with?(url, "http"),
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
HTTPoison.get(
url,
[Accept: "application/activity+json"],
follow_redirect: true,
timeout: 10_000,
recv_timeout: 20_000
),
{:ok, data} <- Jason.decode(body),
nil <- Events.get_event_by_url(data["id"]),
nil <- Events.get_comment_from_url(data["id"]),
params <- %{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
"actor" => data["attributedTo"],
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
case data["type"] do
"Event" ->
{:ok, Events.get_event_by_url!(activity.data["object"]["id"])}
"Note" ->
{:ok, Events.get_comment_from_url!(activity.data["object"]["id"])}
end
else
object = %Event{} -> {:ok, object}
object = %Comment{} -> {:ok, object}
e -> {:error, e}
end
end
@spec fetch_object_from_url(String.t()) :: tuple()
def fetch_event_from_url(url) do
with nil <- Events.get_event_by_url(url) do
Logger.info("Fetching #{url} via AP")
fetch_object_from_url(url)
else
%Event{} = comment ->
{:ok, comment}
end
end
@spec fetch_object_from_url(String.t()) :: tuple()
def fetch_note_from_url(url) do
with nil <- Events.get_comment_from_url(url) do
Logger.info("Fetching #{url} via AP")
with true <- String.starts_with?(url, "http"),
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
HTTPoison.get(
url,
[Accept: "application/activity+json"],
follow_redirect: true,
timeout: 10_000,
recv_timeout: 20_000
),
{:ok, data} <- Jason.decode(body),
nil <- Events.get_event_by_url!(data["id"]),
params <- %{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
"actor" => data["attributedTo"],
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, Events.get_event_by_url!(activity.data["object"]["id"])}
else
object = %Event{} -> {:ok, object}
e -> e
end
fetch_object_from_url(url)
else
%Comment{} = comment ->
{:ok, comment}
end
end
@ -127,7 +156,7 @@ defmodule Eventos.Service.ActivityPub do
end
end
def follow(follower, followed, activity_id \\ nil, local \\ true) do
def follow(%Actor{} = follower, %Actor{} = followed, activity_id \\ nil, local \\ true) do
with data <- make_follow_data(follower, followed, activity_id),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
@ -135,7 +164,9 @@ defmodule Eventos.Service.ActivityPub do
end
end
def delete(%Event{url: url, organizer_actor: actor} = event, local \\ true) do
def delete(object, local \\ true)
def delete(%Event{url: url, organizer_actor: actor} = event, local) do
data = %{
"type" => "Delete",
"actor" => actor.url,
@ -150,6 +181,21 @@ defmodule Eventos.Service.ActivityPub do
end
end
def delete(%Comment{url: url, actor: actor} = comment, local) do
data = %{
"type" => "Delete",
"actor" => actor.url,
"object" => url,
"to" => [actor.url <> "/followers", "https://www.w3.org/ns/activitystreams#Public"]
}
with Events.delete_comment(comment),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
end
end
def create_public_activities(%Actor{} = actor) do
end
@ -285,13 +331,22 @@ defmodule Eventos.Service.ActivityPub do
case actor.type do
:Person ->
{:ok, events, total} = Events.get_events_for_actor(actor, page, limit)
{:ok, comments, total} = Events.get_comments_for_actor(actor, page, limit)
activities =
event_activities =
Enum.map(events, fn event ->
{:ok, activity} = event_to_activity(event)
activity
end)
comment_activities =
Enum.map(comments, fn comment ->
{:ok, activity} = comment_to_activity(comment)
activity
end)
activities = event_activities ++ comment_activities
{activities, total}
:Service ->
@ -322,6 +377,7 @@ defmodule Eventos.Service.ActivityPub do
defp event_to_activity(%Event{} = event, local \\ true) do
activity = %Activity{
type: :Event,
data: event,
local: local,
actor: event.organizer_actor.url,
@ -333,6 +389,20 @@ defmodule Eventos.Service.ActivityPub do
{:ok, activity}
end
defp comment_to_activity(%Comment{} = comment, local \\ true) do
activity = %Activity{
type: :Comment,
data: comment,
local: local,
actor: comment.actor.url,
recipients: ["https://www.w3.org/ns/activitystreams#Public"]
}
# Notification.create_notifications(activity)
# stream_out(activity)
{:ok, activity}
end
defp ical_event_to_activity(%ExIcal.Event{} = ical_event, %Actor{} = actor, source) do
# Logger.debug(inspect ical_event)
# TODO : refactor me !

View file

@ -4,7 +4,7 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do
"""
alias Eventos.Actors.Actor
alias Eventos.Actors
alias Eventos.Events.Event
alias Eventos.Events.{Event, Comment}
alias Eventos.Service.ActivityPub
import Ecto.Query
@ -77,9 +77,9 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do
# - tags
# - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
Logger.debug("Handle incoming to create notes")
Logger.info("Handle incoming to create notes")
with %Actor{} = actor <- Actor.get_or_fetch_by_url(data["actor"]) do
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(data["actor"]) do
Logger.debug("found actor")
object = fix_object(data["object"])
@ -104,8 +104,8 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do
def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
) do
with %Actor{} = followed <- Actors.get_actor_by_url(followed),
%Actor{} = follower <- Actors.get_or_fetch_by_url(follower),
with {:ok, %Actor{} = followed} <- Actors.get_or_fetch_by_url(followed),
{:ok, %Actor{} = follower} <- Actors.get_or_fetch_by_url(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
ActivityPub.accept(%{to: [follower.url], actor: followed.url, object: data, local: true})
@ -133,7 +133,7 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do
def handle_incoming(
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
) do
with %Actor{} = actor <- Actors.get_or_fetch_by_url(actor),
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
{:ok, object} <-
get_obj_helper(object_id) || ActivityPub.fetch_event_from_url(object_id),
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
@ -268,6 +268,17 @@ defmodule Eventos.Service.ActivityPub.Transmogrifier do
{:ok, event}
end
def prepare_outgoing(%Comment{} = comment) do
comment =
comment
|> Map.from_struct()
|> Map.drop([:__meta__])
|> Map.put(:"@context", "https://www.w3.org/ns/activitystreams")
|> prepare_object
{:ok, comment}
end
#
# def maybe_fix_object_url(data) do
# if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do

View file

@ -132,20 +132,23 @@ defmodule Eventos.Service.ActivityPub.Utils do
"""
def insert_full_object(%{"object" => %{"type" => type} = object_data})
when is_map(object_data) and type == "Note" do
import Logger
Logger.debug("insert full object")
Logger.debug(inspect(object_data))
actor = Actors.get_actor_by_url(object_data["actor"])
with {:ok, %Actor{id: actor_id}} <- Actors.get_or_fetch_by_url(object_data["actor"]) do
data = %{
"text" => object_data["content"],
"url" => object_data["id"],
"actor_id" => actor_id,
"in_reply_to_comment_id" => object_data["inReplyTo"]
}
data = %{
"text" => object_data["content"],
"url" => object_data["id"],
"actor_id" => actor.id,
"in_reply_to_comment_id" => object_data["inReplyTo"]
}
require Logger
Logger.info("comment data ready to be inserted")
Logger.info(inspect(data))
with {:ok, _} <- Events.create_comment(data) do
:ok
with {:ok, comm} <- Events.create_comment(data) do
Logger.info("comment inserted")
Logger.info(inspect(comm))
:ok
end
end
end

View file

@ -65,7 +65,6 @@ defmodule Eventos.Mixfile do
{:timex_ecto, "~> 3.0"},
{:icalendar, "~> 0.6"},
{:exgravatar, "~> 2.0.1"},
{:littlefinger, "~> 0.1"},
{:httpoison, "~> 1.0"},
{:json_ld, "~> 0.2"},
{:jason, "~> 1.0"},

View file

@ -0,0 +1,19 @@
defmodule Eventos.Repo.Migrations.AddPrimaryKeyToMember do
use Ecto.Migration
def up do
execute("ALTER TABLE members DROP CONSTRAINT IF EXISTS members_pkey")
drop_if_exists index(:members, ["members_account_id_index"])
create unique_index(:members, [:actor_id, :parent_id], name: :members_actor_parent_unique_index)
alter table(:members) do
add :id, :serial, primary_key: true
end
end
def down do
drop index(:members, [:actor_id, :parent_id], name: :members_actor_parent_unique_index)
alter table(:members) do
remove :id
end
end
end

View file

@ -9,7 +9,7 @@ defmodule Eventos.ActorsTest do
@valid_attrs %{
summary: "some description",
name: "some name",
name: "Bobby Blank",
domain: "some domain",
keys: "some keypair",
suspended: true,
@ -74,7 +74,7 @@ defmodule Eventos.ActorsTest do
end
test "get_actor_by_name/1 returns a remote actor" do
assert %Actor{} = actor = Actors.get_or_fetch_by_url(@remote_account_url)
assert {:ok, %Actor{} = actor} = Actors.get_or_fetch_by_url(@remote_account_url)
actor_found = Actors.get_actor_by_name("#{actor.preferred_username}@#{actor.domain}")
assert actor_found = actor
end
@ -107,7 +107,7 @@ defmodule Eventos.ActorsTest do
end
test "get_actor_by_name_with_everything!/1 returns the remote actor with it's organized events" do
assert %Actor{} = actor = Actors.get_or_fetch_by_url(@remote_account_url)
assert {:ok, %Actor{} = actor} = Actors.get_or_fetch_by_url(@remote_account_url)
assert Actors.get_actor_by_name_with_everything(
"#{actor.preferred_username}@#{actor.domain}"
@ -124,12 +124,15 @@ defmodule Eventos.ActorsTest do
test "get_or_fetch_by_url/1 returns the local actor for the url", %{
actor: actor
} do
assert Actors.get_or_fetch_by_url(actor.url).preferred_username == actor.preferred_username
assert Actors.get_or_fetch_by_url(actor.url).domain == nil
preferred_username = actor.preferred_username
assert {:ok, %Actor{preferred_username: preferred_username, domain: nil} = actor_found} =
Actors.get_or_fetch_by_url(actor.url)
end
test "get_or_fetch_by_url/1 returns the remote actor for the url" do
assert %Actor{preferred_username: @remote_account_username, domain: @remote_account_domain} =
assert {:ok,
%Actor{preferred_username: @remote_account_username, domain: @remote_account_domain}} =
Actors.get_or_fetch_by_url(@remote_account_url)
end
@ -141,12 +144,21 @@ defmodule Eventos.ActorsTest do
assert actors = [actor, actor2]
end
test "test find_actors_by_username/1 returns actors with similar usernames", %{actor: actor} do
%Actor{} = actor2 = Actors.get_or_fetch_by_url(@remote_account_url)
actors = Actors.find_actors_by_username("t")
test "test find_actors_by_username_or_name/1 returns actors with similar usernames", %{
actor: actor
} do
{:ok, %Actor{} = actor2} = Actors.get_or_fetch_by_url(@remote_account_url)
actors = Actors.find_actors_by_username_or_name("t")
assert actors = [actor, actor2]
end
test "test find_actors_by_username_or_name/1 returns actors with similar names", %{
actor: actor
} do
actors = Actors.find_actors_by_username_or_name("ohno")
assert actors == []
end
test "test search/1 returns accounts for search with existing accounts", %{actor: actor} do
assert {:ok, [actor]} = Actors.search("t")
end
@ -180,7 +192,7 @@ defmodule Eventos.ActorsTest do
test "create_actor/1 with valid data creates a actor" do
assert {:ok, %Actor{} = actor} = Actors.create_actor(@valid_attrs)
assert actor.summary == "some description"
assert actor.name == "some name"
assert actor.name == "Bobby Blank"
assert actor.domain == "some domain"
assert actor.keys == "some keypair"
assert actor.suspended
@ -484,4 +496,97 @@ defmodule Eventos.ActorsTest do
assert %Ecto.Changeset{} = Actors.change_follower(follower)
end
end
describe "members" do
alias Eventos.Actors.Member
alias Eventos.Actors.Actor
@valid_attrs %{approved: true, role: 0}
@update_attrs %{approved: false, role: 1}
@invalid_attrs %{approved: nil, role: nil}
setup do
actor = insert(:actor)
group = insert(:group)
{:ok, actor: actor, group: group}
end
defp create_member(%{actor: actor, group: group}) do
insert(:member, actor: actor, parent: group)
end
test "get_member!/1 returns the member with given id", context do
member = create_member(context)
assert member = Actors.get_member!(member.id)
end
test "create_member/1 with valid data creates a member", %{
actor: actor,
group: group
} do
valid_attrs =
@valid_attrs
|> Map.put(:actor_id, actor.id)
|> Map.put(:parent_id, group.id)
assert {:ok, %Member{} = member} = Actors.create_member(valid_attrs)
assert member.approved == true
assert member.role == 0
assert [group] = Actor.get_groups_member_of(actor)
assert [actor] = Actor.get_members_for_group(group)
end
test "create_member/1 with valid data but same actors fails to create a member", %{
actor: actor,
group: group
} do
create_member(%{actor: actor, group: group})
valid_attrs =
@valid_attrs
|> Map.put(:actor_id, actor.id)
|> Map.put(:parent_id, group.id)
assert {:error, _member} = Actors.create_member(valid_attrs)
end
test "create_member/1 with invalid data returns error changeset", %{
actor: actor,
group: group
} do
invalid_attrs =
@invalid_attrs
|> Map.put(:actor_id, nil)
|> Map.put(:parent_id, nil)
assert {:error, %Ecto.Changeset{}} = Actors.create_member(invalid_attrs)
end
test "update_member/2 with valid data updates the member", context do
member = create_member(context)
assert {:ok, member} = Actors.update_member(member, @update_attrs)
assert %Member{} = member
assert member.approved == false
assert member.role == 1
end
# This can't happen, since attrs are optional
# test "update_member/2 with invalid data returns error changeset", context do
# member = create_member(context)
# assert {:error, %Ecto.Changeset{}} = Actors.update_member(member, @invalid_attrs)
# assert member = Actors.get_member!(member.id)
# end
test "delete_member/1 deletes the member", context do
member = create_member(context)
assert {:ok, %Member{}} = Actors.delete_member(member)
assert_raise Ecto.NoResultsError, fn -> Actors.get_member!(member.id) end
end
test "change_member/1 returns a member changeset", context do
member = create_member(context)
assert %Ecto.Changeset{} = Actors.change_member(member)
end
end
end

View file

@ -32,6 +32,12 @@ defmodule Eventos.EventsTest do
describe "events" do
alias Eventos.Events.Event
setup do
actor = insert(:actor)
event = insert(:event, organizer_actor: actor)
{:ok, actor: actor, event: event}
end
@valid_attrs %{
begins_on: "2010-04-17 14:00:00.000000Z",
description: "some description",
@ -46,14 +52,32 @@ defmodule Eventos.EventsTest do
}
@invalid_attrs %{begins_on: nil, description: nil, ends_on: nil, title: nil}
test "list_events/0 returns all events" do
event = event_fixture()
assert hd(Events.list_events()).title == event.title
test "list_events/0 returns all events", %{event: event} do
assert event.title == hd(Events.list_events()).title
end
test "get_event!/1 returns the event with given id" do
event = event_fixture()
test "get_event!/1 returns the event with given id", %{event: event} do
assert Events.get_event!(event.id).title == event.title
refute Ecto.assoc_loaded?(Events.get_event!(event.id).organizer_actor)
end
test "get_event_full!/1 returns the event with given id", %{event: event} do
assert Events.get_event_full!(event.id).organizer_actor.preferred_username ==
event.organizer_actor.preferred_username
assert Events.get_event_full!(event.id).participants == []
end
test "find_events_by_name/1 returns events for a given name", %{event: event} do
assert event.title == hd(Events.find_events_by_name(event.title)).title
event2 = insert(:event, title: "Special event")
assert event2.title == hd(Events.find_events_by_name("Special")).title
event2 = insert(:event, title: "Special event")
assert event2.title == hd(Events.find_events_by_name(" Special ")).title
assert [] == Events.find_events_by_name("")
end
test "create_event/1 with valid data creates a event" do
@ -79,8 +103,7 @@ defmodule Eventos.EventsTest do
assert {:error, %Ecto.Changeset{}} = Events.create_event(@invalid_attrs)
end
test "update_event/2 with valid data updates the event" do
event = event_fixture()
test "update_event/2 with valid data updates the event", %{event: event} do
assert {:ok, event} = Events.update_event(event, @update_attrs)
assert %Event{} = event
assert event.begins_on == DateTime.from_naive!(~N[2011-05-18 15:01:01.000000Z], "Etc/UTC")
@ -89,27 +112,44 @@ defmodule Eventos.EventsTest do
assert event.title == "some updated title"
end
test "update_event/2 with invalid data returns error changeset" do
event = event_fixture()
test "update_event/2 with invalid data returns error changeset", %{event: event} do
assert {:error, %Ecto.Changeset{}} = Events.update_event(event, @invalid_attrs)
assert event.title == Events.get_event!(event.id).title
end
test "delete_event/1 deletes the event" do
event = event_fixture()
test "delete_event/1 deletes the event", %{event: event} do
assert {:ok, %Event{}} = Events.delete_event(event)
assert_raise Ecto.NoResultsError, fn -> Events.get_event!(event.id) end
end
test "change_event/1 returns a event changeset" do
event = event_fixture()
test "change_event/1 returns a event changeset", %{event: event} do
assert %Ecto.Changeset{} = Events.change_event(event)
end
test "get_events_for_actor/1", %{actor: actor, event: event} do
assert {:ok, [event_found], 1} = Events.get_events_for_actor(actor)
assert event_found.title == event.title
end
test "get_events_for_actor/3", %{actor: actor, event: event} do
event1 = insert(:event, organizer_actor: actor)
assert {:ok, [event_found, event1_found], 2} = Events.get_events_for_actor(actor, 1, 10)
end
test "get_events_for_actor/3 with limited results", %{actor: actor, event: event} do
event1 = insert(:event, organizer_actor: actor)
assert {:ok, [event_found], 2} = Events.get_events_for_actor(actor, 1, 1)
end
end
describe "categories" do
alias Eventos.Events.Category
setup do
category = insert(:category)
{:ok, category: category}
end
@valid_attrs %{description: "some description", picture: "some picture", title: "some title"}
@update_attrs %{
description: "some updated description",
@ -118,16 +158,18 @@ defmodule Eventos.EventsTest do
}
@invalid_attrs %{description: nil, picture: nil, title: nil}
test "list_categories/0 returns all categories" do
category = category_fixture()
test "list_categories/0 returns all categories", %{category: category} do
assert Events.list_categories() == [category]
end
test "get_category!/1 returns the category with given id" do
category = category_fixture()
test "get_category!/1 returns the category with given id", %{category: category} do
assert Events.get_category!(category.id) == category
end
test "get_category_by_title/1 return the category with given title", %{category: category} do
assert Events.get_category_by_title(category.title) == category
end
test "create_category/1 with valid data creates a category" do
assert {:ok, %Category{} = category} = Events.create_category(@valid_attrs)
assert category.description == "some description"
@ -139,8 +181,7 @@ defmodule Eventos.EventsTest do
assert {:error, %Ecto.Changeset{}} = Events.create_category(@invalid_attrs)
end
test "update_category/2 with valid data updates the category" do
category = category_fixture()
test "update_category/2 with valid data updates the category", %{category: category} do
assert {:ok, category} = Events.update_category(category, @update_attrs)
assert %Category{} = category
assert category.description == "some updated description"
@ -148,20 +189,17 @@ defmodule Eventos.EventsTest do
assert category.title == "some updated title"
end
test "update_category/2 with invalid data returns error changeset" do
category = category_fixture()
test "update_category/2 with invalid data returns error changeset", %{category: category} do
assert {:error, %Ecto.Changeset{}} = Events.update_category(category, @invalid_attrs)
assert category == Events.get_category!(category.id)
end
test "delete_category/1 deletes the category" do
category = category_fixture()
test "delete_category/1 deletes the category", %{category: category} do
assert {:ok, %Category{}} = Events.delete_category(category)
assert_raise Ecto.NoResultsError, fn -> Events.get_category!(category.id) end
end
test "change_category/1 returns a category changeset" do
category = category_fixture()
test "change_category/1 returns a category changeset", %{category: category} do
assert %Ecto.Changeset{} = Events.change_category(category)
end
end
@ -227,35 +265,31 @@ defmodule Eventos.EventsTest do
end
describe "participants" do
alias Eventos.Events.Participant
alias Eventos.Events.{Participant, Event}
alias Eventos.Actors.Actor
@valid_attrs %{role: 42}
@update_attrs %{role: 43}
@invalid_attrs %{role: nil}
def participant_fixture(attrs \\ %{}) do
event = event_fixture()
actor = actor_fixture()
valid_attrs = Map.put(@valid_attrs, :event_id, event.id)
valid_attrs = Map.put(valid_attrs, :actor_id, actor.id)
{:ok, participant} =
attrs
|> Enum.into(valid_attrs)
|> Events.create_participant()
participant
setup do
actor = insert(:actor)
event = insert(:event, organizer_actor: actor)
participant = insert(:participant, actor: actor, event: event)
{:ok, participant: participant, event: event, actor: actor}
end
test "list_participants/0 returns all participants" do
participant = participant_fixture()
assert Events.list_participants() == [participant]
test "list_participants/0 returns all participants", %{participant: participant} do
assert [%Participant{} = participant] = Events.list_participants()
end
# test "get_participant!/1 returns the participant with given id" do
# participant = participant_fixture()
# assert Events.get_participant!(participant.id) == participant
# end
test "get_participant!/1 returns the participant for a given event and given actor", %{
event: %Event{id: event_id} = _event,
actor: %Actor{id: actor_id} = _actor
} do
assert %Participant{event_id: event_id, actor_id: actor_id} =
_participant = Events.get_participant!(event_id, actor_id)
end
test "create_participant/1 with valid data creates a participant" do
actor = actor_fixture()
@ -270,25 +304,25 @@ defmodule Eventos.EventsTest do
assert {:error, %Ecto.Changeset{}} = Events.create_participant(@invalid_attrs)
end
test "update_participant/2 with valid data updates the participant" do
participant = participant_fixture()
test "update_participant/2 with valid data updates the participant", %{
participant: participant
} do
assert {:ok, participant} = Events.update_participant(participant, @update_attrs)
assert %Participant{} = participant
assert participant.role == 43
end
test "update_participant/2 with invalid data returns error changeset" do
participant = participant_fixture()
test "update_participant/2 with invalid data returns error changeset", %{
participant: participant
} do
assert {:error, %Ecto.Changeset{}} = Events.update_participant(participant, @invalid_attrs)
end
test "delete_participant/1 deletes the participant" do
participant = participant_fixture()
test "delete_participant/1 deletes the participant", %{participant: participant} do
assert {:ok, %Participant{}} = Events.delete_participant(participant)
end
test "change_participant/1 returns a participant changeset" do
participant = participant_fixture()
test "change_participant/1 returns a participant changeset", %{participant: participant} do
assert %Ecto.Changeset{} = Events.change_participant(participant)
end
end

View file

@ -5,14 +5,20 @@ defmodule Eventos.Service.Activitypub.ActivitypubTest do
alias Eventos.Events
alias Eventos.Actors.Actor
alias Eventos.Actors
alias Eventos.Service.ActivityPub
alias Eventos.Activity
describe "fetching actor from it's url" do
test "returns an actor" do
test "returns an actor from nickname" do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"} = actor} =
ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
end
test "returns an actor from url" do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
Actors.get_or_fetch_by_url("https://framapiaf.org/users/tcit")
end
end
describe "create activities" do
@ -33,8 +39,8 @@ defmodule Eventos.Service.Activitypub.ActivitypubTest do
end
end
describe "fetching an object" do
test "it fetches an object" do
describe "fetching an" do
test "event by url" do
{:ok, object} =
ActivityPub.fetch_event_from_url("https://social.tcit.fr/@tcit/99908779444618462")
@ -55,7 +61,19 @@ defmodule Eventos.Service.Activitypub.ActivitypubTest do
assert delete.data["actor"] == event.organizer_actor.url
assert delete.data["object"] == event.url
assert Events.get_event_by_url!(event.url) == nil
assert Events.get_event_by_url(event.url) == nil
end
test "it creates a delete activity and deletes the original comment" do
comment = insert(:comment)
comment = Events.get_comment_full_from_url!(comment.url)
{:ok, delete} = ActivityPub.delete(comment)
assert delete.data["type"] == "Delete"
assert delete.data["actor"] == comment.actor.url
assert delete.data["object"] == comment.url
assert Events.get_comment_from_url(comment.url) == nil
end
end

View file

@ -3,10 +3,11 @@ defmodule EventosWeb.ActivityPubControllerTest do
import Eventos.Factory
alias EventosWeb.ActivityPub.{ActorView, ObjectView}
alias Eventos.{Repo, Actors, Actors.Actor}
alias Eventos.Service.ActivityPub
alias Eventos.Activity
import Logger
describe "/@:username" do
describe "/@:preferred_username" do
test "it returns a json representation of the actor", %{conn: conn} do
actor = insert(:actor)
@ -22,8 +23,8 @@ defmodule EventosWeb.ActivityPubControllerTest do
end
end
describe "/events/uuid" do
test "it returns a json representation of the object", %{conn: conn} do
describe "/events/:uuid" do
test "it returns a json representation of the event", %{conn: conn} do
event = insert(:event)
conn =
@ -34,23 +35,60 @@ defmodule EventosWeb.ActivityPubControllerTest do
assert json_response(conn, 200) == ObjectView.render("event.json", %{event: event})
Logger.error(inspect(ObjectView.render("event.json", %{event: event})))
end
test "it returns 404 for non-public events", %{conn: conn} do
event = insert(:event, public: false)
conn =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/events/#{event.uuid}")
assert json_response(conn, 404)
end
end
# describe "/actors/:username/inbox" do
# test "it inserts an incoming activity into the database", %{conn: conn} do
# data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
#
# conn =
# conn
# |> assign(:valid_signature, true)
# |> put_req_header("content-type", "application/activity+json")
# |> post("/inbox", data)
#
# assert "ok" == json_response(conn, 200)
# :timer.sleep(500)
# assert Activity.get_by_ap_id(data["id"])
# end
# end
describe "/@:preferred_username/inbox" do
test "it inserts an incoming event into the database", %{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
conn =
conn
|> assign(:valid_signature, true)
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
assert "ok" == json_response(conn, 200)
:timer.sleep(500)
assert ActivityPub.fetch_object_from_url(data["object"]["id"], :note)
end
end
describe "/@:preferred_username/outbox" do
test "it returns a note activity in a collection", %{conn: conn} do
actor = insert(:actor)
comment = insert(:comment, actor: actor)
conn =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/@#{actor.preferred_username}/outbox")
assert response(conn, 200) =~ comment.text
end
test "it returns an event activity in a collection", %{conn: conn} do
actor = insert(:actor)
event = insert(:event, organizer_actor: actor)
conn =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/@#{actor.preferred_username}/outbox")
assert response(conn, 200) =~ event.title
end
end
# describe "/actors/:nickname/followers" do
# test "it returns the followers in a collection", %{conn: conn} do
@ -93,43 +131,43 @@ defmodule EventosWeb.ActivityPubControllerTest do
# end
# end
#
# describe "/users/:nickname/following" do
# describe "/@:preferred_username/following" do
# test "it returns the following in a collection", %{conn: conn} do
# user = insert(:user)
# user_two = insert(:user)
# User.follow(user, user_two)
#
# actor = insert(:actor)
# actor2 = insert(:actor)
# Eventos.Service.ActivityPub.follow(actor, actor2)
# result =
# conn
# |> get("/users/#{user.nickname}/following")
# |> get("/@#{actor.preferred_username}/following")
# |> json_response(200)
#
# assert result["first"]["orderedItems"] == [user_two.ap_id]
# assert result["first"]["orderedItems"] == [actor2.url]
# end
#
# test "it works for more than 10 users", %{conn: conn} do
# user = insert(:user)
#
# test "it works for more than 10 actors", %{conn: conn} do
# actor = insert(:actor)
# Enum.each(1..15, fn _ ->
# user = Repo.get(User, user.id)
# other_user = insert(:user)
# User.follow(user, other_user)
# actor = Repo.get(Actor, actor.id)
# other_actor = insert(:actor)
# Actor.follow(actor, other_actor)
# end)
#
# result =
# conn
# |> get("/users/#{user.nickname}/following")
# |> get("/@#{actor.preferred_username}/following")
# |> json_response(200)
#
# assert length(result["first"]["orderedItems"]) == 10
# assert result["first"]["totalItems"] == 15
# assert result["totalItems"] == 15
#
# result =
# conn
# |> get("/users/#{user.nickname}/following?page=2")
# |> get("/@#{actor.preferred_username}/following?page=2")
# |> json_response(200)
#
# assert length(result["orderedItems"]) == 5
# assert result["totalItems"] == 15
# end

View file

@ -31,6 +31,64 @@ defmodule EventosWeb.ActorControllerTest do
end
end
describe "show actor" do
test "show existing actor", %{conn: conn, actor: actor} do
actor_id = actor.id
conn = get(conn, actor_path(conn, :show, actor.preferred_username))
assert %{"data" => %{"id" => actor_id}} = json_response(conn, 200)
end
test "show non-existing actor", %{conn: conn, actor: actor} do
actor_id = actor.id
conn = get(conn, actor_path(conn, :show, "nonexisting"))
assert "" == response(conn, 404)
end
end
describe "search for actors" do
test "search for existing actors", %{conn: conn, actor: actor} do
actor_username = actor.preferred_username
conn = get(conn, actor_path(conn, :search, actor_username))
assert %{"data" => [%{"username" => actor_username}]} = json_response(conn, 200)
end
test "search for existing actors with similar username", %{conn: conn, actor: actor} do
actor_username = actor.preferred_username
conn = get(conn, actor_path(conn, :search, "thom"))
assert %{"data" => [%{"username" => actor_username}]} = json_response(conn, 200)
end
test "search for nothing", %{conn: conn, actor: actor} do
actor_username = actor.preferred_username
conn = get(conn, actor_path(conn, :search, "nothing"))
assert %{"data" => []} = json_response(conn, 200)
end
end
describe "update actor" do
test "update actor with valid attrs", %{conn: conn, user: user, actor: actor} do
conn = auth_conn(conn, user)
conn =
patch(conn, actor_path(conn, :update, actor.preferred_username), %{
"actor" => %{"name" => "glouglou"}
})
assert %{"data" => %{"display_name" => "glouglou"}} = json_response(conn, 200)
end
test "update actor with invalid attrs", %{conn: conn, user: user, actor: actor} do
conn = auth_conn(conn, user)
conn =
patch(conn, actor_path(conn, :update, actor.preferred_username), %{
"actor" => %{"preferred_username" => nil}
})
assert json_response(conn, 422)["errors"] != %{}
end
end
###
# Not possible atm
###

View file

@ -34,7 +34,7 @@ defmodule EventosWeb.AddressControllerTest do
floor: nil,
postalCode: nil,
streetAddress: nil,
geom: %{type: nil, data: %{latitude: nil, longitude: nil}}
geom: %{type: "oh no", data: %{latitude: nil, longitude: nil}}
}
def fixture(:address) do

View file

@ -5,6 +5,7 @@ defmodule EventosWeb.UserControllerTest do
alias Eventos.Actors
alias Eventos.Actors.User
use Bamboo.Test
@create_attrs %{email: "foo@bar.tld", password: "some password_hash", username: "some username"}
# @update_attrs %{email: "foo@fighters.tld", password: "some updated password_hash", username: "some updated username"}
@ -18,7 +19,7 @@ defmodule EventosWeb.UserControllerTest do
setup %{conn: conn} do
user = insert(:user)
actor = insert(:actor, user: user)
{:ok, conn: conn, user: user}
{:ok, conn: conn, user: user, actor: actor}
end
describe "index" do
@ -31,12 +32,14 @@ defmodule EventosWeb.UserControllerTest do
describe "create user" do
test "renders user when data is valid", %{conn: conn} do
conn = post(conn, user_path(conn, :create), @create_attrs)
conn = post(conn, user_path(conn, :register), @create_attrs)
assert %{"email" => "foo@bar.tld"} = json_response(conn, 201)
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(@create_attrs.email)
assert_delivered_email(Eventos.Email.User.confirmation_email(user))
end
test "renders errors when data is invalid", %{conn: conn} do
conn = post(conn, user_path(conn, :create), @invalid_attrs)
conn = post(conn, user_path(conn, :register), @invalid_attrs)
assert json_response(conn, 422)["errors"] != %{}
end
@ -47,11 +50,139 @@ defmodule EventosWeb.UserControllerTest do
username: "framasoft"
}
conn = post(conn, user_path(conn, :create), attrs)
conn = post(conn, user_path(conn, :register), attrs)
assert %{"email" => "contact@framasoft.org"} = json_response(conn, 201)
end
end
describe "validating user" do
test "validate user when token is valid", %{conn: conn} do
conn = post(conn, user_path(conn, :create), @create_attrs)
assert %{"email" => "foo@bar.tld"} = json_response(conn, 201)
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(@create_attrs.email)
assert_delivered_email(Eventos.Email.User.confirmation_email(user))
conn = get(conn, user_path(conn, :validate, user.confirmation_token))
assert %{"user" => _, "token" => _} = json_response(conn, 200)
end
test "validate user when token is invalid", %{conn: conn} do
conn = post(conn, user_path(conn, :create), @create_attrs)
assert %{"email" => "foo@bar.tld"} = json_response(conn, 201)
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(@create_attrs.email)
assert_delivered_email(Eventos.Email.User.confirmation_email(user))
conn = get(conn, user_path(conn, :validate, "toto"))
assert %{"error" => _} = json_response(conn, 404)
end
end
describe "revalidating user" do
test "ask to resend token to user when too soon", %{conn: conn} do
conn = post(conn, user_path(conn, :create), @create_attrs)
assert %{"email" => "foo@bar.tld"} = json_response(conn, 201)
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(@create_attrs.email)
assert_delivered_email(Eventos.Email.User.confirmation_email(user))
conn = post(conn, user_path(conn, :resend_confirmation), %{"email" => @create_attrs.email})
assert %{"error" => _} = json_response(conn, 404)
end
test "ask to resend token to user when the time is right", %{conn: conn} do
conn = post(conn, user_path(conn, :create), @create_attrs)
assert %{"email" => "foo@bar.tld"} = json_response(conn, 201)
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(@create_attrs.email)
assert_delivered_email(Eventos.Email.User.confirmation_email(user))
# Hammer time !
{:ok, %User{} = user} =
Eventos.Actors.update_user(user, %{
confirmation_sent_at: Timex.shift(user.confirmation_sent_at, hours: -3)
})
conn = post(conn, user_path(conn, :resend_confirmation), %{"email" => @create_attrs.email})
assert_delivered_email(Eventos.Email.User.confirmation_email(user))
assert %{"email" => "foo@bar.tld"} = json_response(conn, 200)
end
end
describe "resetting user's password" do
test "ask for reset", %{conn: conn, user: user} do
user_email = user.email
# Send reset email
conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user_email})
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(user.email)
assert_delivered_email(Eventos.Email.User.reset_password_email(user))
assert %{"email" => user_email} = json_response(conn, 200)
# Call reset route
conn =
post(conn, user_path(conn, :reset_password), %{
"password" => "new password",
"token" => user.reset_password_token
})
user_id = user.id
assert %{"user" => %{"id" => user_id}} = json_response(conn, 200)
end
test "ask twice for reset too soon", %{conn: conn, user: user} do
user_email = user.email
# Send reset email
conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email})
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(user.email)
assert_delivered_email(Eventos.Email.User.reset_password_email(user))
assert %{"email" => user_email} = json_response(conn, 200)
# Send reset email again
conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email})
assert %{"errors" => "You requested a new reset password too early"} =
json_response(conn, 404)
end
test "ask twice for reset after a while", %{conn: conn, user: user} do
user_email = user.email
# Send reset email
conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email})
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(user.email)
assert_delivered_email(Eventos.Email.User.reset_password_email(user))
assert %{"email" => user_email} = json_response(conn, 200)
# Hammer time !
{:ok, %User{} = user} =
Eventos.Actors.update_user(user, %{
reset_password_sent_at: Timex.shift(user.reset_password_sent_at, hours: -3)
})
# Send reset email again
conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email})
assert {:ok, %User{} = user} = Eventos.Actors.get_user_by_email(user.email)
assert_delivered_email(Eventos.Email.User.reset_password_email(user))
assert %{"email" => user_email} = json_response(conn, 200)
end
test "ask for reset with wrong address", %{conn: conn} do
conn = post(conn, user_path(conn, :send_reset_password), %{"email" => "yolo@coucou"})
assert %{"errors" => "Unable to find an user with this email"} = json_response(conn, 404)
end
test "calling reset route with wrong token", %{conn: conn} do
conn =
post(conn, user_path(conn, :reset_password), %{
"password" => "new password",
"token" => "just wrong"
})
assert %{"errors" => %{"token" => ["Wrong token for password reset"]}} =
json_response(conn, 404)
end
end
# describe "update user" do
# setup [:create_user]
#

View file

@ -0,0 +1,65 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"Emoji": "toot:Emoji",
"Hashtag": "as:Hashtag",
"atomUri": "ostatus:atomUri",
"conversation": "ostatus:conversation",
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"movedTo": "as:movedTo",
"ostatus": "http://ostatus.org#",
"sensitive": "as:sensitive",
"toot": "http://joinmastodon.org/ns#"
}
],
"actor": "http://framapiaf.org/users/admin",
"cc": [
"http://framapiaf.org/users/admin/followers",
"http://eventos.com/@tcit"
],
"id": "http://framapiaf.org/users/admin/statuses/99512778738411822/activity",
"nickname": "lain",
"object": {
"atomUri": "http://framapiaf.org/users/admin/statuses/99512778738411822",
"attachment": [],
"attributedTo": "http://framapiaf.org/users/admin",
"cc": [
"http://framapiaf.org/users/admin/followers",
"http://localtesting.pleroma.lol/users/lain"
],
"content": "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>",
"conversation": "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation",
"id": "http://framapiaf.org/users/admin/statuses/99512778738411822",
"inReplyTo": null,
"inReplyToAtomUri": null,
"published": "2018-02-12T14:08:20Z",
"sensitive": true,
"summary": "cw",
"tag": [
{
"href": "http://localtesting.pleroma.lol/users/lain",
"name": "@lain@localtesting.pleroma.lol",
"type": "Mention"
}
],
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Note",
"url": "http://framapiaf.org/@admin/99512778738411822"
},
"published": "2018-02-12T14:08:20Z",
"signature": {
"created": "2018-02-12T14:08:20Z",
"creator": "http://framapiaf.org/users/admin#main-key",
"signatureValue": "rnNfcopkc6+Ju73P806popcfwrK9wGYHaJVG1/ZvrlEbWVDzaHjkXqj9Q3/xju5l8CSn9tvSgCCtPFqZsFQwn/pFIFUcw7ZWB2xi4bDm3NZ3S4XQ8JRaaX7og5hFxAhWkGhJhAkfxVnOg2hG+w2d/7d7vRVSC1vo5ip4erUaA/PkWusZvPIpxnRWoXaxJsFmVx0gJgjpJkYDyjaXUlp+jmaoseeZ4EPQUWqHLKJ59PRG0mg8j2xAjYH9nQaN14qMRmTGPxY8gfv/CUFcatA+8VJU9KEsJkDAwLVvglydNTLGrxpAJU78a2eaht0foV43XUIZGe3DKiJPgE+UOKGCJw==",
"type": "RsaSignature2017"
},
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"type": "Create"
}

View file

@ -67,11 +67,14 @@ defmodule Eventos.Factory do
end
def comment_factory do
uuid = Ecto.UUID.generate()
%Eventos.Events.Comment{
text: "My Comment",
actor: build(:actor),
event: build(:event),
uuid: Ecto.UUID.generate()
uuid: uuid,
url: "#{EventosWeb.Endpoint.url()}/comments/#{uuid}"
}
end
@ -86,10 +89,18 @@ defmodule Eventos.Factory do
organizer_actor: actor,
category: build(:category),
physical_address: build(:address),
public: true,
url: "#{EventosWeb.Endpoint.url()}/@#{actor.url}/#{Ecto.UUID.generate()}"
}
end
def participant_factory do
%Eventos.Events.Participant{
event: build(:event),
actor: build(:actor)
}
end
def session_factory do
%Eventos.Events.Session{
title: sequence("MySession"),