First
[anni] / test / pleroma / web / feed / user_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.Feed.UserControllerTest do
6   use Pleroma.Web.ConnCase
7
8   import Pleroma.Factory
9   import SweetXml
10
11   alias Pleroma.Object
12   alias Pleroma.User
13   alias Pleroma.Web.CommonAPI
14   alias Pleroma.Web.Feed.FeedView
15
16   setup do: clear_config([:static_fe, :enabled], false)
17
18   describe "feed" do
19     setup do: clear_config([:feed])
20
21     setup do
22       clear_config(
23         [:feed, :post_title],
24         %{max_length: 15, omission: "..."}
25       )
26
27       activity = insert(:note_activity)
28
29       note =
30         insert(:note,
31           data: %{
32             "content" => "This & this is :moominmamma: note ",
33             "source" => "This & this is :moominmamma: note ",
34             "attachment" => [
35               %{
36                 "url" => [
37                   %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
38                 ]
39               }
40             ],
41             "inReplyTo" => activity.data["id"],
42             "context" => "2hu & as",
43             "summary" => "2hu & as"
44           }
45         )
46
47       note_activity = insert(:note_activity, note: note)
48       user = User.get_cached_by_ap_id(note_activity.data["actor"])
49
50       note2 =
51         insert(:note,
52           user: user,
53           data: %{
54             "content" => "42 & This is :moominmamma: note ",
55             "inReplyTo" => activity.data["id"]
56           }
57         )
58
59       note_activity2 = insert(:note_activity, note: note2)
60
61       note3 =
62         insert(:note,
63           user: user,
64           data: %{
65             "content" => "This note tests whether HTML entities are truncated properly",
66             "summary" => "Won't, didn't fail",
67             "inReplyTo" => note_activity2.id
68           }
69         )
70
71       _note_activity3 = insert(:note_activity, note: note3)
72       object = Object.normalize(note_activity, fetch: false)
73
74       encoded_title = FeedView.activity_title(note3.data)
75
76       [user: user, object: object, max_id: note_activity2.id, encoded_title: encoded_title]
77     end
78
79     test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do
80       resp =
81         conn
82         |> put_req_header("accept", "application/atom+xml")
83         |> get(user_feed_path(conn, :feed, user.nickname))
84         |> response(200)
85
86       activity_titles =
87         resp
88         |> SweetXml.parse()
89         |> SweetXml.xpath(~x"//entry/title/text()"l)
90
91       assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as']
92       assert resp =~ FeedView.escape(object.data["content"])
93       assert resp =~ FeedView.escape(object.data["summary"])
94       assert resp =~ FeedView.escape(object.data["context"])
95
96       resp =
97         conn
98         |> put_req_header("accept", "application/atom+xml")
99         |> get("/users/#{user.nickname}/feed", %{"max_id" => max_id})
100         |> response(200)
101
102       activity_titles =
103         resp
104         |> SweetXml.parse()
105         |> SweetXml.xpath(~x"//entry/title/text()"l)
106
107       assert activity_titles == ['2hu & as']
108     end
109
110     test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id} do
111       resp =
112         conn
113         |> put_req_header("accept", "application/rss+xml")
114         |> get("/users/#{user.nickname}/feed.rss")
115         |> response(200)
116
117       activity_titles =
118         resp
119         |> SweetXml.parse()
120         |> SweetXml.xpath(~x"//item/title/text()"l)
121
122       assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as']
123       assert resp =~ FeedView.escape(object.data["content"])
124       assert resp =~ FeedView.escape(object.data["summary"])
125       assert resp =~ FeedView.escape(object.data["context"])
126
127       resp =
128         conn
129         |> put_req_header("accept", "application/rss+xml")
130         |> get("/users/#{user.nickname}/feed.rss", %{"max_id" => max_id})
131         |> response(200)
132
133       activity_titles =
134         resp
135         |> SweetXml.parse()
136         |> SweetXml.xpath(~x"//item/title/text()"l)
137
138       assert activity_titles == ['2hu & as']
139     end
140
141     test "returns 404 for a missing feed", %{conn: conn} do
142       conn =
143         conn
144         |> put_req_header("accept", "application/atom+xml")
145         |> get(user_feed_path(conn, :feed, "nonexisting"))
146
147       assert response(conn, 404)
148     end
149
150     test "returns feed with public and unlisted activities", %{conn: conn} do
151       user = insert(:user)
152
153       {:ok, _} = CommonAPI.post(user, %{status: "public", visibility: "public"})
154       {:ok, _} = CommonAPI.post(user, %{status: "direct", visibility: "direct"})
155       {:ok, _} = CommonAPI.post(user, %{status: "unlisted", visibility: "unlisted"})
156       {:ok, _} = CommonAPI.post(user, %{status: "private", visibility: "private"})
157
158       resp =
159         conn
160         |> put_req_header("accept", "application/atom+xml")
161         |> get(user_feed_path(conn, :feed, user.nickname))
162         |> response(200)
163
164       activity_titles =
165         resp
166         |> SweetXml.parse()
167         |> SweetXml.xpath(~x"//entry/title/text()"l)
168         |> Enum.sort()
169
170       assert activity_titles == ['public', 'unlisted']
171     end
172
173     test "returns 404 when the user is remote", %{conn: conn} do
174       user = insert(:user, local: false)
175
176       {:ok, _} = CommonAPI.post(user, %{status: "test"})
177
178       assert conn
179              |> put_req_header("accept", "application/atom+xml")
180              |> get(user_feed_path(conn, :feed, user.nickname))
181              |> response(404)
182     end
183
184     test "does not require authentication on non-federating instances", %{conn: conn} do
185       clear_config([:instance, :federating], false)
186       user = insert(:user)
187
188       conn
189       |> put_req_header("accept", "application/rss+xml")
190       |> get("/users/#{user.nickname}/feed.rss")
191       |> response(200)
192     end
193
194     test "does not mangle HTML entities midway", %{
195       conn: conn,
196       user: user,
197       object: object,
198       encoded_title: encoded_title
199     } do
200       resp =
201         conn
202         |> put_req_header("accept", "application/atom+xml")
203         |> get(user_feed_path(conn, :feed, user.nickname))
204         |> response(200)
205
206       activity_titles =
207         resp
208         |> SweetXml.parse()
209         |> SweetXml.xpath(~x"//entry/title/text()"l)
210
211       assert activity_titles == ['Won\'t, didn\'...', '2hu', '2hu & as']
212       assert resp =~ FeedView.escape(object.data["content"])
213       assert resp =~ FeedView.escape(object.data["summary"])
214       assert resp =~ FeedView.escape(object.data["context"])
215       assert resp =~ encoded_title
216     end
217   end
218
219   # Note: see ActivityPubControllerTest for JSON format tests
220   describe "feed_redirect" do
221     test "with html format, it redirects to user feed", %{conn: conn} do
222       note_activity = insert(:note_activity)
223       user = User.get_cached_by_ap_id(note_activity.data["actor"])
224
225       response =
226         conn
227         |> get("/users/#{user.nickname}")
228         |> response(200)
229
230       assert response ==
231                Pleroma.Web.Fallback.RedirectController.redirector_with_meta(
232                  conn,
233                  %{user: user}
234                ).resp_body
235     end
236
237     test "with html format, it falls back to frontend when user is remote", %{conn: conn} do
238       user = insert(:user, local: false)
239
240       {:ok, _} = CommonAPI.post(user, %{status: "test"})
241
242       response =
243         conn
244         |> get("/users/#{user.nickname}")
245         |> response(200)
246
247       assert response =~ "</html>"
248     end
249
250     test "with html format, it falls back to frontend when user is not found", %{conn: conn} do
251       response =
252         conn
253         |> get("/users/jimm")
254         |> response(200)
255
256       assert response =~ "</html>"
257     end
258
259     test "with non-html / non-json format, it redirects to user feed in atom format", %{
260       conn: conn
261     } do
262       note_activity = insert(:note_activity)
263       user = User.get_cached_by_ap_id(note_activity.data["actor"])
264
265       conn =
266         conn
267         |> put_req_header("accept", "application/xml")
268         |> get("/users/#{user.nickname}")
269
270       assert conn.status == 302
271
272       assert redirected_to(conn) ==
273                "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.atom"
274     end
275
276     test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
277       response =
278         conn
279         |> put_req_header("accept", "application/xml")
280         |> get(user_feed_path(conn, :feed, "jimm"))
281         |> response(404)
282
283       assert response == ~S({"error":"Not found"})
284     end
285   end
286
287   describe "private instance" do
288     setup do: clear_config([:instance, :public])
289
290     test "returns 404 for user feed", %{conn: conn} do
291       clear_config([:instance, :public], false)
292       user = insert(:user)
293
294       {:ok, _} = CommonAPI.post(user, %{status: "test"})
295
296       assert conn
297              |> put_req_header("accept", "application/atom+xml")
298              |> get(user_feed_path(conn, :feed, user.nickname))
299              |> response(404)
300     end
301   end
302 end