Use actual ISO8601 timestamps for masto API (#425)
[akkoma] / test / pleroma / web / common_api / utils_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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 end
182
183 test "link with local mention" do
184 insert(:user, %{nickname: "lain"})
185
186 code = "https://example.com/@lain"
187 {result, [], []} = Utils.format_input(code, "text/markdown")
188 assert result == ~s[<p><a href="#{code}">#{code}</a></p>]
189 end
190
191 test "local mentions" do
192 mario = insert(:user, %{nickname: "mario"})
193 luigi = insert(:user, %{nickname: "luigi"})
194
195 code = "@mario @luigi yo what's up?"
196 {result, _, []} = Utils.format_input(code, "text/markdown")
197
198 assert result ==
199 ~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>]
200 end
201
202 test "remote mentions" do
203 mario = insert(:user, %{nickname: "mario@mushroom.world", local: false})
204 luigi = insert(:user, %{nickname: "luigi@mushroom.world", local: false})
205
206 code = "@mario@mushroom.world @luigi@mushroom.world yo what's up?"
207 {result, _, []} = Utils.format_input(code, "text/markdown")
208
209 assert result ==
210 ~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>]
211 end
212
213 test "raw HTML" do
214 code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
215 {result, [], []} = Utils.format_input(code, "text/markdown")
216 assert result == ~s[<a href="http://example.org/">OwO</a>]
217 end
218
219 test "rulers" do
220 code = ~s[before\n\n-----\n\nafter]
221 {result, [], []} = Utils.format_input(code, "text/markdown")
222 assert result == "<p>before</p><hr/><p>after</p>"
223 end
224
225 test "blockquote" do
226 code = ~s[> whoms't are you quoting?]
227 {result, [], []} = Utils.format_input(code, "text/markdown")
228 assert result == "<blockquote><p>whoms’t are you quoting?</p></blockquote>"
229 end
230
231 test "code" do
232 code = ~s[`mix`]
233 {result, [], []} = Utils.format_input(code, "text/markdown")
234 assert result == ~s[<p><code class="inline">mix</code></p>]
235
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[```\nputs "Hello World"\n```]
241 {result, [], []} = Utils.format_input(code, "text/markdown")
242 assert result == ~s[<pre><code>puts &quot;Hello World&quot;</code></pre>]
243
244 code = ~s[ <div>\n </div>]
245 {result, [], []} = Utils.format_input(code, "text/markdown")
246 assert result == ~s[<pre><code>&lt;div&gt;\n&lt;/div&gt;</code></pre>]
247 end
248
249 test "lists" do
250 code = ~s[- one\n- two\n- three\n- four]
251 {result, [], []} = Utils.format_input(code, "text/markdown")
252 assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
253
254 code = ~s[1. one\n2. two\n3. three\n4. four\n]
255 {result, [], []} = Utils.format_input(code, "text/markdown")
256 assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
257 end
258
259 test "delegated renderers" do
260 code = ~s[*aaaa~*]
261 {result, [], []} = Utils.format_input(code, "text/markdown")
262 assert result == ~s[<p><em>aaaa~</em></p>]
263
264 code = ~s[**aaaa~**]
265 {result, [], []} = Utils.format_input(code, "text/markdown")
266 assert result == ~s[<p><strong>aaaa~</strong></p>]
267
268 # strikethrough
269 code = ~s[~~aaaa~~~]
270 {result, [], []} = Utils.format_input(code, "text/markdown")
271 assert result == ~s[<p><del>aaaa</del>~</p>]
272 end
273 end
274
275 describe "formats date to asctime" do
276 test "when date is in ISO 8601 format" do
277 date = DateTime.utc_now() |> DateTime.to_iso8601()
278
279 expected =
280 date
281 |> DateTime.from_iso8601()
282 |> elem(1)
283 |> Calendar.Strftime.strftime!("%a %b %d %H:%M:%S %z %Y")
284
285 assert Utils.date_to_asctime(date) == expected
286 end
287
288 test "when date is a binary in wrong format" do
289 date = DateTime.utc_now()
290
291 expected = ""
292
293 assert capture_log(fn ->
294 assert Utils.date_to_asctime(date) == expected
295 end) =~ "[warning] Date #{date} in wrong format, must be ISO 8601"
296 end
297
298 test "when date is a Unix timestamp" do
299 date = DateTime.utc_now() |> DateTime.to_unix()
300
301 expected = ""
302
303 assert capture_log(fn ->
304 assert Utils.date_to_asctime(date) == expected
305 end) =~ "[warning] Date #{date} in wrong format, must be ISO 8601"
306 end
307
308 test "when date is nil" do
309 expected = ""
310
311 assert capture_log(fn ->
312 assert Utils.date_to_asctime(nil) == expected
313 end) =~ "[warning] Date in wrong format, must be ISO 8601"
314 end
315
316 test "when date is a random string" do
317 assert capture_log(fn ->
318 assert Utils.date_to_asctime("foo") == ""
319 end) =~ "[warning] Date foo in wrong format, must be ISO 8601"
320 end
321 end
322
323 describe "get_to_and_cc" do
324 test "for public posts, not a reply" do
325 user = insert(:user)
326 mentioned_user = insert(:user)
327 draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "public"}
328
329 {to, cc} = Utils.get_to_and_cc(draft)
330
331 assert length(to) == 2
332 assert length(cc) == 1
333
334 assert @public_address in to
335 assert mentioned_user.ap_id in to
336 assert user.follower_address in cc
337 end
338
339 test "for public posts, a reply" do
340 user = insert(:user)
341 mentioned_user = insert(:user)
342 third_user = insert(:user)
343 {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
344
345 draft = %ActivityDraft{
346 user: user,
347 mentions: [mentioned_user.ap_id],
348 visibility: "public",
349 in_reply_to: activity
350 }
351
352 {to, cc} = Utils.get_to_and_cc(draft)
353
354 assert length(to) == 3
355 assert length(cc) == 1
356
357 assert @public_address in to
358 assert mentioned_user.ap_id in to
359 assert third_user.ap_id in to
360 assert user.follower_address in cc
361 end
362
363 test "for unlisted posts, not a reply" do
364 user = insert(:user)
365 mentioned_user = insert(:user)
366 draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "unlisted"}
367
368 {to, cc} = Utils.get_to_and_cc(draft)
369
370 assert length(to) == 2
371 assert length(cc) == 1
372
373 assert @public_address in cc
374 assert mentioned_user.ap_id in to
375 assert user.follower_address in to
376 end
377
378 test "for unlisted posts, a reply" do
379 user = insert(:user)
380 mentioned_user = insert(:user)
381 third_user = insert(:user)
382 {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
383
384 draft = %ActivityDraft{
385 user: user,
386 mentions: [mentioned_user.ap_id],
387 visibility: "unlisted",
388 in_reply_to: activity
389 }
390
391 {to, cc} = Utils.get_to_and_cc(draft)
392
393 assert length(to) == 3
394 assert length(cc) == 1
395
396 assert @public_address in cc
397 assert mentioned_user.ap_id in to
398 assert third_user.ap_id in to
399 assert user.follower_address in to
400 end
401
402 test "for private posts, not a reply" do
403 user = insert(:user)
404 mentioned_user = insert(:user)
405 draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "private"}
406
407 {to, cc} = Utils.get_to_and_cc(draft)
408 assert length(to) == 2
409 assert Enum.empty?(cc)
410
411 assert mentioned_user.ap_id in to
412 assert user.follower_address in to
413 end
414
415 test "for private posts, a reply" do
416 user = insert(:user)
417 mentioned_user = insert(:user)
418 third_user = insert(:user)
419 {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
420
421 draft = %ActivityDraft{
422 user: user,
423 mentions: [mentioned_user.ap_id],
424 visibility: "private",
425 in_reply_to: activity
426 }
427
428 {to, cc} = Utils.get_to_and_cc(draft)
429
430 assert length(to) == 2
431 assert Enum.empty?(cc)
432
433 assert mentioned_user.ap_id in to
434 assert user.follower_address in to
435 end
436
437 test "for direct posts, not a reply" do
438 user = insert(:user)
439 mentioned_user = insert(:user)
440 draft = %ActivityDraft{user: user, mentions: [mentioned_user.ap_id], visibility: "direct"}
441
442 {to, cc} = Utils.get_to_and_cc(draft)
443
444 assert length(to) == 1
445 assert Enum.empty?(cc)
446
447 assert mentioned_user.ap_id in to
448 end
449
450 test "for direct posts, a reply" do
451 user = insert(:user)
452 mentioned_user = insert(:user)
453 third_user = insert(:user)
454 {:ok, activity} = CommonAPI.post(third_user, %{status: "uguu"})
455
456 draft = %ActivityDraft{
457 user: user,
458 mentions: [mentioned_user.ap_id],
459 visibility: "direct",
460 in_reply_to: activity
461 }
462
463 {to, cc} = Utils.get_to_and_cc(draft)
464
465 assert length(to) == 1
466 assert Enum.empty?(cc)
467
468 assert mentioned_user.ap_id in to
469
470 {:ok, direct_activity} = CommonAPI.post(third_user, %{status: "uguu", visibility: "direct"})
471
472 draft = %ActivityDraft{
473 user: user,
474 mentions: [mentioned_user.ap_id],
475 visibility: "direct",
476 in_reply_to: direct_activity
477 }
478
479 {to, cc} = Utils.get_to_and_cc(draft)
480
481 assert length(to) == 2
482 assert Enum.empty?(cc)
483
484 assert mentioned_user.ap_id in to
485 assert third_user.ap_id in to
486 end
487 end
488
489 describe "to_master_date/1" do
490 test "removes microseconds from date (NaiveDateTime)" do
491 assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z"
492 end
493
494 test "removes microseconds from date (String)" do
495 assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z"
496 end
497
498 test "returns unix epoch when date invalid" do
499 assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == "1970-01-01T00:00:00Z"
500 end
501
502 test "returns unix epoch when date is before the introduction of the Gregorian Calendar" do
503 assert Utils.to_masto_date("0621-01-01T00:00:00Z") == "1970-01-01T00:00:00Z"
504 end
505
506 test "returns unix epoch when date is BCE" do
507 assert Utils.to_masto_date("-0420-01-01T00:00:00Z") == "1970-01-01T00:00:00Z"
508 end
509 end
510
511 describe "maybe_notify_mentioned_recipients/2" do
512 test "returns recipients when activity is not `Create`" do
513 activity = insert(:like_activity)
514 assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == ["test"]
515 end
516
517 test "returns recipients from tag" do
518 user = insert(:user)
519
520 object =
521 insert(:note,
522 user: user,
523 data: %{
524 "tag" => [
525 %{"type" => "Hashtag"},
526 "",
527 %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"},
528 %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"},
529 %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}
530 ]
531 }
532 )
533
534 activity = insert(:note_activity, user: user, note: object)
535
536 assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [
537 "test",
538 "https://testing.pleroma.lol/users/lain",
539 "https://shitposter.club/user/5381"
540 ]
541 end
542
543 test "returns recipients when object is map" do
544 user = insert(:user)
545 object = insert(:note, user: user)
546
547 activity =
548 insert(:note_activity,
549 user: user,
550 note: object,
551 data_attrs: %{
552 "object" => %{
553 "tag" => [
554 %{"type" => "Hashtag"},
555 "",
556 %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"},
557 %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"},
558 %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}
559 ]
560 }
561 }
562 )
563
564 Pleroma.Repo.delete(object)
565
566 assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [
567 "test",
568 "https://testing.pleroma.lol/users/lain",
569 "https://shitposter.club/user/5381"
570 ]
571 end
572
573 test "returns recipients when object not found" do
574 user = insert(:user)
575 object = insert(:note, user: user)
576
577 activity = insert(:note_activity, user: user, note: object)
578 Pleroma.Repo.delete(object)
579
580 obj_url = activity.data["object"]
581
582 Tesla.Mock.mock(fn
583 %{method: :get, url: ^obj_url} ->
584 %Tesla.Env{status: 404, body: ""}
585 end)
586
587 assert Utils.maybe_notify_mentioned_recipients(["test-test"], activity) == [
588 "test-test"
589 ]
590 end
591 end
592
593 describe "attachments_from_ids_descs/2" do
594 test "returns [] when attachment ids is empty" do
595 assert Utils.attachments_from_ids_descs([], "{}") == []
596 end
597
598 test "returns list attachments with desc" do
599 object = insert(:note)
600 desc = Jason.encode!(%{object.id => "test-desc"})
601
602 assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [
603 Map.merge(object.data, %{"name" => "test-desc"})
604 ]
605 end
606 end
607
608 describe "attachments_from_ids/1" do
609 test "returns attachments with descs" do
610 object = insert(:note)
611 desc = Jason.encode!(%{object.id => "test-desc"})
612
613 assert Utils.attachments_from_ids(%{
614 media_ids: ["#{object.id}"],
615 descriptions: desc
616 }) == [
617 Map.merge(object.data, %{"name" => "test-desc"})
618 ]
619 end
620
621 test "returns attachments without descs" do
622 object = insert(:note)
623 assert Utils.attachments_from_ids(%{media_ids: ["#{object.id}"]}) == [object.data]
624 end
625
626 test "returns [] when not pass media_ids" do
627 assert Utils.attachments_from_ids(%{}) == []
628 end
629 end
630
631 describe "maybe_add_list_data/3" do
632 test "adds list params when found user list" do
633 user = insert(:user)
634 {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user)
635
636 assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) ==
637 %{
638 additional: %{"bcc" => [list.ap_id], "listMessage" => list.ap_id},
639 object: %{"listMessage" => list.ap_id}
640 }
641 end
642
643 test "returns original params when list not found" do
644 user = insert(:user)
645 {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user))
646
647 assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) ==
648 %{additional: %{}, object: %{}}
649 end
650 end
651
652 describe "maybe_add_attachments/3" do
653 test "returns parsed results when attachment_links is false" do
654 assert Utils.maybe_add_attachments(
655 {"test", [], ["tags"]},
656 [],
657 false
658 ) == {"test", [], ["tags"]}
659 end
660
661 test "adds attachments to parsed results" do
662 attachment = %{"url" => [%{"href" => "SakuraPM.png"}]}
663
664 assert Utils.maybe_add_attachments(
665 {"test", [], ["tags"]},
666 [attachment],
667 true
668 ) == {
669 "test<br><a href=\"SakuraPM.png\" class='attachment'>SakuraPM.png</a>",
670 [],
671 ["tags"]
672 }
673 end
674 end
675 end