Home timeline tests: Add failing test for relationships
[akkoma] / test / web / mastodon_api / controllers / timeline_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
6 use Pleroma.Web.ConnCase
7
8 import Pleroma.Factory
9 import Tesla.Mock
10
11 alias Pleroma.Config
12 alias Pleroma.User
13 alias Pleroma.Web.CommonAPI
14
15 setup do
16 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
17 :ok
18 end
19
20 describe "home" do
21 setup do: oauth_access(["read:statuses"])
22
23 test "the home timeline", %{user: user, conn: conn} do
24 following = insert(:user, nickname: "followed")
25 third_user = insert(:user, nickname: "repeated")
26
27 {:ok, _activity} = CommonAPI.post(following, %{"status" => "post"})
28 {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
29 {:ok, _, _} = CommonAPI.repeat(activity.id, following)
30
31 ret_conn = get(conn, "/api/v1/timelines/home")
32
33 assert Enum.empty?(json_response(ret_conn, :ok))
34
35 {:ok, _user} = User.follow(user, following)
36
37 ret_conn = get(conn, "/api/v1/timelines/home")
38
39 assert [
40 %{
41 "reblog" => %{
42 "content" => "repeated post",
43 "account" => %{
44 "pleroma" => %{
45 "relationship" => %{"following" => false, "followed_by" => false}
46 }
47 }
48 },
49 "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
50 },
51 %{
52 "content" => "post",
53 "account" => %{
54 "acct" => "followed",
55 "pleroma" => %{"relationship" => %{"following" => true}}
56 }
57 }
58 ] = json_response(ret_conn, :ok)
59
60 {:ok, _user} = User.follow(third_user, user)
61
62 ret_conn = get(conn, "/api/v1/timelines/home")
63
64 assert [
65 %{
66 "reblog" => %{
67 "content" => "repeated post",
68 "account" => %{
69 "acct" => "repeated",
70 "pleroma" => %{
71 # This part does not match correctly
72 "relationship" => %{"following" => false, "followed_by" => true}
73 }
74 }
75 },
76 "account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
77 },
78 %{
79 "content" => "post",
80 "account" => %{
81 "acct" => "followed",
82 "pleroma" => %{"relationship" => %{"following" => true}}
83 }
84 }
85 ] = json_response(ret_conn, :ok)
86 end
87
88 test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do
89 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
90 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
91
92 {:ok, unlisted_activity} =
93 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
94
95 {:ok, private_activity} =
96 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
97
98 conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]})
99
100 assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"])
101 assert public_activity.id in status_ids
102 assert unlisted_activity.id in status_ids
103 assert private_activity.id in status_ids
104 refute direct_activity.id in status_ids
105 end
106 end
107
108 describe "public" do
109 @tag capture_log: true
110 test "the public timeline", %{conn: conn} do
111 following = insert(:user)
112
113 {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"})
114
115 _activity = insert(:note_activity, local: false)
116
117 conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"})
118
119 assert length(json_response(conn, :ok)) == 2
120
121 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"})
122
123 assert [%{"content" => "test"}] = json_response(conn, :ok)
124
125 conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"})
126
127 assert [%{"content" => "test"}] = json_response(conn, :ok)
128 end
129
130 test "the public timeline includes only public statuses for an authenticated user" do
131 %{user: user, conn: conn} = oauth_access(["read:statuses"])
132
133 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
134 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
135 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
136 {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
137
138 res_conn = get(conn, "/api/v1/timelines/public")
139 assert length(json_response(res_conn, 200)) == 1
140 end
141 end
142
143 defp local_and_remote_activities do
144 insert(:note_activity)
145 insert(:note_activity, local: false)
146 :ok
147 end
148
149 describe "public with restrict unauthenticated timeline for local and federated timelines" do
150 setup do: local_and_remote_activities()
151
152 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
153
154 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
155
156 test "if user is unauthenticated", %{conn: conn} do
157 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
158
159 assert json_response(res_conn, :unauthorized) == %{
160 "error" => "authorization required for timeline view"
161 }
162
163 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
164
165 assert json_response(res_conn, :unauthorized) == %{
166 "error" => "authorization required for timeline view"
167 }
168 end
169
170 test "if user is authenticated" do
171 %{conn: conn} = oauth_access(["read:statuses"])
172
173 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
174 assert length(json_response(res_conn, 200)) == 1
175
176 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
177 assert length(json_response(res_conn, 200)) == 2
178 end
179 end
180
181 describe "public with restrict unauthenticated timeline for local" do
182 setup do: local_and_remote_activities()
183
184 setup do: clear_config([:restrict_unauthenticated, :timelines, :local], true)
185
186 test "if user is unauthenticated", %{conn: conn} do
187 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
188
189 assert json_response(res_conn, :unauthorized) == %{
190 "error" => "authorization required for timeline view"
191 }
192
193 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
194 assert length(json_response(res_conn, 200)) == 2
195 end
196
197 test "if user is authenticated", %{conn: _conn} do
198 %{conn: conn} = oauth_access(["read:statuses"])
199
200 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
201 assert length(json_response(res_conn, 200)) == 1
202
203 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
204 assert length(json_response(res_conn, 200)) == 2
205 end
206 end
207
208 describe "public with restrict unauthenticated timeline for remote" do
209 setup do: local_and_remote_activities()
210
211 setup do: clear_config([:restrict_unauthenticated, :timelines, :federated], true)
212
213 test "if user is unauthenticated", %{conn: conn} do
214 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
215 assert length(json_response(res_conn, 200)) == 1
216
217 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
218
219 assert json_response(res_conn, :unauthorized) == %{
220 "error" => "authorization required for timeline view"
221 }
222 end
223
224 test "if user is authenticated", %{conn: _conn} do
225 %{conn: conn} = oauth_access(["read:statuses"])
226
227 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"})
228 assert length(json_response(res_conn, 200)) == 1
229
230 res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"})
231 assert length(json_response(res_conn, 200)) == 2
232 end
233 end
234
235 describe "direct" do
236 test "direct timeline", %{conn: conn} do
237 user_one = insert(:user)
238 user_two = insert(:user)
239
240 {:ok, user_two} = User.follow(user_two, user_one)
241
242 {:ok, direct} =
243 CommonAPI.post(user_one, %{
244 "status" => "Hi @#{user_two.nickname}!",
245 "visibility" => "direct"
246 })
247
248 {:ok, _follower_only} =
249 CommonAPI.post(user_one, %{
250 "status" => "Hi @#{user_two.nickname}!",
251 "visibility" => "private"
252 })
253
254 conn_user_two =
255 conn
256 |> assign(:user, user_two)
257 |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
258
259 # Only direct should be visible here
260 res_conn = get(conn_user_two, "api/v1/timelines/direct")
261
262 [status] = json_response(res_conn, :ok)
263
264 assert %{"visibility" => "direct"} = status
265 assert status["url"] != direct.data["id"]
266
267 # User should be able to see their own direct message
268 res_conn =
269 build_conn()
270 |> assign(:user, user_one)
271 |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"]))
272 |> get("api/v1/timelines/direct")
273
274 [status] = json_response(res_conn, :ok)
275
276 assert %{"visibility" => "direct"} = status
277
278 # Both should be visible here
279 res_conn = get(conn_user_two, "api/v1/timelines/home")
280
281 [_s1, _s2] = json_response(res_conn, :ok)
282
283 # Test pagination
284 Enum.each(1..20, fn _ ->
285 {:ok, _} =
286 CommonAPI.post(user_one, %{
287 "status" => "Hi @#{user_two.nickname}!",
288 "visibility" => "direct"
289 })
290 end)
291
292 res_conn = get(conn_user_two, "api/v1/timelines/direct")
293
294 statuses = json_response(res_conn, :ok)
295 assert length(statuses) == 20
296
297 res_conn =
298 get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
299
300 [status] = json_response(res_conn, :ok)
301
302 assert status["url"] != direct.data["id"]
303 end
304
305 test "doesn't include DMs from blocked users" do
306 %{user: blocker, conn: conn} = oauth_access(["read:statuses"])
307 blocked = insert(:user)
308 other_user = insert(:user)
309 {:ok, _user_relationship} = User.block(blocker, blocked)
310
311 {:ok, _blocked_direct} =
312 CommonAPI.post(blocked, %{
313 "status" => "Hi @#{blocker.nickname}!",
314 "visibility" => "direct"
315 })
316
317 {:ok, direct} =
318 CommonAPI.post(other_user, %{
319 "status" => "Hi @#{blocker.nickname}!",
320 "visibility" => "direct"
321 })
322
323 res_conn = get(conn, "api/v1/timelines/direct")
324
325 [status] = json_response(res_conn, :ok)
326 assert status["id"] == direct.id
327 end
328 end
329
330 describe "list" do
331 setup do: oauth_access(["read:lists"])
332
333 test "list timeline", %{user: user, conn: conn} do
334 other_user = insert(:user)
335 {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."})
336 {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
337 {:ok, list} = Pleroma.List.create("name", user)
338 {:ok, list} = Pleroma.List.follow(list, other_user)
339
340 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
341
342 assert [%{"id" => id}] = json_response(conn, :ok)
343
344 assert id == to_string(activity_two.id)
345 end
346
347 test "list timeline does not leak non-public statuses for unfollowed users", %{
348 user: user,
349 conn: conn
350 } do
351 other_user = insert(:user)
352 {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."})
353
354 {:ok, _activity_two} =
355 CommonAPI.post(other_user, %{
356 "status" => "Marisa is cute.",
357 "visibility" => "private"
358 })
359
360 {:ok, list} = Pleroma.List.create("name", user)
361 {:ok, list} = Pleroma.List.follow(list, other_user)
362
363 conn = get(conn, "/api/v1/timelines/list/#{list.id}")
364
365 assert [%{"id" => id}] = json_response(conn, :ok)
366
367 assert id == to_string(activity_one.id)
368 end
369 end
370
371 describe "hashtag" do
372 setup do: oauth_access(["n/a"])
373
374 @tag capture_log: true
375 test "hashtag timeline", %{conn: conn} do
376 following = insert(:user)
377
378 {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"})
379
380 nconn = get(conn, "/api/v1/timelines/tag/2hu")
381
382 assert [%{"id" => id}] = json_response(nconn, :ok)
383
384 assert id == to_string(activity.id)
385
386 # works for different capitalization too
387 nconn = get(conn, "/api/v1/timelines/tag/2HU")
388
389 assert [%{"id" => id}] = json_response(nconn, :ok)
390
391 assert id == to_string(activity.id)
392 end
393
394 test "multi-hashtag timeline", %{conn: conn} do
395 user = insert(:user)
396
397 {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
398 {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
399 {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
400
401 any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]})
402
403 [status_none, status_test1, status_test] = json_response(any_test, :ok)
404
405 assert to_string(activity_test.id) == status_test["id"]
406 assert to_string(activity_test1.id) == status_test1["id"]
407 assert to_string(activity_none.id) == status_none["id"]
408
409 restricted_test =
410 get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
411
412 assert [status_test1] == json_response(restricted_test, :ok)
413
414 all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]})
415
416 assert [status_none] == json_response(all_test, :ok)
417 end
418 end
419 end