fix csp-induced HTML match error
[akkoma] / test / pleroma / web / feed / user_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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 object = Object.normalize(note_activity, fetch: false)
61
62 [user: user, object: object, max_id: note_activity2.id]
63 end
64
65 test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do
66 resp =
67 conn
68 |> put_req_header("accept", "application/atom+xml")
69 |> get(user_feed_path(conn, :feed, user.nickname))
70 |> response(200)
71
72 activity_titles =
73 resp
74 |> SweetXml.parse()
75 |> SweetXml.xpath(~x"//entry/title/text()"l)
76
77 assert activity_titles == ['42 &amp; Thi...', 'This &amp; t...']
78 assert resp =~ FeedView.escape(object.data["content"])
79 assert resp =~ FeedView.escape(object.data["summary"])
80 assert resp =~ FeedView.escape(object.data["context"])
81
82 resp =
83 conn
84 |> put_req_header("accept", "application/atom+xml")
85 |> get("/users/#{user.nickname}/feed", %{"max_id" => max_id})
86 |> response(200)
87
88 activity_titles =
89 resp
90 |> SweetXml.parse()
91 |> SweetXml.xpath(~x"//entry/title/text()"l)
92
93 assert activity_titles == ['This &amp; t...']
94 end
95
96 test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id} do
97 resp =
98 conn
99 |> put_req_header("accept", "application/rss+xml")
100 |> get("/users/#{user.nickname}/feed.rss")
101 |> response(200)
102
103 activity_titles =
104 resp
105 |> SweetXml.parse()
106 |> SweetXml.xpath(~x"//item/title/text()"l)
107
108 assert activity_titles == ['42 &amp; Thi...', 'This &amp; t...']
109 assert resp =~ FeedView.escape(object.data["content"])
110 assert resp =~ FeedView.escape(object.data["summary"])
111 assert resp =~ FeedView.escape(object.data["context"])
112
113 resp =
114 conn
115 |> put_req_header("accept", "application/rss+xml")
116 |> get("/users/#{user.nickname}/feed.rss", %{"max_id" => max_id})
117 |> response(200)
118
119 activity_titles =
120 resp
121 |> SweetXml.parse()
122 |> SweetXml.xpath(~x"//item/title/text()"l)
123
124 assert activity_titles == ['This &amp; t...']
125 end
126
127 test "returns 404 for a missing feed", %{conn: conn} do
128 conn =
129 conn
130 |> put_req_header("accept", "application/atom+xml")
131 |> get(user_feed_path(conn, :feed, "nonexisting"))
132
133 assert response(conn, 404)
134 end
135
136 test "returns feed with public and unlisted activities", %{conn: conn} do
137 user = insert(:user)
138
139 {:ok, _} = CommonAPI.post(user, %{status: "public", visibility: "public"})
140 {:ok, _} = CommonAPI.post(user, %{status: "direct", visibility: "direct"})
141 {:ok, _} = CommonAPI.post(user, %{status: "unlisted", visibility: "unlisted"})
142 {:ok, _} = CommonAPI.post(user, %{status: "private", visibility: "private"})
143
144 resp =
145 conn
146 |> put_req_header("accept", "application/atom+xml")
147 |> get(user_feed_path(conn, :feed, user.nickname))
148 |> response(200)
149
150 activity_titles =
151 resp
152 |> SweetXml.parse()
153 |> SweetXml.xpath(~x"//entry/title/text()"l)
154 |> Enum.sort()
155
156 assert activity_titles == ['public', 'unlisted']
157 end
158
159 test "returns 404 when the user is remote", %{conn: conn} do
160 user = insert(:user, local: false)
161
162 {:ok, _} = CommonAPI.post(user, %{status: "test"})
163
164 assert conn
165 |> put_req_header("accept", "application/atom+xml")
166 |> get(user_feed_path(conn, :feed, user.nickname))
167 |> response(404)
168 end
169
170 test "does not require authentication on non-federating instances", %{conn: conn} do
171 clear_config([:instance, :federating], false)
172 user = insert(:user)
173
174 conn
175 |> put_req_header("accept", "application/rss+xml")
176 |> get("/users/#{user.nickname}/feed.rss")
177 |> response(200)
178 end
179 end
180
181 # Note: see ActivityPubControllerTest for JSON format tests
182 describe "feed_redirect" do
183 test "with html format, it redirects to user feed", %{conn: conn} do
184 note_activity = insert(:note_activity)
185 user = User.get_cached_by_ap_id(note_activity.data["actor"])
186
187 %{assigns: %{csp_nonce: nonce}} = resp_conn = get(conn, "/users/#{user.nickname}")
188
189 response =
190 resp_conn
191 |> response(200)
192
193 assert response ==
194 Pleroma.Web.Fallback.RedirectController.redirector_with_meta(
195 assign(conn, :csp_nonce, nonce),
196 %{user: user}
197 ).resp_body
198 end
199
200 test "with html format, it falls back to frontend when user is remote", %{conn: conn} do
201 user = insert(:user, local: false)
202
203 {:ok, _} = CommonAPI.post(user, %{status: "test"})
204
205 response =
206 conn
207 |> get("/users/#{user.nickname}")
208 |> response(200)
209
210 assert response =~ "</html>"
211 end
212
213 test "with html format, it falls back to frontend when user is not found", %{conn: conn} do
214 response =
215 conn
216 |> get("/users/jimm")
217 |> response(200)
218
219 assert response =~ "</html>"
220 end
221
222 test "with non-html / non-json format, it redirects to user feed in atom format", %{
223 conn: conn
224 } do
225 note_activity = insert(:note_activity)
226 user = User.get_cached_by_ap_id(note_activity.data["actor"])
227
228 conn =
229 conn
230 |> put_req_header("accept", "application/xml")
231 |> get("/users/#{user.nickname}")
232
233 assert conn.status == 302
234
235 assert redirected_to(conn) ==
236 "#{Pleroma.Web.Endpoint.url()}/users/#{user.nickname}/feed.atom"
237 end
238
239 test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
240 response =
241 conn
242 |> put_req_header("accept", "application/xml")
243 |> get(user_feed_path(conn, :feed, "jimm"))
244 |> response(404)
245
246 assert response == ~S({"error":"Not found"})
247 end
248 end
249
250 describe "private instance" do
251 setup do: clear_config([:instance, :public])
252
253 test "returns 404 for user feed", %{conn: conn} do
254 clear_config([:instance, :public], false)
255 user = insert(:user)
256
257 {:ok, _} = CommonAPI.post(user, %{status: "test"})
258
259 assert conn
260 |> put_req_header("accept", "application/atom+xml")
261 |> get(user_feed_path(conn, :feed, user.nickname))
262 |> response(404)
263 end
264 end
265 end