commit: 897c1ced5f3f5a17ee80f34c0e7d8b378237c3e1
parent 8d0b29d7183f11c05f695d0c3cf4b4ec1d2d2d67
Author: Lain Soykaf <lain@lain.com>
Date: Thu, 7 Aug 2025 13:47:54 +0400
EmojiPackControllerDownloadZipTest: Add test.
Diffstat:
1 file changed, 311 insertions(+), 0 deletions(-)
diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_download_zip_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_download_zip_test.exs
@@ -0,0 +1,311 @@
+# Pleroma: A lightweight social networking server
+# Copyright © Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerDownloadZipTest do
+ use Pleroma.Web.ConnCase, async: false
+
+ import Tesla.Mock
+ import Pleroma.Factory
+
+ @emoji_path Path.join(
+ Pleroma.Config.get!([:instance, :static_dir]),
+ "emoji"
+ )
+
+ setup do
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ admin_conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ Pleroma.Emoji.reload()
+
+ # Clean up any test packs from previous runs
+ on_exit(fn ->
+ test_packs = [
+ "test_zip_pack",
+ "test_zip_pack_url",
+ "test_zip_pack_malicious",
+ "test_invalid_pack",
+ "test_bad_url_pack",
+ "test_no_source_pack"
+ ]
+
+ Enum.each(test_packs, fn pack_name ->
+ pack_path = Path.join(@emoji_path, pack_name)
+
+ if File.exists?(pack_path) do
+ File.rm_rf!(pack_path)
+ end
+ end)
+ end)
+
+ {:ok, %{admin_conn: admin_conn}}
+ end
+
+ describe "POST /api/pleroma/emoji/packs/download_zip" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
+ end
+
+ test "creates pack from uploaded ZIP file", %{admin_conn: admin_conn} do
+ # Create a test ZIP file with emojis
+ {:ok, zip_path} = create_test_emoji_zip()
+
+ upload = %Plug.Upload{
+ content_type: "application/zip",
+ path: zip_path,
+ filename: "test_pack.zip"
+ }
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_zip_pack",
+ file: upload
+ })
+ |> json_response_and_validate_schema(200) == "ok"
+
+ # Verify pack was created
+ assert File.exists?("#{@emoji_path}/test_zip_pack/pack.json")
+ assert File.exists?("#{@emoji_path}/test_zip_pack/test_emoji.png")
+
+ # Verify pack.json contents
+ {:ok, pack_json} = File.read("#{@emoji_path}/test_zip_pack/pack.json")
+ pack_data = Jason.decode!(pack_json)
+
+ assert pack_data["files"]["test_emoji"] == "test_emoji.png"
+ assert pack_data["pack"]["src_sha256"] != nil
+
+ # Clean up
+ File.rm!(zip_path)
+ end
+
+ test "creates pack from URL", %{admin_conn: admin_conn} do
+ # Mock HTTP request to download ZIP
+ {:ok, zip_path} = create_test_emoji_zip()
+ {:ok, zip_data} = File.read(zip_path)
+
+ mock(fn
+ %{method: :get, url: "https://example.com/emoji_pack.zip"} ->
+ %Tesla.Env{status: 200, body: zip_data}
+ end)
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_zip_pack_url",
+ url: "https://example.com/emoji_pack.zip"
+ })
+ |> json_response_and_validate_schema(200) == "ok"
+
+ # Verify pack was created
+ assert File.exists?("#{@emoji_path}/test_zip_pack_url/pack.json")
+ assert File.exists?("#{@emoji_path}/test_zip_pack_url/test_emoji.png")
+
+ # Verify pack.json has URL as source
+ {:ok, pack_json} = File.read("#{@emoji_path}/test_zip_pack_url/pack.json")
+ pack_data = Jason.decode!(pack_json)
+
+ assert pack_data["pack"]["src"] == "https://example.com/emoji_pack.zip"
+ assert pack_data["pack"]["src_sha256"] != nil
+
+ # Clean up
+ File.rm!(zip_path)
+ end
+
+ test "refuses to overwrite existing pack", %{admin_conn: admin_conn} do
+ # Create existing pack
+ pack_path = Path.join(@emoji_path, "test_zip_pack")
+ File.mkdir_p!(pack_path)
+ File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(%{files: %{}}))
+
+ {:ok, zip_path} = create_test_emoji_zip()
+
+ upload = %Plug.Upload{
+ content_type: "application/zip",
+ path: zip_path,
+ filename: "test_pack.zip"
+ }
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_zip_pack",
+ file: upload
+ })
+ |> json_response_and_validate_schema(400) == %{
+ "error" => "Pack already exists, refusing to import test_zip_pack"
+ }
+
+ # Clean up
+ File.rm!(zip_path)
+ end
+
+ test "handles invalid ZIP file", %{admin_conn: admin_conn} do
+ # Create invalid ZIP file
+ invalid_zip_path = Path.join(System.tmp_dir!(), "invalid.zip")
+ File.write!(invalid_zip_path, "not a zip file")
+
+ upload = %Plug.Upload{
+ content_type: "application/zip",
+ path: invalid_zip_path,
+ filename: "invalid.zip"
+ }
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_invalid_pack",
+ file: upload
+ })
+ |> json_response_and_validate_schema(400) == %{
+ "error" => "Could not unzip pack"
+ }
+
+ # Clean up
+ File.rm!(invalid_zip_path)
+ end
+
+ test "handles URL download failure", %{admin_conn: admin_conn} do
+ mock(fn
+ %{method: :get, url: "https://example.com/bad_pack.zip"} ->
+ %Tesla.Env{status: 404, body: "Not found"}
+ end)
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_bad_url_pack",
+ url: "https://example.com/bad_pack.zip"
+ })
+ |> json_response_and_validate_schema(400) == %{
+ "error" => "Could not download pack"
+ }
+ end
+
+ test "requires either file or URL parameter", %{admin_conn: admin_conn} do
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_no_source_pack"
+ })
+ |> json_response_and_validate_schema(400) == %{
+ "error" => "Neither file nor URL was present in the request"
+ }
+ end
+
+ test "preserves existing pack.json if present in ZIP", %{admin_conn: admin_conn} do
+ # Create ZIP with pack.json
+ {:ok, zip_path} = create_test_emoji_zip_with_pack_json()
+
+ upload = %Plug.Upload{
+ content_type: "application/zip",
+ path: zip_path,
+ filename: "test_pack_with_json.zip"
+ }
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: "test_zip_pack",
+ file: upload
+ })
+ |> json_response_and_validate_schema(200) == "ok"
+
+ # Verify original pack.json was preserved
+ {:ok, pack_json} = File.read("#{@emoji_path}/test_zip_pack/pack.json")
+ pack_data = Jason.decode!(pack_json)
+
+ assert pack_data["pack"]["description"] == "Test pack from ZIP"
+ assert pack_data["pack"]["license"] == "Test License"
+
+ # Clean up
+ File.rm!(zip_path)
+ end
+
+ test "rejects malicious pack names", %{admin_conn: admin_conn} do
+ {:ok, zip_path} = create_test_emoji_zip()
+
+ upload = %Plug.Upload{
+ content_type: "application/zip",
+ path: zip_path,
+ filename: "test_pack.zip"
+ }
+
+ # Test path traversal attempts
+ malicious_names = ["../evil", "../../evil", ".", "..", "evil/../../../etc"]
+
+ Enum.each(malicious_names, fn name ->
+ assert_raise RuntimeError, ~r/Invalid or malicious pack name/, fn ->
+ admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download_zip", %{
+ name: name,
+ file: upload
+ })
+ end
+ end)
+
+ # Clean up
+ File.rm!(zip_path)
+ end
+ end
+
+ defp create_test_emoji_zip do
+ tmp_dir = System.tmp_dir!()
+ zip_path = Path.join(tmp_dir, "test_emoji_pack_#{:rand.uniform(10000)}.zip")
+
+ # 1x1 pixel PNG
+ png_data =
+ Base.decode64!(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
+ )
+
+ files = [
+ {~c"test_emoji.png", png_data},
+ # Will be treated as GIF based on extension
+ {~c"another_emoji.gif", png_data}
+ ]
+
+ {:ok, {_name, zip_binary}} = :zip.zip(~c"test_pack.zip", files, [:memory])
+ File.write!(zip_path, zip_binary)
+
+ {:ok, zip_path}
+ end
+
+ defp create_test_emoji_zip_with_pack_json do
+ tmp_dir = System.tmp_dir!()
+ zip_path = Path.join(tmp_dir, "test_emoji_pack_json_#{:rand.uniform(10000)}.zip")
+
+ png_data =
+ Base.decode64!(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
+ )
+
+ pack_json =
+ Jason.encode!(%{
+ pack: %{
+ description: "Test pack from ZIP",
+ license: "Test License"
+ },
+ files: %{
+ "test_emoji" => "test_emoji.png"
+ }
+ })
+
+ files = [
+ {~c"test_emoji.png", png_data},
+ {~c"pack.json", pack_json}
+ ]
+
+ {:ok, {_name, zip_binary}} = :zip.zip(~c"test_pack.zip", files, [:memory])
+ File.write!(zip_path, zip_binary)
+
+ {:ok, zip_path}
+ end
+end