Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1459 lines
48KB

  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
  5. use Pleroma.DataCase
  6. alias Pleroma.Activity
  7. alias Pleroma.Object
  8. alias Pleroma.Object.Fetcher
  9. alias Pleroma.Repo
  10. alias Pleroma.Tests.ObanHelpers
  11. alias Pleroma.User
  12. alias Pleroma.Web.ActivityPub.ActivityPub
  13. alias Pleroma.Web.ActivityPub.Transmogrifier
  14. alias Pleroma.Web.CommonAPI
  15. alias Pleroma.Web.OStatus
  16. alias Pleroma.Web.Websub.WebsubClientSubscription
  17. import Mock
  18. import Pleroma.Factory
  19. import ExUnit.CaptureLog
  20. setup_all do
  21. Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  22. :ok
  23. end
  24. clear_config([:instance, :max_remote_account_fields])
  25. describe "handle_incoming" do
  26. test "it ignores an incoming notice if we already have it" do
  27. activity = insert(:note_activity)
  28. data =
  29. File.read!("test/fixtures/mastodon-post-activity.json")
  30. |> Poison.decode!()
  31. |> Map.put("object", Object.normalize(activity).data)
  32. {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
  33. assert activity == returned_activity
  34. end
  35. test "it fetches replied-to activities if we don't have them" do
  36. data =
  37. File.read!("test/fixtures/mastodon-post-activity.json")
  38. |> Poison.decode!()
  39. object =
  40. data["object"]
  41. |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
  42. data = Map.put(data, "object", object)
  43. {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
  44. returned_object = Object.normalize(returned_activity, false)
  45. assert activity =
  46. Activity.get_create_by_object_ap_id(
  47. "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
  48. )
  49. assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
  50. end
  51. test "it does not fetch replied-to activities beyond max_replies_depth" do
  52. data =
  53. File.read!("test/fixtures/mastodon-post-activity.json")
  54. |> Poison.decode!()
  55. object =
  56. data["object"]
  57. |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
  58. data = Map.put(data, "object", object)
  59. with_mock Pleroma.Web.Federator,
  60. allowed_incoming_reply_depth?: fn _ -> false end do
  61. {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
  62. returned_object = Object.normalize(returned_activity, false)
  63. refute Activity.get_create_by_object_ap_id(
  64. "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
  65. )
  66. assert returned_object.data["inReplyToAtomUri"] ==
  67. "https://shitposter.club/notice/2827873"
  68. end
  69. end
  70. test "it does not crash if the object in inReplyTo can't be fetched" do
  71. data =
  72. File.read!("test/fixtures/mastodon-post-activity.json")
  73. |> Poison.decode!()
  74. object =
  75. data["object"]
  76. |> Map.put("inReplyTo", "https://404.site/whatever")
  77. data =
  78. data
  79. |> Map.put("object", object)
  80. assert capture_log(fn ->
  81. {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
  82. end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"
  83. end
  84. test "it works for incoming notices" do
  85. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
  86. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  87. assert data["id"] ==
  88. "http://mastodon.example.org/users/admin/statuses/99512778738411822/activity"
  89. assert data["context"] ==
  90. "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
  91. assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
  92. assert data["cc"] == [
  93. "http://mastodon.example.org/users/admin/followers",
  94. "http://localtesting.pleroma.lol/users/lain"
  95. ]
  96. assert data["actor"] == "http://mastodon.example.org/users/admin"
  97. object_data = Object.normalize(data["object"]).data
  98. assert object_data["id"] ==
  99. "http://mastodon.example.org/users/admin/statuses/99512778738411822"
  100. assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
  101. assert object_data["cc"] == [
  102. "http://mastodon.example.org/users/admin/followers",
  103. "http://localtesting.pleroma.lol/users/lain"
  104. ]
  105. assert object_data["actor"] == "http://mastodon.example.org/users/admin"
  106. assert object_data["attributedTo"] == "http://mastodon.example.org/users/admin"
  107. assert object_data["context"] ==
  108. "tag:mastodon.example.org,2018-02-12:objectId=20:objectType=Conversation"
  109. assert object_data["sensitive"] == true
  110. user = User.get_cached_by_ap_id(object_data["actor"])
  111. assert user.info.note_count == 1
  112. end
  113. test "it works for incoming notices with hashtags" do
  114. data = File.read!("test/fixtures/mastodon-post-activity-hashtag.json") |> Poison.decode!()
  115. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  116. object = Object.normalize(data["object"])
  117. assert Enum.at(object.data["tag"], 2) == "moo"
  118. end
  119. test "it works for incoming questions" do
  120. data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
  121. {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
  122. object = Object.normalize(activity)
  123. assert Enum.all?(object.data["oneOf"], fn choice ->
  124. choice["name"] in [
  125. "Dunno",
  126. "Everyone knows that!",
  127. "25 char limit is dumb",
  128. "I can't even fit a funny"
  129. ]
  130. end)
  131. end
  132. test "it rewrites Note votes to Answers and increments vote counters on question activities" do
  133. user = insert(:user)
  134. {:ok, activity} =
  135. CommonAPI.post(user, %{
  136. "status" => "suya...",
  137. "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
  138. })
  139. object = Object.normalize(activity)
  140. data =
  141. File.read!("test/fixtures/mastodon-vote.json")
  142. |> Poison.decode!()
  143. |> Kernel.put_in(["to"], user.ap_id)
  144. |> Kernel.put_in(["object", "inReplyTo"], object.data["id"])
  145. |> Kernel.put_in(["object", "to"], user.ap_id)
  146. {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
  147. answer_object = Object.normalize(activity)
  148. assert answer_object.data["type"] == "Answer"
  149. object = Object.get_by_ap_id(object.data["id"])
  150. assert Enum.any?(
  151. object.data["oneOf"],
  152. fn
  153. %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true
  154. _ -> false
  155. end
  156. )
  157. end
  158. test "it works for incoming notices with contentMap" do
  159. data =
  160. File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!()
  161. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  162. object = Object.normalize(data["object"])
  163. assert object.data["content"] ==
  164. "<p><span class=\"h-card\"><a href=\"http://localtesting.pleroma.lol/users/lain\" class=\"u-url mention\">@<span>lain</span></a></span></p>"
  165. end
  166. test "it works for incoming notices with to/cc not being an array (kroeg)" do
  167. data = File.read!("test/fixtures/kroeg-post-activity.json") |> Poison.decode!()
  168. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  169. object = Object.normalize(data["object"])
  170. assert object.data["content"] ==
  171. "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>"
  172. end
  173. test "it works for incoming announces with actor being inlined (kroeg)" do
  174. data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!()
  175. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  176. assert data["actor"] == "https://puckipedia.com/"
  177. end
  178. test "it works for incoming notices with tag not being an array (kroeg)" do
  179. data = File.read!("test/fixtures/kroeg-array-less-emoji.json") |> Poison.decode!()
  180. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  181. object = Object.normalize(data["object"])
  182. assert object.data["emoji"] == %{
  183. "icon_e_smile" => "https://puckipedia.com/forum/images/smilies/icon_e_smile.png"
  184. }
  185. data = File.read!("test/fixtures/kroeg-array-less-hashtag.json") |> Poison.decode!()
  186. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  187. object = Object.normalize(data["object"])
  188. assert "test" in object.data["tag"]
  189. end
  190. test "it works for incoming notices with url not being a string (prismo)" do
  191. data = File.read!("test/fixtures/prismo-url-map.json") |> Poison.decode!()
  192. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  193. object = Object.normalize(data["object"])
  194. assert object.data["url"] == "https://prismo.news/posts/83"
  195. end
  196. test "it cleans up incoming notices which are not really DMs" do
  197. user = insert(:user)
  198. other_user = insert(:user)
  199. to = [user.ap_id, other_user.ap_id]
  200. data =
  201. File.read!("test/fixtures/mastodon-post-activity.json")
  202. |> Poison.decode!()
  203. |> Map.put("to", to)
  204. |> Map.put("cc", [])
  205. object =
  206. data["object"]
  207. |> Map.put("to", to)
  208. |> Map.put("cc", [])
  209. data = Map.put(data, "object", object)
  210. {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
  211. assert data["to"] == []
  212. assert data["cc"] == to
  213. object_data = Object.normalize(activity).data
  214. assert object_data["to"] == []
  215. assert object_data["cc"] == to
  216. end
  217. test "it works for incoming likes" do
  218. user = insert(:user)
  219. {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"})
  220. data =
  221. File.read!("test/fixtures/mastodon-like.json")
  222. |> Poison.decode!()
  223. |> Map.put("object", activity.data["object"])
  224. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  225. assert data["actor"] == "http://mastodon.example.org/users/admin"
  226. assert data["type"] == "Like"
  227. assert data["id"] == "http://mastodon.example.org/users/admin#likes/2"
  228. assert data["object"] == activity.data["object"]
  229. end
  230. test "it returns an error for incoming unlikes wihout a like activity" do
  231. user = insert(:user)
  232. {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
  233. data =
  234. File.read!("test/fixtures/mastodon-undo-like.json")
  235. |> Poison.decode!()
  236. |> Map.put("object", activity.data["object"])
  237. assert Transmogrifier.handle_incoming(data) == :error
  238. end
  239. test "it works for incoming unlikes with an existing like activity" do
  240. user = insert(:user)
  241. {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"})
  242. like_data =
  243. File.read!("test/fixtures/mastodon-like.json")
  244. |> Poison.decode!()
  245. |> Map.put("object", activity.data["object"])
  246. {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data)
  247. data =
  248. File.read!("test/fixtures/mastodon-undo-like.json")
  249. |> Poison.decode!()
  250. |> Map.put("object", like_data)
  251. |> Map.put("actor", like_data["actor"])
  252. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  253. assert data["actor"] == "http://mastodon.example.org/users/admin"
  254. assert data["type"] == "Undo"
  255. assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo"
  256. assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2"
  257. end
  258. test "it works for incoming announces" do
  259. data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
  260. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  261. assert data["actor"] == "http://mastodon.example.org/users/admin"
  262. assert data["type"] == "Announce"
  263. assert data["id"] ==
  264. "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
  265. assert data["object"] ==
  266. "http://mastodon.example.org/users/admin/statuses/99541947525187367"
  267. assert Activity.get_create_by_object_ap_id(data["object"])
  268. end
  269. test "it works for incoming announces with an existing activity" do
  270. user = insert(:user)
  271. {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
  272. data =
  273. File.read!("test/fixtures/mastodon-announce.json")
  274. |> Poison.decode!()
  275. |> Map.put("object", activity.data["object"])
  276. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  277. assert data["actor"] == "http://mastodon.example.org/users/admin"
  278. assert data["type"] == "Announce"
  279. assert data["id"] ==
  280. "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
  281. assert data["object"] == activity.data["object"]
  282. assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id
  283. end
  284. test "it does not clobber the addressing on announce activities" do
  285. user = insert(:user)
  286. {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
  287. data =
  288. File.read!("test/fixtures/mastodon-announce.json")
  289. |> Poison.decode!()
  290. |> Map.put("object", Object.normalize(activity).data["id"])
  291. |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
  292. |> Map.put("cc", [])
  293. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  294. assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
  295. end
  296. test "it ensures that as:Public activities make it to their followers collection" do
  297. user = insert(:user)
  298. data =
  299. File.read!("test/fixtures/mastodon-post-activity.json")
  300. |> Poison.decode!()
  301. |> Map.put("actor", user.ap_id)
  302. |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
  303. |> Map.put("cc", [])
  304. object =
  305. data["object"]
  306. |> Map.put("attributedTo", user.ap_id)
  307. |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"])
  308. |> Map.put("cc", [])
  309. |> Map.put("id", user.ap_id <> "/activities/12345678")
  310. data = Map.put(data, "object", object)
  311. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  312. assert data["cc"] == [User.ap_followers(user)]
  313. end
  314. test "it ensures that address fields become lists" do
  315. user = insert(:user)
  316. data =
  317. File.read!("test/fixtures/mastodon-post-activity.json")
  318. |> Poison.decode!()
  319. |> Map.put("actor", user.ap_id)
  320. |> Map.put("to", nil)
  321. |> Map.put("cc", nil)
  322. object =
  323. data["object"]
  324. |> Map.put("attributedTo", user.ap_id)
  325. |> Map.put("to", nil)
  326. |> Map.put("cc", nil)
  327. |> Map.put("id", user.ap_id <> "/activities/12345678")
  328. data = Map.put(data, "object", object)
  329. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  330. assert !is_nil(data["to"])
  331. assert !is_nil(data["cc"])
  332. end
  333. test "it strips internal likes" do
  334. data =
  335. File.read!("test/fixtures/mastodon-post-activity.json")
  336. |> Poison.decode!()
  337. likes = %{
  338. "first" =>
  339. "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
  340. "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
  341. "totalItems" => 3,
  342. "type" => "OrderedCollection"
  343. }
  344. object = Map.put(data["object"], "likes", likes)
  345. data = Map.put(data, "object", object)
  346. {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
  347. refute Map.has_key?(object.data, "likes")
  348. end
  349. test "it works for incoming update activities" do
  350. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
  351. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  352. update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
  353. object =
  354. update_data["object"]
  355. |> Map.put("actor", data["actor"])
  356. |> Map.put("id", data["actor"])
  357. update_data =
  358. update_data
  359. |> Map.put("actor", data["actor"])
  360. |> Map.put("object", object)
  361. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
  362. user = User.get_cached_by_ap_id(data["actor"])
  363. assert user.name == "gargle"
  364. assert user.avatar["url"] == [
  365. %{
  366. "href" =>
  367. "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
  368. }
  369. ]
  370. assert user.info.banner["url"] == [
  371. %{
  372. "href" =>
  373. "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
  374. }
  375. ]
  376. assert user.bio == "<p>Some bio</p>"
  377. end
  378. test "it works with custom profile fields" do
  379. {:ok, activity} =
  380. "test/fixtures/mastodon-post-activity.json"
  381. |> File.read!()
  382. |> Poison.decode!()
  383. |> Transmogrifier.handle_incoming()
  384. user = User.get_cached_by_ap_id(activity.actor)
  385. assert User.Info.fields(user.info) == [
  386. %{"name" => "foo", "value" => "bar"},
  387. %{"name" => "foo1", "value" => "bar1"}
  388. ]
  389. update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
  390. object =
  391. update_data["object"]
  392. |> Map.put("actor", user.ap_id)
  393. |> Map.put("id", user.ap_id)
  394. update_data =
  395. update_data
  396. |> Map.put("actor", user.ap_id)
  397. |> Map.put("object", object)
  398. {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
  399. user = User.get_cached_by_ap_id(user.ap_id)
  400. assert User.Info.fields(user.info) == [
  401. %{"name" => "foo", "value" => "updated"},
  402. %{"name" => "foo1", "value" => "updated"}
  403. ]
  404. Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
  405. update_data =
  406. put_in(update_data, ["object", "attachment"], [
  407. %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
  408. %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
  409. %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
  410. ])
  411. {:ok, _} = Transmogrifier.handle_incoming(update_data)
  412. user = User.get_cached_by_ap_id(user.ap_id)
  413. assert User.Info.fields(user.info) == [
  414. %{"name" => "foo", "value" => "updated"},
  415. %{"name" => "foo1", "value" => "updated"}
  416. ]
  417. update_data = put_in(update_data, ["object", "attachment"], [])
  418. {:ok, _} = Transmogrifier.handle_incoming(update_data)
  419. user = User.get_cached_by_ap_id(user.ap_id)
  420. assert User.Info.fields(user.info) == []
  421. end
  422. test "it works for incoming update activities which lock the account" do
  423. data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
  424. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  425. update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
  426. object =
  427. update_data["object"]
  428. |> Map.put("actor", data["actor"])
  429. |> Map.put("id", data["actor"])
  430. |> Map.put("manuallyApprovesFollowers", true)
  431. update_data =
  432. update_data
  433. |> Map.put("actor", data["actor"])
  434. |> Map.put("object", object)
  435. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
  436. user = User.get_cached_by_ap_id(data["actor"])
  437. assert user.info.locked == true
  438. end
  439. test "it works for incoming deletes" do
  440. activity = insert(:note_activity)
  441. data =
  442. File.read!("test/fixtures/mastodon-delete.json")
  443. |> Poison.decode!()
  444. object =
  445. data["object"]
  446. |> Map.put("id", activity.data["object"])
  447. data =
  448. data
  449. |> Map.put("object", object)
  450. |> Map.put("actor", activity.data["actor"])
  451. {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data)
  452. refute Activity.get_by_id(activity.id)
  453. end
  454. test "it fails for incoming deletes with spoofed origin" do
  455. activity = insert(:note_activity)
  456. data =
  457. File.read!("test/fixtures/mastodon-delete.json")
  458. |> Poison.decode!()
  459. object =
  460. data["object"]
  461. |> Map.put("id", activity.data["object"])
  462. data =
  463. data
  464. |> Map.put("object", object)
  465. assert capture_log(fn ->
  466. :error = Transmogrifier.handle_incoming(data)
  467. end) =~
  468. "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}"
  469. assert Activity.get_by_id(activity.id)
  470. end
  471. test "it works for incoming user deletes" do
  472. %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
  473. data =
  474. File.read!("test/fixtures/mastodon-delete-user.json")
  475. |> Poison.decode!()
  476. {:ok, _} = Transmogrifier.handle_incoming(data)
  477. ObanHelpers.perform_all()
  478. refute User.get_cached_by_ap_id(ap_id)
  479. end
  480. test "it fails for incoming user deletes with spoofed origin" do
  481. %{ap_id: ap_id} = insert(:user)
  482. data =
  483. File.read!("test/fixtures/mastodon-delete-user.json")
  484. |> Poison.decode!()
  485. |> Map.put("actor", ap_id)
  486. assert :error == Transmogrifier.handle_incoming(data)
  487. assert User.get_cached_by_ap_id(ap_id)
  488. end
  489. test "it works for incoming unannounces with an existing notice" do
  490. user = insert(:user)
  491. {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
  492. announce_data =
  493. File.read!("test/fixtures/mastodon-announce.json")
  494. |> Poison.decode!()
  495. |> Map.put("object", activity.data["object"])
  496. {:ok, %Activity{data: announce_data, local: false}} =
  497. Transmogrifier.handle_incoming(announce_data)
  498. data =
  499. File.read!("test/fixtures/mastodon-undo-announce.json")
  500. |> Poison.decode!()
  501. |> Map.put("object", announce_data)
  502. |> Map.put("actor", announce_data["actor"])
  503. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  504. assert data["type"] == "Undo"
  505. assert object_data = data["object"]
  506. assert object_data["type"] == "Announce"
  507. assert object_data["object"] == activity.data["object"]
  508. assert object_data["id"] ==
  509. "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity"
  510. end
  511. test "it works for incomming unfollows with an existing follow" do
  512. user = insert(:user)
  513. follow_data =
  514. File.read!("test/fixtures/mastodon-follow-activity.json")
  515. |> Poison.decode!()
  516. |> Map.put("object", user.ap_id)
  517. {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data)
  518. data =
  519. File.read!("test/fixtures/mastodon-unfollow-activity.json")
  520. |> Poison.decode!()
  521. |> Map.put("object", follow_data)
  522. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  523. assert data["type"] == "Undo"
  524. assert data["object"]["type"] == "Follow"
  525. assert data["object"]["object"] == user.ap_id
  526. assert data["actor"] == "http://mastodon.example.org/users/admin"
  527. refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
  528. end
  529. test "it works for incoming blocks" do
  530. user = insert(:user)
  531. data =
  532. File.read!("test/fixtures/mastodon-block-activity.json")
  533. |> Poison.decode!()
  534. |> Map.put("object", user.ap_id)
  535. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  536. assert data["type"] == "Block"
  537. assert data["object"] == user.ap_id
  538. assert data["actor"] == "http://mastodon.example.org/users/admin"
  539. blocker = User.get_cached_by_ap_id(data["actor"])
  540. assert User.blocks?(blocker, user)
  541. end
  542. test "incoming blocks successfully tear down any follow relationship" do
  543. blocker = insert(:user)
  544. blocked = insert(:user)
  545. data =
  546. File.read!("test/fixtures/mastodon-block-activity.json")
  547. |> Poison.decode!()
  548. |> Map.put("object", blocked.ap_id)
  549. |> Map.put("actor", blocker.ap_id)
  550. {:ok, blocker} = User.follow(blocker, blocked)
  551. {:ok, blocked} = User.follow(blocked, blocker)
  552. assert User.following?(blocker, blocked)
  553. assert User.following?(blocked, blocker)
  554. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  555. assert data["type"] == "Block"
  556. assert data["object"] == blocked.ap_id
  557. assert data["actor"] == blocker.ap_id
  558. blocker = User.get_cached_by_ap_id(data["actor"])
  559. blocked = User.get_cached_by_ap_id(data["object"])
  560. assert User.blocks?(blocker, blocked)
  561. refute User.following?(blocker, blocked)
  562. refute User.following?(blocked, blocker)
  563. end
  564. test "it works for incoming unblocks with an existing block" do
  565. user = insert(:user)
  566. block_data =
  567. File.read!("test/fixtures/mastodon-block-activity.json")
  568. |> Poison.decode!()
  569. |> Map.put("object", user.ap_id)
  570. {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data)
  571. data =
  572. File.read!("test/fixtures/mastodon-unblock-activity.json")
  573. |> Poison.decode!()
  574. |> Map.put("object", block_data)
  575. {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
  576. assert data["type"] == "Undo"
  577. assert data["object"]["type"] == "Block"
  578. assert data["object"]["object"] == user.ap_id
  579. assert data["actor"] == "http://mastodon.example.org/users/admin"
  580. blocker = User.get_cached_by_ap_id(data["actor"])
  581. refute User.blocks?(blocker, user)
  582. end
  583. test "it works for incoming accepts which were pre-accepted" do
  584. follower = insert(:user)
  585. followed = insert(:user)
  586. {:ok, follower} = User.follow(follower, followed)
  587. assert User.following?(follower, followed) == true
  588. {:ok, follow_activity} = ActivityPub.follow(follower, followed)
  589. accept_data =
  590. File.read!("test/fixtures/mastodon-accept-activity.json")
  591. |> Poison.decode!()
  592. |> Map.put("actor", followed.ap_id)
  593. object =
  594. accept_data["object"]
  595. |> Map.put("actor", follower.ap_id)
  596. |> Map.put("id", follow_activity.data["id"])
  597. accept_data = Map.put(accept_data, "object", object)
  598. {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
  599. refute activity.local
  600. assert activity.data["object"] == follow_activity.data["id"]
  601. follower = User.get_cached_by_id(follower.id)
  602. assert User.following?(follower, followed) == true
  603. end
  604. test "it works for incoming accepts which were orphaned" do
  605. follower = insert(:user)
  606. followed = insert(:user, %{info: %User.Info{locked: true}})
  607. {:ok, follow_activity} = ActivityPub.follow(follower, followed)
  608. accept_data =
  609. File.read!("test/fixtures/mastodon-accept-activity.json")
  610. |> Poison.decode!()
  611. |> Map.put("actor", followed.ap_id)
  612. accept_data =
  613. Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
  614. {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
  615. assert activity.data["object"] == follow_activity.data["id"]
  616. follower = User.get_cached_by_id(follower.id)
  617. assert User.following?(follower, followed) == true
  618. end
  619. test "it works for incoming accepts which are referenced by IRI only" do
  620. follower = insert(:user)
  621. followed = insert(:user, %{info: %User.Info{locked: true}})
  622. {:ok, follow_activity} = ActivityPub.follow(follower, followed)
  623. accept_data =
  624. File.read!("test/fixtures/mastodon-accept-activity.json")
  625. |> Poison.decode!()
  626. |> Map.put("actor", followed.ap_id)
  627. |> Map.put("object", follow_activity.data["id"])
  628. {:ok, activity} = Transmogrifier.handle_incoming(accept_data)
  629. assert activity.data["object"] == follow_activity.data["id"]
  630. follower = User.get_cached_by_id(follower.id)
  631. assert User.following?(follower, followed) == true
  632. end
  633. test "it fails for incoming accepts which cannot be correlated" do
  634. follower = insert(:user)
  635. followed = insert(:user, %{info: %User.Info{locked: true}})
  636. accept_data =
  637. File.read!("test/fixtures/mastodon-accept-activity.json")
  638. |> Poison.decode!()
  639. |> Map.put("actor", followed.ap_id)
  640. accept_data =
  641. Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
  642. :error = Transmogrifier.handle_incoming(accept_data)
  643. follower = User.get_cached_by_id(follower.id)
  644. refute User.following?(follower, followed) == true
  645. end
  646. test "it fails for incoming rejects which cannot be correlated" do
  647. follower = insert(:user)
  648. followed = insert(:user, %{info: %User.Info{locked: true}})
  649. accept_data =
  650. File.read!("test/fixtures/mastodon-reject-activity.json")
  651. |> Poison.decode!()
  652. |> Map.put("actor", followed.ap_id)
  653. accept_data =
  654. Map.put(accept_data, "object", Map.put(accept_data["object"], "actor", follower.ap_id))
  655. :error = Transmogrifier.handle_incoming(accept_data)
  656. follower = User.get_cached_by_id(follower.id)
  657. refute User.following?(follower, followed) == true
  658. end
  659. test "it works for incoming rejects which are orphaned" do
  660. follower = insert(:user)
  661. followed = insert(:user, %{info: %User.Info{locked: true}})
  662. {:ok, follower} = User.follow(follower, followed)
  663. {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
  664. assert User.following?(follower, followed) == true
  665. reject_data =
  666. File.read!("test/fixtures/mastodon-reject-activity.json")
  667. |> Poison.decode!()
  668. |> Map.put("actor", followed.ap_id)
  669. reject_data =
  670. Map.put(reject_data, "object", Map.put(reject_data["object"], "actor", follower.ap_id))
  671. {:ok, activity} = Transmogrifier.handle_incoming(reject_data)
  672. refute activity.local
  673. follower = User.get_cached_by_id(follower.id)
  674. assert User.following?(follower, followed) == false
  675. end
  676. test "it works for incoming rejects which are referenced by IRI only" do
  677. follower = insert(:user)
  678. followed = insert(:user, %{info: %User.Info{locked: true}})
  679. {:ok, follower} = User.follow(follower, followed)
  680. {:ok, follow_activity} = ActivityPub.follow(follower, followed)
  681. assert User.following?(follower, followed) == true
  682. reject_data =
  683. File.read!("test/fixtures/mastodon-reject-activity.json")
  684. |> Poison.decode!()
  685. |> Map.put("actor", followed.ap_id)
  686. |> Map.put("object", follow_activity.data["id"])
  687. {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data)
  688. follower = User.get_cached_by_id(follower.id)
  689. assert User.following?(follower, followed) == false
  690. end
  691. test "it rejects activities without a valid ID" do
  692. user = insert(:user)
  693. data =
  694. File.read!("test/fixtures/mastodon-follow-activity.json")
  695. |> Poison.decode!()
  696. |> Map.put("object", user.ap_id)
  697. |> Map.put("id", "")
  698. :error = Transmogrifier.handle_incoming(data)
  699. end
  700. test "it remaps video URLs as attachments if necessary" do
  701. {:ok, object} =
  702. Fetcher.fetch_object_from_id(
  703. "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
  704. )
  705. attachment = %{
  706. "type" => "Link",
  707. "mediaType" => "video/mp4",
  708. "href" =>
  709. "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
  710. "mimeType" => "video/mp4",
  711. "size" => 5_015_880,
  712. "url" => [
  713. %{
  714. "href" =>
  715. "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
  716. "mediaType" => "video/mp4",
  717. "type" => "Link"
  718. }
  719. ],
  720. "width" => 480
  721. }
  722. assert object.data["url"] ==
  723. "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
  724. assert object.data["attachment"] == [attachment]
  725. end
  726. test "it accepts Flag activities" do
  727. user = insert(:user)
  728. other_user = insert(:user)
  729. {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
  730. object = Object.normalize(activity)
  731. message = %{
  732. "@context" => "https://www.w3.org/ns/activitystreams",
  733. "cc" => [user.ap_id],
  734. "object" => [user.ap_id, object.data["id"]],
  735. "type" => "Flag",
  736. "content" => "blocked AND reported!!!",
  737. "actor" => other_user.ap_id
  738. }
  739. assert {:ok, activity} = Transmogrifier.handle_incoming(message)
  740. assert activity.data["object"] == [user.ap_id, object.data["id"]]
  741. assert activity.data["content"] == "blocked AND reported!!!"
  742. assert activity.data["actor"] == other_user.ap_id
  743. assert activity.data["cc"] == [user.ap_id]
  744. end
  745. end
  746. describe "prepare outgoing" do
  747. test "it turns mentions into tags" do
  748. user = insert(:user)
  749. other_user = insert(:user)
  750. {:ok, activity} =
  751. CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"})
  752. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  753. object = modified["object"]
  754. expected_mention = %{
  755. "href" => other_user.ap_id,
  756. "name" => "@#{other_user.nickname}",
  757. "type" => "Mention"
  758. }
  759. expected_tag = %{
  760. "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
  761. "type" => "Hashtag",
  762. "name" => "#2hu"
  763. }
  764. assert Enum.member?(object["tag"], expected_tag)
  765. assert Enum.member?(object["tag"], expected_mention)
  766. end
  767. test "it adds the sensitive property" do
  768. user = insert(:user)
  769. {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"})
  770. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  771. assert modified["object"]["sensitive"]
  772. end
  773. test "it adds the json-ld context and the conversation property" do
  774. user = insert(:user)
  775. {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
  776. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  777. assert modified["@context"] ==
  778. Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
  779. assert modified["object"]["conversation"] == modified["context"]
  780. end
  781. test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do
  782. user = insert(:user)
  783. {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
  784. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  785. assert modified["object"]["actor"] == modified["object"]["attributedTo"]
  786. end
  787. test "it translates ostatus IDs to external URLs" do
  788. incoming = File.read!("test/fixtures/incoming_note_activity.xml")
  789. {:ok, [referent_activity]} = OStatus.handle_incoming(incoming)
  790. user = insert(:user)
  791. {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user)
  792. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  793. assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29"
  794. end
  795. test "it translates ostatus reply_to IDs to external URLs" do
  796. incoming = File.read!("test/fixtures/incoming_note_activity.xml")
  797. {:ok, [referred_activity]} = OStatus.handle_incoming(incoming)
  798. user = insert(:user)
  799. {:ok, activity} =
  800. CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id})
  801. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  802. assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29"
  803. end
  804. test "it strips internal hashtag data" do
  805. user = insert(:user)
  806. {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"})
  807. expected_tag = %{
  808. "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu",
  809. "type" => "Hashtag",
  810. "name" => "#2hu"
  811. }
  812. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  813. assert modified["object"]["tag"] == [expected_tag]
  814. end
  815. test "it strips internal fields" do
  816. user = insert(:user)
  817. {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"})
  818. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  819. assert length(modified["object"]["tag"]) == 2
  820. assert is_nil(modified["object"]["emoji"])
  821. assert is_nil(modified["object"]["like_count"])
  822. assert is_nil(modified["object"]["announcements"])
  823. assert is_nil(modified["object"]["announcement_count"])
  824. assert is_nil(modified["object"]["context_id"])
  825. end
  826. test "it strips internal fields of article" do
  827. activity = insert(:article_activity)
  828. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  829. assert length(modified["object"]["tag"]) == 2
  830. assert is_nil(modified["object"]["emoji"])
  831. assert is_nil(modified["object"]["like_count"])
  832. assert is_nil(modified["object"]["announcements"])
  833. assert is_nil(modified["object"]["announcement_count"])
  834. assert is_nil(modified["object"]["context_id"])
  835. assert is_nil(modified["object"]["likes"])
  836. end
  837. test "the directMessage flag is present" do
  838. user = insert(:user)
  839. other_user = insert(:user)
  840. {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"})
  841. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  842. assert modified["directMessage"] == false
  843. {:ok, activity} =
  844. CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"})
  845. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  846. assert modified["directMessage"] == false
  847. {:ok, activity} =
  848. CommonAPI.post(user, %{
  849. "status" => "@#{other_user.nickname} :moominmamma:",
  850. "visibility" => "direct"
  851. })
  852. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  853. assert modified["directMessage"] == true
  854. end
  855. test "it strips BCC field" do
  856. user = insert(:user)
  857. {:ok, list} = Pleroma.List.create("foo", user)
  858. {:ok, activity} =
  859. CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
  860. {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
  861. assert is_nil(modified["bcc"])
  862. end
  863. end
  864. describe "user upgrade" do
  865. test "it upgrades a user to activitypub" do
  866. user =
  867. insert(:user, %{
  868. nickname: "rye@niu.moe",
  869. local: false,
  870. ap_id: "https://niu.moe/users/rye",
  871. follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
  872. })
  873. user_two = insert(:user, %{following: [user.follower_address]})
  874. {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
  875. {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
  876. assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
  877. user = User.get_cached_by_id(user.id)
  878. assert user.info.note_count == 1
  879. {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
  880. ObanHelpers.perform_all()
  881. assert user.info.ap_enabled
  882. assert user.info.note_count == 1
  883. assert user.follower_address == "https://niu.moe/users/rye/followers"
  884. assert user.following_address == "https://niu.moe/users/rye/following"
  885. user = User.get_cached_by_id(user.id)
  886. assert user.info.note_count == 1
  887. activity = Activity.get_by_id(activity.id)
  888. assert user.follower_address in activity.recipients
  889. assert %{
  890. "url" => [
  891. %{
  892. "href" =>
  893. "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
  894. }
  895. ]
  896. } = user.avatar
  897. assert %{
  898. "url" => [
  899. %{
  900. "href" =>
  901. "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
  902. }
  903. ]
  904. } = user.info.banner
  905. refute "..." in activity.recipients
  906. unrelated_activity = Activity.get_by_id(unrelated_activity.id)
  907. refute user.follower_address in unrelated_activity.recipients
  908. user_two = User.get_cached_by_id(user_two.id)
  909. assert user.follower_address in user_two.following
  910. refute "..." in user_two.following
  911. end
  912. end
  913. describe "maybe_retire_websub" do
  914. test "it deletes all websub client subscripitions with the user as topic" do
  915. subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"}
  916. {:ok, ws} = Repo.insert(subscription)
  917. subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"}
  918. {:ok, ws2} = Repo.insert(subscription)
  919. Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye")
  920. refute Repo.get(WebsubClientSubscription, ws.id)
  921. assert Repo.get(WebsubClientSubscription, ws2.id)
  922. end
  923. end
  924. describe "actor rewriting" do
  925. test "it fixes the actor URL property to be a proper URI" do
  926. data = %{
  927. "url" => %{"href" => "http://example.com"}
  928. }
  929. rewritten = Transmogrifier.maybe_fix_user_object(data)
  930. assert rewritten["url"] == "http://example.com"
  931. end
  932. end
  933. describe "actor origin containment" do
  934. test "it rejects activities which reference objects with bogus origins" do
  935. data = %{
  936. "@context" => "https://www.w3.org/ns/activitystreams",
  937. "id" => "http://mastodon.example.org/users/admin/activities/1234",
  938. "actor" => "http://mastodon.example.org/users/admin",
  939. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  940. "object" => "https://info.pleroma.site/activity.json",
  941. "type" => "Announce"
  942. }
  943. :error = Transmogrifier.handle_incoming(data)
  944. end
  945. test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
  946. data = %{
  947. "@context" => "https://www.w3.org/ns/activitystreams",
  948. "id" => "http://mastodon.example.org/users/admin/activities/1234",
  949. "actor" => "http://mastodon.example.org/users/admin",
  950. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  951. "object" => "https://info.pleroma.site/activity2.json",
  952. "type" => "Announce"
  953. }
  954. :error = Transmogrifier.handle_incoming(data)
  955. end
  956. test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
  957. data = %{
  958. "@context" => "https://www.w3.org/ns/activitystreams",
  959. "id" => "http://mastodon.example.org/users/admin/activities/1234",
  960. "actor" => "http://mastodon.example.org/users/admin",
  961. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  962. "object" => "https://info.pleroma.site/activity3.json",
  963. "type" => "Announce"
  964. }
  965. :error = Transmogrifier.handle_incoming(data)
  966. end
  967. end
  968. describe "reserialization" do
  969. test "successfully reserializes a message with inReplyTo == nil" do
  970. user = insert(:user)
  971. message = %{
  972. "@context" => "https://www.w3.org/ns/activitystreams",
  973. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  974. "cc" => [],
  975. "type" => "Create",
  976. "object" => %{
  977. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  978. "cc" => [],
  979. "type" => "Note",
  980. "content" => "Hi",
  981. "inReplyTo" => nil,
  982. "attributedTo" => user.ap_id
  983. },
  984. "actor" => user.ap_id
  985. }
  986. {:ok, activity} = Transmogrifier.handle_incoming(message)
  987. {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
  988. end
  989. test "successfully reserializes a message with AS2 objects in IR" do
  990. user = insert(:user)
  991. message = %{
  992. "@context" => "https://www.w3.org/ns/activitystreams",
  993. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  994. "cc" => [],
  995. "type" => "Create",
  996. "object" => %{
  997. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  998. "cc" => [],
  999. "type" => "Note",
  1000. "content" => "Hi",
  1001. "inReplyTo" => nil,
  1002. "attributedTo" => user.ap_id,
  1003. "tag" => [
  1004. %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"},
  1005. %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"}
  1006. ]
  1007. },
  1008. "actor" => user.ap_id
  1009. }
  1010. {:ok, activity} = Transmogrifier.handle_incoming(message)
  1011. {:ok, _} = Transmogrifier.prepare_outgoing(activity.data)
  1012. end
  1013. end
  1014. test "Rewrites Answers to Notes" do
  1015. user = insert(:user)
  1016. {:ok, poll_activity} =
  1017. CommonAPI.post(user, %{
  1018. "status" => "suya...",
  1019. "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10}
  1020. })
  1021. poll_object = Object.normalize(poll_activity)
  1022. # TODO: Replace with CommonAPI vote creation when implemented
  1023. data =
  1024. File.read!("test/fixtures/mastodon-vote.json")
  1025. |> Poison.decode!()
  1026. |> Kernel.put_in(["to"], user.ap_id)
  1027. |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"])
  1028. |> Kernel.put_in(["object", "to"], user.ap_id)
  1029. {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
  1030. {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
  1031. assert data["object"]["type"] == "Note"
  1032. end
  1033. describe "fix_explicit_addressing" do
  1034. setup do
  1035. user = insert(:user)
  1036. [user: user]
  1037. end
  1038. test "moves non-explicitly mentioned actors to cc", %{user: user} do
  1039. explicitly_mentioned_actors = [
  1040. "https://pleroma.gold/users/user1",
  1041. "https://pleroma.gold/user2"
  1042. ]
  1043. object = %{
  1044. "actor" => user.ap_id,
  1045. "to" => explicitly_mentioned_actors ++ ["https://social.beepboop.ga/users/dirb"],
  1046. "cc" => [],
  1047. "tag" =>
  1048. Enum.map(explicitly_mentioned_actors, fn href ->
  1049. %{"type" => "Mention", "href" => href}
  1050. end)
  1051. }
  1052. fixed_object = Transmogrifier.fix_explicit_addressing(object)
  1053. assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
  1054. refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
  1055. assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
  1056. end
  1057. test "does not move actor's follower collection to cc", %{user: user} do
  1058. object = %{
  1059. "actor" => user.ap_id,
  1060. "to" => [user.follower_address],
  1061. "cc" => []
  1062. }
  1063. fixed_object = Transmogrifier.fix_explicit_addressing(object)
  1064. assert user.follower_address in fixed_object["to"]
  1065. refute user.follower_address in fixed_object["cc"]
  1066. end
  1067. test "removes recipient's follower collection from cc", %{user: user} do
  1068. recipient = insert(:user)
  1069. object = %{
  1070. "actor" => user.ap_id,
  1071. "to" => [recipient.ap_id, "https://www.w3.org/ns/activitystreams#Public"],
  1072. "cc" => [user.follower_address, recipient.follower_address]
  1073. }
  1074. fixed_object = Transmogrifier.fix_explicit_addressing(object)
  1075. assert user.follower_address in fixed_object["cc"]
  1076. refute recipient.follower_address in fixed_object["cc"]
  1077. refute recipient.follower_address in fixed_object["to"]
  1078. end
  1079. end
  1080. end