151f69cde7f7d4a65e29c5aa5a8ef429df3012b1
[akkoma] / test / pleroma / web / pleroma_api / controllers / emoji_pack_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.PleromaAPI.EmojiPackControllerTest do
6 use Pleroma.Web.ConnCase, async: false
7
8 import Tesla.Mock
9 import Pleroma.Factory
10
11 @emoji_path Path.join(
12 Pleroma.Config.get!([:instance, :static_dir]),
13 "emoji"
14 )
15 setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
16
17 setup do: clear_config([:instance, :public], true)
18
19 setup do
20 admin = insert(:user, is_admin: true)
21 token = insert(:oauth_admin_token, user: admin)
22
23 admin_conn =
24 build_conn()
25 |> assign(:user, admin)
26 |> assign(:token, token)
27
28 Pleroma.Emoji.reload()
29 {:ok, %{admin_conn: admin_conn}}
30 end
31
32 test "GET /api/pleroma/emoji/packs when :public: false", %{conn: conn} do
33 Config.put([:instance, :public], false)
34 conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
35 end
36
37 test "GET /api/pleroma/emoji/packs", %{conn: conn} do
38 resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
39
40 assert resp["count"] == 4
41
42 assert resp["packs"]
43 |> Map.keys()
44 |> length() == 4
45
46 shared = resp["packs"]["test_pack"]
47 assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
48 assert Map.has_key?(shared["pack"], "download-sha256")
49 assert shared["pack"]["can-download"]
50 assert shared["pack"]["share-files"]
51
52 non_shared = resp["packs"]["test_pack_nonshared"]
53 assert non_shared["pack"]["share-files"] == false
54 assert non_shared["pack"]["can-download"] == false
55
56 resp =
57 conn
58 |> get("/api/pleroma/emoji/packs?page_size=1")
59 |> json_response_and_validate_schema(200)
60
61 assert resp["count"] == 4
62
63 packs = Map.keys(resp["packs"])
64
65 assert length(packs) == 1
66
67 [pack1] = packs
68
69 resp =
70 conn
71 |> get("/api/pleroma/emoji/packs?page_size=1&page=2")
72 |> json_response_and_validate_schema(200)
73
74 assert resp["count"] == 4
75 packs = Map.keys(resp["packs"])
76 assert length(packs) == 1
77 [pack2] = packs
78
79 resp =
80 conn
81 |> get("/api/pleroma/emoji/packs?page_size=1&page=3")
82 |> json_response_and_validate_schema(200)
83
84 assert resp["count"] == 4
85 packs = Map.keys(resp["packs"])
86 assert length(packs) == 1
87 [pack3] = packs
88
89 resp =
90 conn
91 |> get("/api/pleroma/emoji/packs?page_size=1&page=4")
92 |> json_response_and_validate_schema(200)
93
94 assert resp["count"] == 4
95 packs = Map.keys(resp["packs"])
96 assert length(packs) == 1
97 [pack4] = packs
98 assert [pack1, pack2, pack3, pack4] |> Enum.uniq() |> length() == 4
99 end
100
101 describe "GET /api/pleroma/emoji/packs/remote" do
102 test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
103 resp =
104 conn
105 |> get("/api/pleroma/emoji/packs?page=2&page_size=1")
106 |> json_response_and_validate_schema(200)
107
108 mock(fn
109 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
110 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
111
112 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
113 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
114
115 %{method: :get, url: "https://example.com/api/pleroma/emoji/packs?page=2&page_size=1"} ->
116 json(resp)
117 end)
118
119 assert admin_conn
120 |> get("/api/pleroma/emoji/packs/remote?url=https://example.com&page=2&page_size=1")
121 |> json_response_and_validate_schema(200) == resp
122 end
123
124 test "non shareable instance", %{admin_conn: admin_conn} do
125 mock(fn
126 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
127 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
128
129 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
130 json(%{metadata: %{features: []}})
131 end)
132
133 assert admin_conn
134 |> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
135 |> json_response_and_validate_schema(500) == %{
136 "error" => "The requested instance does not support sharing emoji packs"
137 }
138 end
139 end
140
141 describe "GET /api/pleroma/emoji/packs/archive?name=:name" do
142 test "download shared pack", %{conn: conn} do
143 resp =
144 conn
145 |> get("/api/pleroma/emoji/packs/archive?name=test_pack")
146 |> response(200)
147
148 {:ok, arch} = :zip.unzip(resp, [:memory])
149
150 assert Enum.find(arch, fn {n, _} -> n == 'pack.json' end)
151 assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end)
152 end
153
154 test "non existing pack", %{conn: conn} do
155 assert conn
156 |> get("/api/pleroma/emoji/packs/archive?name=test_pack_for_import")
157 |> json_response_and_validate_schema(:not_found) == %{
158 "error" => "Pack test_pack_for_import does not exist"
159 }
160 end
161
162 test "non downloadable pack", %{conn: conn} do
163 assert conn
164 |> get("/api/pleroma/emoji/packs/archive?name=test_pack_nonshared")
165 |> json_response_and_validate_schema(:forbidden) == %{
166 "error" =>
167 "Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
168 }
169 end
170 end
171
172 describe "POST /api/pleroma/emoji/packs/download" do
173 test "shared pack from remote and non shared from fallback-src", %{
174 admin_conn: admin_conn,
175 conn: conn
176 } do
177 mock(fn
178 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
179 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
180
181 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
182 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
183
184 %{
185 method: :get,
186 url: "https://example.com/api/pleroma/emoji/pack?name=test_pack"
187 } ->
188 conn
189 |> get("/api/pleroma/emoji/pack?name=test_pack")
190 |> json_response_and_validate_schema(200)
191 |> json()
192
193 %{
194 method: :get,
195 url: "https://example.com/api/pleroma/emoji/packs/archive?name=test_pack"
196 } ->
197 conn
198 |> get("/api/pleroma/emoji/packs/archive?name=test_pack")
199 |> response(200)
200 |> text()
201
202 %{
203 method: :get,
204 url: "https://example.com/api/pleroma/emoji/pack?name=test_pack_nonshared"
205 } ->
206 conn
207 |> get("/api/pleroma/emoji/pack?name=test_pack_nonshared")
208 |> json_response_and_validate_schema(200)
209 |> json()
210
211 %{
212 method: :get,
213 url: "https://nonshared-pack"
214 } ->
215 text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
216 end)
217
218 assert admin_conn
219 |> put_req_header("content-type", "multipart/form-data")
220 |> post("/api/pleroma/emoji/packs/download", %{
221 url: "https://example.com",
222 name: "test_pack",
223 as: "test_pack2"
224 })
225 |> json_response_and_validate_schema(200) == "ok"
226
227 assert File.exists?("#{@emoji_path}/test_pack2/pack.json")
228 assert File.exists?("#{@emoji_path}/test_pack2/blank.png")
229
230 assert admin_conn
231 |> delete("/api/pleroma/emoji/pack?name=test_pack2")
232 |> json_response_and_validate_schema(200) == "ok"
233
234 refute File.exists?("#{@emoji_path}/test_pack2")
235
236 assert admin_conn
237 |> put_req_header("content-type", "multipart/form-data")
238 |> post(
239 "/api/pleroma/emoji/packs/download",
240 %{
241 url: "https://example.com",
242 name: "test_pack_nonshared",
243 as: "test_pack_nonshared2"
244 }
245 )
246 |> json_response_and_validate_schema(200) == "ok"
247
248 assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json")
249 assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png")
250
251 assert admin_conn
252 |> delete("/api/pleroma/emoji/pack?name=test_pack_nonshared2")
253 |> json_response_and_validate_schema(200) == "ok"
254
255 refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
256 end
257
258 test "nonshareable instance", %{admin_conn: admin_conn} do
259 mock(fn
260 %{method: :get, url: "https://old-instance/.well-known/nodeinfo"} ->
261 json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]})
262
263 %{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} ->
264 json(%{metadata: %{features: []}})
265 end)
266
267 assert admin_conn
268 |> put_req_header("content-type", "multipart/form-data")
269 |> post(
270 "/api/pleroma/emoji/packs/download",
271 %{
272 url: "https://old-instance",
273 name: "test_pack",
274 as: "test_pack2"
275 }
276 )
277 |> json_response_and_validate_schema(500) == %{
278 "error" => "The requested instance does not support sharing emoji packs"
279 }
280 end
281
282 test "checksum fail", %{admin_conn: admin_conn} do
283 mock(fn
284 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
285 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
286
287 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
288 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
289
290 %{
291 method: :get,
292 url: "https://example.com/api/pleroma/emoji/pack?name=pack_bad_sha"
293 } ->
294 {:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha")
295 %Tesla.Env{status: 200, body: Jason.encode!(pack)}
296
297 %{
298 method: :get,
299 url: "https://example.com/api/pleroma/emoji/packs/archive?name=pack_bad_sha"
300 } ->
301 %Tesla.Env{
302 status: 200,
303 body: File.read!("test/instance_static/emoji/pack_bad_sha/pack_bad_sha.zip")
304 }
305 end)
306
307 assert admin_conn
308 |> put_req_header("content-type", "multipart/form-data")
309 |> post("/api/pleroma/emoji/packs/download", %{
310 url: "https://example.com",
311 name: "pack_bad_sha",
312 as: "pack_bad_sha2"
313 })
314 |> json_response_and_validate_schema(:internal_server_error) == %{
315 "error" => "SHA256 for the pack doesn't match the one sent by the server"
316 }
317 end
318
319 test "other error", %{admin_conn: admin_conn} do
320 mock(fn
321 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
322 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
323
324 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
325 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
326
327 %{
328 method: :get,
329 url: "https://example.com/api/pleroma/emoji/pack?name=test_pack"
330 } ->
331 {:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack")
332 %Tesla.Env{status: 200, body: Jason.encode!(pack)}
333 end)
334
335 assert admin_conn
336 |> put_req_header("content-type", "multipart/form-data")
337 |> post("/api/pleroma/emoji/packs/download", %{
338 url: "https://example.com",
339 name: "test_pack",
340 as: "test_pack2"
341 })
342 |> json_response_and_validate_schema(:internal_server_error) == %{
343 "error" =>
344 "The pack was not set as shared and there is no fallback src to download from"
345 }
346 end
347 end
348
349 describe "PATCH/update /api/pleroma/emoji/pack?name=:name" do
350 setup do
351 pack_file = "#{@emoji_path}/test_pack/pack.json"
352 original_content = File.read!(pack_file)
353
354 on_exit(fn ->
355 File.write!(pack_file, original_content)
356 end)
357
358 {:ok,
359 pack_file: pack_file,
360 new_data: %{
361 "license" => "Test license changed",
362 "homepage" => "https://pleroma.social",
363 "description" => "Test description",
364 "share-files" => false
365 }}
366 end
367
368 test "returns error when file system not writable", %{admin_conn: conn} = ctx do
369 {:ok, %File.Stat{mode: mode}} = File.stat(@emoji_path)
370
371 try do
372 File.chmod!(@emoji_path, 0o400)
373
374 assert conn
375 |> put_req_header("content-type", "multipart/form-data")
376 |> patch(
377 "/api/pleroma/emoji/pack?name=test_pack",
378 %{"metadata" => ctx[:new_data]}
379 )
380 |> json_response_and_validate_schema(500)
381 after
382 File.chmod!(@emoji_path, mode)
383 end
384 end
385
386 test "for a pack without a fallback source", ctx do
387 assert ctx[:admin_conn]
388 |> put_req_header("content-type", "multipart/form-data")
389 |> patch("/api/pleroma/emoji/pack?name=test_pack", %{
390 "metadata" => ctx[:new_data]
391 })
392 |> json_response_and_validate_schema(200) == ctx[:new_data]
393
394 assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
395 end
396
397 test "for a pack with a fallback source", ctx do
398 mock(fn
399 %{
400 method: :get,
401 url: "https://nonshared-pack"
402 } ->
403 text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
404 end)
405
406 new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
407
408 new_data_with_sha =
409 Map.put(
410 new_data,
411 "fallback-src-sha256",
412 "1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D"
413 )
414
415 assert ctx[:admin_conn]
416 |> put_req_header("content-type", "multipart/form-data")
417 |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
418 |> json_response_and_validate_schema(200) == new_data_with_sha
419
420 assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
421 end
422
423 test "when the fallback source doesn't have all the files", ctx do
424 mock(fn
425 %{
426 method: :get,
427 url: "https://nonshared-pack"
428 } ->
429 {:ok, {'empty.zip', empty_arch}} = :zip.zip('empty.zip', [], [:memory])
430 text(empty_arch)
431 end)
432
433 new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
434
435 assert ctx[:admin_conn]
436 |> put_req_header("content-type", "multipart/form-data")
437 |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
438 |> json_response_and_validate_schema(:bad_request) == %{
439 "error" => "The fallback archive does not have all files specified in pack.json"
440 }
441 end
442 end
443
444 describe "POST/DELETE /api/pleroma/emoji/pack?name=:name" do
445 test "returns error when file system not writable", %{admin_conn: admin_conn} do
446 {:ok, %File.Stat{mode: mode}} = File.stat(@emoji_path)
447
448 try do
449 File.chmod!(@emoji_path, 0o400)
450
451 assert admin_conn
452 |> post("/api/pleroma/emoji/pack?name=test_pack")
453 |> json_response_and_validate_schema(500) == %{
454 "error" =>
455 "Unexpected error occurred while creating pack. (POSIX error: Permission denied)"
456 }
457 after
458 File.chmod!(@emoji_path, mode)
459 end
460 end
461
462 test "returns an error on deletes pack when the file system is not writable", %{
463 admin_conn: admin_conn
464 } do
465 {:ok, _pack} = Pleroma.Emoji.Pack.create("test_pack2")
466 {:ok, %File.Stat{mode: mode}} = File.stat(@emoji_path)
467
468 try do
469 File.chmod!(@emoji_path, 0o400)
470
471 assert admin_conn
472 |> delete("/api/pleroma/emoji/pack?name=test_pack")
473 |> json_response_and_validate_schema(500) == %{
474 "error" => "Couldn't delete the pack test_pack (POSIX error: Permission denied)"
475 }
476 after
477 File.chmod!(@emoji_path, mode)
478 File.rm_rf!(Path.join([@emoji_path, "test_pack2"]))
479 end
480 end
481
482 test "creating and deleting a pack", %{admin_conn: admin_conn} do
483 assert admin_conn
484 |> post("/api/pleroma/emoji/pack?name=test_created")
485 |> json_response_and_validate_schema(200) == "ok"
486
487 assert File.exists?("#{@emoji_path}/test_created/pack.json")
488
489 assert Jason.decode!(File.read!("#{@emoji_path}/test_created/pack.json")) == %{
490 "pack" => %{},
491 "files" => %{},
492 "files_count" => 0
493 }
494
495 assert admin_conn
496 |> delete("/api/pleroma/emoji/pack?name=test_created")
497 |> json_response_and_validate_schema(200) == "ok"
498
499 refute File.exists?("#{@emoji_path}/test_created/pack.json")
500 end
501
502 test "if pack exists", %{admin_conn: admin_conn} do
503 path = Path.join(@emoji_path, "test_created")
504 File.mkdir(path)
505 pack_file = Jason.encode!(%{files: %{}, pack: %{}})
506 File.write!(Path.join(path, "pack.json"), pack_file)
507
508 assert admin_conn
509 |> post("/api/pleroma/emoji/pack?name=test_created")
510 |> json_response_and_validate_schema(:conflict) == %{
511 "error" => "A pack named \"test_created\" already exists"
512 }
513
514 on_exit(fn -> File.rm_rf(path) end)
515 end
516
517 test "with empty name", %{admin_conn: admin_conn} do
518 assert admin_conn
519 |> post("/api/pleroma/emoji/pack?name= ")
520 |> json_response_and_validate_schema(:bad_request) == %{
521 "error" => "pack name cannot be empty"
522 }
523 end
524 end
525
526 test "deleting nonexisting pack", %{admin_conn: admin_conn} do
527 assert admin_conn
528 |> delete("/api/pleroma/emoji/pack?name=non_existing")
529 |> json_response_and_validate_schema(:not_found) == %{
530 "error" => "Pack non_existing does not exist"
531 }
532 end
533
534 test "deleting with empty name", %{admin_conn: admin_conn} do
535 assert admin_conn
536 |> delete("/api/pleroma/emoji/pack?name= ")
537 |> json_response_and_validate_schema(:bad_request) == %{
538 "error" => "pack name cannot be empty"
539 }
540 end
541
542 test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
543 on_exit(fn ->
544 File.rm!("#{@emoji_path}/test_pack_for_import/emoji.txt")
545 File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
546 end)
547
548 resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
549
550 refute Map.has_key?(resp["packs"], "test_pack_for_import")
551
552 assert admin_conn
553 |> get("/api/pleroma/emoji/packs/import")
554 |> json_response_and_validate_schema(200) == ["test_pack_for_import"]
555
556 resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
557 assert resp["packs"]["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
558
559 File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
560 refute File.exists?("#{@emoji_path}/test_pack_for_import/pack.json")
561
562 emoji_txt_content = """
563 blank, blank.png, Fun
564 blank2, blank.png
565 foo, /emoji/test_pack_for_import/blank.png
566 bar
567 """
568
569 File.write!("#{@emoji_path}/test_pack_for_import/emoji.txt", emoji_txt_content)
570
571 assert admin_conn
572 |> get("/api/pleroma/emoji/packs/import")
573 |> json_response_and_validate_schema(200) == ["test_pack_for_import"]
574
575 resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
576
577 assert resp["packs"]["test_pack_for_import"]["files"] == %{
578 "blank" => "blank.png",
579 "blank2" => "blank.png",
580 "foo" => "blank.png"
581 }
582 end
583
584 describe "GET /api/pleroma/emoji/pack?name=:name" do
585 test "shows pack.json", %{conn: conn} do
586 assert %{
587 "files" => files,
588 "files_count" => 2,
589 "pack" => %{
590 "can-download" => true,
591 "description" => "Test description",
592 "download-sha256" => _,
593 "homepage" => "https://pleroma.social",
594 "license" => "Test license",
595 "share-files" => true
596 }
597 } =
598 conn
599 |> get("/api/pleroma/emoji/pack?name=test_pack")
600 |> json_response_and_validate_schema(200)
601
602 assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"}
603
604 assert %{
605 "files" => files,
606 "files_count" => 2
607 } =
608 conn
609 |> get("/api/pleroma/emoji/pack?name=test_pack&page_size=1")
610 |> json_response_and_validate_schema(200)
611
612 assert files |> Map.keys() |> length() == 1
613
614 assert %{
615 "files" => files,
616 "files_count" => 2
617 } =
618 conn
619 |> get("/api/pleroma/emoji/pack?name=test_pack&page_size=1&page=2")
620 |> json_response_and_validate_schema(200)
621
622 assert files |> Map.keys() |> length() == 1
623 end
624
625 test "for pack name with special chars", %{conn: conn} do
626 assert %{
627 "files" => _files,
628 "files_count" => 1,
629 "pack" => %{
630 "can-download" => true,
631 "description" => "Test description",
632 "download-sha256" => _,
633 "homepage" => "https://pleroma.social",
634 "license" => "Test license",
635 "share-files" => true
636 }
637 } =
638 conn
639 |> get("/api/pleroma/emoji/pack?name=blobs.gg")
640 |> json_response_and_validate_schema(200)
641 end
642
643 test "non existing pack", %{conn: conn} do
644 assert conn
645 |> get("/api/pleroma/emoji/pack?name=non_existing")
646 |> json_response_and_validate_schema(:not_found) == %{
647 "error" => "Pack non_existing does not exist"
648 }
649 end
650
651 test "error name", %{conn: conn} do
652 assert conn
653 |> get("/api/pleroma/emoji/pack?name= ")
654 |> json_response_and_validate_schema(:bad_request) == %{
655 "error" => "pack name cannot be empty"
656 }
657 end
658 end
659 end