7912b1d5f714b71a9a687f057c33a93f7a39f724
[anni] / test / pleroma / web / mastodon_api / controllers / poll_controller_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.MastodonAPI.PollControllerTest do
6   use Pleroma.Web.ConnCase, async: true
7
8   alias Pleroma.Object
9   alias Pleroma.Web.CommonAPI
10
11   import Pleroma.Factory
12
13   describe "GET /api/v1/polls/:id" do
14     setup do: oauth_access(["read:statuses"])
15
16     test "returns poll entity for object id", %{user: user, conn: conn} do
17       {:ok, activity} =
18         CommonAPI.post(user, %{
19           status: "Pleroma does",
20           poll: %{options: ["what Mastodon't", "n't what Mastodoes"], expires_in: 20}
21         })
22
23       object = Object.normalize(activity, fetch: false)
24
25       conn = get(conn, "/api/v1/polls/#{object.id}")
26
27       response = json_response_and_validate_schema(conn, 200)
28       id = to_string(object.id)
29       assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
30     end
31
32     test "does not expose polls for private statuses", %{conn: conn} do
33       other_user = insert(:user)
34
35       {:ok, activity} =
36         CommonAPI.post(other_user, %{
37           status: "Pleroma does",
38           poll: %{options: ["what Mastodon't", "n't what Mastodoes"], expires_in: 20},
39           visibility: "private"
40         })
41
42       object = Object.normalize(activity, fetch: false)
43
44       conn = get(conn, "/api/v1/polls/#{object.id}")
45
46       assert json_response_and_validate_schema(conn, 404)
47     end
48   end
49
50   test "own_votes" do
51     %{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
52
53     other_user = insert(:user)
54
55     {:ok, activity} =
56       CommonAPI.post(other_user, %{
57         status: "A very delicious sandwich",
58         poll: %{
59           options: ["Lettuce", "Grilled Bacon", "Tomato"],
60           expires_in: 20,
61           multiple: true
62         }
63       })
64
65     object = Object.normalize(activity, fetch: false)
66
67     conn
68     |> put_req_header("content-type", "application/json")
69     |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 2]})
70     |> json_response_and_validate_schema(200)
71
72     object = Object.get_by_id(object.id)
73
74     assert [
75              %{
76                "name" => "Lettuce",
77                "replies" => %{"totalItems" => 1, "type" => "Collection"},
78                "type" => "Note"
79              },
80              %{
81                "name" => "Grilled Bacon",
82                "replies" => %{"totalItems" => 0, "type" => "Collection"},
83                "type" => "Note"
84              },
85              %{
86                "name" => "Tomato",
87                "replies" => %{"totalItems" => 1, "type" => "Collection"},
88                "type" => "Note"
89              }
90            ] == object.data["anyOf"]
91
92     assert %{"replies" => %{"totalItems" => 0}} =
93              Enum.find(object.data["anyOf"], fn %{"name" => name} -> name == "Grilled Bacon" end)
94
95     Enum.each(["Lettuce", "Tomato"], fn title ->
96       %{"replies" => %{"totalItems" => total_items}} =
97         Enum.find(object.data["anyOf"], fn %{"name" => name} -> name == title end)
98
99       assert total_items == 1
100     end)
101
102     assert %{
103              "own_votes" => own_votes,
104              "voted" => true
105            } =
106              conn
107              |> get("/api/v1/polls/#{object.id}")
108              |> json_response_and_validate_schema(200)
109
110     assert 0 in own_votes
111     assert 2 in own_votes
112     # for non authenticated user
113     response =
114       build_conn()
115       |> get("/api/v1/polls/#{object.id}")
116       |> json_response_and_validate_schema(200)
117
118     refute Map.has_key?(response, "own_votes")
119     refute Map.has_key?(response, "voted")
120   end
121
122   describe "POST /api/v1/polls/:id/votes" do
123     setup do: oauth_access(["write:statuses"])
124
125     test "votes are added to the poll", %{conn: conn} do
126       other_user = insert(:user)
127
128       {:ok, activity} =
129         CommonAPI.post(other_user, %{
130           status: "A very delicious sandwich",
131           poll: %{
132             options: ["Lettuce", "Grilled Bacon", "Tomato"],
133             expires_in: 20,
134             multiple: true
135           }
136         })
137
138       object = Object.normalize(activity, fetch: false)
139
140       conn
141       |> put_req_header("content-type", "application/json")
142       |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
143       |> json_response_and_validate_schema(200)
144
145       object = Object.get_by_id(object.id)
146
147       assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
148                total_items == 1
149              end)
150     end
151
152     test "author can't vote", %{user: user, conn: conn} do
153       {:ok, activity} =
154         CommonAPI.post(user, %{
155           status: "Am I cute?",
156           poll: %{options: ["Yes", "No"], expires_in: 20}
157         })
158
159       object = Object.normalize(activity, fetch: false)
160
161       assert conn
162              |> put_req_header("content-type", "application/json")
163              |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
164              |> json_response_and_validate_schema(422) == %{"error" => "Poll's author can't vote"}
165
166       object = Object.get_by_id(object.id)
167
168       refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
169     end
170
171     test "does not allow multiple choices on a single-choice question", %{conn: conn} do
172       other_user = insert(:user)
173
174       {:ok, activity} =
175         CommonAPI.post(other_user, %{
176           status: "The glass is",
177           poll: %{options: ["half empty", "half full"], expires_in: 20}
178         })
179
180       object = Object.normalize(activity, fetch: false)
181
182       assert conn
183              |> put_req_header("content-type", "application/json")
184              |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
185              |> json_response_and_validate_schema(422) == %{"error" => "Too many choices"}
186
187       object = Object.get_by_id(object.id)
188
189       refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
190                total_items == 1
191              end)
192     end
193
194     test "does not allow choice index to be greater than options count", %{conn: conn} do
195       other_user = insert(:user)
196
197       {:ok, activity} =
198         CommonAPI.post(other_user, %{
199           status: "Am I cute?",
200           poll: %{options: ["Yes", "No"], expires_in: 20}
201         })
202
203       object = Object.normalize(activity, fetch: false)
204
205       conn =
206         conn
207         |> put_req_header("content-type", "application/json")
208         |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
209
210       assert json_response_and_validate_schema(conn, 422) == %{"error" => "Invalid indices"}
211     end
212
213     test "returns 404 error when object is not exist", %{conn: conn} do
214       conn =
215         conn
216         |> put_req_header("content-type", "application/json")
217         |> post("/api/v1/polls/1/votes", %{"choices" => [0]})
218
219       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
220     end
221
222     test "returns 404 when poll is private and not available for user", %{conn: conn} do
223       other_user = insert(:user)
224
225       {:ok, activity} =
226         CommonAPI.post(other_user, %{
227           status: "Am I cute?",
228           poll: %{options: ["Yes", "No"], expires_in: 20},
229           visibility: "private"
230         })
231
232       object = Object.normalize(activity, fetch: false)
233
234       conn =
235         conn
236         |> put_req_header("content-type", "application/json")
237         |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
238
239       assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
240     end
241   end
242 end