First
[anni] / test / pleroma / web / common_api / utils_test.exs
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
5 defmodule Pleroma.Web.CommonAPI.UtilsTest do
6   alias Pleroma.Builders.UserBuilder
7   alias Pleroma.Web.CommonAPI
8   alias Pleroma.Web.CommonAPI.ActivityDraft
9   alias Pleroma.Web.CommonAPI.Utils
10   use Pleroma.DataCase
11
12   import ExUnit.CaptureLog
13   import Pleroma.Factory
14
15   @public_address "https://www.w3.org/ns/activitystreams#Public"
16
17   describe "add_attachments/2" do
18     setup do
19       name =
20         "Sakura Mana – Turned on by a Senior OL with a Temptating Tight Skirt-s Full Hipline and Panty Shot- Beautiful Thick Thighs- and Erotic Ass- -2015- -- Oppaitime 8-28-2017 6-50-33 PM.png"
21
22       attachment = %{
23         "url" => [%{"href" => URI.encode(name)}]
24       }
25
26       %{name: name, attachment: attachment}
27     end
28
29     test "it adds attachment links to a given text and attachment set", %{
30       name: name,
31       attachment: attachment
32     } do
33       len = 10
34       clear_config([Pleroma.Upload, :filename_display_max_length], len)
35
36       expected =
37         "<br><a href=\"#{URI.encode(name)}\" class='attachment'>#{String.slice(name, 0..len)}…</a>"
38
39       assert Utils.add_attachments("", [attachment]) == expected
40     end
41
42     test "doesn't truncate file name if config for truncate is set to 0", %{
43       name: name,
44       attachment: attachment
45     } do
46       clear_config([Pleroma.Upload, :filename_display_max_length], 0)
47
48       expected = "<br><a href=\"#{URI.encode(name)}\" class='attachment'>#{name}</a>"
49
50       assert Utils.add_attachments("", [attachment]) == expected
51     end
52   end
53
54   describe "it confirms the password given is the current users password" do
55     test "incorrect password given" do
56       {:ok, user} = UserBuilder.insert()
57
58       assert Utils.confirm_current_password(user, "") == {:error, "Invalid password."}
59     end
60
61     test "correct password given" do
62       {:ok, user} = UserBuilder.insert()
63       assert Utils.confirm_current_password(user, "test") == {:ok, user}
64     end
65   end
66
67   describe "format_input/3" do
68     test "works for bare text/plain" do
69       text = "hello world!"
70       expected = "hello world!"
71
72       {output, [], []} = Utils.format_input(text, "text/plain")
73
74       assert output == expected
75
76       text = "hello world!\n\nsecond paragraph!"
77       expected = "hello world!<br><br>second paragraph!"
78
79       {output, [], []} = Utils.format_input(text, "text/plain")
80
81       assert output == expected
82     end
83
84     test "works for bare text/html" do
85       text = "<p>hello world!</p>"
86       expected = "<p>hello world!</p>"
87
88       {output, [], []} = Utils.format_input(text, "text/html")
89
90       assert output == expected
91
92       text = "<p>hello world!</p><br/>\n<p>second paragraph</p>"
93       expected = "<p>hello world!</p><br/>\n<p>second paragraph</p>"
94
95       {output, [], []} = Utils.format_input(text, "text/html")
96
97       assert output == expected
98     end
99
100     test "works for bare text/markdown" do
101       text = "**hello world**"
102       expected = "<p><strong>hello world</strong></p>"
103
104       {output, [], []} = Utils.format_input(text, "text/markdown")
105
106       assert output == expected
107
108       text = "**hello world**\n\n*another paragraph*"
109       expected = "<p><strong>hello world</strong></p><p><em>another paragraph</em></p>"
110
111       {output, [], []} = Utils.format_input(text, "text/markdown")
112
113       assert output == expected
114
115       text = """
116       > cool quote
117
118       by someone
119       """
120
121       expected = "<blockquote><p>cool quote</p></blockquote><p>by someone</p>"
122
123       {output, [], []} = Utils.format_input(text, "text/markdown")
124
125       assert output == expected
126     end
127
128     test "works for bare text/bbcode" do
129       text = "[b]hello world[/b]"
130       expected = "<strong>hello world</strong>"
131
132       {output, [], []} = Utils.format_input(text, "text/bbcode")
133
134       assert output == expected
135
136       text = "[b]hello world![/b]\n\nsecond paragraph!"
137       expected = "<strong>hello world!</strong><br><br>second paragraph!"
138
139       {output, [], []} = Utils.format_input(text, "text/bbcode")
140
141       assert output == expected
142
143       text = "[b]hello world![/b]\n\n<strong>second paragraph!</strong>"
144
145       expected =
146         "<strong>hello world!</strong><br><br>&lt;strong&gt;second paragraph!&lt;/strong&gt;"
147
148       {output, [], []} = Utils.format_input(text, "text/bbcode")
149
150       assert output == expected
151     end
152
153     test "works for text/markdown with mentions" do
154       {:ok, user} =
155         UserBuilder.insert(%{nickname: "user__test", ap_id: "http://foo.com/user__test"})
156
157       text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"
158
159       {output, _, _} = Utils.format_input(text, "text/markdown")
160
161       assert output ==
162                ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
163     end
164   end
165
166   describe "format_input/3 with markdown" do
167     test "Paragraph" do
168       code = ~s[Hello\n\nWorld!]
169       {result, [], []} = Utils.format_input(code, "text/markdown")
170       assert result == "<p>Hello</p><p>World!</p>"
171     end
172
173     test "links" do
174       code = "https://en.wikipedia.org/wiki/Animal_Crossing_(video_game)"
175       {result, [], []} = Utils.format_input(code, "text/markdown")
176       assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
177
178       code = "https://github.com/pragdave/earmark/"
179       {result, [], []} = Utils.format_input(code, "text/markdown")
180       assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
181
182       code = "https://github.com/~foo/bar"
183       {result, [], []} = Utils.format_input(code, "text/markdown")
184       assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
185     end
186
187     test "link with local mention" do
188       insert(:user, %{nickname: "lain"})
189
190       code = "https://example.com/@lain"
191       {result, [], []} = Utils.format_input(code, "text/markdown")
192       assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
193     end
194
195     test "local mentions" do
196       mario = insert(:user, %{nickname: "mario"})
197       luigi = insert(:user, %{nickname: "luigi"})
198
199       code = "@mario @luigi yo what's up?"
200       {result, _, []} = Utils.format_input(code, "text/markdown")
201
202       assert result ==
203                ~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{mario.ap_id}" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{luigi.id}" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
204     end
205
206     test "remote mentions" do
207       mario = insert(:user, %{nickname: "mario@mushroom.world", local: false})
208       luigi = insert(:user, %{nickname: "luigi@mushroom.world", local: false})
209
210       code = "@mario@mushroom.world @luigi@mushroom.world yo what's up?"
211       {result, _, []} = Utils.format_input(code, "text/markdown")
212
213       assert result ==
214                ~s[<p><span class="h-card"><a class="u-url mention" data-user="#{mario.id}" href="#{mario.ap_id}" rel="ugc">@<span>mario</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{luigi.id}" href="#{luigi.ap_id}" rel="ugc">@<span>luigi</span></a></span> yo what’s up?</p>]
215     end
216
217     test "raw HTML" do
218       code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
219       {result, [], []} = Utils.format_input(code, "text/markdown")
220       assert result == ~s[<a href="http://example.org/">OwO</a>]
221     end
222
223     test "rulers" do
224       code = ~s[before\n\n-----\n\nafter]
225       {result, [], []} = Utils.format_input(code, "text/markdown")
226       assert result == "<p>before</p><hr/><p>after</p>"
227     end
228
229     test "blockquote" do
230       code = ~s[> whoms't are you quoting?]
231       {result, [], []} = Utils.format_input(code, "text/markdown")
232       assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
233     end
234
235     test "code" do
236       code = ~s[`mix`]
237       {result, [], []} = Utils.format_input(code, "text/markdown")
238       assert result == ~s[<p><code class="inline">mix</code></p>]
239
240       code = ~s[``mix``]
241       {result, [], []} = Utils.format_input(code, "text/markdown")
242       assert result == ~s[<p><code class="inline">mix</code></p>]
243
244       code = ~s[```\nputs "Hello World"\n```]
245       {result, [], []} = Utils.format_input(code, "text/markdown")
246       assert result == ~s[<pre><code>puts &quot;Hello World&quot;</code></pre>]
247
248       code = ~s[    <div>\n    </div>]
249       {result, [], []} = Utils.format_input(code, "text/markdown")
250       assert result == ~s[<pre><code>&lt;div&gt;\n&lt;/div&gt;</code></pre>]
251     end
252
253     test "lists" do
254       code = ~s[- one\n- two\n- three\n- four]
255       {result, [], []} = Utils.format_input(code, "text/markdown")
256       assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
257
258       code = ~s[1. one\n2. two\n3. three\n4. four\n]
259       {result, [], []} = Utils.format_input(code, "text/markdown")
260       assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
261     end
262
263     test "delegated renderers" do
264       code = ~s[*aaaa~*]
265       {result, [], []} = Utils.format_input(code, "text/markdown")
266       assert result == ~s[<p><em>aaaa~</em></p>]
267
268       code = ~s[**aaaa~**]
269       {result, [], []} = Utils.format_input(code, "text/markdown")
270       assert result == ~s[<p><strong>aaaa~</strong></p>]
271
272       # strikethrough
273       code = ~s[~~aaaa~~~]
274       {result, [], []} = Utils.format_input(code, "text/markdown")
275       assert result == ~s[<p><del>aaaa</del>~</p>]
276     end
277   end
278
279   describe "formats date to asctime" do
280     test "when date is in ISO 8601 format" do
281       date = DateTime.utc_now() |> DateTime.to_iso8601()
282
283       expected =
284         date
285         |> DateTime.from_iso8601()
286         |> elem(1)
287         |> Calendar.Strftime.strftime!("%a %b %d %H:%M:%S %z %Y")
288
289       assert Utils.date_to_asctime(date) == expected
290     end
291
292     test "when date is a binary in wrong format" do
293       date = DateTime.utc_now()
294
295       expected = ""
296
297       assert capture_log(fn ->
298                assert Utils.date_to_asctime(date) == expected
299              end) =~ "Date #{date} in wrong format, must be ISO 8601"
300     end
301
302     test "when date is a Unix timestamp" do
303       date = DateTime.utc_now() |> DateTime.to_unix()
304
305       expected = ""
306
307       assert capture_log(fn ->
308                assert Utils.date_to_asctime(date) == expected
309              end) =~ "Date #{date} in wrong format, must be ISO 8601"
310     end
311
312     test "when date is nil" do
313       expected = ""
314
315       assert capture_log(fn ->
316                assert Utils.date_to_asctime(nil) == expected
317              end) =~ "Date  in wrong format, must be ISO 8601"
318     end
319
320     test "when date is a random string" do
321       assert capture_log(fn ->
322                assert Utils.date_to_asctime("foo") == ""
323              end) =~ "Date foo in wrong format, must be ISO 8601"
324     end
325   end
326
327   describe "get_to_and_cc" do
328     test "for public posts, not a reply" do
329       user = insert(:user)
330       mentioned_user = insert(:user)
331       draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "public"}
332
333       {to, cc} = Utils.get_to_and_cc(draft)
334
335       assert length(to) == 2
336       assert length(cc) == 1
337
338       assert @public_address in to
339       assert mentioned_user.ap_id in to
340       assert user.follower_address in cc
341     end
342
343     test "for public posts, a reply" do
344       user = insert(:user)
345       mentioned_user = insert(:user)
346       third_user = insert(:user)
347       {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
348
349       draft = %ActivityDraft{
350         user: user,
351         mentions: [mentioned_user.ap_id],
352         visibility: "public",
353         in_reply_to: activity
354       }
355
356       {to, cc} = Utils.get_to_and_cc(draft)
357
358       assert length(to) == 3
359       assert length(cc) == 1
360
361       assert @public_address in to
362       assert mentioned_user.ap_id in to
363       assert third_user.ap_id in to
364       assert user.follower_address in cc
365     end
366
367     test "for unlisted posts, not a reply" do
368       user = insert(:user)
369       mentioned_user = insert(:user)
370       draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "unlisted"}
371
372       {to, cc} = Utils.get_to_and_cc(draft)
373
374       assert length(to) == 2
375       assert length(cc) == 1
376
377       assert @public_address in cc
378       assert mentioned_user.ap_id in to
379       assert user.follower_address in to
380     end
381
382     test "for unlisted posts, a reply" do
383       user = insert(:user)
384       mentioned_user = insert(:user)
385       third_user = insert(:user)
386       {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
387
388       draft = %ActivityDraft{
389         user: user,
390         mentions: [mentioned_user.ap_id],
391         visibility: "unlisted",
392         in_reply_to: activity
393       }
394
395       {to, cc} = Utils.get_to_and_cc(draft)
396
397       assert length(to) == 3
398       assert length(cc) == 1
399
400       assert @public_address in cc
401       assert mentioned_user.ap_id in to
402       assert third_user.ap_id in to
403       assert user.follower_address in to
404     end
405
406     test "for private posts, not a reply" do
407       user = insert(:user)
408       mentioned_user = insert(:user)
409       draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "private"}
410
411       {to, cc} = Utils.get_to_and_cc(draft)
412       assert length(to) == 2
413       assert Enum.empty?(cc)
414
415       assert mentioned_user.ap_id in to
416       assert user.follower_address in to
417     end
418
419     test "for private posts, a reply" do
420       user = insert(:user)
421       mentioned_user = insert(:user)
422       third_user = insert(:user)
423       {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
424
425       draft = %ActivityDraft{
426         user: user,
427         mentions: [mentioned_user.ap_id],
428         visibility: "private",
429         in_reply_to: activity
430       }
431
432       {to, cc} = Utils.get_to_and_cc(draft)
433
434       assert length(to) == 2
435       assert Enum.empty?(cc)
436
437       assert mentioned_user.ap_id in to
438       assert user.follower_address in to
439     end
440
441     test "for direct posts, not a reply" do
442       user = insert(:user)
443       mentioned_user = insert(:user)
444       draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "direct"}
445
446       {to, cc} = Utils.get_to_and_cc(draft)
447
448       assert length(to) == 1
449       assert Enum.empty?(cc)
450
451       assert mentioned_user.ap_id in to
452     end
453
454     test "for direct posts, a reply" do
455       user = insert(:user)
456       mentioned_user = insert(:user)
457       third_user = insert(:user)
458       {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
459
460       draft = %ActivityDraft{
461         user: user,
462         mentions: [mentioned_user.ap_id],
463         visibility: "direct",
464         in_reply_to: activity
465       }
466
467       {to, cc} = Utils.get_to_and_cc(draft)
468
469       assert length(to) == 1
470       assert Enum.empty?(cc)
471
472       assert mentioned_user.ap_id in to
473
474       {:ok, direct_activity} = CommonAPI.post(third_user, %{status: "uguu", visibility: "direct"})
475
476       draft = %ActivityDraft{
477         user: user,
478         mentions: [mentioned_user.ap_id],
479         visibility: "direct",
480         in_reply_to: direct_activity
481       }
482
483       {to, cc} = Utils.get_to_and_cc(draft)
484
485       assert length(to) == 2
486       assert Enum.empty?(cc)
487
488       assert mentioned_user.ap_id in to
489       assert third_user.ap_id in to
490     end
491   end
492
493   describe "to_master_date/1" do
494     test "removes microseconds from date (NaiveDateTime)" do
495       assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z"
496     end
497
498     test "removes microseconds from date (String)" do
499       assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z"
500     end
501
502     test "returns empty string when date invalid" do
503       assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == ""
504     end
505   end
506
507   describe "maybe_notify_mentioned_recipients/2" do
508     test "returns recipients when activity is not `Create`" do
509       activity = insert(:like_activity)
510       assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == ["test"]
511     end
512
513     test "returns recipients from tag" do
514       user = insert(:user)
515
516       object =
517         insert(:note,
518           user: user,
519           data: %{
520             "tag" => [
521               %{"type" => "Hashtag"},
522               "",
523               %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"},
524               %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"},
525               %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}
526             ]
527           }
528         )
529
530       activity = insert(:note_activity, user: user, note: object)
531
532       assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [
533                "test",
534                "https://testing.pleroma.lol/users/lain",
535                "https://shitposter.club/user/5381"
536              ]
537     end
538
539     test "returns recipients when object is map" do
540       user = insert(:user)
541       object = insert(:note, user: user)
542
543       activity =
544         insert(:note_activity,
545           user: user,
546           note: object,
547           data_attrs: %{
548             "object" => %{
549               "tag" => [
550                 %{"type" => "Hashtag"},
551                 "",
552                 %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"},
553                 %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"},
554                 %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}
555               ]
556             }
557           }
558         )
559
560       Pleroma.Repo.delete(object)
561
562       assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [
563                "test",
564                "https://testing.pleroma.lol/users/lain",
565                "https://shitposter.club/user/5381"
566              ]
567     end
568
569     test "returns recipients when object not found" do
570       user = insert(:user)
571       object = insert(:note, user: user)
572
573       activity = insert(:note_activity, user: user, note: object)
574       Pleroma.Repo.delete(object)
575
576       obj_url = activity.data["object"]
577
578       Tesla.Mock.mock(fn
579         %{method: :get, url: ^obj_url} ->
580           %Tesla.Env{status: 404, body: ""}
581       end)
582
583       assert Utils.maybe_notify_mentioned_recipients(["test-test"], activity) == [
584                "test-test"
585              ]
586     end
587   end
588
589   describe "attachments_from_ids_descs/2" do
590     test "returns [] when attachment ids is empty" do
591       assert Utils.attachments_from_ids_descs([], "{}") == []
592     end
593
594     test "returns list attachments with desc" do
595       object = insert(:note)
596       desc = Jason.encode!(%{object.id => "test-desc"})
597
598       assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [
599                Map.merge(object.data, %{"name" => "test-desc"})
600              ]
601     end
602   end
603
604   describe "attachments_from_ids/1" do
605     test "returns attachments with descs" do
606       object = insert(:note)
607       desc = Jason.encode!(%{object.id => "test-desc"})
608
609       assert Utils.attachments_from_ids(%{
610                media_ids: ["#{object.id}"],
611                descriptions: desc
612              }) == [
613                Map.merge(object.data, %{"name" => "test-desc"})
614              ]
615     end
616
617     test "returns attachments without descs" do
618       object = insert(:note)
619       assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data]
620     end
621
622     test "returns [] when not pass media_ids" do
623       assert Utils.attachments_from_ids(%{}) == []
624     end
625   end
626
627   describe "maybe_add_list_data/3" do
628     test "adds list params when found user list" do
629       user = insert(:user)
630       {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user)
631
632       assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) ==
633                %{
634                  additional: %{"bcc" => [list.ap_id], "listMessage" => list.ap_id},
635                  object: %{"listMessage" => list.ap_id}
636                }
637     end
638
639     test "returns original params when list not found" do
640       user = insert(:user)
641       {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user))
642
643       assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) ==
644                %{additional: %{}, object: %{}}
645     end
646   end
647
648   describe "maybe_add_attachments/3" do
649     test "returns parsed results when attachment_links is false" do
650       assert Utils.maybe_add_attachments(
651                {"test", [], ["tags"]},
652                [],
653                false
654              ) == {"test", [], ["tags"]}
655     end
656
657     test "adds attachments to parsed results" do
658       attachment = %{"url" => [%{"href" => "SakuraPM.png"}]}
659
660       assert Utils.maybe_add_attachments(
661                {"test", [], ["tags"]},
662                [attachment],
663                true
664              ) == {
665                "test<br><a href=\"SakuraPM.png\" class='attachment'>SakuraPM.png</a>",
666                [],
667                ["tags"]
668              }
669     end
670   end
671 end