Merge branch 'optimize-command_available' into 'develop'
[akkoma] / test / pleroma / web / pleroma_api / controllers / emoji_pack_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.PleromaAPI.EmojiPackControllerTest do
6 use Pleroma.Web.ConnCase, async: false
7
8 import Mock
9 import Tesla.Mock
10 import Pleroma.Factory
11
12 @emoji_path Path.join(
13 Pleroma.Config.get!([:instance, :static_dir]),
14 "emoji"
15 )
16 setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
17
18 setup do: clear_config([:instance, :public], true)
19
20 setup do
21 admin = insert(:user, is_admin: true)
22 token = insert(:oauth_admin_token, user: admin)
23
24 admin_conn =
25 build_conn()
26 |> assign(:user, admin)
27 |> assign(:token, token)
28
29 Pleroma.Emoji.reload()
30 {:ok, %{admin_conn: admin_conn}}
31 end
32
33 test "GET /api/pleroma/emoji/packs when :public: false", %{conn: conn} do
34 Config.put([:instance, :public], false)
35 conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
36 end
37
38 test "GET /api/pleroma/emoji/packs", %{conn: conn} do
39 resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
40
41 assert resp["count"] == 4
42
43 assert resp["packs"]
44 |> Map.keys()
45 |> length() == 4
46
47 shared = resp["packs"]["test_pack"]
48 assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
49 assert Map.has_key?(shared["pack"], "download-sha256")
50 assert shared["pack"]["can-download"]
51 assert shared["pack"]["share-files"]
52
53 non_shared = resp["packs"]["test_pack_nonshared"]
54 assert non_shared["pack"]["share-files"] == false
55 assert non_shared["pack"]["can-download"] == false
56
57 resp =
58 conn
59 |> get("/api/pleroma/emoji/packs?page_size=1")
60 |> json_response_and_validate_schema(200)
61
62 assert resp["count"] == 4
63
64 packs = Map.keys(resp["packs"])
65
66 assert length(packs) == 1
67
68 [pack1] = packs
69
70 resp =
71 conn
72 |> get("/api/pleroma/emoji/packs?page_size=1&page=2")
73 |> json_response_and_validate_schema(200)
74
75 assert resp["count"] == 4
76 packs = Map.keys(resp["packs"])
77 assert length(packs) == 1
78 [pack2] = packs
79
80 resp =
81 conn
82 |> get("/api/pleroma/emoji/packs?page_size=1&page=3")
83 |> json_response_and_validate_schema(200)
84
85 assert resp["count"] == 4
86 packs = Map.keys(resp["packs"])
87 assert length(packs) == 1
88 [pack3] = packs
89
90 resp =
91 conn
92 |> get("/api/pleroma/emoji/packs?page_size=1&page=4")
93 |> json_response_and_validate_schema(200)
94
95 assert resp["count"] == 4
96 packs = Map.keys(resp["packs"])
97 assert length(packs) == 1
98 [pack4] = packs
99 assert [pack1, pack2, pack3, pack4] |> Enum.uniq() |> length() == 4
100 end
101
102 describe "GET /api/pleroma/emoji/packs/remote" do
103 test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
104 resp =
105 conn
106 |> get("/api/pleroma/emoji/packs?page=2&page_size=1")
107 |> json_response_and_validate_schema(200)
108
109 mock(fn
110 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
111 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
112
113 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
114 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
115
116 %{method: :get, url: "https://example.com/api/pleroma/emoji/packs?page=2&page_size=1"} ->
117 json(resp)
118 end)
119
120 assert admin_conn
121 |> get("/api/pleroma/emoji/packs/remote?url=https://example.com&page=2&page_size=1")
122 |> json_response_and_validate_schema(200) == resp
123 end
124
125 test "non shareable instance", %{admin_conn: admin_conn} do
126 mock(fn
127 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
128 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
129
130 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
131 json(%{metadata: %{features: []}})
132 end)
133
134 assert admin_conn
135 |> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
136 |> json_response_and_validate_schema(500) == %{
137 "error" => "The requested instance does not support sharing emoji packs"
138 }
139 end
140 end
141
142 describe "GET /api/pleroma/emoji/packs/archive?name=:name" do
143 test "download shared pack", %{conn: conn} do
144 resp =
145 conn
146 |> get("/api/pleroma/emoji/packs/archive?name=test_pack")
147 |> response(200)
148
149 {:ok, arch} = :zip.unzip(resp, [:memory])
150
151 assert Enum.find(arch, fn {n, _} -> n == 'pack.json' end)
152 assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end)
153 end
154
155 test "non existing pack", %{conn: conn} do
156 assert conn
157 |> get("/api/pleroma/emoji/packs/archive?name=test_pack_for_import")
158 |> json_response_and_validate_schema(:not_found) == %{
159 "error" => "Pack test_pack_for_import does not exist"
160 }
161 end
162
163 test "non downloadable pack", %{conn: conn} do
164 assert conn
165 |> get("/api/pleroma/emoji/packs/archive?name=test_pack_nonshared")
166 |> json_response_and_validate_schema(:forbidden) == %{
167 "error" =>
168 "Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
169 }
170 end
171 end
172
173 describe "POST /api/pleroma/emoji/packs/download" do
174 test "shared pack from remote and non shared from fallback-src", %{
175 admin_conn: admin_conn,
176 conn: conn
177 } do
178 mock(fn
179 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
180 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
181
182 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
183 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
184
185 %{
186 method: :get,
187 url: "https://example.com/api/pleroma/emoji/pack?name=test_pack"
188 } ->
189 conn
190 |> get("/api/pleroma/emoji/pack?name=test_pack")
191 |> json_response_and_validate_schema(200)
192 |> json()
193
194 %{
195 method: :get,
196 url: "https://example.com/api/pleroma/emoji/packs/archive?name=test_pack"
197 } ->
198 conn
199 |> get("/api/pleroma/emoji/packs/archive?name=test_pack")
200 |> response(200)
201 |> text()
202
203 %{
204 method: :get,
205 url: "https://example.com/api/pleroma/emoji/pack?name=test_pack_nonshared"
206 } ->
207 conn
208 |> get("/api/pleroma/emoji/pack?name=test_pack_nonshared")
209 |> json_response_and_validate_schema(200)
210 |> json()
211
212 %{
213 method: :get,
214 url: "https://nonshared-pack"
215 } ->
216 text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
217 end)
218
219 assert admin_conn
220 |> put_req_header("content-type", "multipart/form-data")
221 |> post("/api/pleroma/emoji/packs/download", %{
222 url: "https://example.com",
223 name: "test_pack",
224 as: "test_pack2"
225 })
226 |> json_response_and_validate_schema(200) == "ok"
227
228 assert File.exists?("#{@emoji_path}/test_pack2/pack.json")
229 assert File.exists?("#{@emoji_path}/test_pack2/blank.png")
230
231 assert admin_conn
232 |> delete("/api/pleroma/emoji/pack?name=test_pack2")
233 |> json_response_and_validate_schema(200) == "ok"
234
235 refute File.exists?("#{@emoji_path}/test_pack2")
236
237 assert admin_conn
238 |> put_req_header("content-type", "multipart/form-data")
239 |> post(
240 "/api/pleroma/emoji/packs/download",
241 %{
242 url: "https://example.com",
243 name: "test_pack_nonshared",
244 as: "test_pack_nonshared2"
245 }
246 )
247 |> json_response_and_validate_schema(200) == "ok"
248
249 assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json")
250 assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png")
251
252 assert admin_conn
253 |> delete("/api/pleroma/emoji/pack?name=test_pack_nonshared2")
254 |> json_response_and_validate_schema(200) == "ok"
255
256 refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
257 end
258
259 test "nonshareable instance", %{admin_conn: admin_conn} do
260 mock(fn
261 %{method: :get, url: "https://old-instance/.well-known/nodeinfo"} ->
262 json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]})
263
264 %{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} ->
265 json(%{metadata: %{features: []}})
266 end)
267
268 assert admin_conn
269 |> put_req_header("content-type", "multipart/form-data")
270 |> post(
271 "/api/pleroma/emoji/packs/download",
272 %{
273 url: "https://old-instance",
274 name: "test_pack",
275 as: "test_pack2"
276 }
277 )
278 |> json_response_and_validate_schema(500) == %{
279 "error" => "The requested instance does not support sharing emoji packs"
280 }
281 end
282
283 test "checksum fail", %{admin_conn: admin_conn} do
284 mock(fn
285 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
286 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
287
288 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
289 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
290
291 %{
292 method: :get,
293 url: "https://example.com/api/pleroma/emoji/pack?name=pack_bad_sha"
294 } ->
295 {:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha")
296 %Tesla.Env{status: 200, body: Jason.encode!(pack)}
297
298 %{
299 method: :get,
300 url: "https://example.com/api/pleroma/emoji/packs/archive?name=pack_bad_sha"
301 } ->
302 %Tesla.Env{
303 status: 200,
304 body: File.read!("test/instance_static/emoji/pack_bad_sha/pack_bad_sha.zip")
305 }
306 end)
307
308 assert admin_conn
309 |> put_req_header("content-type", "multipart/form-data")
310 |> post("/api/pleroma/emoji/packs/download", %{
311 url: "https://example.com",
312 name: "pack_bad_sha",
313 as: "pack_bad_sha2"
314 })
315 |> json_response_and_validate_schema(:internal_server_error) == %{
316 "error" => "SHA256 for the pack doesn't match the one sent by the server"
317 }
318 end
319
320 test "other error", %{admin_conn: admin_conn} do
321 mock(fn
322 %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
323 json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]})
324
325 %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
326 json(%{metadata: %{features: ["shareable_emoji_packs"]}})
327
328 %{
329 method: :get,
330 url: "https://example.com/api/pleroma/emoji/pack?name=test_pack"
331 } ->
332 {:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack")
333 %Tesla.Env{status: 200, body: Jason.encode!(pack)}
334 end)
335
336 assert admin_conn
337 |> put_req_header("content-type", "multipart/form-data")
338 |> post("/api/pleroma/emoji/packs/download", %{
339 url: "https://example.com",
340 name: "test_pack",
341 as: "test_pack2"
342 })
343 |> json_response_and_validate_schema(:internal_server_error) == %{
344 "error" =>
345 "The pack was not set as shared and there is no fallback src to download from"
346 }
347 end
348 end
349
350 describe "PATCH/update /api/pleroma/emoji/pack?name=:name" do
351 setup do
352 pack_file = "#{@emoji_path}/test_pack/pack.json"
353 original_content = File.read!(pack_file)
354
355 on_exit(fn ->
356 File.write!(pack_file, original_content)
357 end)
358
359 {:ok,
360 pack_file: pack_file,
361 new_data: %{
362 "license" => "Test license changed",
363 "homepage" => "https://pleroma.social",
364 "description" => "Test description",
365 "share-files" => false
366 }}
367 end
368
369 test "returns error when file system not writable", %{admin_conn: conn} = ctx do
370 with_mocks([
371 {File, [:passthrough], [stat: fn _ -> {:error, :eacces} end]}
372 ]) do
373 assert conn
374 |> put_req_header("content-type", "multipart/form-data")
375 |> patch(
376 "/api/pleroma/emoji/pack?name=test_pack",
377 %{"metadata" => ctx[:new_data]}
378 )
379 |> json_response_and_validate_schema(500)
380 end
381 end
382
383 test "for a pack without a fallback source", ctx do
384 assert ctx[:admin_conn]
385 |> put_req_header("content-type", "multipart/form-data")
386 |> patch("/api/pleroma/emoji/pack?name=test_pack", %{
387 "metadata" => ctx[:new_data]
388 })
389 |> json_response_and_validate_schema(200) == ctx[:new_data]
390
391 assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
392 end
393
394 test "for a pack with a fallback source", ctx do
395 mock(fn
396 %{
397 method: :get,
398 url: "https://nonshared-pack"
399 } ->
400 text(File.read!("#{@emoji_path}/test_pack_nonshared/nonshared.zip"))
401 end)
402
403 new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
404
405 new_data_with_sha =
406 Map.put(
407 new_data,
408 "fallback-src-sha256",
409 "1967BB4E42BCC34BCC12D57BE7811D3B7BE52F965BCE45C87BD377B9499CE11D"
410 )
411
412 assert ctx[:admin_conn]
413 |> put_req_header("content-type", "multipart/form-data")
414 |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
415 |> json_response_and_validate_schema(200) == new_data_with_sha
416
417 assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
418 end
419
420 test "when the fallback source doesn't have all the files", ctx do
421 mock(fn
422 %{
423 method: :get,
424 url: "https://nonshared-pack"
425 } ->
426 {:ok, {'empty.zip', empty_arch}} = :zip.zip('empty.zip', [], [:memory])
427 text(empty_arch)
428 end)
429
430 new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
431
432 assert ctx[:admin_conn]
433 |> put_req_header("content-type", "multipart/form-data")
434 |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
435 |> json_response_and_validate_schema(:bad_request) == %{
436 "error" => "The fallback archive does not have all files specified in pack.json"
437 }
438 end
439 end
440
441 describe "POST/DELETE /api/pleroma/emoji/pack?name=:name" do
442 test "returns an error on creates pack when file system not writable", %{
443 admin_conn: admin_conn
444 } do
445 path_pack = Path.join(@emoji_path, "test_pack")
446
447 with_mocks([
448 {File, [:passthrough], [mkdir: fn ^path_pack -> {:error, :eacces} end]}
449 ]) do
450 assert admin_conn
451 |> post("/api/pleroma/emoji/pack?name=test_pack")
452 |> json_response_and_validate_schema(500) == %{
453 "error" =>
454 "Unexpected error occurred while creating pack. (POSIX error: Permission denied)"
455 }
456 end
457 end
458
459 test "returns an error on deletes pack when the file system is not writable", %{
460 admin_conn: admin_conn
461 } do
462 path_pack = Path.join(@emoji_path, "test_emoji_pack")
463
464 try do
465 {:ok, _pack} = Pleroma.Emoji.Pack.create("test_emoji_pack")
466
467 with_mocks([
468 {File, [:passthrough], [rm_rf: fn ^path_pack -> {:error, :eacces, path_pack} end]}
469 ]) do
470 assert admin_conn
471 |> delete("/api/pleroma/emoji/pack?name=test_emoji_pack")
472 |> json_response_and_validate_schema(500) == %{
473 "error" =>
474 "Couldn't delete the `test_emoji_pack` pack (POSIX error: Permission denied)"
475 }
476 end
477 after
478 File.rm_rf(path_pack)
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