f637097b8fdd055866d66fa85bc56baf63b7bc88
[akkoma] / test / web / mastodon_api / status_view_test.exs
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
5 defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
6 use Pleroma.DataCase
7
8 alias Pleroma.Activity
9 alias Pleroma.Bookmark
10 alias Pleroma.Object
11 alias Pleroma.Repo
12 alias Pleroma.User
13 alias Pleroma.Web.CommonAPI
14 alias Pleroma.Web.CommonAPI.Utils
15 alias Pleroma.Web.MastodonAPI.AccountView
16 alias Pleroma.Web.MastodonAPI.StatusView
17 alias Pleroma.Web.OStatus
18 import Pleroma.Factory
19 import Tesla.Mock
20
21 setup do
22 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
23 :ok
24 end
25
26 test "returns a temporary ap_id based user for activities missing db users" do
27 user = insert(:user)
28
29 {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
30
31 Repo.delete(user)
32 Cachex.clear(:user_cache)
33
34 %{account: ms_user} = StatusView.render("status.json", activity: activity)
35
36 assert ms_user.acct == "erroruser@example.com"
37 end
38
39 test "tries to get a user by nickname if fetching by ap_id doesn't work" do
40 user = insert(:user)
41
42 {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
43
44 {:ok, user} =
45 user
46 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
47 |> Repo.update()
48
49 Cachex.clear(:user_cache)
50
51 result = StatusView.render("status.json", activity: activity)
52
53 assert result[:account][:id] == to_string(user.id)
54 end
55
56 test "a note with null content" do
57 note = insert(:note_activity)
58 note_object = Object.normalize(note)
59
60 data =
61 note_object.data
62 |> Map.put("content", nil)
63
64 Object.change(note_object, %{data: data})
65 |> Object.update_and_set_cache()
66
67 User.get_cached_by_ap_id(note.data["actor"])
68
69 status = StatusView.render("status.json", %{activity: note})
70
71 assert status.content == ""
72 end
73
74 test "a note activity" do
75 note = insert(:note_activity)
76 object_data = Object.normalize(note).data
77 user = User.get_cached_by_ap_id(note.data["actor"])
78
79 convo_id = Utils.context_to_conversation_id(object_data["context"])
80
81 status = StatusView.render("status.json", %{activity: note})
82
83 created_at =
84 (object_data["published"] || "")
85 |> String.replace(~r/\.\d+Z/, ".000Z")
86
87 expected = %{
88 id: to_string(note.id),
89 uri: object_data["id"],
90 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note),
91 account: AccountView.render("account.json", %{user: user}),
92 in_reply_to_id: nil,
93 in_reply_to_account_id: nil,
94 card: nil,
95 reblog: nil,
96 content: HtmlSanitizeEx.basic_html(object_data["content"]),
97 created_at: created_at,
98 reblogs_count: 0,
99 replies_count: 0,
100 favourites_count: 0,
101 reblogged: false,
102 bookmarked: false,
103 favourited: false,
104 muted: false,
105 pinned: false,
106 sensitive: false,
107 poll: nil,
108 spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]),
109 visibility: "public",
110 media_attachments: [],
111 mentions: [],
112 tags: [
113 %{
114 name: "#{object_data["tag"]}",
115 url: "/tag/#{object_data["tag"]}"
116 }
117 ],
118 application: %{
119 name: "Web",
120 website: nil
121 },
122 language: nil,
123 emojis: [
124 %{
125 shortcode: "2hu",
126 url: "corndog.png",
127 static_url: "corndog.png",
128 visible_in_picker: false
129 }
130 ],
131 pleroma: %{
132 local: true,
133 conversation_id: convo_id,
134 in_reply_to_account_acct: nil,
135 content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])},
136 spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}
137 }
138 }
139
140 assert status == expected
141 end
142
143 test "tells if the message is muted for some reason" do
144 user = insert(:user)
145 other_user = insert(:user)
146
147 {:ok, user} = User.mute(user, other_user)
148
149 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
150 status = StatusView.render("status.json", %{activity: activity})
151
152 assert status.muted == false
153
154 status = StatusView.render("status.json", %{activity: activity, for: user})
155
156 assert status.muted == true
157 end
158
159 test "tells if the status is bookmarked" do
160 user = insert(:user)
161
162 {:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"})
163 status = StatusView.render("status.json", %{activity: activity})
164
165 assert status.bookmarked == false
166
167 status = StatusView.render("status.json", %{activity: activity, for: user})
168
169 assert status.bookmarked == false
170
171 {:ok, _bookmark} = Bookmark.create(user.id, activity.id)
172
173 activity = Activity.get_by_id_with_object(activity.id)
174
175 status = StatusView.render("status.json", %{activity: activity, for: user})
176
177 assert status.bookmarked == true
178 end
179
180 test "a reply" do
181 note = insert(:note_activity)
182 user = insert(:user)
183
184 {:ok, activity} =
185 CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id})
186
187 status = StatusView.render("status.json", %{activity: activity})
188
189 assert status.in_reply_to_id == to_string(note.id)
190
191 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
192
193 assert status.in_reply_to_id == to_string(note.id)
194 end
195
196 test "contains mentions" do
197 incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml")
198 # a user with this ap id might be in the cache.
199 recipient = "https://pleroma.soykaf.com/users/lain"
200 user = insert(:user, %{ap_id: recipient})
201
202 {:ok, [activity]} = OStatus.handle_incoming(incoming)
203
204 status = StatusView.render("status.json", %{activity: activity})
205
206 actor = User.get_cached_by_ap_id(activity.actor)
207
208 assert status.mentions ==
209 Enum.map([user, actor], fn u -> AccountView.render("mention.json", %{user: u}) end)
210 end
211
212 test "attachments" do
213 object = %{
214 "type" => "Image",
215 "url" => [
216 %{
217 "mediaType" => "image/png",
218 "href" => "someurl"
219 }
220 ],
221 "uuid" => 6
222 }
223
224 expected = %{
225 id: "1638338801",
226 type: "image",
227 url: "someurl",
228 remote_url: "someurl",
229 preview_url: "someurl",
230 text_url: "someurl",
231 description: nil,
232 pleroma: %{mime_type: "image/png"}
233 }
234
235 assert expected == StatusView.render("attachment.json", %{attachment: object})
236
237 # If theres a "id", use that instead of the generated one
238 object = Map.put(object, "id", 2)
239 assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object})
240 end
241
242 test "a reblog" do
243 user = insert(:user)
244 activity = insert(:note_activity)
245
246 {:ok, reblog, _} = CommonAPI.repeat(activity.id, user)
247
248 represented = StatusView.render("status.json", %{for: user, activity: reblog})
249
250 assert represented[:id] == to_string(reblog.id)
251 assert represented[:reblog][:id] == to_string(activity.id)
252 assert represented[:emojis] == []
253 end
254
255 test "a peertube video" do
256 user = insert(:user)
257
258 {:ok, object} =
259 Pleroma.Object.Fetcher.fetch_object_from_id(
260 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
261 )
262
263 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])
264
265 represented = StatusView.render("status.json", %{for: user, activity: activity})
266
267 assert represented[:id] == to_string(activity.id)
268 assert length(represented[:media_attachments]) == 1
269 end
270
271 describe "build_tags/1" do
272 test "it returns a a dictionary tags" do
273 object_tags = [
274 "fediverse",
275 "mastodon",
276 "nextcloud",
277 %{
278 "href" => "https://kawen.space/users/lain",
279 "name" => "@lain@kawen.space",
280 "type" => "Mention"
281 }
282 ]
283
284 assert StatusView.build_tags(object_tags) == [
285 %{name: "fediverse", url: "/tag/fediverse"},
286 %{name: "mastodon", url: "/tag/mastodon"},
287 %{name: "nextcloud", url: "/tag/nextcloud"}
288 ]
289 end
290 end
291
292 describe "rich media cards" do
293 test "a rich media card without a site name renders correctly" do
294 page_url = "http://example.com"
295
296 card = %{
297 url: page_url,
298 image: page_url <> "/example.jpg",
299 title: "Example website"
300 }
301
302 %{provider_name: "example.com"} =
303 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
304 end
305
306 test "a rich media card without a site name or image renders correctly" do
307 page_url = "http://example.com"
308
309 card = %{
310 url: page_url,
311 title: "Example website"
312 }
313
314 %{provider_name: "example.com"} =
315 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
316 end
317
318 test "a rich media card without an image renders correctly" do
319 page_url = "http://example.com"
320
321 card = %{
322 url: page_url,
323 site_name: "Example site name",
324 title: "Example website"
325 }
326
327 %{provider_name: "Example site name"} =
328 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
329 end
330
331 test "a rich media card with all relevant data renders correctly" do
332 page_url = "http://example.com"
333
334 card = %{
335 url: page_url,
336 site_name: "Example site name",
337 title: "Example website",
338 image: page_url <> "/example.jpg",
339 description: "Example description"
340 }
341
342 %{provider_name: "Example site name"} =
343 StatusView.render("card.json", %{page_url: page_url, rich_media: card})
344 end
345 end
346
347 describe "poll view" do
348 test "renders a poll" do
349 user = insert(:user)
350
351 {:ok, activity} =
352 CommonAPI.post(user, %{
353 "status" => "Is Tenshi eating a corndog cute?",
354 "poll" => %{
355 "options" => ["absolutely!", "sure", "yes", "why are you even asking?"],
356 "expires_in" => 20
357 }
358 })
359
360 object = Object.normalize(activity)
361
362 expected = %{
363 emojis: [],
364 expired: false,
365 id: object.id,
366 multiple: false,
367 options: [
368 %{title: "absolutely!", votes_count: 0},
369 %{title: "sure", votes_count: 0},
370 %{title: "yes", votes_count: 0},
371 %{title: "why are you even asking?", votes_count: 0}
372 ],
373 voted: false,
374 votes_count: 0
375 }
376
377 result = StatusView.render("poll.json", %{object: object})
378 expires_at = result.expires_at
379 result = Map.delete(result, :expires_at)
380
381 assert result == expected
382
383 expires_at = NaiveDateTime.from_iso8601!(expires_at)
384 assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20
385 end
386
387 test "detects if it is multiple choice" do
388 user = insert(:user)
389
390 {:ok, activity} =
391 CommonAPI.post(user, %{
392 "status" => "Which Mastodon developer is your favourite?",
393 "poll" => %{
394 "options" => ["Gargron", "Eugen"],
395 "expires_in" => 20,
396 "multiple" => true
397 }
398 })
399
400 object = Object.normalize(activity)
401
402 assert %{multiple: true} = StatusView.render("poll.json", %{object: object})
403 end
404
405 test "detects emoji" do
406 user = insert(:user)
407
408 {:ok, activity} =
409 CommonAPI.post(user, %{
410 "status" => "What's with the smug face?",
411 "poll" => %{
412 "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"],
413 "expires_in" => 20
414 }
415 })
416
417 object = Object.normalize(activity)
418
419 assert %{emojis: [%{shortcode: "blank"}]} =
420 StatusView.render("poll.json", %{object: object})
421 end
422
423 test "detects vote status" do
424 user = insert(:user)
425 other_user = insert(:user)
426
427 {:ok, activity} =
428 CommonAPI.post(user, %{
429 "status" => "Which input devices do you use?",
430 "poll" => %{
431 "options" => ["mouse", "trackball", "trackpoint"],
432 "multiple" => true,
433 "expires_in" => 20
434 }
435 })
436
437 object = Object.normalize(activity)
438
439 {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2])
440
441 result = StatusView.render("poll.json", %{object: object, for: other_user})
442
443 assert result[:voted] == true
444 assert Enum.at(result[:options], 1)[:votes_count] == 1
445 assert Enum.at(result[:options], 2)[:votes_count] == 1
446 end
447 end
448 end