Merge branch 'feature/unrepeats' into 'develop'
Add unrepeats Closes #69 See merge request pleroma/pleroma!113
This commit is contained in:
commit
aeff2d6474
|
@ -144,6 +144,24 @@ def announce(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unannounce(
|
||||||
|
%User{} = actor,
|
||||||
|
%Object{} = object,
|
||||||
|
activity_id \\ nil,
|
||||||
|
local \\ true
|
||||||
|
) do
|
||||||
|
with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
|
||||||
|
unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
|
||||||
|
{:ok, unannounce_activity} <- insert(unannounce_data, local),
|
||||||
|
:ok <- maybe_federate(unannounce_activity),
|
||||||
|
{:ok, _activity} <- Repo.delete(announce_activity),
|
||||||
|
{:ok, object} <- remove_announce_from_object(announce_activity, object) do
|
||||||
|
{:ok, unannounce_activity, announce_activity, object}
|
||||||
|
else
|
||||||
|
_e -> {:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def follow(follower, followed, activity_id \\ nil, local \\ true) do
|
def follow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||||
with data <- make_follow_data(follower, followed, activity_id),
|
with data <- make_follow_data(follower, followed, activity_id),
|
||||||
{:ok, activity} <- insert(data, local),
|
{:ok, activity} <- insert(data, local),
|
||||||
|
|
|
@ -223,9 +223,27 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_incoming(
|
||||||
|
%{
|
||||||
|
"type" => "Undo",
|
||||||
|
"object" => %{"type" => "Announce", "object" => object_id},
|
||||||
|
"actor" => actor,
|
||||||
|
"id" => id
|
||||||
|
} = data
|
||||||
|
) do
|
||||||
|
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
{:ok, object} <-
|
||||||
|
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||||
|
{:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||||
|
{:ok, activity}
|
||||||
|
else
|
||||||
|
e -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Accept
|
# Accept
|
||||||
# Undo
|
# Undo for non-Announce
|
||||||
|
|
||||||
def handle_incoming(_), do: :error
|
def handle_incoming(_), do: :error
|
||||||
|
|
||||||
|
|
|
@ -237,6 +237,28 @@ def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||||
|
|
||||||
#### Announce-related helpers
|
#### Announce-related helpers
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Retruns an existing announce activity if the notice has already been announced
|
||||||
|
"""
|
||||||
|
def get_existing_announce(actor, %{data: %{"id" => id}}) do
|
||||||
|
query =
|
||||||
|
from(
|
||||||
|
activity in Activity,
|
||||||
|
where: fragment("(?)->>'actor' = ?", activity.data, ^actor),
|
||||||
|
# this is to use the index
|
||||||
|
where:
|
||||||
|
fragment(
|
||||||
|
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||||
|
activity.data,
|
||||||
|
activity.data,
|
||||||
|
^id
|
||||||
|
),
|
||||||
|
where: fragment("(?)->>'type' = 'Announce'", activity.data)
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.one(query)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Make announce activity data for the given actor and object
|
Make announce activity data for the given actor and object
|
||||||
"""
|
"""
|
||||||
|
@ -257,12 +279,38 @@ def make_announce_data(
|
||||||
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Make unannounce activity data for the given actor and object
|
||||||
|
"""
|
||||||
|
def make_unannounce_data(
|
||||||
|
%User{ap_id: ap_id} = user,
|
||||||
|
%Activity{data: %{"context" => context}} = activity,
|
||||||
|
activity_id
|
||||||
|
) do
|
||||||
|
data = %{
|
||||||
|
"type" => "Undo",
|
||||||
|
"actor" => ap_id,
|
||||||
|
"object" => activity.data,
|
||||||
|
"to" => [user.follower_address, activity.data["actor"]],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"context" => context
|
||||||
|
}
|
||||||
|
|
||||||
|
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||||
|
end
|
||||||
|
|
||||||
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
|
||||||
with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
|
with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
|
||||||
update_element_in_object("announcement", announcements, object)
|
update_element_in_object("announcement", announcements, object)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do
|
||||||
|
with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do
|
||||||
|
update_element_in_object("announcement", announcements, object)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
#### Unfollow-related helpers
|
#### Unfollow-related helpers
|
||||||
|
|
||||||
def make_unfollow_data(follower, followed, follow_activity) do
|
def make_unfollow_data(follower, followed, follow_activity) do
|
||||||
|
|
|
@ -24,6 +24,16 @@ def repeat(id_or_ap_id, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unrepeat(id_or_ap_id, user) do
|
||||||
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
|
object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
|
||||||
|
ActivityPub.unannounce(user, object)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
{:error, "Could not unrepeat"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def favorite(id_or_ap_id, user) do
|
def favorite(id_or_ap_id, user) do
|
||||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||||
false <- activity.data["actor"] == user.ap_id,
|
false <- activity.data["actor"] == user.ap_id,
|
||||||
|
|
|
@ -308,6 +308,13 @@ def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
|
with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unrepeat(ap_id_or_id, user),
|
||||||
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
|
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||||
with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
|
|
|
@ -239,27 +239,35 @@ def to_simple_form(%{data: %{"type" => "Undo"}} = activity, user, with_author) d
|
||||||
inserted_at = activity.data["published"]
|
inserted_at = activity.data["published"]
|
||||||
|
|
||||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||||
follow_activity = Activity.get_by_ap_id(activity.data["object"])
|
|
||||||
|
follow_activity =
|
||||||
|
if is_map(activity.data["object"]) do
|
||||||
|
Activity.get_by_ap_id(activity.data["object"]["id"])
|
||||||
|
else
|
||||||
|
Activity.get_by_ap_id(activity.data["object"])
|
||||||
|
end
|
||||||
|
|
||||||
mentions = (activity.recipients || []) |> get_mentions
|
mentions = (activity.recipients || []) |> get_mentions
|
||||||
|
|
||||||
[
|
if follow_activity do
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
[
|
||||||
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
|
||||||
{:id, h.(activity.data["id"])},
|
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/unfollow']},
|
||||||
{:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
{:id, h.(activity.data["id"])},
|
||||||
{:content, [type: 'html'],
|
{:title, ['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||||
['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
{:content, [type: 'html'],
|
||||||
{:published, h.(inserted_at)},
|
['#{user.nickname} stopped following #{follow_activity.data["object"]}']},
|
||||||
{:updated, h.(updated_at)},
|
{:published, h.(inserted_at)},
|
||||||
{:"activity:object",
|
{:updated, h.(updated_at)},
|
||||||
[
|
{:"activity:object",
|
||||||
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
[
|
||||||
{:id, h.(follow_activity.data["object"])},
|
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/person']},
|
||||||
{:uri, h.(follow_activity.data["object"])}
|
{:id, h.(follow_activity.data["object"])},
|
||||||
]},
|
{:uri, h.(follow_activity.data["object"])}
|
||||||
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
]},
|
||||||
] ++ mentions ++ author
|
{:link, [rel: 'self', type: ['application/atom+xml'], href: h.(activity.data["id"])], []}
|
||||||
|
] ++ mentions ++ author
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
|
def to_simple_form(%{data: %{"type" => "Delete"}} = activity, user, with_author) do
|
||||||
|
|
|
@ -112,6 +112,7 @@ def user_fetcher(username) do
|
||||||
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
delete("/statuses/:id", MastodonAPIController, :delete_status)
|
||||||
|
|
||||||
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
|
post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
|
||||||
|
post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
|
||||||
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
|
post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
|
||||||
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
|
||||||
|
|
||||||
|
|
|
@ -187,13 +187,14 @@ def publish(user, activity, poster \\ &@httpoison.post/4)
|
||||||
|
|
||||||
def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster)
|
def publish(%{info: %{"keys" => keys}} = user, %{data: %{"type" => type}} = activity, poster)
|
||||||
when type in @supported_activities do
|
when type in @supported_activities do
|
||||||
feed =
|
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
||||||
ActivityRepresenter.to_simple_form(activity, user, true)
|
|
||||||
|> ActivityRepresenter.wrap_with_entry()
|
|
||||||
|> :xmerl.export_simple(:xmerl_xml)
|
|
||||||
|> to_string
|
|
||||||
|
|
||||||
if feed do
|
if feed do
|
||||||
|
feed =
|
||||||
|
ActivityRepresenter.wrap_with_entry(feed)
|
||||||
|
|> :xmerl.export_simple(:xmerl_xml)
|
||||||
|
|> to_string
|
||||||
|
|
||||||
{:ok, private, _} = keys_from_pem(keys)
|
{:ok, private, _} = keys_from_pem(keys)
|
||||||
{:ok, feed} = encode(private, feed)
|
{:ok, feed} = encode(private, feed)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,18 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
||||||
CommonAPI.post(user, data)
|
CommonAPI.post(user, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delete(%User{} = user, id) do
|
||||||
|
# TwitterAPI does not have an "unretweet" endpoint; instead this is done
|
||||||
|
# via the "destroy" endpoint. Therefore, we need to handle
|
||||||
|
# when the status to "delete" is actually an Announce (repeat) object.
|
||||||
|
with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id) do
|
||||||
|
case type do
|
||||||
|
"Announce" -> unrepeat(user, id)
|
||||||
|
_ -> CommonAPI.delete(id, user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def follow(%User{} = follower, params) do
|
def follow(%User{} = follower, params) do
|
||||||
with {:ok, %User{} = followed} <- get_user(params),
|
with {:ok, %User{} = followed} <- get_user(params),
|
||||||
{:ok, follower} <- User.follow(follower, followed),
|
{:ok, follower} <- User.follow(follower, followed),
|
||||||
|
@ -63,6 +75,12 @@ def repeat(%User{} = user, ap_id_or_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp unrepeat(%User{} = user, ap_id_or_id) do
|
||||||
|
with {:ok, _unannounce, activity, _object} <- CommonAPI.unrepeat(ap_id_or_id, user) do
|
||||||
|
{:ok, activity}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fav(%User{} = user, ap_id_or_id) do
|
def fav(%User{} = user, ap_id_or_id) do
|
||||||
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user),
|
||||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||||
|
|
|
@ -157,8 +157,8 @@ def unblock(%{assigns: %{user: user}} = conn, params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||||
with {:ok, delete} <- CommonAPI.delete(id, user) do
|
with {:ok, activity} <- TwitterAPI.delete(user, id) do
|
||||||
render(conn, ActivityView, "activity.json", %{activity: delete, for: user})
|
render(conn, ActivityView, "activity.json", %{activity: activity, for: user})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"type": "Undo",
|
||||||
|
"signature": {
|
||||||
|
"type": "RsaSignature2017",
|
||||||
|
"signatureValue": "VU9AmHf3Pus9cWtMG/TOdxr+MRQfPHdTVKBBgFJBXhAlMhxEtcbxsu7zmqBgfIz6u0HpTCi5jRXEMftc228OJf/aBUkr4hyWADgcdmhPQgpibouDLgQf9BmnrPqb2rMbzZyt49GJkQZma8taLh077TTq6OKcnsAAJ1evEKOcRYS4OxBSwh4nI726bOXzZWoNzpTcrnm+llcUEN980sDSAS0uyZdb8AxZdfdG6DJQX4AkUD5qTpfqP/vC1ISirrNphvVhlxjUV9Amr4SYTsLx80vdZe5NjeL5Ir4jTIIQLedpxaDu1M9Q+Jpc0fYByQ2hOwUq8JxEmvHvarKjrq0Oww==",
|
||||||
|
"creator": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"created": "2018-05-11T16:23:45Z"
|
||||||
|
},
|
||||||
|
"object": {
|
||||||
|
"type": "Announce",
|
||||||
|
"to": [
|
||||||
|
"http://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"published": "2018-05-11T16:23:37Z",
|
||||||
|
"object": "http://mastodon.example.org/@admin/99541947525187367",
|
||||||
|
"id": "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity",
|
||||||
|
"cc": [
|
||||||
|
"http://mastodon.example.org/users/admin",
|
||||||
|
"http://mastodon.example.org/users/admin/followers"
|
||||||
|
],
|
||||||
|
"atomUri": "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin"
|
||||||
|
},
|
||||||
|
"id": "http://mastodon.example.org/users/admin#announces/100011594053806179/undo",
|
||||||
|
"actor": "http://mastodon.example.org/users/admin",
|
||||||
|
"@context": [
|
||||||
|
"http://www.w3.org/ns/activitystreams",
|
||||||
|
"http://w3id.org/security/v1",
|
||||||
|
{
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"focalPoint": {
|
||||||
|
"@id": "toot:focalPoint",
|
||||||
|
"@container": "@list"
|
||||||
|
},
|
||||||
|
"featured": "toot:featured",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -302,6 +302,38 @@ test "adds an announce activity to the db" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "unannouncing an object" do
|
||||||
|
test "unannouncing a previously announced object" do
|
||||||
|
note_activity = insert(:note_activity)
|
||||||
|
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
# Unannouncing an object that is not announced does nothing
|
||||||
|
# {:ok, object} = ActivityPub.unannounce(user, object)
|
||||||
|
# assert object.data["announcement_count"] == 0
|
||||||
|
|
||||||
|
{:ok, announce_activity, object} = ActivityPub.announce(user, object)
|
||||||
|
assert object.data["announcement_count"] == 1
|
||||||
|
|
||||||
|
{:ok, unannounce_activity, activity, object} = ActivityPub.unannounce(user, object)
|
||||||
|
assert object.data["announcement_count"] == 0
|
||||||
|
|
||||||
|
assert activity == announce_activity
|
||||||
|
|
||||||
|
assert unannounce_activity.data["to"] == [
|
||||||
|
User.ap_followers(user),
|
||||||
|
announce_activity.data["actor"]
|
||||||
|
]
|
||||||
|
|
||||||
|
assert unannounce_activity.data["type"] == "Undo"
|
||||||
|
assert unannounce_activity.data["object"] == announce_activity.data
|
||||||
|
assert unannounce_activity.data["actor"] == user.ap_id
|
||||||
|
assert unannounce_activity.data["context"] == announce_activity.data["context"]
|
||||||
|
|
||||||
|
assert Repo.get(Activity, announce_activity.id) == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "uploading files" do
|
describe "uploading files" do
|
||||||
test "copies the file to the configured folder" do
|
test "copies the file to the configured folder" do
|
||||||
file = %Plug.Upload{
|
file = %Plug.Upload{
|
||||||
|
|
|
@ -232,6 +232,34 @@ test "it works for incoming deletes" do
|
||||||
|
|
||||||
refute Repo.get(Activity, activity.id)
|
refute Repo.get(Activity, activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works for incoming unannounces with an existing notice" do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||||
|
|
||||||
|
announce_data =
|
||||||
|
File.read!("test/fixtures/mastodon-announce.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", activity.data["object"]["id"])
|
||||||
|
|
||||||
|
{:ok, %Activity{data: announce_data, local: false}} =
|
||||||
|
Transmogrifier.handle_incoming(announce_data)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-undo-announce.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("object", announce_data)
|
||||||
|
|> Map.put("actor", announce_data["actor"])
|
||||||
|
|
||||||
|
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert data["type"] == "Undo"
|
||||||
|
assert data["object"]["type"] == "Announce"
|
||||||
|
assert data["object"]["object"] == activity.data["object"]["id"]
|
||||||
|
|
||||||
|
assert data["object"]["id"] ==
|
||||||
|
"http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "prepare outgoing" do
|
describe "prepare outgoing" do
|
||||||
|
|
|
@ -298,6 +298,24 @@ test "reblogs and returns the reblogged status", %{conn: conn} do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "unreblogging" do
|
||||||
|
test "unreblogs and returns the unreblogged status", %{conn: conn} do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _} = CommonAPI.repeat(activity.id, user)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/statuses/#{activity.id}/unreblog")
|
||||||
|
|
||||||
|
assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
|
||||||
|
|
||||||
|
assert to_string(activity.id) == id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "favoriting" do
|
describe "favoriting" do
|
||||||
test "favs a status and returns it", %{conn: conn} do
|
test "favs a status and returns it", %{conn: conn} do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
|
Loading…
Reference in New Issue