status_view_test.exs (16479B)
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 the direct conversation id when given the `with_conversation_id` option" do 27 user = insert(:user) 28 29 {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) 30 31 status = 32 StatusView.render("status.json", 33 activity: activity, 34 with_direct_conversation_id: true, 35 for: user 36 ) 37 38 assert status[:pleroma][:direct_conversation_id] 39 end 40 41 test "returns a temporary ap_id based user for activities missing db users" do 42 user = insert(:user) 43 44 {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) 45 46 Repo.delete(user) 47 Cachex.clear(:user_cache) 48 49 %{account: ms_user} = StatusView.render("status.json", activity: activity) 50 51 assert ms_user.acct == "erroruser@example.com" 52 end 53 54 test "tries to get a user by nickname if fetching by ap_id doesn't work" do 55 user = insert(:user) 56 57 {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) 58 59 {:ok, user} = 60 user 61 |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"}) 62 |> Repo.update() 63 64 Cachex.clear(:user_cache) 65 66 result = StatusView.render("status.json", activity: activity) 67 68 assert result[:account][:id] == to_string(user.id) 69 end 70 71 test "a note with null content" do 72 note = insert(:note_activity) 73 note_object = Object.normalize(note) 74 75 data = 76 note_object.data 77 |> Map.put("content", nil) 78 79 Object.change(note_object, %{data: data}) 80 |> Object.update_and_set_cache() 81 82 User.get_cached_by_ap_id(note.data["actor"]) 83 84 status = StatusView.render("status.json", %{activity: note}) 85 86 assert status.content == "" 87 end 88 89 test "a note activity" do 90 note = insert(:note_activity) 91 object_data = Object.normalize(note).data 92 user = User.get_cached_by_ap_id(note.data["actor"]) 93 94 convo_id = Utils.context_to_conversation_id(object_data["context"]) 95 96 status = StatusView.render("status.json", %{activity: note}) 97 98 created_at = 99 (object_data["published"] || "") 100 |> String.replace(~r/\.\d+Z/, ".000Z") 101 102 expected = %{ 103 id: to_string(note.id), 104 uri: object_data["id"], 105 url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note), 106 account: AccountView.render("account.json", %{user: user}), 107 in_reply_to_id: nil, 108 in_reply_to_account_id: nil, 109 card: nil, 110 reblog: nil, 111 content: HtmlSanitizeEx.basic_html(object_data["content"]), 112 created_at: created_at, 113 reblogs_count: 0, 114 replies_count: 0, 115 favourites_count: 0, 116 reblogged: false, 117 bookmarked: false, 118 favourited: false, 119 muted: false, 120 pinned: false, 121 sensitive: false, 122 poll: nil, 123 spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]), 124 visibility: "public", 125 media_attachments: [], 126 mentions: [], 127 tags: [ 128 %{ 129 name: "#{object_data["tag"]}", 130 url: "/tag/#{object_data["tag"]}" 131 } 132 ], 133 application: %{ 134 name: "Web", 135 website: nil 136 }, 137 language: nil, 138 emojis: [ 139 %{ 140 shortcode: "2hu", 141 url: "corndog.png", 142 static_url: "corndog.png", 143 visible_in_picker: false 144 } 145 ], 146 pleroma: %{ 147 local: true, 148 conversation_id: convo_id, 149 in_reply_to_account_acct: nil, 150 content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, 151 spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])}, 152 expires_at: nil, 153 direct_conversation_id: nil 154 } 155 } 156 157 assert status == expected 158 end 159 160 test "tells if the message is muted for some reason" do 161 user = insert(:user) 162 other_user = insert(:user) 163 164 {:ok, user} = User.mute(user, other_user) 165 166 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) 167 status = StatusView.render("status.json", %{activity: activity}) 168 169 assert status.muted == false 170 171 status = StatusView.render("status.json", %{activity: activity, for: user}) 172 173 assert status.muted == true 174 end 175 176 test "tells if the status is bookmarked" do 177 user = insert(:user) 178 179 {:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"}) 180 status = StatusView.render("status.json", %{activity: activity}) 181 182 assert status.bookmarked == false 183 184 status = StatusView.render("status.json", %{activity: activity, for: user}) 185 186 assert status.bookmarked == false 187 188 {:ok, _bookmark} = Bookmark.create(user.id, activity.id) 189 190 activity = Activity.get_by_id_with_object(activity.id) 191 192 status = StatusView.render("status.json", %{activity: activity, for: user}) 193 194 assert status.bookmarked == true 195 end 196 197 test "a reply" do 198 note = insert(:note_activity) 199 user = insert(:user) 200 201 {:ok, activity} = 202 CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id}) 203 204 status = StatusView.render("status.json", %{activity: activity}) 205 206 assert status.in_reply_to_id == to_string(note.id) 207 208 [status] = StatusView.render("index.json", %{activities: [activity], as: :activity}) 209 210 assert status.in_reply_to_id == to_string(note.id) 211 end 212 213 test "contains mentions" do 214 incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") 215 # a user with this ap id might be in the cache. 216 recipient = "https://pleroma.soykaf.com/users/lain" 217 user = insert(:user, %{ap_id: recipient}) 218 219 {:ok, [activity]} = OStatus.handle_incoming(incoming) 220 221 status = StatusView.render("status.json", %{activity: activity}) 222 223 assert status.mentions == 224 Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end) 225 end 226 227 test "create mentions from the 'to' field" do 228 %User{ap_id: recipient_ap_id} = insert(:user) 229 cc = insert_pair(:user) |> Enum.map(& &1.ap_id) 230 231 object = 232 insert(:note, %{ 233 data: %{ 234 "to" => [recipient_ap_id], 235 "cc" => cc 236 } 237 }) 238 239 activity = 240 insert(:note_activity, %{ 241 note: object, 242 recipients: [recipient_ap_id | cc] 243 }) 244 245 assert length(activity.recipients) == 3 246 247 %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) 248 249 assert length(mentions) == 1 250 assert mention.url == recipient_ap_id 251 end 252 253 test "create mentions from the 'tag' field" do 254 recipient = insert(:user) 255 cc = insert_pair(:user) |> Enum.map(& &1.ap_id) 256 257 object = 258 insert(:note, %{ 259 data: %{ 260 "cc" => cc, 261 "tag" => [ 262 %{ 263 "href" => recipient.ap_id, 264 "name" => recipient.nickname, 265 "type" => "Mention" 266 }, 267 %{ 268 "href" => "https://example.com/search?tag=test", 269 "name" => "#test", 270 "type" => "Hashtag" 271 } 272 ] 273 } 274 }) 275 276 activity = 277 insert(:note_activity, %{ 278 note: object, 279 recipients: [recipient.ap_id | cc] 280 }) 281 282 assert length(activity.recipients) == 3 283 284 %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) 285 286 assert length(mentions) == 1 287 assert mention.url == recipient.ap_id 288 end 289 290 test "attachments" do 291 object = %{ 292 "type" => "Image", 293 "url" => [ 294 %{ 295 "mediaType" => "image/png", 296 "href" => "someurl" 297 } 298 ], 299 "uuid" => 6 300 } 301 302 expected = %{ 303 id: "1638338801", 304 type: "image", 305 url: "someurl", 306 remote_url: "someurl", 307 preview_url: "someurl", 308 text_url: "someurl", 309 description: nil, 310 pleroma: %{mime_type: "image/png"} 311 } 312 313 assert expected == StatusView.render("attachment.json", %{attachment: object}) 314 315 # If theres a "id", use that instead of the generated one 316 object = Map.put(object, "id", 2) 317 assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) 318 end 319 320 test "put the url advertised in the Activity in to the url attribute" do 321 id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" 322 [activity] = Activity.search(nil, id) 323 324 status = StatusView.render("status.json", %{activity: activity}) 325 326 assert status.uri == id 327 assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" 328 end 329 330 test "a reblog" do 331 user = insert(:user) 332 activity = insert(:note_activity) 333 334 {:ok, reblog, _} = CommonAPI.repeat(activity.id, user) 335 336 represented = StatusView.render("status.json", %{for: user, activity: reblog}) 337 338 assert represented[:id] == to_string(reblog.id) 339 assert represented[:reblog][:id] == to_string(activity.id) 340 assert represented[:emojis] == [] 341 end 342 343 test "a peertube video" do 344 user = insert(:user) 345 346 {:ok, object} = 347 Pleroma.Object.Fetcher.fetch_object_from_id( 348 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" 349 ) 350 351 %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) 352 353 represented = StatusView.render("status.json", %{for: user, activity: activity}) 354 355 assert represented[:id] == to_string(activity.id) 356 assert length(represented[:media_attachments]) == 1 357 end 358 359 describe "build_tags/1" do 360 test "it returns a a dictionary tags" do 361 object_tags = [ 362 "fediverse", 363 "mastodon", 364 "nextcloud", 365 %{ 366 "href" => "https://kawen.space/users/lain", 367 "name" => "@lain@kawen.space", 368 "type" => "Mention" 369 } 370 ] 371 372 assert StatusView.build_tags(object_tags) == [ 373 %{name: "fediverse", url: "/tag/fediverse"}, 374 %{name: "mastodon", url: "/tag/mastodon"}, 375 %{name: "nextcloud", url: "/tag/nextcloud"} 376 ] 377 end 378 end 379 380 describe "rich media cards" do 381 test "a rich media card without a site name renders correctly" do 382 page_url = "http://example.com" 383 384 card = %{ 385 url: page_url, 386 image: page_url <> "/example.jpg", 387 title: "Example website" 388 } 389 390 %{provider_name: "example.com"} = 391 StatusView.render("card.json", %{page_url: page_url, rich_media: card}) 392 end 393 394 test "a rich media card without a site name or image renders correctly" do 395 page_url = "http://example.com" 396 397 card = %{ 398 url: page_url, 399 title: "Example website" 400 } 401 402 %{provider_name: "example.com"} = 403 StatusView.render("card.json", %{page_url: page_url, rich_media: card}) 404 end 405 406 test "a rich media card without an image renders correctly" do 407 page_url = "http://example.com" 408 409 card = %{ 410 url: page_url, 411 site_name: "Example site name", 412 title: "Example website" 413 } 414 415 %{provider_name: "Example site name"} = 416 StatusView.render("card.json", %{page_url: page_url, rich_media: card}) 417 end 418 419 test "a rich media card with all relevant data renders correctly" do 420 page_url = "http://example.com" 421 422 card = %{ 423 url: page_url, 424 site_name: "Example site name", 425 title: "Example website", 426 image: page_url <> "/example.jpg", 427 description: "Example description" 428 } 429 430 %{provider_name: "Example site name"} = 431 StatusView.render("card.json", %{page_url: page_url, rich_media: card}) 432 end 433 end 434 435 describe "poll view" do 436 test "renders a poll" do 437 user = insert(:user) 438 439 {:ok, activity} = 440 CommonAPI.post(user, %{ 441 "status" => "Is Tenshi eating a corndog cute?", 442 "poll" => %{ 443 "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], 444 "expires_in" => 20 445 } 446 }) 447 448 object = Object.normalize(activity) 449 450 expected = %{ 451 emojis: [], 452 expired: false, 453 id: to_string(object.id), 454 multiple: false, 455 options: [ 456 %{title: "absolutely!", votes_count: 0}, 457 %{title: "sure", votes_count: 0}, 458 %{title: "yes", votes_count: 0}, 459 %{title: "why are you even asking?", votes_count: 0} 460 ], 461 voted: false, 462 votes_count: 0 463 } 464 465 result = StatusView.render("poll.json", %{object: object}) 466 expires_at = result.expires_at 467 result = Map.delete(result, :expires_at) 468 469 assert result == expected 470 471 expires_at = NaiveDateTime.from_iso8601!(expires_at) 472 assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 473 end 474 475 test "detects if it is multiple choice" do 476 user = insert(:user) 477 478 {:ok, activity} = 479 CommonAPI.post(user, %{ 480 "status" => "Which Mastodon developer is your favourite?", 481 "poll" => %{ 482 "options" => ["Gargron", "Eugen"], 483 "expires_in" => 20, 484 "multiple" => true 485 } 486 }) 487 488 object = Object.normalize(activity) 489 490 assert %{multiple: true} = StatusView.render("poll.json", %{object: object}) 491 end 492 493 test "detects emoji" do 494 user = insert(:user) 495 496 {:ok, activity} = 497 CommonAPI.post(user, %{ 498 "status" => "What's with the smug face?", 499 "poll" => %{ 500 "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], 501 "expires_in" => 20 502 } 503 }) 504 505 object = Object.normalize(activity) 506 507 assert %{emojis: [%{shortcode: "blank"}]} = 508 StatusView.render("poll.json", %{object: object}) 509 end 510 511 test "detects vote status" do 512 user = insert(:user) 513 other_user = insert(:user) 514 515 {:ok, activity} = 516 CommonAPI.post(user, %{ 517 "status" => "Which input devices do you use?", 518 "poll" => %{ 519 "options" => ["mouse", "trackball", "trackpoint"], 520 "multiple" => true, 521 "expires_in" => 20 522 } 523 }) 524 525 object = Object.normalize(activity) 526 527 {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) 528 529 result = StatusView.render("poll.json", %{object: object, for: other_user}) 530 531 assert result[:voted] == true 532 assert Enum.at(result[:options], 1)[:votes_count] == 1 533 assert Enum.at(result[:options], 2)[:votes_count] == 1 534 end 535 end 536 537 test "embeds a relationship in the account" do 538 user = insert(:user) 539 other_user = insert(:user) 540 541 {:ok, activity} = 542 CommonAPI.post(user, %{ 543 "status" => "drink more water" 544 }) 545 546 result = StatusView.render("status.json", %{activity: activity, for: other_user}) 547 548 assert result[:account][:pleroma][:relationship] == 549 AccountView.render("relationship.json", %{user: other_user, target: user}) 550 end 551 552 test "embeds a relationship in the account in reposts" do 553 user = insert(:user) 554 other_user = insert(:user) 555 556 {:ok, activity} = 557 CommonAPI.post(user, %{ 558 "status" => "˙˙ɐʎns" 559 }) 560 561 {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user) 562 563 result = StatusView.render("status.json", %{activity: activity, for: user}) 564 565 assert result[:account][:pleroma][:relationship] == 566 AccountView.render("relationship.json", %{user: user, target: other_user}) 567 568 assert result[:reblog][:account][:pleroma][:relationship] == 569 AccountView.render("relationship.json", %{user: user, target: user}) 570 end 571 572 test "visibility/list" do 573 user = insert(:user) 574 575 {:ok, list} = Pleroma.List.create("foo", user) 576 577 {:ok, activity} = 578 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) 579 580 status = StatusView.render("status.json", activity: activity) 581 582 assert status.visibility == "list" 583 end 584 end