diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/fixtures/httpoison_mock/rinpatch.json | 64 | ||||
| -rw-r--r-- | test/fixtures/mastodon-question-activity.json | 99 | ||||
| -rw-r--r-- | test/fixtures/mastodon-vote.json | 16 | ||||
| -rw-r--r-- | test/support/http_request_mock.ex | 8 | ||||
| -rw-r--r-- | test/web/activity_pub/transmogrifier_test.exs | 73 | ||||
| -rw-r--r-- | test/web/mastodon_api/mastodon_api_controller_test.exs | 220 | ||||
| -rw-r--r-- | test/web/mastodon_api/status_view_test.exs | 103 | 
7 files changed, 582 insertions, 1 deletions
| diff --git a/test/fixtures/httpoison_mock/rinpatch.json b/test/fixtures/httpoison_mock/rinpatch.json new file mode 100644 index 000000000..59311ecb6 --- /dev/null +++ b/test/fixtures/httpoison_mock/rinpatch.json @@ -0,0 +1,64 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    "https://w3id.org/security/v1", +    { +      "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", +      "toot": "http://joinmastodon.org/ns#", +      "featured": { +        "@id": "toot:featured", +        "@type": "@id" +      }, +      "alsoKnownAs": { +        "@id": "as:alsoKnownAs", +        "@type": "@id" +      }, +      "movedTo": { +        "@id": "as:movedTo", +        "@type": "@id" +      }, +      "schema": "http://schema.org#", +      "PropertyValue": "schema:PropertyValue", +      "value": "schema:value", +      "Hashtag": "as:Hashtag", +      "Emoji": "toot:Emoji", +      "IdentityProof": "toot:IdentityProof", +      "focalPoint": { +        "@container": "@list", +        "@id": "toot:focalPoint" +      } +    } +  ], +  "id": "https://mastodon.sdf.org/users/rinpatch", +  "type": "Person", +  "following": "https://mastodon.sdf.org/users/rinpatch/following", +  "followers": "https://mastodon.sdf.org/users/rinpatch/followers", +  "inbox": "https://mastodon.sdf.org/users/rinpatch/inbox", +  "outbox": "https://mastodon.sdf.org/users/rinpatch/outbox", +  "featured": "https://mastodon.sdf.org/users/rinpatch/collections/featured", +  "preferredUsername": "rinpatch", +  "name": "rinpatch", +  "summary": "<p>umu</p>", +  "url": "https://mastodon.sdf.org/@rinpatch", +  "manuallyApprovesFollowers": false, +  "publicKey": { +    "id": "https://mastodon.sdf.org/users/rinpatch#main-key", +    "owner": "https://mastodon.sdf.org/users/rinpatch", +    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1vbhYKDopb5xzfJB2TZY\n0ZvgxqdAhbSKKkQC5Q2b0ofhvueDy2AuZTnVk1/BbHNlqKlwhJUSpA6LiTZVvtcc\nMn6cmSaJJEg30gRF5GARP8FMcuq8e2jmceiW99NnUX17MQXsddSf2JFUwD0rUE8H\nBsgD7UzE9+zlA/PJOTBO7fvBEz9PTQ3r4sRMTJVFvKz2MU/U+aRNTuexRKMMPnUw\nfp6VWh1F44VWJEQOs4tOEjGiQiMQh5OfBk1w2haT3vrDbQvq23tNpUP1cRomLUtx\nEBcGKi5DMMBzE1RTVT1YUykR/zLWlA+JSmw7P6cWtsHYZovs8dgn8Po3X//6N+ng\nTQIDAQAB\n-----END PUBLIC KEY-----\n" +  }, +  "tag": [], +  "attachment": [], +  "endpoints": { +    "sharedInbox": "https://mastodon.sdf.org/inbox" +  }, +  "icon": { +    "type": "Image", +    "mediaType": "image/jpeg", +    "url": "https://mastodon.sdf.org/system/accounts/avatars/000/067/580/original/bf05521bf711b7a0.jpg?1533238802" +  }, +  "image": { +    "type": "Image", +    "mediaType": "image/gif", +    "url": "https://mastodon.sdf.org/system/accounts/headers/000/067/580/original/a99b987e798f7063.gif?1533278217" +  } +} diff --git a/test/fixtures/mastodon-question-activity.json b/test/fixtures/mastodon-question-activity.json new file mode 100644 index 000000000..ac329c7d5 --- /dev/null +++ b/test/fixtures/mastodon-question-activity.json @@ -0,0 +1,99 @@ +{ +  "@context": [ +    "https://www.w3.org/ns/activitystreams", +    { +      "ostatus": "http://ostatus.org#", +      "atomUri": "ostatus:atomUri", +      "inReplyToAtomUri": "ostatus:inReplyToAtomUri", +      "conversation": "ostatus:conversation", +      "sensitive": "as:sensitive", +      "Hashtag": "as:Hashtag", +      "toot": "http://joinmastodon.org/ns#", +      "Emoji": "toot:Emoji", +      "focalPoint": { +        "@container": "@list", +        "@id": "toot:focalPoint" +      } +    } +  ], +  "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/activity", +  "type": "Create", +  "actor": "https://mastodon.sdf.org/users/rinpatch", +  "published": "2019-05-10T09:03:36Z", +  "to": [ +    "https://www.w3.org/ns/activitystreams#Public" +  ], +  "cc": [ +    "https://mastodon.sdf.org/users/rinpatch/followers" +  ], +  "object": { +    "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304", +    "type": "Question", +    "summary": null, +    "inReplyTo": null, +    "published": "2019-05-10T09:03:36Z", +    "url": "https://mastodon.sdf.org/@rinpatch/102070944809637304", +    "attributedTo": "https://mastodon.sdf.org/users/rinpatch", +    "to": [ +      "https://www.w3.org/ns/activitystreams#Public" +    ], +    "cc": [ +      "https://mastodon.sdf.org/users/rinpatch/followers" +    ], +    "sensitive": false, +    "atomUri": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304", +    "inReplyToAtomUri": null, +    "conversation": "tag:mastodon.sdf.org,2019-05-10:objectId=15095122:objectType=Conversation", +    "content": "<p>Why is Tenshi eating a corndog so cute?</p>", +    "contentMap": { +      "en": "<p>Why is Tenshi eating a corndog so cute?</p>" +    }, +    "endTime": "2019-05-11T09:03:36Z", +    "closed": "2019-05-11T09:03:36Z", +    "attachment": [], +    "tag": [], +    "replies": { +      "id": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/replies", +      "type": "Collection", +      "first": { +        "type": "CollectionPage", +        "partOf": "https://mastodon.sdf.org/users/rinpatch/statuses/102070944809637304/replies", +        "items": [] +      } +    }, +    "oneOf": [ +      { +        "type": "Note", +        "name": "Dunno", +        "replies": { +          "type": "Collection", +          "totalItems": 0 +        } +      }, +      { +        "type": "Note", +        "name": "Everyone knows that!", +        "replies": { +          "type": "Collection", +          "totalItems": 1 +        } +      }, +      { +        "type": "Note", +        "name": "25 char limit is dumb", +        "replies": { +          "type": "Collection", +          "totalItems": 0 +        } +      }, +      { +        "type": "Note", +        "name": "I can't even fit a funny", +        "replies": { +          "type": "Collection", +          "totalItems": 1 +        } +      } +    ] +  } +} diff --git a/test/fixtures/mastodon-vote.json b/test/fixtures/mastodon-vote.json new file mode 100644 index 000000000..c2c5f40c0 --- /dev/null +++ b/test/fixtures/mastodon-vote.json @@ -0,0 +1,16 @@ +{ +  "@context": "https://www.w3.org/ns/activitystreams", +  "actor": "https://mastodon.sdf.org/users/rinpatch", +  "id": "https://mastodon.sdf.org/users/rinpatch#votes/387/activity", +  "nickname": "rin", +  "object": { +    "attributedTo": "https://mastodon.sdf.org/users/rinpatch", +    "id": "https://mastodon.sdf.org/users/rinpatch#votes/387", +    "inReplyTo": "https://testing.uguu.ltd/objects/9d300947-2dcb-445d-8978-9a3b4b84fa14", +    "name": "suya..", +    "to": "https://testing.uguu.ltd/users/rin", +    "type": "Note" +  }, +  "to": "https://testing.uguu.ltd/users/rin", +  "type": "Create" +} diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 66d7d5ba9..36b9265e7 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -52,6 +52,14 @@ defmodule HttpRequestMock do       }}    end +  def get("https://mastodon.sdf.org/users/rinpatch", _, _, _) do +    {:ok, +     %Tesla.Env{ +       status: 200, +       body: File.read!("test/fixtures/httpoison_mock/rinpatch.json") +     }} +  end +    def get(          "https://mastodon.social/.well-known/webfinger?resource=https://mastodon.social/users/emelie",          _, diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index bcc460f1c..89c8f79c9 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -113,6 +113,55 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert Enum.at(object.data["tag"], 2) == "moo"      end +    test "it works for incoming questions" do +      data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!() + +      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + +      object = Object.normalize(activity) + +      assert Enum.all?(object.data["oneOf"], fn choice -> +               choice["name"] in [ +                 "Dunno", +                 "Everyone knows that!", +                 "25 char limit is dumb", +                 "I can't even fit a funny" +               ] +             end) +    end + +    test "it rewrites Note votes to Answers and increments vote counters on question activities" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "suya...", +          "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10} +        }) + +      object = Object.normalize(activity) + +      data = +        File.read!("test/fixtures/mastodon-vote.json") +        |> Poison.decode!() +        |> Kernel.put_in(["to"], user.ap_id) +        |> Kernel.put_in(["object", "inReplyTo"], object.data["id"]) +        |> Kernel.put_in(["object", "to"], user.ap_id) + +      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) +      answer_object = Object.normalize(activity) +      assert answer_object.data["type"] == "Answer" +      object = Object.get_by_ap_id(object.data["id"]) + +      assert Enum.any?( +               object.data["oneOf"], +               fn +                 %{"name" => "suya..", "replies" => %{"totalItems" => 1}} -> true +                 _ -> false +               end +             ) +    end +      test "it works for incoming notices with contentMap" do        data =          File.read!("test/fixtures/mastodon-post-activity-contentmap.json") |> Poison.decode!() @@ -1210,6 +1259,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      end    end +  test "Rewrites Answers to Notes" do +    user = insert(:user) + +    {:ok, poll_activity} = +      CommonAPI.post(user, %{ +        "status" => "suya...", +        "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10} +      }) + +    poll_object = Object.normalize(poll_activity) +    # TODO: Replace with CommonAPI vote creation when implemented +    data = +      File.read!("test/fixtures/mastodon-vote.json") +      |> Poison.decode!() +      |> Kernel.put_in(["to"], user.ap_id) +      |> Kernel.put_in(["object", "inReplyTo"], poll_object.data["id"]) +      |> Kernel.put_in(["object", "to"], user.ap_id) + +    {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) +    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) + +    assert data["object"]["type"] == "Note" +  end +    describe "fix_explicit_addressing" do      setup do        user = insert(:user) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 9482f4cb8..e941aae5b 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -146,6 +146,103 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      refute id == third_id    end +  describe "posting polls" do +    test "posting a poll", %{conn: conn} do +      user = insert(:user) +      time = NaiveDateTime.utc_now() + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "Who is the #bestgrill?", +          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} +        }) + +      response = json_response(conn, 200) + +      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> +               title in ["Rei", "Asuka", "Misato"] +             end) + +      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 +      refute response["poll"]["expred"] +    end + +    test "option limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Pleroma.Config.get([:instance, :poll_limits, :max_options]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "desu~", +          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll can't contain more than #{limit} options" +    end + +    test "option character limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "...", +          "poll" => %{ +            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], +            "expires_in" => 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll options cannot be longer than #{limit} characters each" +    end + +    test "minimal date limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit - 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too soon" +    end + +    test "maximum date limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit + 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too far in the future" +    end +  end +    test "posting a sensitive status", %{conn: conn} do      user = insert(:user) @@ -2542,7 +2639,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do               "stats" => _,               "thumbnail" => _,               "languages" => _, -             "registrations" => _ +             "registrations" => _, +             "poll_limits" => _             } = result      assert email == from_config_email @@ -3441,4 +3539,124 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}      end    end + +  describe "GET /api/v1/polls/:id" do +    test "returns poll entity for object id", %{conn: conn} do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Pleroma does", +          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/polls/#{object.id}") + +      response = json_response(conn, 200) +      id = object.id +      assert %{"id" => ^id, "expired" => false, "multiple" => false} = response +    end + +    test "does not expose polls for private statuses", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Pleroma does", +          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, +          "visibility" => "private" +        }) + +      object = Object.normalize(activity) + +      conn = +        conn +        |> assign(:user, other_user) +        |> get("/api/v1/polls/#{object.id}") + +      assert json_response(conn, 404) +    end +  end + +  describe "POST /api/v1/polls/:id/votes" do +    test "votes are added to the poll", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "A very delicious sandwich", +          "poll" => %{ +            "options" => ["Lettuce", "Grilled Bacon", "Tomato"], +            "expires_in" => 20, +            "multiple" => true +          } +        }) + +      object = Object.normalize(activity) + +      conn = +        conn +        |> assign(:user, other_user) +        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) + +      assert json_response(conn, 200) +      object = Object.get_by_id(object.id) + +      assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> +               total_items == 1 +             end) +    end + +    test "author can't vote", %{conn: conn} do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Am I cute?", +          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      assert conn +             |> assign(:user, user) +             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) +             |> json_response(422) == %{"error" => "Poll's author can't vote"} + +      object = Object.get_by_id(object.id) + +      refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 +    end + +    test "does not allow multiple choices on a single-choice question", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "The glass is", +          "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      assert conn +             |> assign(:user, other_user) +             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) +             |> json_response(422) == %{"error" => "Too many choices"} + +      object = Object.get_by_id(object.id) + +      refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> +               total_items == 1 +             end) +    end +  end  end diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/status_view_test.exs index d7c800e83..ec75150ab 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/status_view_test.exs @@ -103,6 +103,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        muted: false,        pinned: false,        sensitive: false, +      poll: nil,        spoiler_text: HtmlSanitizeEx.basic_html(note.data["object"]["summary"]),        visibility: "public",        media_attachments: [], @@ -341,4 +342,106 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          StatusView.render("card.json", %{page_url: page_url, rich_media: card})      end    end + +  describe "poll view" do +    test "renders a poll" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Is Tenshi eating a corndog cute?", +          "poll" => %{ +            "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) + +      expected = %{ +        emojis: [], +        expired: false, +        id: object.id, +        multiple: false, +        options: [ +          %{title: "absolutely!", votes_count: 0}, +          %{title: "sure", votes_count: 0}, +          %{title: "yes", votes_count: 0}, +          %{title: "why are you even asking?", votes_count: 0} +        ], +        voted: false, +        votes_count: 0 +      } + +      result = StatusView.render("poll.json", %{object: object}) +      expires_at = result.expires_at +      result = Map.delete(result, :expires_at) + +      assert result == expected + +      expires_at = NaiveDateTime.from_iso8601!(expires_at) +      assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 +    end + +    test "detects if it is multiple choice" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Which Mastodon developer is your favourite?", +          "poll" => %{ +            "options" => ["Gargron", "Eugen"], +            "expires_in" => 20, +            "multiple" => true +          } +        }) + +      object = Object.normalize(activity) + +      assert %{multiple: true} = StatusView.render("poll.json", %{object: object}) +    end + +    test "detects emoji" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "What's with the smug face?", +          "poll" => %{ +            "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) + +      assert %{emojis: [%{shortcode: "blank"}]} = +               StatusView.render("poll.json", %{object: object}) +    end + +    test "detects vote status" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Which input devices do you use?", +          "poll" => %{ +            "options" => ["mouse", "trackball", "trackpoint"], +            "multiple" => true, +            "expires_in" => 20 +          } +        }) + +      object = Object.normalize(activity) + +      {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) + +      result = StatusView.render("poll.json", %{object: object, for: other_user}) + +      assert result[:voted] == true +      assert Enum.at(result[:options], 1)[:votes_count] == 1 +      assert Enum.at(result[:options], 2)[:votes_count] == 1 +    end +  end  end | 
