logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

activity_pub_test.exs (86734B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
  5. use Pleroma.DataCase
  6. use Oban.Testing, repo: Pleroma.Repo
  7. alias Pleroma.Activity
  8. alias Pleroma.Builders.ActivityBuilder
  9. alias Pleroma.Config
  10. alias Pleroma.Notification
  11. alias Pleroma.Object
  12. alias Pleroma.UnstubbedConfigMock, as: ConfigMock
  13. alias Pleroma.User
  14. alias Pleroma.Web.ActivityPub.ActivityPub
  15. alias Pleroma.Web.ActivityPub.Utils
  16. alias Pleroma.Web.AdminAPI.AccountView
  17. alias Pleroma.Web.CommonAPI
  18. import ExUnit.CaptureLog
  19. import Mock
  20. import Mox
  21. import Pleroma.Factory
  22. import Tesla.Mock
  23. setup do
  24. mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
  25. ConfigMock
  26. |> stub_with(Pleroma.Test.StaticConfig)
  27. :ok
  28. end
  29. setup do: clear_config([:instance, :federating])
  30. describe "streaming out participations" do
  31. test "it streams them out" do
  32. user = insert(:user)
  33. {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  34. {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
  35. participations =
  36. conversation.participations
  37. |> Repo.preload(:user)
  38. with_mock Pleroma.Web.Streamer,
  39. stream: fn _, _ -> nil end do
  40. ActivityPub.stream_out_participations(conversation.participations)
  41. assert called(Pleroma.Web.Streamer.stream("participation", participations))
  42. end
  43. end
  44. test "streams them out on activity creation" do
  45. user_one = insert(:user)
  46. user_two = insert(:user)
  47. with_mock Pleroma.Web.Streamer,
  48. stream: fn _, _ -> nil end do
  49. {:ok, activity} =
  50. CommonAPI.post(user_one, %{
  51. status: "@#{user_two.nickname}",
  52. visibility: "direct"
  53. })
  54. conversation =
  55. activity.data["context"]
  56. |> Pleroma.Conversation.get_for_ap_id()
  57. |> Repo.preload(participations: :user)
  58. assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations))
  59. end
  60. end
  61. end
  62. describe "fetching restricted by visibility" do
  63. test "it restricts by the appropriate visibility" do
  64. user = insert(:user)
  65. {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
  66. {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  67. {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
  68. {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
  69. activities = ActivityPub.fetch_activities([], %{visibility: "direct", actor_id: user.ap_id})
  70. assert activities == [direct_activity]
  71. activities =
  72. ActivityPub.fetch_activities([], %{visibility: "unlisted", actor_id: user.ap_id})
  73. assert activities == [unlisted_activity]
  74. activities =
  75. ActivityPub.fetch_activities([], %{visibility: "private", actor_id: user.ap_id})
  76. assert activities == [private_activity]
  77. activities = ActivityPub.fetch_activities([], %{visibility: "public", actor_id: user.ap_id})
  78. assert activities == [public_activity]
  79. activities =
  80. ActivityPub.fetch_activities([], %{
  81. visibility: ~w[private public],
  82. actor_id: user.ap_id
  83. })
  84. assert activities == [public_activity, private_activity]
  85. end
  86. end
  87. describe "fetching excluded by visibility" do
  88. test "it excludes by the appropriate visibility" do
  89. user = insert(:user)
  90. {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"})
  91. {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  92. {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
  93. {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"})
  94. activities =
  95. ActivityPub.fetch_activities([], %{
  96. exclude_visibilities: "direct",
  97. actor_id: user.ap_id
  98. })
  99. assert public_activity in activities
  100. assert unlisted_activity in activities
  101. assert private_activity in activities
  102. refute direct_activity in activities
  103. activities =
  104. ActivityPub.fetch_activities([], %{
  105. exclude_visibilities: "unlisted",
  106. actor_id: user.ap_id
  107. })
  108. assert public_activity in activities
  109. refute unlisted_activity in activities
  110. assert private_activity in activities
  111. assert direct_activity in activities
  112. activities =
  113. ActivityPub.fetch_activities([], %{
  114. exclude_visibilities: "private",
  115. actor_id: user.ap_id
  116. })
  117. assert public_activity in activities
  118. assert unlisted_activity in activities
  119. refute private_activity in activities
  120. assert direct_activity in activities
  121. activities =
  122. ActivityPub.fetch_activities([], %{
  123. exclude_visibilities: "public",
  124. actor_id: user.ap_id
  125. })
  126. refute public_activity in activities
  127. assert unlisted_activity in activities
  128. assert private_activity in activities
  129. assert direct_activity in activities
  130. end
  131. end
  132. describe "building a user from his ap id" do
  133. test "it returns a user" do
  134. user_id = "http://mastodon.example.org/users/admin"
  135. {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
  136. assert user.ap_id == user_id
  137. assert user.nickname == "admin@mastodon.example.org"
  138. assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
  139. end
  140. test "it returns a user that is invisible" do
  141. user_id = "http://mastodon.example.org/users/relay"
  142. {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
  143. assert User.invisible?(user)
  144. end
  145. test "it returns a user that accepts chat messages" do
  146. user_id = "http://mastodon.example.org/users/admin"
  147. {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
  148. assert user.accepts_chat_messages
  149. end
  150. test "works for guppe actors" do
  151. user_id = "https://gup.pe/u/bernie2020"
  152. Tesla.Mock.mock(fn
  153. %{method: :get, url: ^user_id} ->
  154. %Tesla.Env{
  155. status: 200,
  156. body: File.read!("test/fixtures/guppe-actor.json"),
  157. headers: [{"content-type", "application/activity+json"}]
  158. }
  159. end)
  160. {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
  161. assert user.name == "Bernie2020 group"
  162. assert user.actor_type == "Group"
  163. end
  164. test "works for bridgy actors" do
  165. user_id = "https://fed.brid.gy/jk.nipponalba.scot"
  166. Tesla.Mock.mock(fn
  167. %{method: :get, url: ^user_id} ->
  168. %Tesla.Env{
  169. status: 200,
  170. body: File.read!("test/fixtures/bridgy/actor.json"),
  171. headers: [{"content-type", "application/activity+json"}]
  172. }
  173. end)
  174. {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
  175. assert user.actor_type == "Person"
  176. assert user.avatar == %{
  177. "type" => "Image",
  178. "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
  179. }
  180. assert user.banner == %{
  181. "type" => "Image",
  182. "url" => [%{"href" => "https://jk.nipponalba.scot/images/profile.jpg"}]
  183. }
  184. end
  185. test "fetches user featured collection" do
  186. ap_id = "https://example.com/users/lain"
  187. featured_url = "https://example.com/users/lain/collections/featured"
  188. user_data =
  189. "test/fixtures/users_mock/user.json"
  190. |> File.read!()
  191. |> String.replace("{{nickname}}", "lain")
  192. |> Jason.decode!()
  193. |> Map.put("featured", featured_url)
  194. |> Jason.encode!()
  195. object_id = Ecto.UUID.generate()
  196. featured_data =
  197. "test/fixtures/mastodon/collections/featured.json"
  198. |> File.read!()
  199. |> String.replace("{{domain}}", "example.com")
  200. |> String.replace("{{nickname}}", "lain")
  201. |> String.replace("{{object_id}}", object_id)
  202. object_url = "https://example.com/objects/#{object_id}"
  203. object_data =
  204. "test/fixtures/statuses/note.json"
  205. |> File.read!()
  206. |> String.replace("{{object_id}}", object_id)
  207. |> String.replace("{{nickname}}", "lain")
  208. Tesla.Mock.mock(fn
  209. %{
  210. method: :get,
  211. url: ^ap_id
  212. } ->
  213. %Tesla.Env{
  214. status: 200,
  215. body: user_data,
  216. headers: [{"content-type", "application/activity+json"}]
  217. }
  218. %{
  219. method: :get,
  220. url: ^featured_url
  221. } ->
  222. %Tesla.Env{
  223. status: 200,
  224. body: featured_data,
  225. headers: [{"content-type", "application/activity+json"}]
  226. }
  227. end)
  228. Tesla.Mock.mock_global(fn
  229. %{
  230. method: :get,
  231. url: ^object_url
  232. } ->
  233. %Tesla.Env{
  234. status: 200,
  235. body: object_data,
  236. headers: [{"content-type", "application/activity+json"}]
  237. }
  238. end)
  239. {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
  240. Process.sleep(50)
  241. assert user.featured_address == featured_url
  242. assert Map.has_key?(user.pinned_objects, object_url)
  243. in_db = Pleroma.User.get_by_ap_id(ap_id)
  244. assert in_db.featured_address == featured_url
  245. assert Map.has_key?(user.pinned_objects, object_url)
  246. assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
  247. end
  248. test "fetches user featured collection without embedded object" do
  249. ap_id = "https://example.com/users/lain"
  250. featured_url = "https://example.com/users/lain/collections/featured"
  251. user_data =
  252. "test/fixtures/users_mock/user.json"
  253. |> File.read!()
  254. |> String.replace("{{nickname}}", "lain")
  255. |> Jason.decode!()
  256. |> Map.put("featured", featured_url)
  257. |> Jason.encode!()
  258. object_id = Ecto.UUID.generate()
  259. featured_data =
  260. "test/fixtures/mastodon/collections/external_featured.json"
  261. |> File.read!()
  262. |> String.replace("{{domain}}", "example.com")
  263. |> String.replace("{{nickname}}", "lain")
  264. |> String.replace("{{object_id}}", object_id)
  265. object_url = "https://example.com/objects/#{object_id}"
  266. object_data =
  267. "test/fixtures/statuses/note.json"
  268. |> File.read!()
  269. |> String.replace("{{object_id}}", object_id)
  270. |> String.replace("{{nickname}}", "lain")
  271. Tesla.Mock.mock(fn
  272. %{
  273. method: :get,
  274. url: ^ap_id
  275. } ->
  276. %Tesla.Env{
  277. status: 200,
  278. body: user_data,
  279. headers: [{"content-type", "application/activity+json"}]
  280. }
  281. %{
  282. method: :get,
  283. url: ^featured_url
  284. } ->
  285. %Tesla.Env{
  286. status: 200,
  287. body: featured_data,
  288. headers: [{"content-type", "application/activity+json"}]
  289. }
  290. end)
  291. Tesla.Mock.mock_global(fn
  292. %{
  293. method: :get,
  294. url: ^object_url
  295. } ->
  296. %Tesla.Env{
  297. status: 200,
  298. body: object_data,
  299. headers: [{"content-type", "application/activity+json"}]
  300. }
  301. end)
  302. {:ok, user} = ActivityPub.make_user_from_ap_id(ap_id)
  303. Process.sleep(50)
  304. assert user.featured_address == featured_url
  305. assert Map.has_key?(user.pinned_objects, object_url)
  306. in_db = Pleroma.User.get_by_ap_id(ap_id)
  307. assert in_db.featured_address == featured_url
  308. assert Map.has_key?(user.pinned_objects, object_url)
  309. assert %{data: %{"id" => ^object_url}} = Object.get_by_ap_id(object_url)
  310. end
  311. test "fetches user birthday information from misskey" do
  312. user_id = "https://misskey.io/@mkljczk"
  313. Tesla.Mock.mock(fn
  314. %{
  315. method: :get,
  316. url: ^user_id
  317. } ->
  318. %Tesla.Env{
  319. status: 200,
  320. body: File.read!("test/fixtures/birthdays/misskey-user.json"),
  321. headers: [{"content-type", "application/activity+json"}]
  322. }
  323. end)
  324. {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
  325. assert user.birthday == ~D[2001-02-12]
  326. end
  327. end
  328. test "it fetches the appropriate tag-restricted posts" do
  329. user = insert(:user)
  330. {:ok, status_one} = CommonAPI.post(user, %{status: ". #TEST"})
  331. {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"})
  332. {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #Reject"})
  333. {:ok, status_four} = CommonAPI.post(user, %{status: ". #Any1 #any2"})
  334. {:ok, status_five} = CommonAPI.post(user, %{status: ". #Any2 #any1"})
  335. for hashtag_timeline_strategy <- [:enabled, :disabled] do
  336. clear_config([:features, :improved_hashtag_timeline], hashtag_timeline_strategy)
  337. fetch_one = ActivityPub.fetch_activities([], %{type: "Create", tag: "test"})
  338. fetch_two = ActivityPub.fetch_activities([], %{type: "Create", tag: ["TEST", "essais"]})
  339. fetch_three =
  340. ActivityPub.fetch_activities([], %{
  341. type: "Create",
  342. tag: ["test", "Essais"],
  343. tag_reject: ["reject"]
  344. })
  345. fetch_four =
  346. ActivityPub.fetch_activities([], %{
  347. type: "Create",
  348. tag: ["test"],
  349. tag_all: ["test", "REJECT"]
  350. })
  351. # Testing that deduplication (if needed) is done on DB (not Ecto) level; :limit is important
  352. fetch_five =
  353. ActivityPub.fetch_activities([], %{
  354. type: "Create",
  355. tag: ["ANY1", "any2"],
  356. limit: 2
  357. })
  358. fetch_six =
  359. ActivityPub.fetch_activities([], %{
  360. type: "Create",
  361. tag: ["any1", "Any2"],
  362. tag_all: [],
  363. tag_reject: []
  364. })
  365. # Regression test: passing empty lists as filter options shouldn't affect the results
  366. assert fetch_five == fetch_six
  367. [fetch_one, fetch_two, fetch_three, fetch_four, fetch_five] =
  368. Enum.map([fetch_one, fetch_two, fetch_three, fetch_four, fetch_five], fn statuses ->
  369. Enum.map(statuses, fn s -> Repo.preload(s, object: :hashtags) end)
  370. end)
  371. assert fetch_one == [status_one, status_three]
  372. assert fetch_two == [status_one, status_two, status_three]
  373. assert fetch_three == [status_one, status_two]
  374. assert fetch_four == [status_three]
  375. assert fetch_five == [status_four, status_five]
  376. end
  377. end
  378. describe "insertion" do
  379. test "drops activities beyond a certain limit" do
  380. limit = Config.get([:instance, :remote_limit])
  381. random_text =
  382. :crypto.strong_rand_bytes(limit + 1)
  383. |> Base.encode64()
  384. |> binary_part(0, limit + 1)
  385. data = %{
  386. "ok" => true,
  387. "object" => %{
  388. "content" => random_text
  389. }
  390. }
  391. assert {:error, :remote_limit} = ActivityPub.insert(data)
  392. end
  393. test "doesn't drop activities with content being null" do
  394. user = insert(:user)
  395. data = %{
  396. "actor" => user.ap_id,
  397. "to" => [],
  398. "object" => %{
  399. "actor" => user.ap_id,
  400. "to" => [],
  401. "type" => "Note",
  402. "content" => nil
  403. }
  404. }
  405. assert {:ok, _} = ActivityPub.insert(data)
  406. end
  407. test "returns the activity if one with the same id is already in" do
  408. activity = insert(:note_activity)
  409. {:ok, new_activity} = ActivityPub.insert(activity.data)
  410. assert activity.id == new_activity.id
  411. end
  412. test "inserts a given map into the activity database, giving it an id if it has none." do
  413. user = insert(:user)
  414. data = %{
  415. "actor" => user.ap_id,
  416. "to" => [],
  417. "object" => %{
  418. "actor" => user.ap_id,
  419. "to" => [],
  420. "type" => "Note",
  421. "content" => "hey"
  422. }
  423. }
  424. {:ok, %Activity{} = activity} = ActivityPub.insert(data)
  425. assert activity.data["ok"] == data["ok"]
  426. assert is_binary(activity.data["id"])
  427. given_id = "bla"
  428. data = %{
  429. "id" => given_id,
  430. "actor" => user.ap_id,
  431. "to" => [],
  432. "context" => "blabla",
  433. "object" => %{
  434. "actor" => user.ap_id,
  435. "to" => [],
  436. "type" => "Note",
  437. "content" => "hey"
  438. }
  439. }
  440. {:ok, %Activity{} = activity} = ActivityPub.insert(data)
  441. assert activity.data["ok"] == data["ok"]
  442. assert activity.data["id"] == given_id
  443. assert activity.data["context"] == "blabla"
  444. end
  445. test "adds a context when none is there" do
  446. user = insert(:user)
  447. data = %{
  448. "actor" => user.ap_id,
  449. "to" => [],
  450. "object" => %{
  451. "actor" => user.ap_id,
  452. "to" => [],
  453. "type" => "Note",
  454. "content" => "hey"
  455. }
  456. }
  457. {:ok, %Activity{} = activity} = ActivityPub.insert(data)
  458. object = Pleroma.Object.normalize(activity, fetch: false)
  459. assert is_binary(activity.data["context"])
  460. assert is_binary(object.data["context"])
  461. end
  462. test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
  463. user = insert(:user)
  464. data = %{
  465. "actor" => user.ap_id,
  466. "to" => [],
  467. "object" => %{
  468. "actor" => user.ap_id,
  469. "to" => [],
  470. "type" => "Note",
  471. "content" => "hey"
  472. }
  473. }
  474. {:ok, %Activity{} = activity} = ActivityPub.insert(data)
  475. assert object = Object.normalize(activity, fetch: false)
  476. assert is_binary(object.data["id"])
  477. end
  478. end
  479. describe "listen activities" do
  480. test "does not increase user note count" do
  481. user = insert(:user)
  482. {:ok, activity} =
  483. ActivityPub.listen(%{
  484. to: ["https://www.w3.org/ns/activitystreams#Public"],
  485. actor: user,
  486. context: "",
  487. object: %{
  488. "actor" => user.ap_id,
  489. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  490. "artist" => "lain",
  491. "title" => "lain radio episode 1",
  492. "length" => 180_000,
  493. "type" => "Audio"
  494. }
  495. })
  496. assert activity.actor == user.ap_id
  497. user = User.get_cached_by_id(user.id)
  498. assert user.note_count == 0
  499. end
  500. test "can be fetched into a timeline" do
  501. _listen_activity_1 = insert(:listen)
  502. _listen_activity_2 = insert(:listen)
  503. _listen_activity_3 = insert(:listen)
  504. timeline = ActivityPub.fetch_activities([], %{type: ["Listen"]})
  505. assert length(timeline) == 3
  506. end
  507. end
  508. describe "create activities" do
  509. setup do
  510. [user: insert(:user)]
  511. end
  512. test "it reverts create", %{user: user} do
  513. with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
  514. assert {:error, :reverted} =
  515. ActivityPub.create(%{
  516. to: ["user1", "user2"],
  517. actor: user,
  518. context: "",
  519. object: %{
  520. "to" => ["user1", "user2"],
  521. "type" => "Note",
  522. "content" => "testing"
  523. }
  524. })
  525. end
  526. assert Repo.aggregate(Activity, :count, :id) == 0
  527. assert Repo.aggregate(Object, :count, :id) == 0
  528. end
  529. test "creates activity if expiration is not configured and expires_at is not passed", %{
  530. user: user
  531. } do
  532. clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false)
  533. assert {:ok, _} =
  534. ActivityPub.create(%{
  535. to: ["user1", "user2"],
  536. actor: user,
  537. context: "",
  538. object: %{
  539. "to" => ["user1", "user2"],
  540. "type" => "Note",
  541. "content" => "testing"
  542. }
  543. })
  544. end
  545. test "rejects activity if expires_at present but expiration is not configured", %{user: user} do
  546. clear_config([Pleroma.Workers.PurgeExpiredActivity, :enabled], false)
  547. assert {:error, :expired_activities_disabled} =
  548. ActivityPub.create(%{
  549. to: ["user1", "user2"],
  550. actor: user,
  551. context: "",
  552. object: %{
  553. "to" => ["user1", "user2"],
  554. "type" => "Note",
  555. "content" => "testing"
  556. },
  557. additional: %{
  558. "expires_at" => DateTime.utc_now()
  559. }
  560. })
  561. assert Repo.aggregate(Activity, :count, :id) == 0
  562. assert Repo.aggregate(Object, :count, :id) == 0
  563. end
  564. test "removes doubled 'to' recipients", %{user: user} do
  565. {:ok, activity} =
  566. ActivityPub.create(%{
  567. to: ["user1", "user1", "user2"],
  568. actor: user,
  569. context: "",
  570. object: %{
  571. "to" => ["user1", "user1", "user2"],
  572. "type" => "Note",
  573. "content" => "testing"
  574. }
  575. })
  576. assert activity.data["to"] == ["user1", "user2"]
  577. assert activity.actor == user.ap_id
  578. assert activity.recipients == ["user1", "user2", user.ap_id]
  579. end
  580. test "increases user note count only for public activities", %{user: user} do
  581. {:ok, _} =
  582. CommonAPI.post(User.get_cached_by_id(user.id), %{
  583. status: "1",
  584. visibility: "public"
  585. })
  586. {:ok, _} =
  587. CommonAPI.post(User.get_cached_by_id(user.id), %{
  588. status: "2",
  589. visibility: "unlisted"
  590. })
  591. {:ok, _} =
  592. CommonAPI.post(User.get_cached_by_id(user.id), %{
  593. status: "2",
  594. visibility: "private"
  595. })
  596. {:ok, _} =
  597. CommonAPI.post(User.get_cached_by_id(user.id), %{
  598. status: "3",
  599. visibility: "direct"
  600. })
  601. user = User.get_cached_by_id(user.id)
  602. assert user.note_count == 2
  603. end
  604. test "increases replies count", %{user: user} do
  605. user2 = insert(:user)
  606. {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"})
  607. ap_id = activity.data["id"]
  608. reply_data = %{status: "1", in_reply_to_status_id: activity.id}
  609. # public
  610. {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "public"))
  611. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  612. assert object.data["repliesCount"] == 1
  613. # unlisted
  614. {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "unlisted"))
  615. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  616. assert object.data["repliesCount"] == 2
  617. # private
  618. {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "private"))
  619. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  620. assert object.data["repliesCount"] == 2
  621. # direct
  622. {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "direct"))
  623. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  624. assert object.data["repliesCount"] == 2
  625. end
  626. test "increates quotes count", %{user: user} do
  627. user2 = insert(:user)
  628. {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"})
  629. ap_id = activity.data["id"]
  630. quote_data = %{status: "1", quote_id: activity.id}
  631. # public
  632. {:ok, _} = CommonAPI.post(user2, Map.put(quote_data, :visibility, "public"))
  633. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  634. assert object.data["quotesCount"] == 1
  635. # unlisted
  636. {:ok, _} = CommonAPI.post(user2, Map.put(quote_data, :visibility, "unlisted"))
  637. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  638. assert object.data["quotesCount"] == 2
  639. # private
  640. {:ok, _} = CommonAPI.post(user2, Map.put(quote_data, :visibility, "private"))
  641. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  642. assert object.data["quotesCount"] == 2
  643. # direct
  644. {:ok, _} = CommonAPI.post(user2, Map.put(quote_data, :visibility, "direct"))
  645. assert %{data: _data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
  646. assert object.data["quotesCount"] == 2
  647. end
  648. end
  649. describe "fetch activities for recipients" do
  650. test "retrieve the activities for certain recipients" do
  651. {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
  652. {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
  653. {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
  654. activities = ActivityPub.fetch_activities(["someone", "someone_else"])
  655. assert length(activities) == 2
  656. assert activities == [activity_one, activity_two]
  657. end
  658. end
  659. describe "fetch activities in context" do
  660. test "retrieves activities that have a given context" do
  661. {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
  662. {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
  663. {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
  664. {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
  665. activity_five = insert(:note_activity)
  666. user = insert(:user)
  667. {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
  668. activities = ActivityPub.fetch_activities_for_context("2hu", %{blocking_user: user})
  669. assert activities == [activity_two, activity]
  670. end
  671. test "doesn't return activities with filtered words" do
  672. user = insert(:user)
  673. user_two = insert(:user)
  674. insert(:filter, user: user, phrase: "test", hide: true)
  675. {:ok, %{id: id1, data: %{"context" => context}}} = CommonAPI.post(user, %{status: "1"})
  676. {:ok, %{id: id2}} = CommonAPI.post(user_two, %{status: "2", in_reply_to_status_id: id1})
  677. {:ok, %{id: id3} = user_activity} =
  678. CommonAPI.post(user, %{status: "3 test?", in_reply_to_status_id: id2})
  679. {:ok, %{id: id4} = filtered_activity} =
  680. CommonAPI.post(user_two, %{status: "4 test!", in_reply_to_status_id: id3})
  681. {:ok, _} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
  682. activities =
  683. context
  684. |> ActivityPub.fetch_activities_for_context(%{user: user})
  685. |> Enum.map(& &1.id)
  686. assert length(activities) == 4
  687. assert user_activity.id in activities
  688. refute filtered_activity.id in activities
  689. end
  690. end
  691. test "doesn't return blocked activities" do
  692. activity_one = insert(:note_activity)
  693. activity_two = insert(:note_activity)
  694. activity_three = insert(:note_activity)
  695. user = insert(:user)
  696. booster = insert(:user)
  697. {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
  698. activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
  699. assert Enum.member?(activities, activity_two)
  700. assert Enum.member?(activities, activity_three)
  701. refute Enum.member?(activities, activity_one)
  702. {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
  703. activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
  704. assert Enum.member?(activities, activity_two)
  705. assert Enum.member?(activities, activity_three)
  706. assert Enum.member?(activities, activity_one)
  707. {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
  708. {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
  709. %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
  710. activity_three = Activity.get_by_id(activity_three.id)
  711. activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
  712. assert Enum.member?(activities, activity_two)
  713. refute Enum.member?(activities, activity_three)
  714. refute Enum.member?(activities, boost_activity)
  715. assert Enum.member?(activities, activity_one)
  716. activities = ActivityPub.fetch_activities([], %{blocking_user: nil, skip_preload: true})
  717. assert Enum.member?(activities, activity_two)
  718. assert Enum.member?(activities, activity_three)
  719. assert Enum.member?(activities, boost_activity)
  720. assert Enum.member?(activities, activity_one)
  721. end
  722. test "doesn't return activities from deactivated users" do
  723. _user = insert(:user)
  724. deactivated = insert(:user)
  725. active = insert(:user)
  726. {:ok, activity_one} = CommonAPI.post(deactivated, %{status: "hey!"})
  727. {:ok, activity_two} = CommonAPI.post(active, %{status: "yay!"})
  728. {:ok, _updated_user} = User.set_activation(deactivated, false)
  729. activities = ActivityPub.fetch_activities([], %{})
  730. refute Enum.member?(activities, activity_one)
  731. assert Enum.member?(activities, activity_two)
  732. end
  733. test "always see your own posts even when they address people you block" do
  734. user = insert(:user)
  735. blockee = insert(:user)
  736. {:ok, _} = User.block(user, blockee)
  737. {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{blockee.nickname}"})
  738. activities = ActivityPub.fetch_activities([], %{blocking_user: user})
  739. assert Enum.member?(activities, activity)
  740. end
  741. test "doesn't return transitive interactions concerning blocked users" do
  742. blocker = insert(:user)
  743. blockee = insert(:user)
  744. friend = insert(:user)
  745. {:ok, _user_relationship} = User.block(blocker, blockee)
  746. {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
  747. {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
  748. {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
  749. {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
  750. activities = ActivityPub.fetch_activities([], %{blocking_user: blocker})
  751. assert Enum.member?(activities, activity_one)
  752. refute Enum.member?(activities, activity_two)
  753. refute Enum.member?(activities, activity_three)
  754. refute Enum.member?(activities, activity_four)
  755. end
  756. test "doesn't return announce activities with blocked users in 'to'" do
  757. blocker = insert(:user)
  758. blockee = insert(:user)
  759. friend = insert(:user)
  760. {:ok, _user_relationship} = User.block(blocker, blockee)
  761. {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
  762. {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
  763. {:ok, activity_three} = CommonAPI.repeat(activity_two.id, friend)
  764. activities =
  765. ActivityPub.fetch_activities([], %{blocking_user: blocker})
  766. |> Enum.map(fn act -> act.id end)
  767. assert Enum.member?(activities, activity_one.id)
  768. refute Enum.member?(activities, activity_two.id)
  769. refute Enum.member?(activities, activity_three.id)
  770. end
  771. test "doesn't return announce activities with blocked users in 'cc'" do
  772. blocker = insert(:user)
  773. blockee = insert(:user)
  774. friend = insert(:user)
  775. {:ok, _user_relationship} = User.block(blocker, blockee)
  776. {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"})
  777. {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
  778. assert object = Pleroma.Object.normalize(activity_two, fetch: false)
  779. data = %{
  780. "actor" => friend.ap_id,
  781. "object" => object.data["id"],
  782. "context" => object.data["context"],
  783. "type" => "Announce",
  784. "to" => ["https://www.w3.org/ns/activitystreams#Public"],
  785. "cc" => [blockee.ap_id]
  786. }
  787. assert {:ok, activity_three} = ActivityPub.insert(data)
  788. activities =
  789. ActivityPub.fetch_activities([], %{blocking_user: blocker})
  790. |> Enum.map(fn act -> act.id end)
  791. assert Enum.member?(activities, activity_one.id)
  792. refute Enum.member?(activities, activity_two.id)
  793. refute Enum.member?(activities, activity_three.id)
  794. end
  795. test "doesn't return activities from blocked domains" do
  796. domain = "dogwhistle.zone"
  797. domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
  798. note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
  799. activity = insert(:note_activity, %{note: note})
  800. user = insert(:user)
  801. {:ok, user} = User.block_domain(user, domain)
  802. activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
  803. refute activity in activities
  804. followed_user = insert(:user)
  805. CommonAPI.follow(user, followed_user)
  806. {:ok, repeat_activity} = CommonAPI.repeat(activity.id, followed_user)
  807. activities = ActivityPub.fetch_activities([], %{blocking_user: user, skip_preload: true})
  808. refute repeat_activity in activities
  809. end
  810. test "see your own posts even when they address actors from blocked domains" do
  811. user = insert(:user)
  812. domain = "dogwhistle.zone"
  813. domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
  814. {:ok, user} = User.block_domain(user, domain)
  815. {:ok, activity} = CommonAPI.post(user, %{status: "hey! @#{domain_user.nickname}"})
  816. activities = ActivityPub.fetch_activities([], %{blocking_user: user})
  817. assert Enum.member?(activities, activity)
  818. end
  819. test "does return activities from followed users on blocked domains" do
  820. domain = "meanies.social"
  821. domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
  822. blocker = insert(:user)
  823. {:ok, blocker, domain_user} = User.follow(blocker, domain_user)
  824. {:ok, blocker} = User.block_domain(blocker, domain)
  825. assert User.following?(blocker, domain_user)
  826. assert User.blocks_domain?(blocker, domain_user)
  827. refute User.blocks?(blocker, domain_user)
  828. note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
  829. activity = insert(:note_activity, %{note: note})
  830. activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
  831. assert activity in activities
  832. # And check that if the guy we DO follow boosts someone else from their domain,
  833. # that should be hidden
  834. another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"})
  835. bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}})
  836. bad_activity = insert(:note_activity, %{note: bad_note})
  837. {:ok, repeat_activity} = CommonAPI.repeat(bad_activity.id, domain_user)
  838. activities = ActivityPub.fetch_activities([], %{blocking_user: blocker, skip_preload: true})
  839. refute repeat_activity in activities
  840. end
  841. test "returns your own posts regardless of mute" do
  842. user = insert(:user)
  843. muted = insert(:user)
  844. {:ok, muted_post} = CommonAPI.post(muted, %{status: "Im stupid"})
  845. {:ok, reply} =
  846. CommonAPI.post(user, %{status: "I'm muting you", in_reply_to_status_id: muted_post.id})
  847. {:ok, _} = User.mute(user, muted)
  848. [activity] = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
  849. assert activity.id == reply.id
  850. end
  851. test "doesn't return muted activities" do
  852. activity_one = insert(:note_activity)
  853. activity_two = insert(:note_activity)
  854. activity_three = insert(:note_activity)
  855. user = insert(:user)
  856. booster = insert(:user)
  857. activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
  858. {:ok, _user_relationships} = User.mute(user, activity_one_actor)
  859. activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
  860. assert Enum.member?(activities, activity_two)
  861. assert Enum.member?(activities, activity_three)
  862. refute Enum.member?(activities, activity_one)
  863. # Calling with 'with_muted' will deliver muted activities, too.
  864. activities =
  865. ActivityPub.fetch_activities([], %{
  866. muting_user: user,
  867. with_muted: true,
  868. skip_preload: true
  869. })
  870. assert Enum.member?(activities, activity_two)
  871. assert Enum.member?(activities, activity_three)
  872. assert Enum.member?(activities, activity_one)
  873. {:ok, _user_mute} = User.unmute(user, activity_one_actor)
  874. activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
  875. assert Enum.member?(activities, activity_two)
  876. assert Enum.member?(activities, activity_three)
  877. assert Enum.member?(activities, activity_one)
  878. activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
  879. {:ok, _user_relationships} = User.mute(user, activity_three_actor)
  880. {:ok, %{data: %{"object" => id}}} = CommonAPI.repeat(activity_three.id, booster)
  881. %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
  882. activity_three = Activity.get_by_id(activity_three.id)
  883. activities = ActivityPub.fetch_activities([], %{muting_user: user, skip_preload: true})
  884. assert Enum.member?(activities, activity_two)
  885. refute Enum.member?(activities, activity_three)
  886. refute Enum.member?(activities, boost_activity)
  887. assert Enum.member?(activities, activity_one)
  888. activities = ActivityPub.fetch_activities([], %{muting_user: nil, skip_preload: true})
  889. assert Enum.member?(activities, activity_two)
  890. assert Enum.member?(activities, activity_three)
  891. assert Enum.member?(activities, boost_activity)
  892. assert Enum.member?(activities, activity_one)
  893. end
  894. test "doesn't return thread muted activities" do
  895. user = insert(:user)
  896. _activity_one = insert(:note_activity)
  897. note_two = insert(:note, data: %{"context" => "suya.."})
  898. activity_two = insert(:note_activity, note: note_two)
  899. {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
  900. assert [_activity_one] = ActivityPub.fetch_activities([], %{muting_user: user})
  901. end
  902. test "returns thread muted activities when with_muted is set" do
  903. user = insert(:user)
  904. _activity_one = insert(:note_activity)
  905. note_two = insert(:note, data: %{"context" => "suya.."})
  906. activity_two = insert(:note_activity, note: note_two)
  907. {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
  908. assert [_activity_two, _activity_one] =
  909. ActivityPub.fetch_activities([], %{muting_user: user, with_muted: true})
  910. end
  911. test "does include announces on request" do
  912. activity_three = insert(:note_activity)
  913. user = insert(:user)
  914. booster = insert(:user)
  915. {:ok, user, booster} = User.follow(user, booster)
  916. {:ok, announce} = CommonAPI.repeat(activity_three.id, booster)
  917. [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])
  918. assert announce_activity.id == announce.id
  919. end
  920. test "excludes reblogs on request" do
  921. user = insert(:user)
  922. {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
  923. {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
  924. [activity] = ActivityPub.fetch_user_activities(user, nil, %{exclude_reblogs: true})
  925. assert activity == expected_activity
  926. end
  927. describe "irreversible filters" do
  928. setup do
  929. user = insert(:user)
  930. user_two = insert(:user)
  931. insert(:filter, user: user_two, phrase: "cofe", hide: true)
  932. insert(:filter, user: user_two, phrase: "ok boomer", hide: true)
  933. insert(:filter, user: user_two, phrase: "test", hide: false)
  934. params = %{
  935. type: ["Create", "Announce"],
  936. user: user_two
  937. }
  938. {:ok, %{user: user, user_two: user_two, params: params}}
  939. end
  940. test "it returns statuses if they don't contain exact filter words", %{
  941. user: user,
  942. params: params
  943. } do
  944. {:ok, _} = CommonAPI.post(user, %{status: "hey"})
  945. {:ok, _} = CommonAPI.post(user, %{status: "got cofefe?"})
  946. {:ok, _} = CommonAPI.post(user, %{status: "I am not a boomer"})
  947. {:ok, _} = CommonAPI.post(user, %{status: "ok boomers"})
  948. {:ok, _} = CommonAPI.post(user, %{status: "ccofee is not a word"})
  949. {:ok, _} = CommonAPI.post(user, %{status: "this is a test"})
  950. activities = ActivityPub.fetch_activities([], params)
  951. assert Enum.count(activities) == 6
  952. end
  953. test "it does not filter user's own statuses", %{user_two: user_two, params: params} do
  954. {:ok, _} = CommonAPI.post(user_two, %{status: "Give me some cofe!"})
  955. {:ok, _} = CommonAPI.post(user_two, %{status: "ok boomer"})
  956. activities = ActivityPub.fetch_activities([], params)
  957. assert Enum.count(activities) == 2
  958. end
  959. test "it excludes statuses with filter words", %{user: user, params: params} do
  960. {:ok, _} = CommonAPI.post(user, %{status: "Give me some cofe!"})
  961. {:ok, _} = CommonAPI.post(user, %{status: "ok boomer"})
  962. {:ok, _} = CommonAPI.post(user, %{status: "is it a cOfE?"})
  963. {:ok, _} = CommonAPI.post(user, %{status: "cofe is all I need"})
  964. {:ok, _} = CommonAPI.post(user, %{status: "— ok BOOMER\n"})
  965. activities = ActivityPub.fetch_activities([], params)
  966. assert Enum.empty?(activities)
  967. end
  968. test "it returns all statuses if user does not have any filters" do
  969. another_user = insert(:user)
  970. {:ok, _} = CommonAPI.post(another_user, %{status: "got cofe?"})
  971. {:ok, _} = CommonAPI.post(another_user, %{status: "test!"})
  972. activities =
  973. ActivityPub.fetch_activities([], %{
  974. type: ["Create", "Announce"],
  975. user: another_user
  976. })
  977. assert Enum.count(activities) == 2
  978. end
  979. end
  980. describe "public fetch activities" do
  981. test "doesn't retrieve unlisted activities" do
  982. user = insert(:user)
  983. {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"})
  984. {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"})
  985. [activity] = ActivityPub.fetch_public_activities()
  986. assert activity == listed_activity
  987. end
  988. test "retrieves public activities" do
  989. _activities = ActivityPub.fetch_public_activities()
  990. %{public: public} = ActivityBuilder.public_and_non_public()
  991. activities = ActivityPub.fetch_public_activities()
  992. assert length(activities) == 1
  993. assert Enum.at(activities, 0) == public
  994. end
  995. test "retrieves a maximum of 20 activities" do
  996. ActivityBuilder.insert_list(10)
  997. expected_activities = ActivityBuilder.insert_list(20)
  998. activities = ActivityPub.fetch_public_activities()
  999. assert collect_ids(activities) == collect_ids(expected_activities)
  1000. assert length(activities) == 20
  1001. end
  1002. test "retrieves ids starting from a since_id" do
  1003. activities = ActivityBuilder.insert_list(30)
  1004. expected_activities = ActivityBuilder.insert_list(10)
  1005. since_id = List.last(activities).id
  1006. activities = ActivityPub.fetch_public_activities(%{since_id: since_id})
  1007. assert collect_ids(activities) == collect_ids(expected_activities)
  1008. assert length(activities) == 10
  1009. end
  1010. test "retrieves ids up to max_id" do
  1011. ActivityBuilder.insert_list(10)
  1012. expected_activities = ActivityBuilder.insert_list(20)
  1013. %{id: max_id} =
  1014. 10
  1015. |> ActivityBuilder.insert_list()
  1016. |> List.first()
  1017. activities = ActivityPub.fetch_public_activities(%{max_id: max_id})
  1018. assert length(activities) == 20
  1019. assert collect_ids(activities) == collect_ids(expected_activities)
  1020. end
  1021. test "paginates via offset/limit" do
  1022. _first_part_activities = ActivityBuilder.insert_list(10)
  1023. second_part_activities = ActivityBuilder.insert_list(10)
  1024. later_activities = ActivityBuilder.insert_list(10)
  1025. activities = ActivityPub.fetch_public_activities(%{page: "2", page_size: "20"}, :offset)
  1026. assert length(activities) == 20
  1027. assert collect_ids(activities) ==
  1028. collect_ids(second_part_activities) ++ collect_ids(later_activities)
  1029. end
  1030. test "doesn't return reblogs for users for whom reblogs have been muted" do
  1031. activity = insert(:note_activity)
  1032. user = insert(:user)
  1033. booster = insert(:user)
  1034. {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
  1035. {:ok, activity} = CommonAPI.repeat(activity.id, booster)
  1036. activities = ActivityPub.fetch_activities([], %{muting_user: user})
  1037. refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
  1038. end
  1039. test "returns reblogs for users for whom reblogs have not been muted" do
  1040. activity = insert(:note_activity)
  1041. user = insert(:user)
  1042. booster = insert(:user)
  1043. {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
  1044. {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
  1045. {:ok, activity} = CommonAPI.repeat(activity.id, booster)
  1046. activities = ActivityPub.fetch_activities([], %{muting_user: user})
  1047. assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
  1048. end
  1049. end
  1050. describe "uploading files" do
  1051. setup do
  1052. test_file = %Plug.Upload{
  1053. content_type: "image/jpeg",
  1054. path: Path.absname("test/fixtures/image.jpg"),
  1055. filename: "an_image.jpg"
  1056. }
  1057. %{test_file: test_file}
  1058. end
  1059. test "strips / from filename", %{test_file: file} do
  1060. file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"}
  1061. {:ok, %Object{} = object} = ActivityPub.upload(file)
  1062. [%{"href" => href}] = object.data["url"]
  1063. assert Regex.match?(~r"/bad.jpg$", href)
  1064. refute Regex.match?(~r"/nested/", href)
  1065. end
  1066. test "sets a description if given", %{test_file: file} do
  1067. {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file")
  1068. assert object.data["name"] == "a cool file"
  1069. end
  1070. test "it sets the default description depending on the configuration", %{test_file: file} do
  1071. clear_config([Pleroma.Upload, :default_description])
  1072. clear_config([Pleroma.Upload, :default_description], nil)
  1073. {:ok, %Object{} = object} = ActivityPub.upload(file)
  1074. assert object.data["name"] == ""
  1075. clear_config([Pleroma.Upload, :default_description], :filename)
  1076. {:ok, %Object{} = object} = ActivityPub.upload(file)
  1077. assert object.data["name"] == "an_image.jpg"
  1078. clear_config([Pleroma.Upload, :default_description], "unnamed attachment")
  1079. {:ok, %Object{} = object} = ActivityPub.upload(file)
  1080. assert object.data["name"] == "unnamed attachment"
  1081. end
  1082. test "copies the file to the configured folder", %{test_file: file} do
  1083. clear_config([Pleroma.Upload, :default_description], :filename)
  1084. {:ok, %Object{} = object} = ActivityPub.upload(file)
  1085. assert object.data["name"] == "an_image.jpg"
  1086. end
  1087. test "works with base64 encoded images" do
  1088. file = %{
  1089. img: data_uri()
  1090. }
  1091. {:ok, %Object{}} = ActivityPub.upload(file)
  1092. end
  1093. end
  1094. describe "fetch the latest Follow" do
  1095. test "fetches the latest Follow activity" do
  1096. %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
  1097. follower = Repo.get_by(User, ap_id: activity.data["actor"])
  1098. followed = Repo.get_by(User, ap_id: activity.data["object"])
  1099. assert activity == Utils.fetch_latest_follow(follower, followed)
  1100. end
  1101. end
  1102. describe "unfollowing" do
  1103. test "it reverts unfollow activity" do
  1104. follower = insert(:user)
  1105. followed = insert(:user)
  1106. {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
  1107. with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
  1108. assert {:error, :reverted} = ActivityPub.unfollow(follower, followed)
  1109. end
  1110. activity = Activity.get_by_id(follow_activity.id)
  1111. assert activity.data["type"] == "Follow"
  1112. assert activity.data["actor"] == follower.ap_id
  1113. assert activity.data["object"] == followed.ap_id
  1114. end
  1115. test "creates an undo activity for the last follow" do
  1116. follower = insert(:user)
  1117. followed = insert(:user)
  1118. {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
  1119. {:ok, activity} = ActivityPub.unfollow(follower, followed)
  1120. assert activity.data["type"] == "Undo"
  1121. assert activity.data["actor"] == follower.ap_id
  1122. embedded_object = activity.data["object"]
  1123. assert is_map(embedded_object)
  1124. assert embedded_object["type"] == "Follow"
  1125. assert embedded_object["object"] == followed.ap_id
  1126. assert embedded_object["id"] == follow_activity.data["id"]
  1127. end
  1128. test "creates an undo activity for a pending follow request" do
  1129. follower = insert(:user)
  1130. followed = insert(:user, %{is_locked: true})
  1131. {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed)
  1132. {:ok, activity} = ActivityPub.unfollow(follower, followed)
  1133. assert activity.data["type"] == "Undo"
  1134. assert activity.data["actor"] == follower.ap_id
  1135. embedded_object = activity.data["object"]
  1136. assert is_map(embedded_object)
  1137. assert embedded_object["type"] == "Follow"
  1138. assert embedded_object["object"] == followed.ap_id
  1139. assert embedded_object["id"] == follow_activity.data["id"]
  1140. end
  1141. end
  1142. describe "timeline post-processing" do
  1143. test "it filters broken threads" do
  1144. user1 = insert(:user)
  1145. user2 = insert(:user)
  1146. user3 = insert(:user)
  1147. {:ok, user1, user3} = User.follow(user1, user3)
  1148. assert User.following?(user1, user3)
  1149. {:ok, user2, user3} = User.follow(user2, user3)
  1150. assert User.following?(user2, user3)
  1151. {:ok, user3, user2} = User.follow(user3, user2)
  1152. assert User.following?(user3, user2)
  1153. {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"})
  1154. {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"})
  1155. {:ok, private_activity_2} =
  1156. CommonAPI.post(user2, %{
  1157. status: "hi 3",
  1158. visibility: "private",
  1159. in_reply_to_status_id: private_activity_1.id
  1160. })
  1161. {:ok, private_activity_3} =
  1162. CommonAPI.post(user3, %{
  1163. status: "hi 4",
  1164. visibility: "private",
  1165. in_reply_to_status_id: private_activity_2.id
  1166. })
  1167. activities =
  1168. ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])
  1169. |> Enum.map(fn a -> a.id end)
  1170. private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
  1171. assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
  1172. assert length(activities) == 3
  1173. activities =
  1174. ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{user: user1})
  1175. |> Enum.map(fn a -> a.id end)
  1176. assert [public_activity.id, private_activity_1.id] == activities
  1177. assert length(activities) == 2
  1178. end
  1179. end
  1180. describe "flag/1" do
  1181. setup do
  1182. reporter = insert(:user)
  1183. target_account = insert(:user)
  1184. content = "foobar"
  1185. {:ok, activity} = CommonAPI.post(target_account, %{status: content})
  1186. context = Utils.generate_context_id()
  1187. reporter_ap_id = reporter.ap_id
  1188. target_ap_id = target_account.ap_id
  1189. activity_ap_id = activity.data["id"]
  1190. object_ap_id = activity.object.data["id"]
  1191. activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id)
  1192. {:ok,
  1193. %{
  1194. reporter: reporter,
  1195. context: context,
  1196. target_account: target_account,
  1197. reported_activity: activity,
  1198. content: content,
  1199. activity_ap_id: activity_ap_id,
  1200. object_ap_id: object_ap_id,
  1201. activity_with_object: activity_with_object,
  1202. reporter_ap_id: reporter_ap_id,
  1203. target_ap_id: target_ap_id
  1204. }}
  1205. end
  1206. test "it can create a Flag activity",
  1207. %{
  1208. reporter: reporter,
  1209. context: context,
  1210. target_account: target_account,
  1211. reported_activity: reported_activity,
  1212. content: content,
  1213. object_ap_id: object_ap_id,
  1214. activity_with_object: activity_with_object,
  1215. reporter_ap_id: reporter_ap_id,
  1216. target_ap_id: target_ap_id
  1217. } do
  1218. assert {:ok, activity} =
  1219. ActivityPub.flag(%{
  1220. actor: reporter,
  1221. context: context,
  1222. account: target_account,
  1223. statuses: [reported_activity],
  1224. content: content
  1225. })
  1226. note_obj = %{
  1227. "type" => "Note",
  1228. "id" => object_ap_id,
  1229. "content" => content,
  1230. "published" => activity_with_object.object.data["published"],
  1231. "actor" =>
  1232. AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
  1233. }
  1234. assert %Activity{
  1235. actor: ^reporter_ap_id,
  1236. data: %{
  1237. "type" => "Flag",
  1238. "content" => ^content,
  1239. "context" => ^context,
  1240. "object" => [^target_ap_id, ^note_obj]
  1241. }
  1242. } = activity
  1243. end
  1244. test_with_mock "strips status data from Flag, before federating it",
  1245. %{
  1246. reporter: reporter,
  1247. context: context,
  1248. target_account: target_account,
  1249. reported_activity: reported_activity,
  1250. object_ap_id: object_ap_id,
  1251. content: content
  1252. },
  1253. Utils,
  1254. [:passthrough],
  1255. [] do
  1256. {:ok, activity} =
  1257. ActivityPub.flag(%{
  1258. actor: reporter,
  1259. context: context,
  1260. account: target_account,
  1261. statuses: [reported_activity],
  1262. content: content
  1263. })
  1264. new_data = put_in(activity.data, ["object"], [target_account.ap_id, object_ap_id])
  1265. assert_called(Utils.maybe_federate(%{activity | data: new_data}))
  1266. end
  1267. test_with_mock "reverts on error",
  1268. %{
  1269. reporter: reporter,
  1270. context: context,
  1271. target_account: target_account,
  1272. reported_activity: reported_activity,
  1273. content: content
  1274. },
  1275. Utils,
  1276. [:passthrough],
  1277. maybe_federate: fn _ -> {:error, :reverted} end do
  1278. assert {:error, :reverted} =
  1279. ActivityPub.flag(%{
  1280. actor: reporter,
  1281. context: context,
  1282. account: target_account,
  1283. statuses: [reported_activity],
  1284. content: content
  1285. })
  1286. assert Repo.aggregate(Activity, :count, :id) == 1
  1287. assert Repo.aggregate(Object, :count, :id) == 1
  1288. assert Repo.aggregate(Notification, :count, :id) == 0
  1289. end
  1290. end
  1291. test "fetch_activities/2 returns activities addressed to a list " do
  1292. user = insert(:user)
  1293. member = insert(:user)
  1294. {:ok, list} = Pleroma.List.create("foo", user)
  1295. {:ok, list} = Pleroma.List.follow(list, member)
  1296. {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
  1297. activity = Repo.preload(activity, :bookmark)
  1298. activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
  1299. assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
  1300. end
  1301. def data_uri do
  1302. File.read!("test/fixtures/avatar_data_uri")
  1303. end
  1304. describe "fetch_activities_bounded" do
  1305. test "fetches private posts for followed users" do
  1306. user = insert(:user)
  1307. {:ok, activity} =
  1308. CommonAPI.post(user, %{
  1309. status: "thought I looked cute might delete later :3",
  1310. visibility: "private"
  1311. })
  1312. [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
  1313. assert result.id == activity.id
  1314. end
  1315. test "fetches only public posts for other users" do
  1316. user = insert(:user)
  1317. {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"})
  1318. {:ok, _private_activity} =
  1319. CommonAPI.post(user, %{
  1320. status: "why is tenshi eating a corndog so cute?",
  1321. visibility: "private"
  1322. })
  1323. [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
  1324. assert result.id == activity.id
  1325. end
  1326. end
  1327. describe "fetch_follow_information_for_user" do
  1328. test "synchronizes following/followers counters" do
  1329. user =
  1330. insert(:user,
  1331. local: false,
  1332. follower_address: "http://localhost:4001/users/fuser2/followers",
  1333. following_address: "http://localhost:4001/users/fuser2/following"
  1334. )
  1335. {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
  1336. assert info.follower_count == 527
  1337. assert info.following_count == 267
  1338. end
  1339. test "detects hidden followers" do
  1340. mock(fn env ->
  1341. case env.url do
  1342. "http://localhost:4001/users/masto_closed/followers?page=1" ->
  1343. %Tesla.Env{status: 403, body: ""}
  1344. _ ->
  1345. apply(HttpRequestMock, :request, [env])
  1346. end
  1347. end)
  1348. user =
  1349. insert(:user,
  1350. local: false,
  1351. follower_address: "http://localhost:4001/users/masto_closed/followers",
  1352. following_address: "http://localhost:4001/users/masto_closed/following"
  1353. )
  1354. {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
  1355. assert follow_info.hide_followers == true
  1356. assert follow_info.hide_follows == false
  1357. end
  1358. test "detects hidden follows" do
  1359. mock(fn env ->
  1360. case env.url do
  1361. "http://localhost:4001/users/masto_closed/following?page=1" ->
  1362. %Tesla.Env{status: 403, body: ""}
  1363. _ ->
  1364. apply(HttpRequestMock, :request, [env])
  1365. end
  1366. end)
  1367. user =
  1368. insert(:user,
  1369. local: false,
  1370. follower_address: "http://localhost:4001/users/masto_closed/followers",
  1371. following_address: "http://localhost:4001/users/masto_closed/following"
  1372. )
  1373. {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
  1374. assert follow_info.hide_followers == false
  1375. assert follow_info.hide_follows == true
  1376. end
  1377. test "detects hidden follows/followers for friendica" do
  1378. user =
  1379. insert(:user,
  1380. local: false,
  1381. follower_address: "http://localhost:8080/followers/fuser3",
  1382. following_address: "http://localhost:8080/following/fuser3"
  1383. )
  1384. {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
  1385. assert follow_info.hide_followers == true
  1386. assert follow_info.follower_count == 296
  1387. assert follow_info.following_count == 32
  1388. assert follow_info.hide_follows == true
  1389. end
  1390. test "doesn't crash when follower and following counters are hidden" do
  1391. mock(fn env ->
  1392. case env.url do
  1393. "http://localhost:4001/users/masto_hidden_counters/following" ->
  1394. json(
  1395. %{
  1396. "@context" => "https://www.w3.org/ns/activitystreams",
  1397. "id" => "http://localhost:4001/users/masto_hidden_counters/followers"
  1398. },
  1399. headers: HttpRequestMock.activitypub_object_headers()
  1400. )
  1401. "http://localhost:4001/users/masto_hidden_counters/following?page=1" ->
  1402. %Tesla.Env{status: 403, body: ""}
  1403. "http://localhost:4001/users/masto_hidden_counters/followers" ->
  1404. json(
  1405. %{
  1406. "@context" => "https://www.w3.org/ns/activitystreams",
  1407. "id" => "http://localhost:4001/users/masto_hidden_counters/following"
  1408. },
  1409. headers: HttpRequestMock.activitypub_object_headers()
  1410. )
  1411. "http://localhost:4001/users/masto_hidden_counters/followers?page=1" ->
  1412. %Tesla.Env{status: 403, body: ""}
  1413. end
  1414. end)
  1415. user =
  1416. insert(:user,
  1417. local: false,
  1418. follower_address: "http://localhost:4001/users/masto_hidden_counters/followers",
  1419. following_address: "http://localhost:4001/users/masto_hidden_counters/following"
  1420. )
  1421. {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
  1422. assert follow_info.hide_followers == true
  1423. assert follow_info.follower_count == 0
  1424. assert follow_info.hide_follows == true
  1425. assert follow_info.following_count == 0
  1426. end
  1427. end
  1428. describe "fetch_favourites/3" do
  1429. test "returns a favourite activities sorted by adds to favorite" do
  1430. user = insert(:user)
  1431. other_user = insert(:user)
  1432. user1 = insert(:user)
  1433. user2 = insert(:user)
  1434. {:ok, a1} = CommonAPI.post(user1, %{status: "bla"})
  1435. {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"})
  1436. {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "})
  1437. {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "})
  1438. {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "})
  1439. {:ok, _} = CommonAPI.favorite(user, a4.id)
  1440. {:ok, _} = CommonAPI.favorite(other_user, a3.id)
  1441. {:ok, _} = CommonAPI.favorite(user, a3.id)
  1442. {:ok, _} = CommonAPI.favorite(other_user, a5.id)
  1443. {:ok, _} = CommonAPI.favorite(user, a5.id)
  1444. {:ok, _} = CommonAPI.favorite(other_user, a4.id)
  1445. {:ok, _} = CommonAPI.favorite(user, a1.id)
  1446. {:ok, _} = CommonAPI.favorite(other_user, a1.id)
  1447. result = ActivityPub.fetch_favourites(user)
  1448. assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
  1449. result = ActivityPub.fetch_favourites(user, %{limit: 2})
  1450. assert Enum.map(result, & &1.id) == [a1.id, a5.id]
  1451. end
  1452. end
  1453. describe "Move activity" do
  1454. test "create" do
  1455. %{ap_id: old_ap_id} = old_user = insert(:user)
  1456. %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
  1457. follower = insert(:user)
  1458. follower_move_opted_out = insert(:user, allow_following_move: false)
  1459. User.follow(follower, old_user)
  1460. User.follow(follower_move_opted_out, old_user)
  1461. assert User.following?(follower, old_user)
  1462. assert User.following?(follower_move_opted_out, old_user)
  1463. assert {:ok, activity} = ActivityPub.move(old_user, new_user)
  1464. assert %Activity{
  1465. actor: ^old_ap_id,
  1466. data: %{
  1467. "actor" => ^old_ap_id,
  1468. "object" => ^old_ap_id,
  1469. "target" => ^new_ap_id,
  1470. "type" => "Move"
  1471. },
  1472. local: true,
  1473. recipients: recipients
  1474. } = activity
  1475. assert old_user.follower_address in recipients
  1476. params = %{
  1477. "op" => "move_following",
  1478. "origin_id" => old_user.id,
  1479. "target_id" => new_user.id
  1480. }
  1481. assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
  1482. Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
  1483. refute User.following?(follower, old_user)
  1484. assert User.following?(follower, new_user)
  1485. assert User.following?(follower_move_opted_out, old_user)
  1486. refute User.following?(follower_move_opted_out, new_user)
  1487. activity = %Activity{activity | object: nil}
  1488. assert [%Notification{activity: ^activity}] = Notification.for_user(follower)
  1489. assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out)
  1490. end
  1491. test "old user must be in the new user's `also_known_as` list" do
  1492. old_user = insert(:user)
  1493. new_user = insert(:user)
  1494. assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
  1495. ActivityPub.move(old_user, new_user)
  1496. end
  1497. test "do not move remote user following relationships" do
  1498. %{ap_id: old_ap_id} = old_user = insert(:user)
  1499. %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
  1500. follower_remote = insert(:user, local: false)
  1501. User.follow(follower_remote, old_user)
  1502. assert User.following?(follower_remote, old_user)
  1503. assert {:ok, activity} = ActivityPub.move(old_user, new_user)
  1504. assert %Activity{
  1505. actor: ^old_ap_id,
  1506. data: %{
  1507. "actor" => ^old_ap_id,
  1508. "object" => ^old_ap_id,
  1509. "target" => ^new_ap_id,
  1510. "type" => "Move"
  1511. },
  1512. local: true
  1513. } = activity
  1514. params = %{
  1515. "op" => "move_following",
  1516. "origin_id" => old_user.id,
  1517. "target_id" => new_user.id
  1518. }
  1519. assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
  1520. Pleroma.Workers.BackgroundWorker.perform(%Oban.Job{args: params})
  1521. assert User.following?(follower_remote, old_user)
  1522. refute User.following?(follower_remote, new_user)
  1523. end
  1524. end
  1525. test "doesn't retrieve replies activities with exclude_replies" do
  1526. user = insert(:user)
  1527. {:ok, activity} = CommonAPI.post(user, %{status: "yeah"})
  1528. {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id})
  1529. [result] = ActivityPub.fetch_public_activities(%{exclude_replies: true})
  1530. assert result.id == activity.id
  1531. assert length(ActivityPub.fetch_public_activities()) == 2
  1532. end
  1533. describe "replies filtering with public messages" do
  1534. setup :public_messages
  1535. test "public timeline", %{users: %{u1: user}} do
  1536. activities_ids =
  1537. %{}
  1538. |> Map.put(:type, ["Create", "Announce"])
  1539. |> Map.put(:local_only, false)
  1540. |> Map.put(:blocking_user, user)
  1541. |> Map.put(:muting_user, user)
  1542. |> Map.put(:reply_filtering_user, user)
  1543. |> ActivityPub.fetch_public_activities()
  1544. |> Enum.map(& &1.id)
  1545. assert length(activities_ids) == 16
  1546. end
  1547. test "public timeline with reply_visibility `following`", %{
  1548. users: %{u1: user},
  1549. u1: u1,
  1550. u2: u2,
  1551. u3: u3,
  1552. u4: u4,
  1553. activities: activities
  1554. } do
  1555. activities_ids =
  1556. %{}
  1557. |> Map.put(:type, ["Create", "Announce"])
  1558. |> Map.put(:local_only, false)
  1559. |> Map.put(:blocking_user, user)
  1560. |> Map.put(:muting_user, user)
  1561. |> Map.put(:reply_visibility, "following")
  1562. |> Map.put(:reply_filtering_user, user)
  1563. |> ActivityPub.fetch_public_activities()
  1564. |> Enum.map(& &1.id)
  1565. assert length(activities_ids) == 14
  1566. visible_ids =
  1567. Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
  1568. assert Enum.all?(visible_ids, &(&1 in activities_ids))
  1569. end
  1570. test "public timeline with reply_visibility `self`", %{
  1571. users: %{u1: user},
  1572. u1: u1,
  1573. u2: u2,
  1574. u3: u3,
  1575. u4: u4,
  1576. activities: activities
  1577. } do
  1578. activities_ids =
  1579. %{}
  1580. |> Map.put(:type, ["Create", "Announce"])
  1581. |> Map.put(:local_only, false)
  1582. |> Map.put(:blocking_user, user)
  1583. |> Map.put(:muting_user, user)
  1584. |> Map.put(:reply_visibility, "self")
  1585. |> Map.put(:reply_filtering_user, user)
  1586. |> ActivityPub.fetch_public_activities()
  1587. |> Enum.map(& &1.id)
  1588. assert length(activities_ids) == 10
  1589. visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
  1590. assert Enum.all?(visible_ids, &(&1 in activities_ids))
  1591. end
  1592. test "home timeline", %{
  1593. users: %{u1: user},
  1594. activities: activities,
  1595. u1: u1,
  1596. u2: u2,
  1597. u3: u3,
  1598. u4: u4
  1599. } do
  1600. params =
  1601. %{}
  1602. |> Map.put(:type, ["Create", "Announce"])
  1603. |> Map.put(:blocking_user, user)
  1604. |> Map.put(:muting_user, user)
  1605. |> Map.put(:user, user)
  1606. |> Map.put(:reply_filtering_user, user)
  1607. activities_ids =
  1608. ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
  1609. |> Enum.map(& &1.id)
  1610. assert length(activities_ids) == 13
  1611. visible_ids =
  1612. Map.values(u1) ++
  1613. Map.values(u3) ++
  1614. [
  1615. activities[:a1],
  1616. activities[:a2],
  1617. activities[:a4],
  1618. u2[:r1],
  1619. u2[:r3],
  1620. u4[:r1],
  1621. u4[:r2]
  1622. ]
  1623. assert Enum.all?(visible_ids, &(&1 in activities_ids))
  1624. end
  1625. test "home timeline with reply_visibility `following`", %{
  1626. users: %{u1: user},
  1627. activities: activities,
  1628. u1: u1,
  1629. u2: u2,
  1630. u3: u3,
  1631. u4: u4
  1632. } do
  1633. params =
  1634. %{}
  1635. |> Map.put(:type, ["Create", "Announce"])
  1636. |> Map.put(:blocking_user, user)
  1637. |> Map.put(:muting_user, user)
  1638. |> Map.put(:user, user)
  1639. |> Map.put(:reply_visibility, "following")
  1640. |> Map.put(:reply_filtering_user, user)
  1641. activities_ids =
  1642. ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
  1643. |> Enum.map(& &1.id)
  1644. assert length(activities_ids) == 11
  1645. visible_ids =
  1646. Map.values(u1) ++
  1647. [
  1648. activities[:a1],
  1649. activities[:a2],
  1650. activities[:a4],
  1651. u2[:r1],
  1652. u2[:r3],
  1653. u3[:r1],
  1654. u4[:r1],
  1655. u4[:r2]
  1656. ]
  1657. assert Enum.all?(visible_ids, &(&1 in activities_ids))
  1658. end
  1659. test "home timeline with reply_visibility `self`", %{
  1660. users: %{u1: user},
  1661. activities: activities,
  1662. u1: u1,
  1663. u2: u2,
  1664. u3: u3,
  1665. u4: u4
  1666. } do
  1667. params =
  1668. %{}
  1669. |> Map.put(:type, ["Create", "Announce"])
  1670. |> Map.put(:blocking_user, user)
  1671. |> Map.put(:muting_user, user)
  1672. |> Map.put(:user, user)
  1673. |> Map.put(:reply_visibility, "self")
  1674. |> Map.put(:reply_filtering_user, user)
  1675. activities_ids =
  1676. ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
  1677. |> Enum.map(& &1.id)
  1678. assert length(activities_ids) == 9
  1679. visible_ids =
  1680. Map.values(u1) ++
  1681. [
  1682. activities[:a1],
  1683. activities[:a2],
  1684. activities[:a4],
  1685. u2[:r1],
  1686. u3[:r1],
  1687. u4[:r1]
  1688. ]
  1689. assert Enum.all?(visible_ids, &(&1 in activities_ids))
  1690. end
  1691. test "filtering out announces where the user is the actor of the announced message" do
  1692. user = insert(:user)
  1693. other_user = insert(:user)
  1694. third_user = insert(:user)
  1695. User.follow(user, other_user)
  1696. {:ok, post} = CommonAPI.post(user, %{status: "yo"})
  1697. {:ok, other_post} = CommonAPI.post(third_user, %{status: "yo"})
  1698. {:ok, _announce} = CommonAPI.repeat(post.id, other_user)
  1699. {:ok, _announce} = CommonAPI.repeat(post.id, third_user)
  1700. {:ok, announce} = CommonAPI.repeat(other_post.id, other_user)
  1701. params = %{
  1702. type: ["Announce"]
  1703. }
  1704. results =
  1705. [user.ap_id | User.following(user)]
  1706. |> ActivityPub.fetch_activities(params)
  1707. assert length(results) == 3
  1708. params = %{
  1709. type: ["Announce"],
  1710. announce_filtering_user: user
  1711. }
  1712. [result] =
  1713. [user.ap_id | User.following(user)]
  1714. |> ActivityPub.fetch_activities(params)
  1715. assert result.id == announce.id
  1716. end
  1717. end
  1718. describe "replies filtering with private messages" do
  1719. setup :private_messages
  1720. test "public timeline", %{users: %{u1: user}} do
  1721. activities_ids =
  1722. %{}
  1723. |> Map.put(:type, ["Create", "Announce"])
  1724. |> Map.put(:local_only, false)
  1725. |> Map.put(:blocking_user, user)
  1726. |> Map.put(:muting_user, user)
  1727. |> Map.put(:user, user)
  1728. |> ActivityPub.fetch_public_activities()
  1729. |> Enum.map(& &1.id)
  1730. assert activities_ids == []
  1731. end
  1732. test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
  1733. activities_ids =
  1734. %{}
  1735. |> Map.put(:type, ["Create", "Announce"])
  1736. |> Map.put(:local_only, false)
  1737. |> Map.put(:blocking_user, user)
  1738. |> Map.put(:muting_user, user)
  1739. |> Map.put(:reply_visibility, "following")
  1740. |> Map.put(:reply_filtering_user, user)
  1741. |> Map.put(:user, user)
  1742. |> ActivityPub.fetch_public_activities()
  1743. |> Enum.map(& &1.id)
  1744. assert activities_ids == []
  1745. end
  1746. test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
  1747. activities_ids =
  1748. %{}
  1749. |> Map.put(:type, ["Create", "Announce"])
  1750. |> Map.put(:local_only, false)
  1751. |> Map.put(:blocking_user, user)
  1752. |> Map.put(:muting_user, user)
  1753. |> Map.put(:reply_visibility, "self")
  1754. |> Map.put(:reply_filtering_user, user)
  1755. |> Map.put(:user, user)
  1756. |> ActivityPub.fetch_public_activities()
  1757. |> Enum.map(& &1.id)
  1758. assert activities_ids == []
  1759. activities_ids =
  1760. %{}
  1761. |> Map.put(:reply_visibility, "self")
  1762. |> Map.put(:reply_filtering_user, nil)
  1763. |> ActivityPub.fetch_public_activities()
  1764. assert activities_ids == []
  1765. end
  1766. test "home timeline", %{users: %{u1: user}} do
  1767. params =
  1768. %{}
  1769. |> Map.put(:type, ["Create", "Announce"])
  1770. |> Map.put(:blocking_user, user)
  1771. |> Map.put(:muting_user, user)
  1772. |> Map.put(:user, user)
  1773. activities_ids =
  1774. ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
  1775. |> Enum.map(& &1.id)
  1776. assert length(activities_ids) == 12
  1777. end
  1778. test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
  1779. params =
  1780. %{}
  1781. |> Map.put(:type, ["Create", "Announce"])
  1782. |> Map.put(:blocking_user, user)
  1783. |> Map.put(:muting_user, user)
  1784. |> Map.put(:user, user)
  1785. |> Map.put(:reply_visibility, "following")
  1786. |> Map.put(:reply_filtering_user, user)
  1787. activities_ids =
  1788. ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
  1789. |> Enum.map(& &1.id)
  1790. assert length(activities_ids) == 12
  1791. end
  1792. test "home timeline with default reply_visibility `self`", %{
  1793. users: %{u1: user},
  1794. activities: activities,
  1795. u1: u1,
  1796. u2: u2,
  1797. u3: u3,
  1798. u4: u4
  1799. } do
  1800. params =
  1801. %{}
  1802. |> Map.put(:type, ["Create", "Announce"])
  1803. |> Map.put(:blocking_user, user)
  1804. |> Map.put(:muting_user, user)
  1805. |> Map.put(:user, user)
  1806. |> Map.put(:reply_visibility, "self")
  1807. |> Map.put(:reply_filtering_user, user)
  1808. activities_ids =
  1809. ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
  1810. |> Enum.map(& &1.id)
  1811. assert length(activities_ids) == 10
  1812. visible_ids =
  1813. Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
  1814. assert Enum.all?(visible_ids, &(&1 in activities_ids))
  1815. end
  1816. end
  1817. defp public_messages(_) do
  1818. [u1, u2, u3, u4] = insert_list(4, :user)
  1819. {:ok, u1, u2} = User.follow(u1, u2)
  1820. {:ok, u2, u1} = User.follow(u2, u1)
  1821. {:ok, u1, u4} = User.follow(u1, u4)
  1822. {:ok, u4, u1} = User.follow(u4, u1)
  1823. {:ok, u2, u3} = User.follow(u2, u3)
  1824. {:ok, u3, u2} = User.follow(u3, u2)
  1825. {:ok, a1} = CommonAPI.post(u1, %{status: "Status"})
  1826. {:ok, r1_1} =
  1827. CommonAPI.post(u2, %{
  1828. status: "@#{u1.nickname} reply from u2 to u1",
  1829. in_reply_to_status_id: a1.id
  1830. })
  1831. {:ok, r1_2} =
  1832. CommonAPI.post(u3, %{
  1833. status: "@#{u1.nickname} reply from u3 to u1",
  1834. in_reply_to_status_id: a1.id
  1835. })
  1836. {:ok, r1_3} =
  1837. CommonAPI.post(u4, %{
  1838. status: "@#{u1.nickname} reply from u4 to u1",
  1839. in_reply_to_status_id: a1.id
  1840. })
  1841. {:ok, a2} = CommonAPI.post(u2, %{status: "Status"})
  1842. {:ok, r2_1} =
  1843. CommonAPI.post(u1, %{
  1844. status: "@#{u2.nickname} reply from u1 to u2",
  1845. in_reply_to_status_id: a2.id
  1846. })
  1847. {:ok, r2_2} =
  1848. CommonAPI.post(u3, %{
  1849. status: "@#{u2.nickname} reply from u3 to u2",
  1850. in_reply_to_status_id: a2.id
  1851. })
  1852. {:ok, r2_3} =
  1853. CommonAPI.post(u4, %{
  1854. status: "@#{u2.nickname} reply from u4 to u2",
  1855. in_reply_to_status_id: a2.id
  1856. })
  1857. {:ok, a3} = CommonAPI.post(u3, %{status: "Status"})
  1858. {:ok, r3_1} =
  1859. CommonAPI.post(u1, %{
  1860. status: "@#{u3.nickname} reply from u1 to u3",
  1861. in_reply_to_status_id: a3.id
  1862. })
  1863. {:ok, r3_2} =
  1864. CommonAPI.post(u2, %{
  1865. status: "@#{u3.nickname} reply from u2 to u3",
  1866. in_reply_to_status_id: a3.id
  1867. })
  1868. {:ok, r3_3} =
  1869. CommonAPI.post(u4, %{
  1870. status: "@#{u3.nickname} reply from u4 to u3",
  1871. in_reply_to_status_id: a3.id
  1872. })
  1873. {:ok, a4} = CommonAPI.post(u4, %{status: "Status"})
  1874. {:ok, r4_1} =
  1875. CommonAPI.post(u1, %{
  1876. status: "@#{u4.nickname} reply from u1 to u4",
  1877. in_reply_to_status_id: a4.id
  1878. })
  1879. {:ok, r4_2} =
  1880. CommonAPI.post(u2, %{
  1881. status: "@#{u4.nickname} reply from u2 to u4",
  1882. in_reply_to_status_id: a4.id
  1883. })
  1884. {:ok, r4_3} =
  1885. CommonAPI.post(u3, %{
  1886. status: "@#{u4.nickname} reply from u3 to u4",
  1887. in_reply_to_status_id: a4.id
  1888. })
  1889. {:ok,
  1890. users: %{u1: u1, u2: u2, u3: u3, u4: u4},
  1891. activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
  1892. u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
  1893. u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
  1894. u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
  1895. u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
  1896. end
  1897. defp private_messages(_) do
  1898. [u1, u2, u3, u4] = insert_list(4, :user)
  1899. {:ok, u1, u2} = User.follow(u1, u2)
  1900. {:ok, u2, u1} = User.follow(u2, u1)
  1901. {:ok, u1, u3} = User.follow(u1, u3)
  1902. {:ok, u3, u1} = User.follow(u3, u1)
  1903. {:ok, u1, u4} = User.follow(u1, u4)
  1904. {:ok, u4, u1} = User.follow(u4, u1)
  1905. {:ok, u2, u3} = User.follow(u2, u3)
  1906. {:ok, u3, u2} = User.follow(u3, u2)
  1907. {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"})
  1908. {:ok, r1_1} =
  1909. CommonAPI.post(u2, %{
  1910. status: "@#{u1.nickname} reply from u2 to u1",
  1911. in_reply_to_status_id: a1.id,
  1912. visibility: "private"
  1913. })
  1914. {:ok, r1_2} =
  1915. CommonAPI.post(u3, %{
  1916. status: "@#{u1.nickname} reply from u3 to u1",
  1917. in_reply_to_status_id: a1.id,
  1918. visibility: "private"
  1919. })
  1920. {:ok, r1_3} =
  1921. CommonAPI.post(u4, %{
  1922. status: "@#{u1.nickname} reply from u4 to u1",
  1923. in_reply_to_status_id: a1.id,
  1924. visibility: "private"
  1925. })
  1926. {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"})
  1927. {:ok, r2_1} =
  1928. CommonAPI.post(u1, %{
  1929. status: "@#{u2.nickname} reply from u1 to u2",
  1930. in_reply_to_status_id: a2.id,
  1931. visibility: "private"
  1932. })
  1933. {:ok, r2_2} =
  1934. CommonAPI.post(u3, %{
  1935. status: "@#{u2.nickname} reply from u3 to u2",
  1936. in_reply_to_status_id: a2.id,
  1937. visibility: "private"
  1938. })
  1939. {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"})
  1940. {:ok, r3_1} =
  1941. CommonAPI.post(u1, %{
  1942. status: "@#{u3.nickname} reply from u1 to u3",
  1943. in_reply_to_status_id: a3.id,
  1944. visibility: "private"
  1945. })
  1946. {:ok, r3_2} =
  1947. CommonAPI.post(u2, %{
  1948. status: "@#{u3.nickname} reply from u2 to u3",
  1949. in_reply_to_status_id: a3.id,
  1950. visibility: "private"
  1951. })
  1952. {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"})
  1953. {:ok, r4_1} =
  1954. CommonAPI.post(u1, %{
  1955. status: "@#{u4.nickname} reply from u1 to u4",
  1956. in_reply_to_status_id: a4.id,
  1957. visibility: "private"
  1958. })
  1959. {:ok,
  1960. users: %{u1: u1, u2: u2, u3: u3, u4: u4},
  1961. activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
  1962. u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
  1963. u2: %{r1: r2_1.id, r2: r2_2.id},
  1964. u3: %{r1: r3_1.id, r2: r3_2.id},
  1965. u4: %{r1: r4_1.id}}
  1966. end
  1967. describe "maybe_update_follow_information/1" do
  1968. setup do
  1969. clear_config([:instance, :external_user_synchronization], true)
  1970. user = %{
  1971. local: false,
  1972. ap_id: "https://gensokyo.2hu/users/raymoo",
  1973. following_address: "https://gensokyo.2hu/users/following",
  1974. follower_address: "https://gensokyo.2hu/users/followers",
  1975. type: "Person"
  1976. }
  1977. %{user: user}
  1978. end
  1979. test "logs an error when it can't fetch the info", %{user: user} do
  1980. assert capture_log(fn ->
  1981. ActivityPub.maybe_update_follow_information(user)
  1982. end) =~ "Follower/Following counter update for #{user.ap_id} failed"
  1983. end
  1984. test "just returns the input if the user type is Application", %{
  1985. user: user
  1986. } do
  1987. user =
  1988. user
  1989. |> Map.put(:type, "Application")
  1990. refute capture_log(fn ->
  1991. assert ^user = ActivityPub.maybe_update_follow_information(user)
  1992. end) =~ "Follower/Following counter update for #{user.ap_id} failed"
  1993. end
  1994. test "it just returns the input if the user has no following/follower addresses", %{
  1995. user: user
  1996. } do
  1997. user =
  1998. user
  1999. |> Map.put(:following_address, nil)
  2000. |> Map.put(:follower_address, nil)
  2001. refute capture_log(fn ->
  2002. assert ^user = ActivityPub.maybe_update_follow_information(user)
  2003. end) =~ "Follower/Following counter update for #{user.ap_id} failed"
  2004. end
  2005. end
  2006. describe "global activity expiration" do
  2007. test "creates an activity expiration for local Create activities" do
  2008. clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
  2009. {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
  2010. {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
  2011. assert_enqueued(
  2012. worker: Pleroma.Workers.PurgeExpiredActivity,
  2013. args: %{activity_id: activity.id},
  2014. scheduled_at:
  2015. activity.inserted_at
  2016. |> DateTime.from_naive!("Etc/UTC")
  2017. |> Timex.shift(days: 365)
  2018. )
  2019. refute_enqueued(
  2020. worker: Pleroma.Workers.PurgeExpiredActivity,
  2021. args: %{activity_id: follow.id}
  2022. )
  2023. end
  2024. end
  2025. describe "handling of clashing nicknames" do
  2026. test "renames an existing user with a clashing nickname and a different ap id" do
  2027. orig_user =
  2028. insert(
  2029. :user,
  2030. local: false,
  2031. nickname: "admin@mastodon.example.org",
  2032. ap_id: "http://mastodon.example.org/users/harinezumigari"
  2033. )
  2034. %{
  2035. nickname: orig_user.nickname,
  2036. ap_id: orig_user.ap_id <> "part_2"
  2037. }
  2038. |> ActivityPub.maybe_handle_clashing_nickname()
  2039. user = User.get_by_id(orig_user.id)
  2040. assert user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
  2041. end
  2042. test "does nothing with a clashing nickname and the same ap id" do
  2043. orig_user =
  2044. insert(
  2045. :user,
  2046. local: false,
  2047. nickname: "admin@mastodon.example.org",
  2048. ap_id: "http://mastodon.example.org/users/harinezumigari"
  2049. )
  2050. %{
  2051. nickname: orig_user.nickname,
  2052. ap_id: orig_user.ap_id
  2053. }
  2054. |> ActivityPub.maybe_handle_clashing_nickname()
  2055. user = User.get_by_id(orig_user.id)
  2056. assert user.nickname == orig_user.nickname
  2057. end
  2058. end
  2059. describe "reply filtering" do
  2060. test "`following` still contains announcements by friends" do
  2061. user = insert(:user)
  2062. followed = insert(:user)
  2063. not_followed = insert(:user)
  2064. User.follow(user, followed)
  2065. {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
  2066. {:ok, not_followed_to_followed} =
  2067. CommonAPI.post(not_followed, %{
  2068. status: "Also hello",
  2069. in_reply_to_status_id: followed_post.id
  2070. })
  2071. {:ok, retoot} = CommonAPI.repeat(not_followed_to_followed.id, followed)
  2072. params =
  2073. %{}
  2074. |> Map.put(:type, ["Create", "Announce"])
  2075. |> Map.put(:blocking_user, user)
  2076. |> Map.put(:muting_user, user)
  2077. |> Map.put(:reply_filtering_user, user)
  2078. |> Map.put(:reply_visibility, "following")
  2079. |> Map.put(:announce_filtering_user, user)
  2080. |> Map.put(:user, user)
  2081. activities =
  2082. [user.ap_id | User.following(user)]
  2083. |> ActivityPub.fetch_activities(params)
  2084. followed_post_id = followed_post.id
  2085. retoot_id = retoot.id
  2086. assert [%{id: ^followed_post_id}, %{id: ^retoot_id}] = activities
  2087. assert length(activities) == 2
  2088. end
  2089. # This test is skipped because, while this is the desired behavior,
  2090. # there seems to be no good way to achieve it with the method that
  2091. # we currently use for detecting to who a reply is directed.
  2092. # This is a TODO and should be fixed by a later rewrite of the code
  2093. # in question.
  2094. @tag skip: true
  2095. test "`following` still contains self-replies by friends" do
  2096. user = insert(:user)
  2097. followed = insert(:user)
  2098. not_followed = insert(:user)
  2099. User.follow(user, followed)
  2100. {:ok, followed_post} = CommonAPI.post(followed, %{status: "Hello"})
  2101. {:ok, not_followed_post} = CommonAPI.post(not_followed, %{status: "Also hello"})
  2102. {:ok, _followed_to_not_followed} =
  2103. CommonAPI.post(followed, %{status: "sup", in_reply_to_status_id: not_followed_post.id})
  2104. {:ok, _followed_self_reply} =
  2105. CommonAPI.post(followed, %{status: "Also cofe", in_reply_to_status_id: followed_post.id})
  2106. params =
  2107. %{}
  2108. |> Map.put(:type, ["Create", "Announce"])
  2109. |> Map.put(:blocking_user, user)
  2110. |> Map.put(:muting_user, user)
  2111. |> Map.put(:reply_filtering_user, user)
  2112. |> Map.put(:reply_visibility, "following")
  2113. |> Map.put(:announce_filtering_user, user)
  2114. |> Map.put(:user, user)
  2115. activities =
  2116. [user.ap_id | User.following(user)]
  2117. |> ActivityPub.fetch_activities(params)
  2118. assert length(activities) == 2
  2119. end
  2120. end
  2121. test "allow fetching of accounts with an empty string name field" do
  2122. Tesla.Mock.mock(fn
  2123. %{method: :get, url: "https://princess.cat/users/mewmew"} ->
  2124. file = File.read!("test/fixtures/mewmew_no_name.json")
  2125. %Tesla.Env{status: 200, body: file, headers: HttpRequestMock.activitypub_object_headers()}
  2126. end)
  2127. {:ok, user} = ActivityPub.make_user_from_ap_id("https://princess.cat/users/mewmew")
  2128. assert user.name == " "
  2129. end
  2130. @tag capture_log: true
  2131. test "pin_data_from_featured_collection will ignore unsupported values" do
  2132. assert %{} ==
  2133. ActivityPub.pin_data_from_featured_collection(%{
  2134. "type" => "OrderedCollection",
  2135. "first" => "https://social.example/users/alice/collections/featured?page=true"
  2136. })
  2137. end
  2138. end