Merge branch 'openapi/pleroma-api/emojis' into 'develop'

Add OpenAPI spec for EmojiPackController

See merge request pleroma/pleroma!2549
This commit is contained in:
lain 2020-05-20 12:08:28 +00:00
commit 265746b21f
7 changed files with 958 additions and 483 deletions

View file

@ -426,7 +426,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
* Authentication: required * Authentication: required
* Params: * Params:
* `file`: file needs to be uploaded with the multipart request or link to remote file. * `file`: file needs to be uploaded with the multipart request or link to remote file.
* `shortcode`: (*optional*) shortcode for new emoji, must be uniq for all emoji. If not sended, shortcode will be taken from original filename. * `shortcode`: (*optional*) shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename.
* `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename. * `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename.
* Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. * Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.

View file

@ -16,162 +16,78 @@ defmodule Pleroma.Emoji.Pack do
alias Pleroma.Emoji alias Pleroma.Emoji
@spec emoji_path() :: Path.t()
def emoji_path do
static = Pleroma.Config.get!([:instance, :static_dir])
Path.join(static, "emoji")
end
@spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values} @spec create(String.t()) :: :ok | {:error, File.posix()} | {:error, :empty_values}
def create(name) when byte_size(name) > 0 do def create(name) do
dir = Path.join(emoji_path(), name) with :ok <- validate_not_empty([name]),
dir <- Path.join(emoji_path(), name),
with :ok <- File.mkdir(dir) do :ok <- File.mkdir(dir) do
%__MODULE__{ %__MODULE__{pack_file: Path.join(dir, "pack.json")}
pack_file: Path.join(dir, "pack.json")
}
|> save_pack() |> save_pack()
end end
end end
def create(_), do: {:error, :empty_values} @spec show(String.t()) :: {:ok, t()} | {:error, atom()}
def show(name) do
@spec show(String.t()) :: {:ok, t()} | {:loaded, nil} | {:error, :empty_values} with :ok <- validate_not_empty([name]),
def show(name) when byte_size(name) > 0 do {:ok, pack} <- load_pack(name) do
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, {:ok, validate_pack(pack)}
{_, pack} <- validate_pack(pack) do
{:ok, pack}
end end
end end
def show(_), do: {:error, :empty_values}
@spec delete(String.t()) :: @spec delete(String.t()) ::
{:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values}
def delete(name) when byte_size(name) > 0 do def delete(name) do
with :ok <- validate_not_empty([name]) do
emoji_path() emoji_path()
|> Path.join(name) |> Path.join(name)
|> File.rm_rf() |> File.rm_rf()
end end
end
def delete(_), do: {:error, :empty_values}
@spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) :: @spec add_file(String.t(), String.t(), Path.t(), Plug.Upload.t() | String.t()) ::
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values} {:ok, t()} | {:error, File.posix() | atom()}
def add_file(name, shortcode, filename, file) def add_file(name, shortcode, filename, file) do
when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(filename) > 0 do with :ok <- validate_not_empty([name, shortcode, filename]),
with {_, nil} <- {:exists, Emoji.get(shortcode)}, :ok <- validate_emoji_not_exists(shortcode),
{_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)} do {:ok, pack} <- load_pack(name),
file_path = Path.join(pack.path, filename) :ok <- save_file(file, pack, filename),
{:ok, updated_pack} <- pack |> put_emoji(shortcode, filename) |> save_pack() do
create_subdirs(file_path)
case file do
%Plug.Upload{path: upload_path} ->
# Copy the uploaded file from the temporary directory
File.copy!(upload_path, file_path)
url when is_binary(url) ->
# Download and write the file
file_contents = Tesla.get!(url).body
File.write!(file_path, file_contents)
end
files = Map.put(pack.files, shortcode, filename)
updated_pack = %{pack | files: files}
case save_pack(updated_pack) do
:ok ->
Emoji.reload() Emoji.reload()
{:ok, updated_pack} {:ok, updated_pack}
e ->
e
end
end
end
def add_file(_, _, _, _), do: {:error, :empty_values}
defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do
file_path
|> Path.dirname()
|> File.mkdir_p!()
end end
end end
@spec delete_file(String.t(), String.t()) :: @spec delete_file(String.t(), String.t()) ::
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values} {:ok, t()} | {:error, File.posix() | atom()}
def delete_file(name, shortcode) when byte_size(name) > 0 and byte_size(shortcode) > 0 do def delete_file(name, shortcode) do
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, with :ok <- validate_not_empty([name, shortcode]),
{_, {filename, files}} when not is_nil(filename) <- {:ok, pack} <- load_pack(name),
{:exists, Map.pop(pack.files, shortcode)}, :ok <- remove_file(pack, shortcode),
emoji <- Path.join(pack.path, filename), {:ok, updated_pack} <- pack |> delete_emoji(shortcode) |> save_pack() do
{_, true} <- {:exists, File.exists?(emoji)} do
emoji_dir = Path.dirname(emoji)
File.rm!(emoji)
if String.contains?(filename, "/") and File.ls!(emoji_dir) == [] do
File.rmdir!(emoji_dir)
end
updated_pack = %{pack | files: files}
case save_pack(updated_pack) do
:ok ->
Emoji.reload() Emoji.reload()
{:ok, updated_pack} {:ok, updated_pack}
e ->
e
end end
end end
end
def delete_file(_, _), do: {:error, :empty_values}
@spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) :: @spec update_file(String.t(), String.t(), String.t(), String.t(), boolean()) ::
{:ok, t()} | {:error, File.posix()} | {:error, :empty_values} {:ok, t()} | {:error, File.posix() | atom()}
def update_file(name, shortcode, new_shortcode, new_filename, force) def update_file(name, shortcode, new_shortcode, new_filename, force) do
when byte_size(name) > 0 and byte_size(shortcode) > 0 and byte_size(new_shortcode) > 0 and with :ok <- validate_not_empty([name, shortcode, new_shortcode, new_filename]),
byte_size(new_filename) > 0 do {:ok, pack} <- load_pack(name),
with {_, %__MODULE__{} = pack} <- {:loaded, load_pack(name)}, {:ok, filename} <- get_filename(pack, shortcode),
{_, {filename, files}} when not is_nil(filename) <- :ok <- validate_emoji_not_exists(new_shortcode, force),
{:exists, Map.pop(pack.files, shortcode)}, :ok <- rename_file(pack, filename, new_filename),
{_, true} <- {:not_used, force or is_nil(Emoji.get(new_shortcode))} do {:ok, updated_pack} <-
old_path = Path.join(pack.path, filename) pack
old_dir = Path.dirname(old_path) |> delete_emoji(shortcode)
new_path = Path.join(pack.path, new_filename) |> put_emoji(new_shortcode, new_filename)
|> save_pack() do
create_subdirs(new_path)
:ok = File.rename(old_path, new_path)
if String.contains?(filename, "/") and File.ls!(old_dir) == [] do
File.rmdir!(old_dir)
end
files = Map.put(files, new_shortcode, new_filename)
updated_pack = %{pack | files: files}
case save_pack(updated_pack) do
:ok ->
Emoji.reload() Emoji.reload()
{:ok, updated_pack} {:ok, updated_pack}
e ->
e
end
end end
end end
def update_file(_, _, _, _, _), do: {:error, :empty_values} @spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, File.posix() | atom()}
@spec import_from_filesystem() :: {:ok, [String.t()]} | {:error, atom()}
def import_from_filesystem do def import_from_filesystem do
emoji_path = emoji_path() emoji_path = emoji_path()
@ -184,7 +100,7 @@ def import_from_filesystem do
File.dir?(path) and File.exists?(Path.join(path, "pack.json")) File.dir?(path) and File.exists?(Path.join(path, "pack.json"))
end) end)
|> Enum.map(&write_pack_contents/1) |> Enum.map(&write_pack_contents/1)
|> Enum.filter(& &1) |> Enum.reject(&is_nil/1)
{:ok, names} {:ok, names}
else else
@ -193,6 +109,117 @@ def import_from_filesystem do
end end
end end
@spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()}
def list_remote(url) do
uri = url |> String.trim() |> URI.parse()
with :ok <- validate_shareable_packs_available(uri) do
uri
|> URI.merge("/api/pleroma/emoji/packs")
|> http_get()
end
end
@spec list_local() :: {:ok, map()}
def list_local do
with {:ok, results} <- list_packs_dir() do
packs =
results
|> Enum.map(fn name ->
case load_pack(name) do
{:ok, pack} -> pack
_ -> nil
end
end)
|> Enum.reject(&is_nil/1)
|> Map.new(fn pack -> {pack.name, validate_pack(pack)} end)
{:ok, packs}
end
end
@spec get_archive(String.t()) :: {:ok, binary()} | {:error, atom()}
def get_archive(name) do
with {:ok, pack} <- load_pack(name),
:ok <- validate_downloadable(pack) do
{:ok, fetch_archive(pack)}
end
end
@spec download(String.t(), String.t(), String.t()) :: :ok | {:error, atom()}
def download(name, url, as) do
uri = url |> String.trim() |> URI.parse()
with :ok <- validate_shareable_packs_available(uri),
{:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(),
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
{:ok, archive} <- download_archive(url, sha),
pack <- copy_as(remote_pack, as || name),
{:ok, _} = unzip(archive, pack_info, remote_pack, pack) do
# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
# in it to depend on itself
if pack_info[:fallback] do
save_pack(pack)
else
{:ok, pack}
end
end
end
@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()}
def save_metadata(metadata, %__MODULE__{} = pack) do
pack
|> Map.put(:pack, metadata)
|> save_pack()
end
@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()}
def update_metadata(name, data) do
with {:ok, pack} <- load_pack(name) do
if fallback_sha_changed?(pack, data) do
update_sha_and_save_metadata(pack, data)
else
save_metadata(data, pack)
end
end
end
@spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found}
def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"])
if File.exists?(pack_file) do
pack =
pack_file
|> File.read!()
|> from_json()
|> Map.put(:pack_file, pack_file)
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)
{:ok, pack}
else
{:error, :not_found}
end
end
@spec emoji_path() :: Path.t()
defp emoji_path do
[:instance, :static_dir]
|> Pleroma.Config.get!()
|> Path.join("emoji")
end
defp validate_emoji_not_exists(shortcode, force \\ false)
defp validate_emoji_not_exists(_shortcode, true), do: :ok
defp validate_emoji_not_exists(shortcode, _) do
case Emoji.get(shortcode) do
nil -> :ok
_ -> {:error, :already_exists}
end
end
defp write_pack_contents(path) do defp write_pack_contents(path) do
pack = %__MODULE__{ pack = %__MODULE__{
files: files_from_path(path), files: files_from_path(path),
@ -201,7 +228,7 @@ defp write_pack_contents(path) do
} }
case save_pack(pack) do case save_pack(pack) do
:ok -> Path.basename(path) {:ok, _pack} -> Path.basename(path)
_ -> nil _ -> nil
end end
end end
@ -216,7 +243,8 @@ defp files_from_path(path) do
# FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2 # FIXME: Copy-pasted from Pleroma.Emoji/load_from_file_stream/2
# Create a map of shortcodes to filenames from emoji.txt # Create a map of shortcodes to filenames from emoji.txt
File.read!(txt_path) txt_path
|> File.read!()
|> String.split("\n") |> String.split("\n")
|> Enum.map(&String.trim/1) |> Enum.map(&String.trim/1)
|> Enum.map(fn line -> |> Enum.map(fn line ->
@ -226,21 +254,18 @@ defp files_from_path(path) do
[name, file | _] -> [name, file | _] ->
file_dir_name = Path.dirname(file) file_dir_name = Path.dirname(file)
file =
if String.ends_with?(path, file_dir_name) do if String.ends_with?(path, file_dir_name) do
Path.basename(file) {name, Path.basename(file)}
else else
file
end
{name, file} {name, file}
end
_ -> _ ->
nil nil
end end
end) end)
|> Enum.filter(& &1) |> Enum.reject(&is_nil/1)
|> Enum.into(%{}) |> Map.new()
else else
# If there's no emoji.txt, assume all files # If there's no emoji.txt, assume all files
# that are of certain extensions from the config are emojis and import them all # that are of certain extensions from the config are emojis and import them all
@ -249,60 +274,20 @@ defp files_from_path(path) do
end end
end end
@spec list_remote(String.t()) :: {:ok, map()}
def list_remote(url) do
uri =
url
|> String.trim()
|> URI.parse()
with {_, true} <- {:shareable, shareable_packs_available?(uri)} do
packs =
uri
|> URI.merge("/api/pleroma/emoji/packs")
|> to_string()
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
{:ok, packs}
end
end
@spec list_local() :: {:ok, map()}
def list_local do
emoji_path = emoji_path()
# Create the directory first if it does not exist. This is probably the first request made
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
packs =
results
|> Enum.map(&load_pack/1)
|> Enum.filter(& &1)
|> Enum.map(&validate_pack/1)
|> Map.new()
{:ok, packs}
end
end
defp validate_pack(pack) do defp validate_pack(pack) do
info =
if downloadable?(pack) do if downloadable?(pack) do
archive = fetch_archive(pack) archive = fetch_archive(pack)
archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16() archive_sha = :crypto.hash(:sha256, archive) |> Base.encode16()
info =
pack.pack pack.pack
|> Map.put("can-download", true) |> Map.put("can-download", true)
|> Map.put("download-sha256", archive_sha) |> Map.put("download-sha256", archive_sha)
{pack.name, Map.put(pack, :pack, info)}
else else
info = Map.put(pack.pack, "can-download", false) Map.put(pack.pack, "can-download", false)
{pack.name, Map.put(pack, :pack, info)}
end end
Map.put(pack, :pack, info)
end end
defp downloadable?(pack) do defp downloadable?(pack) do
@ -315,26 +300,6 @@ defp downloadable?(pack) do
end) end)
end end
@spec get_archive(String.t()) :: {:ok, binary()}
def get_archive(name) do
with {_, %__MODULE__{} = pack} <- {:exists?, load_pack(name)},
{_, true} <- {:can_download?, downloadable?(pack)} do
{:ok, fetch_archive(pack)}
end
end
defp fetch_archive(pack) do
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
case Cachex.get!(:emoji_packs_cache, pack.name) do
%{hash: ^hash, pack_data: archive} ->
archive
_ ->
create_archive_and_cache(pack, hash)
end
end
defp create_archive_and_cache(pack, hash) do defp create_archive_and_cache(pack, hash) do
files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)] files = ['pack.json' | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)]
@ -356,23 +321,158 @@ defp create_archive_and_cache(pack, hash) do
result result
end end
@spec download(String.t(), String.t(), String.t()) :: :ok defp save_pack(pack) do
def download(name, url, as) do with {:ok, json} <- Jason.encode(pack, pretty: true),
uri = :ok <- File.write(pack.pack_file, json) do
url {:ok, pack}
|> String.trim() end
|> URI.parse() end
with {_, true} <- {:shareable, shareable_packs_available?(uri)} do defp from_json(json) do
remote_pack = map = Jason.decode!(json)
uri
|> URI.merge("/api/pleroma/emoji/packs/#{name}")
|> to_string()
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
result = struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
end
defp validate_shareable_packs_available(uri) do
with {:ok, %{"links" => links}} <- uri |> URI.merge("/.well-known/nodeinfo") |> http_get(),
# Get the actual nodeinfo address and fetch it
{:ok, %{"metadata" => %{"features" => features}}} <-
links |> List.last() |> Map.get("href") |> http_get() do
if Enum.member?(features, "shareable_emoji_packs") do
:ok
else
{:error, :not_shareable}
end
end
end
defp validate_not_empty(list) do
if Enum.all?(list, fn i -> is_binary(i) and i != "" end) do
:ok
else
{:error, :empty_values}
end
end
defp save_file(file, pack, filename) do
file_path = Path.join(pack.path, filename)
create_subdirs(file_path)
case file do
%Plug.Upload{path: upload_path} ->
# Copy the uploaded file from the temporary directory
with {:ok, _} <- File.copy(upload_path, file_path), do: :ok
url when is_binary(url) ->
# Download and write the file
file_contents = Tesla.get!(url).body
File.write(file_path, file_contents)
end
end
defp put_emoji(pack, shortcode, filename) do
files = Map.put(pack.files, shortcode, filename)
%{pack | files: files}
end
defp delete_emoji(pack, shortcode) do
files = Map.delete(pack.files, shortcode)
%{pack | files: files}
end
defp rename_file(pack, filename, new_filename) do
old_path = Path.join(pack.path, filename)
new_path = Path.join(pack.path, new_filename)
create_subdirs(new_path)
with :ok <- File.rename(old_path, new_path) do
remove_dir_if_empty(old_path, filename)
end
end
defp create_subdirs(file_path) do
if String.contains?(file_path, "/") do
file_path
|> Path.dirname()
|> File.mkdir_p!()
end
end
defp remove_file(pack, shortcode) do
with {:ok, filename} <- get_filename(pack, shortcode),
emoji <- Path.join(pack.path, filename),
:ok <- File.rm(emoji) do
remove_dir_if_empty(emoji, filename)
end
end
defp remove_dir_if_empty(emoji, filename) do
dir = Path.dirname(emoji)
if String.contains?(filename, "/") and File.ls!(dir) == [] do
File.rmdir!(dir)
else
:ok
end
end
defp get_filename(pack, shortcode) do
with %{^shortcode => filename} when is_binary(filename) <- pack.files,
true <- pack.path |> Path.join(filename) |> File.exists?() do
{:ok, filename}
else
_ -> {:error, :doesnt_exist}
end
end
defp http_get(%URI{} = url), do: url |> to_string() |> http_get()
defp http_get(url) do
with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do
Jason.decode(body)
end
end
defp list_packs_dir do
emoji_path = emoji_path()
# Create the directory first if it does not exist. This is probably the first request made
# with the API so it should be sufficient
with {:create_dir, :ok} <- {:create_dir, File.mkdir_p(emoji_path)},
{:ls, {:ok, results}} <- {:ls, File.ls(emoji_path)} do
{:ok, results}
else
{:create_dir, {:error, e}} -> {:error, :create_dir, e}
{:ls, {:error, e}} -> {:error, :ls, e}
end
end
defp validate_downloadable(pack) do
if downloadable?(pack), do: :ok, else: {:error, :cant_download}
end
defp copy_as(remote_pack, local_name) do
path = Path.join(emoji_path(), local_name)
%__MODULE__{
name: local_name,
path: path,
files: remote_pack["files"],
pack_file: Path.join(path, "pack.json")
}
end
defp unzip(archive, pack_info, remote_pack, local_pack) do
with :ok <- File.mkdir_p!(local_pack.path) do
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
# Fallback cannot contain a pack.json file
files = if pack_info[:fallback], do: files, else: ['pack.json' | files]
:zip.unzip(archive, cwd: to_charlist(local_pack.path), file_list: files)
end
end
defp fetch_pack_info(remote_pack, uri, name) do
case remote_pack["pack"] do case remote_pack["pack"] do
%{"share-files" => true, "can-download" => true, "download-sha256" => sha} -> %{"share-files" => true, "can-download" => true, "download-sha256" => sha} ->
{:ok, {:ok,
@ -390,118 +490,52 @@ def download(name, url, as) do
}} }}
_ -> _ ->
{:error, {:error, "The pack was not set as shared and there is no fallback src to download from"}
"The pack was not set as shared and there is no fallback src to download from"} end
end end
with {:ok, %{sha: sha, url: url} = pinfo} <- result, defp download_archive(url, sha) do
%{body: archive} <- Tesla.get!(url), with {:ok, %{body: archive}} <- Tesla.get(url) do
{_, true} <- {:checksum, Base.decode16!(sha) == :crypto.hash(:sha256, archive)} do if Base.decode16!(sha) == :crypto.hash(:sha256, archive) do
local_name = as || name {:ok, archive}
else
path = Path.join(emoji_path(), local_name) {:error, :imvalid_checksum}
pack = %__MODULE__{
name: local_name,
path: path,
files: remote_pack["files"],
pack_file: Path.join(path, "pack.json")
}
File.mkdir_p!(pack.path)
files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
# Fallback cannot contain a pack.json file
files = if pinfo[:fallback], do: files, else: ['pack.json' | files]
{:ok, _} = :zip.unzip(archive, cwd: to_charlist(pack.path), file_list: files)
# Fallback can't contain a pack.json file, since that would cause the fallback-src-sha256
# in it to depend on itself
if pinfo[:fallback] do
save_pack(pack)
end
:ok
end end
end end
end end
defp save_pack(pack), do: File.write(pack.pack_file, Jason.encode!(pack, pretty: true)) defp fetch_archive(pack) do
hash = :crypto.hash(:md5, File.read!(pack.pack_file))
@spec save_metadata(map(), t()) :: {:ok, t()} | {:error, File.posix()} case Cachex.get!(:emoji_packs_cache, pack.name) do
def save_metadata(metadata, %__MODULE__{} = pack) do %{hash: ^hash, pack_data: archive} -> archive
pack = Map.put(pack, :pack, metadata) _ -> create_archive_and_cache(pack, hash)
with :ok <- save_pack(pack) do
{:ok, pack}
end end
end end
@spec update_metadata(String.t(), map()) :: {:ok, t()} | {:error, File.posix()} defp fallback_sha_changed?(pack, data) do
def update_metadata(name, data) do is_binary(data[:"fallback-src"]) and data[:"fallback-src"] != pack.pack["fallback-src"]
pack = load_pack(name) end
fb_sha_changed? = defp update_sha_and_save_metadata(pack, data) do
not is_nil(data["fallback-src"]) and data["fallback-src"] != pack.pack["fallback-src"] with {:ok, %{body: zip}} <- Tesla.get(data[:"fallback-src"]),
:ok <- validate_has_all_files(pack, zip) do
with {_, true} <- {:update?, fb_sha_changed?}, fallback_sha = :sha256 |> :crypto.hash(zip) |> Base.encode16()
{:ok, %{body: zip}} <- Tesla.get(data["fallback-src"]),
{:ok, f_list} <- :zip.unzip(zip, [:memory]),
{_, true} <- {:has_all_files?, has_all_files?(pack.files, f_list)} do
fallback_sha = :crypto.hash(:sha256, zip) |> Base.encode16()
data data
|> Map.put("fallback-src-sha256", fallback_sha) |> Map.put("fallback-src-sha256", fallback_sha)
|> save_metadata(pack) |> save_metadata(pack)
else
{:update?, _} -> save_metadata(data, pack)
e -> e
end end
end end
defp validate_has_all_files(pack, zip) do
with {:ok, f_list} <- :zip.unzip(zip, [:memory]) do
# Check if all files from the pack.json are in the archive # Check if all files from the pack.json are in the archive
defp has_all_files?(files, f_list) do pack.files
Enum.all?(files, fn {_, from_manifest} -> |> Enum.all?(fn {_, from_manifest} ->
List.keyfind(f_list, to_charlist(from_manifest), 0) List.keyfind(f_list, to_charlist(from_manifest), 0)
end) end)
|> if(do: :ok, else: {:error, :incomplete})
end end
@spec load_pack(String.t()) :: t() | nil
def load_pack(name) do
pack_file = Path.join([emoji_path(), name, "pack.json"])
if File.exists?(pack_file) do
pack_file
|> File.read!()
|> from_json()
|> Map.put(:pack_file, pack_file)
|> Map.put(:path, Path.dirname(pack_file))
|> Map.put(:name, name)
end
end
defp from_json(json) do
map = Jason.decode!(json)
struct(__MODULE__, %{files: map["files"], pack: map["pack"]})
end
defp shareable_packs_available?(uri) do
uri
|> URI.merge("/.well-known/nodeinfo")
|> to_string()
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
|> Map.get("links")
|> List.last()
|> Map.get("href")
# Get the actual nodeinfo address and fetch it
|> Tesla.get!()
|> Map.get(:body)
|> Jason.decode!()
|> get_in(["metadata", "features"])
|> Enum.member?("shareable_emoji_packs")
end end
end end

View file

@ -0,0 +1,390 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def remote_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Make request to another instance for emoji packs list",
security: [%{"oAuth" => ["write"]}],
parameters: [url_param()],
operationId: "PleromaAPI.EmojiPackController.remote",
responses: %{
200 => emoji_packs_response(),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end
def index_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Lists local custom emoji packs",
operationId: "PleromaAPI.EmojiPackController.index",
responses: %{
200 => emoji_packs_response()
}
}
end
def show_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Show emoji pack",
operationId: "PleromaAPI.EmojiPackController.show",
parameters: [name_param()],
responses: %{
200 => Operation.response("Emoji Pack", "application/json", emoji_pack()),
400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def archive_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Requests a local pack archive from the instance",
operationId: "PleromaAPI.EmojiPackController.archive",
parameters: [name_param()],
responses: %{
200 =>
Operation.response("Archive file", "application/octet-stream", %Schema{
type: :string,
format: :binary
}),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def download_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Download pack from another instance",
operationId: "PleromaAPI.EmojiPackController.download",
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", download_request(), required: true),
responses: %{
200 => ok_response(),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end
defp download_request do
%Schema{
type: :object,
required: [:url, :name],
properties: %{
url: %Schema{
type: :string,
format: :uri,
description: "URL of the instance to download from"
},
name: %Schema{type: :string, format: :uri, description: "Pack Name"},
as: %Schema{type: :string, format: :uri, description: "Save as"}
}
}
end
def create_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Create an empty pack",
operationId: "PleromaAPI.EmojiPackController.create",
security: [%{"oAuth" => ["write"]}],
parameters: [name_param()],
responses: %{
200 => ok_response(),
400 => Operation.response("Not Found", "application/json", ApiError),
409 => Operation.response("Conflict", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Delete a custom emoji pack",
operationId: "PleromaAPI.EmojiPackController.delete",
security: [%{"oAuth" => ["write"]}],
parameters: [name_param()],
responses: %{
200 => ok_response(),
400 => Operation.response("Bad Request", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def update_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Updates (replaces) pack metadata",
operationId: "PleromaAPI.EmojiPackController.update",
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", update_request(), required: true),
parameters: [name_param()],
responses: %{
200 => Operation.response("Metadata", "application/json", metadata()),
400 => Operation.response("Bad Request", "application/json", ApiError)
}
}
end
def add_file_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Add new file to the pack",
operationId: "PleromaAPI.EmojiPackController.add_file",
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", add_file_request(), required: true),
parameters: [name_param()],
responses: %{
200 => Operation.response("Files Object", "application/json", files_object()),
400 => Operation.response("Bad Request", "application/json", ApiError),
409 => Operation.response("Conflict", "application/json", ApiError)
}
}
end
defp add_file_request do
%Schema{
type: :object,
required: [:file],
properties: %{
file: %Schema{
description:
"File needs to be uploaded with the multipart request or link to remote file",
anyOf: [
%Schema{type: :string, format: :binary},
%Schema{type: :string, format: :uri}
]
},
shortcode: %Schema{
type: :string,
description:
"Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename."
},
filename: %Schema{
type: :string,
description:
"New emoji file name. If not specified will be taken from original filename."
}
}
}
end
def update_file_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Add new file to the pack",
operationId: "PleromaAPI.EmojiPackController.update_file",
security: [%{"oAuth" => ["write"]}],
requestBody: request_body("Parameters", update_file_request(), required: true),
parameters: [name_param()],
responses: %{
200 => Operation.response("Files Object", "application/json", files_object()),
400 => Operation.response("Bad Request", "application/json", ApiError),
409 => Operation.response("Conflict", "application/json", ApiError)
}
}
end
defp update_file_request do
%Schema{
type: :object,
required: [:shortcode, :new_shortcode, :new_filename],
properties: %{
shortcode: %Schema{
type: :string,
description: "Emoji file shortcode"
},
new_shortcode: %Schema{
type: :string,
description: "New emoji file shortcode"
},
new_filename: %Schema{
type: :string,
description: "New filename for emoji file"
},
force: %Schema{
type: :boolean,
description: "With true value to overwrite existing emoji with new shortcode",
default: false
}
}
}
end
def delete_file_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Delete emoji file from pack",
operationId: "PleromaAPI.EmojiPackController.delete_file",
security: [%{"oAuth" => ["write"]}],
parameters: [
name_param(),
Operation.parameter(:shortcode, :query, :string, "File shortcode",
example: "cofe",
required: true
)
],
responses: %{
200 => Operation.response("Files Object", "application/json", files_object()),
400 => Operation.response("Bad Request", "application/json", ApiError)
}
}
end
def import_from_filesystem_operation do
%Operation{
tags: ["Emoji Packs"],
summary: "Imports packs from filesystem",
operationId: "PleromaAPI.EmojiPackController.import",
security: [%{"oAuth" => ["write"]}],
responses: %{
200 =>
Operation.response("Array of imported pack names", "application/json", %Schema{
type: :array,
items: %Schema{type: :string}
})
}
}
end
defp name_param do
Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true)
end
defp url_param do
Operation.parameter(
:url,
:query,
%Schema{type: :string, format: :uri},
"URL of the instance",
required: true
)
end
defp ok_response do
Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"})
end
defp emoji_packs_response do
Operation.response(
"Object with pack names as keys and pack contents as values",
"application/json",
%Schema{
type: :object,
additionalProperties: emoji_pack(),
example: %{
"emojos" => emoji_pack().example
}
}
)
end
defp emoji_pack do
%Schema{
title: "EmojiPack",
type: :object,
properties: %{
files: files_object(),
pack: %Schema{
type: :object,
properties: %{
license: %Schema{type: :string},
homepage: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
"can-download": %Schema{type: :boolean},
"share-files": %Schema{type: :boolean},
"download-sha256": %Schema{type: :string}
}
}
},
example: %{
"files" => %{"emacs" => "emacs.png", "guix" => "guix.png"},
"pack" => %{
"license" => "Test license",
"homepage" => "https://pleroma.social",
"description" => "Test description",
"can-download" => true,
"share-files" => true,
"download-sha256" => "57482F30674FD3DE821FF48C81C00DA4D4AF1F300209253684ABA7075E5FC238"
}
}
}
end
defp files_object do
%Schema{
type: :object,
additionalProperties: %Schema{type: :string},
description: "Object with emoji names as keys and filenames as values"
}
end
defp update_request do
%Schema{
type: :object,
properties: %{
metadata: %Schema{
type: :object,
description: "Metadata to replace the old one",
properties: %{
license: %Schema{type: :string},
homepage: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
"fallback-src": %Schema{
type: :string,
format: :uri,
description: "Fallback url to download pack from"
},
"fallback-src-sha256": %Schema{
type: :string,
description: "SHA256 encoded for fallback pack archive"
},
"share-files": %Schema{type: :boolean, description: "Is pack allowed for sharing?"}
}
}
}
}
end
defp metadata do
%Schema{
type: :object,
properties: %{
license: %Schema{type: :string},
homepage: %Schema{type: :string, format: :uri},
description: %Schema{type: :string},
"fallback-src": %Schema{
type: :string,
format: :uri,
description: "Fallback url to download pack from"
},
"fallback-src-sha256": %Schema{
type: :string,
description: "SHA256 encoded for fallback pack archive"
},
"share-files": %Schema{type: :boolean, description: "Is pack allowed for sharing?"}
}
}
end
end

View file

@ -1,8 +1,10 @@
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Emoji.Pack alias Pleroma.Emoji.Pack
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.OAuthScopesPlug,
%{scopes: ["write"], admin: true} %{scopes: ["write"], admin: true}
@ -19,39 +21,37 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
] ]
) )
plug( @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
:skip_plug, plug(:skip_plug, @skip_plugs when action in [:archive, :show, :list])
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug]
when action in [:archive, :show, :list]
)
def remote(conn, %{"url" => url}) do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
def remote(conn, %{url: url}) do
with {:ok, packs} <- Pack.list_remote(url) do with {:ok, packs} <- Pack.list_remote(url) do
json(conn, packs) json(conn, packs)
else else
{:shareable, _} -> {:error, :not_shareable} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "The requested instance does not support sharing emoji packs"}) |> json(%{error: "The requested instance does not support sharing emoji packs"})
end end
end end
def list(conn, _params) do def index(conn, _params) do
emoji_path = emoji_path =
Path.join( [:instance, :static_dir]
Pleroma.Config.get!([:instance, :static_dir]), |> Pleroma.Config.get!()
"emoji" |> Path.join("emoji")
)
with {:ok, packs} <- Pack.list_local() do with {:ok, packs} <- Pack.list_local() do
json(conn, packs) json(conn, packs)
else else
{:create_dir, {:error, e}} -> {:error, :create_dir, e} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"}) |> json(%{error: "Failed to create the emoji pack directory at #{emoji_path}: #{e}"})
{:ls, {:error, e}} -> {:error, :ls, e} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{ |> json(%{
@ -60,13 +60,13 @@ def list(conn, _params) do
end end
end end
def show(conn, %{"name" => name}) do def show(conn, %{name: name}) do
name = String.trim(name) name = String.trim(name)
with {:ok, pack} <- Pack.show(name) do with {:ok, pack} <- Pack.show(name) do
json(conn, pack) json(conn, pack)
else else
{:loaded, _} -> {:error, :not_found} ->
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"}) |> json(%{error: "Pack #{name} does not exist"})
@ -78,11 +78,11 @@ def show(conn, %{"name" => name}) do
end end
end end
def archive(conn, %{"name" => name}) do def archive(conn, %{name: name}) do
with {:ok, archive} <- Pack.get_archive(name) do with {:ok, archive} <- Pack.get_archive(name) do
send_download(conn, {:binary, archive}, filename: "#{name}.zip") send_download(conn, {:binary, archive}, filename: "#{name}.zip")
else else
{:can_download?, _} -> {:error, :cant_download} ->
conn conn
|> put_status(:forbidden) |> put_status(:forbidden)
|> json(%{ |> json(%{
@ -90,23 +90,23 @@ def archive(conn, %{"name" => name}) do
"Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
}) })
{:exists?, _} -> {:error, :not_found} ->
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(%{error: "Pack #{name} does not exist"}) |> json(%{error: "Pack #{name} does not exist"})
end end
end end
def download(conn, %{"url" => url, "name" => name} = params) do def download(%{body_params: %{url: url, name: name} = params} = conn, _) do
with :ok <- Pack.download(name, url, params["as"]) do with {:ok, _pack} <- Pack.download(name, url, params[:as]) do
json(conn, "ok") json(conn, "ok")
else else
{:shareable, _} -> {:error, :not_shareable} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "The requested instance does not support sharing emoji packs"}) |> json(%{error: "The requested instance does not support sharing emoji packs"})
{:checksum, _} -> {:error, :imvalid_checksum} ->
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"})
@ -118,10 +118,10 @@ def download(conn, %{"url" => url, "name" => name} = params) do
end end
end end
def create(conn, %{"name" => name}) do def create(conn, %{name: name}) do
name = String.trim(name) name = String.trim(name)
with :ok <- Pack.create(name) do with {:ok, _pack} <- Pack.create(name) do
json(conn, "ok") json(conn, "ok")
else else
{:error, :eexist} -> {:error, :eexist} ->
@ -143,7 +143,7 @@ def create(conn, %{"name" => name}) do
end end
end end
def delete(conn, %{"name" => name}) do def delete(conn, %{name: name}) do
name = String.trim(name) name = String.trim(name)
with {:ok, deleted} when deleted != [] <- Pack.delete(name) do with {:ok, deleted} when deleted != [] <- Pack.delete(name) do
@ -166,11 +166,11 @@ def delete(conn, %{"name" => name}) do
end end
end end
def update(conn, %{"name" => name, "metadata" => metadata}) do def update(%{body_params: %{metadata: metadata}} = conn, %{name: name}) do
with {:ok, pack} <- Pack.update_metadata(name, metadata) do with {:ok, pack} <- Pack.update_metadata(name, metadata) do
json(conn, pack.pack) json(conn, pack.pack)
else else
{:has_all_files?, _} -> {:error, :incomplete} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "The fallback archive does not have all files specified in pack.json"}) |> json(%{error: "The fallback archive does not have all files specified in pack.json"})
@ -184,19 +184,19 @@ def update(conn, %{"name" => name, "metadata" => metadata}) do
end end
end end
def add_file(conn, %{"name" => name} = params) do def add_file(%{body_params: params} = conn, %{name: name}) do
filename = params["filename"] || get_filename(params["file"]) filename = params[:filename] || get_filename(params[:file])
shortcode = params["shortcode"] || Path.basename(filename, Path.extname(filename)) shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename))
with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params["file"]) do with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do
json(conn, pack.files) json(conn, pack.files)
else else
{:exists, _} -> {:error, :already_exists} ->
conn conn
|> put_status(:conflict) |> put_status(:conflict)
|> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"}) |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})
{:loaded, _} -> {:error, :not_found} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack \"#{name}\" is not found"}) |> json(%{error: "pack \"#{name}\" is not found"})
@ -215,20 +215,20 @@ def add_file(conn, %{"name" => name} = params) do
end end
end end
def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do def update_file(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: name}) do
new_shortcode = params["new_shortcode"] new_shortcode = params[:new_shortcode]
new_filename = params["new_filename"] new_filename = params[:new_filename]
force = params["force"] == true force = params[:force]
with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do
json(conn, pack.files) json(conn, pack.files)
else else
{:exists, _} -> {:error, :doesnt_exist} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
{:not_used, _} -> {:error, :already_exists} ->
conn conn
|> put_status(:conflict) |> put_status(:conflict)
|> json(%{ |> json(%{
@ -236,7 +236,7 @@ def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
"New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option" "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
}) })
{:loaded, _} -> {:error, :not_found} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack \"#{name}\" is not found"}) |> json(%{error: "pack \"#{name}\" is not found"})
@ -255,16 +255,16 @@ def update_file(conn, %{"name" => name, "shortcode" => shortcode} = params) do
end end
end end
def delete_file(conn, %{"name" => name, "shortcode" => shortcode}) do def delete_file(conn, %{name: name, shortcode: shortcode}) do
with {:ok, pack} <- Pack.delete_file(name, shortcode) do with {:ok, pack} <- Pack.delete_file(name, shortcode) do
json(conn, pack.files) json(conn, pack.files)
else else
{:exists, _} -> {:error, :doesnt_exist} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "Emoji \"#{shortcode}\" does not exist"}) |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
{:loaded, _} -> {:error, :not_found} ->
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(%{error: "pack \"#{name}\" is not found"}) |> json(%{error: "pack \"#{name}\" is not found"})

View file

@ -216,24 +216,25 @@ defmodule Pleroma.Web.Router do
scope "/packs" do scope "/packs" do
pipe_through(:admin_api) pipe_through(:admin_api)
get("/import", EmojiAPIController, :import_from_filesystem) get("/import", EmojiPackController, :import_from_filesystem)
get("/remote", EmojiAPIController, :remote) get("/remote", EmojiPackController, :remote)
post("/download", EmojiAPIController, :download) post("/download", EmojiPackController, :download)
post("/:name", EmojiAPIController, :create) post("/:name", EmojiPackController, :create)
patch("/:name", EmojiAPIController, :update) patch("/:name", EmojiPackController, :update)
delete("/:name", EmojiAPIController, :delete) delete("/:name", EmojiPackController, :delete)
post("/:name/files", EmojiAPIController, :add_file) post("/:name/files", EmojiPackController, :add_file)
patch("/:name/files", EmojiAPIController, :update_file) patch("/:name/files", EmojiPackController, :update_file)
delete("/:name/files", EmojiAPIController, :delete_file) delete("/:name/files", EmojiPackController, :delete_file)
end end
# Pack info / downloading # Pack info / downloading
scope "/packs" do scope "/packs" do
get("/", EmojiAPIController, :list) pipe_through(:api)
get("/:name", EmojiAPIController, :show) get("/", EmojiPackController, :index)
get("/:name/archive", EmojiAPIController, :archive) get("/:name", EmojiPackController, :show)
get("/:name/archive", EmojiPackController, :archive)
end end
end end

View file

@ -28,7 +28,7 @@ test "request body schema example matches schema" do
end end
end end
for {status, response} <- operation.responses do for {status, response} <- operation.responses, is_map(response.content[@content_type]) do
describe "#{operation.operationId} - #{status} Response" do describe "#{operation.operationId} - #{status} Response" do
@schema resolve_schema(response.content[@content_type].schema) @schema resolve_schema(response.content[@content_type].schema)

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Tesla.Mock import Tesla.Mock
@ -28,7 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
end end
test "GET /api/pleroma/emoji/packs", %{conn: conn} do test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
shared = resp["test_pack"] shared = resp["test_pack"]
assert shared["files"] == %{"blank" => "blank.png"} assert shared["files"] == %{"blank" => "blank.png"}
@ -46,7 +46,7 @@ test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
resp = resp =
conn conn
|> get("/api/pleroma/emoji/packs") |> get("/api/pleroma/emoji/packs")
|> json_response(200) |> json_response_and_validate_schema(200)
mock(fn mock(fn
%{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> %{method: :get, url: "https://example.com/.well-known/nodeinfo"} ->
@ -60,10 +60,8 @@ test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
end) end)
assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/remote", %{ |> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
url: "https://example.com" |> json_response_and_validate_schema(200) == resp
})
|> json_response(200) == resp
end end
test "non shareable instance", %{admin_conn: admin_conn} do test "non shareable instance", %{admin_conn: admin_conn} do
@ -76,8 +74,8 @@ test "non shareable instance", %{admin_conn: admin_conn} do
end) end)
assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/remote", %{url: "https://example.com"}) |> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
|> json_response(500) == %{ |> json_response_and_validate_schema(500) == %{
"error" => "The requested instance does not support sharing emoji packs" "error" => "The requested instance does not support sharing emoji packs"
} }
end end
@ -99,7 +97,7 @@ test "download shared pack", %{conn: conn} do
test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/test_pack_for_import/archive") |> get("/api/pleroma/emoji/packs/test_pack_for_import/archive")
|> json_response(:not_found) == %{ |> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack test_pack_for_import does not exist" "error" => "Pack test_pack_for_import does not exist"
} }
end end
@ -107,7 +105,7 @@ test "non existing pack", %{conn: conn} do
test "non downloadable pack", %{conn: conn} do test "non downloadable pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive") |> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive")
|> json_response(:forbidden) == %{ |> json_response_and_validate_schema(:forbidden) == %{
"error" => "error" =>
"Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" "Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
} }
@ -132,7 +130,7 @@ test "shared pack from remote and non shared from fallback-src", %{
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/packs/test_pack")
|> json_response(200) |> json_response_and_validate_schema(200)
|> json() |> json()
%{ %{
@ -150,7 +148,7 @@ test "shared pack from remote and non shared from fallback-src", %{
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared") |> get("/api/pleroma/emoji/packs/test_pack_nonshared")
|> json_response(200) |> json_response_and_validate_schema(200)
|> json() |> json()
%{ %{
@ -161,23 +159,25 @@ test "shared pack from remote and non shared from fallback-src", %{
end) end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{ |> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com", url: "https://example.com",
name: "test_pack", name: "test_pack",
as: "test_pack2" as: "test_pack2"
}) })
|> json_response(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_pack2/pack.json") assert File.exists?("#{@emoji_path}/test_pack2/pack.json")
assert File.exists?("#{@emoji_path}/test_pack2/blank.png") assert File.exists?("#{@emoji_path}/test_pack2/blank.png")
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack2") |> delete("/api/pleroma/emoji/packs/test_pack2")
|> json_response(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_pack2") refute File.exists?("#{@emoji_path}/test_pack2")
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post( |> post(
"/api/pleroma/emoji/packs/download", "/api/pleroma/emoji/packs/download",
%{ %{
@ -186,14 +186,14 @@ test "shared pack from remote and non shared from fallback-src", %{
as: "test_pack_nonshared2" as: "test_pack_nonshared2"
} }
) )
|> json_response(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json") assert File.exists?("#{@emoji_path}/test_pack_nonshared2/pack.json")
assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png") assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png")
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack_nonshared2") |> delete("/api/pleroma/emoji/packs/test_pack_nonshared2")
|> json_response(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_pack_nonshared2") refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
end end
@ -208,6 +208,7 @@ test "nonshareable instance", %{admin_conn: admin_conn} do
end) end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post( |> post(
"/api/pleroma/emoji/packs/download", "/api/pleroma/emoji/packs/download",
%{ %{
@ -216,7 +217,7 @@ test "nonshareable instance", %{admin_conn: admin_conn} do
as: "test_pack2" as: "test_pack2"
} }
) )
|> json_response(500) == %{ |> json_response_and_validate_schema(500) == %{
"error" => "The requested instance does not support sharing emoji packs" "error" => "The requested instance does not support sharing emoji packs"
} }
end end
@ -233,10 +234,8 @@ test "checksum fail", %{admin_conn: admin_conn} do
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha" url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha"
} -> } ->
%Tesla.Env{ {:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha")
status: 200, %Tesla.Env{status: 200, body: Jason.encode!(pack)}
body: Pleroma.Emoji.Pack.load_pack("pack_bad_sha") |> Jason.encode!()
}
%{ %{
method: :get, method: :get,
@ -249,12 +248,13 @@ test "checksum fail", %{admin_conn: admin_conn} do
end) end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{ |> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com", url: "https://example.com",
name: "pack_bad_sha", name: "pack_bad_sha",
as: "pack_bad_sha2" as: "pack_bad_sha2"
}) })
|> json_response(:internal_server_error) == %{ |> json_response_and_validate_schema(:internal_server_error) == %{
"error" => "SHA256 for the pack doesn't match the one sent by the server" "error" => "SHA256 for the pack doesn't match the one sent by the server"
} }
end end
@ -271,19 +271,18 @@ test "other error", %{admin_conn: admin_conn} do
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack" url: "https://example.com/api/pleroma/emoji/packs/test_pack"
} -> } ->
%Tesla.Env{ {:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack")
status: 200, %Tesla.Env{status: 200, body: Jason.encode!(pack)}
body: Pleroma.Emoji.Pack.load_pack("test_pack") |> Jason.encode!()
}
end) end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/download", %{ |> post("/api/pleroma/emoji/packs/download", %{
url: "https://example.com", url: "https://example.com",
name: "test_pack", name: "test_pack",
as: "test_pack2" as: "test_pack2"
}) })
|> json_response(:internal_server_error) == %{ |> json_response_and_validate_schema(:internal_server_error) == %{
"error" => "error" =>
"The pack was not set as shared and there is no fallback src to download from" "The pack was not set as shared and there is no fallback src to download from"
} }
@ -311,8 +310,9 @@ test "other error", %{admin_conn: admin_conn} do
test "for a pack without a fallback source", ctx do test "for a pack without a fallback source", ctx do
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]}) |> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]})
|> json_response(200) == ctx[:new_data] |> json_response_and_validate_schema(200) == ctx[:new_data]
assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data] assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
end end
@ -336,8 +336,9 @@ test "for a pack with a fallback source", ctx do
) )
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) |> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data})
|> json_response(200) == new_data_with_sha |> json_response_and_validate_schema(200) == new_data_with_sha
assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
end end
@ -355,8 +356,9 @@ test "when the fallback source doesn't have all the files", ctx do
new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack")
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) |> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data})
|> json_response(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "The fallback archive does not have all files specified in pack.json" "error" => "The fallback archive does not have all files specified in pack.json"
} }
end end
@ -376,6 +378,7 @@ test "when the fallback source doesn't have all the files", ctx do
test "create shortcode exists", %{admin_conn: admin_conn} do test "create shortcode exists", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank", shortcode: "blank",
filename: "dir/blank.png", filename: "dir/blank.png",
@ -384,7 +387,7 @@ test "create shortcode exists", %{admin_conn: admin_conn} do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(:conflict) == %{ |> json_response_and_validate_schema(:conflict) == %{
"error" => "An emoji with the \"blank\" shortcode already exists" "error" => "An emoji with the \"blank\" shortcode already exists"
} }
end end
@ -393,6 +396,7 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end) on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir/") end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "dir/blank.png", filename: "dir/blank.png",
@ -401,17 +405,21 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(200) == %{"blank" => "blank.png", "blank2" => "dir/blank.png"} |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank", shortcode: "blank",
new_shortcode: "blank2", new_shortcode: "blank2",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:conflict) == %{ |> json_response_and_validate_schema(:conflict) == %{
"error" => "error" =>
"New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option" "New shortcode \"blank2\" is already used. If you want to override emoji use 'force' option"
} }
@ -421,6 +429,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end) on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/dir_2/") end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "dir/blank.png", filename: "dir/blank.png",
@ -429,18 +438,22 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(200) == %{"blank" => "blank.png", "blank2" => "dir/blank.png"} |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank2" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png", new_filename: "dir_2/blank_3.png",
force: true force: true
}) })
|> json_response(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank3" => "dir_2/blank_3.png" "blank3" => "dir_2/blank_3.png"
} }
@ -450,6 +463,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
test "with empty filename", %{admin_conn: admin_conn} do test "with empty filename", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "", filename: "",
@ -458,13 +472,14 @@ test "with empty filename", %{admin_conn: admin_conn} do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name, shortcode or filename cannot be empty" "error" => "pack name, shortcode or filename cannot be empty"
} }
end end
test "add file with not loaded pack", %{admin_conn: admin_conn} do test "add file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/not_loaded/files", %{ |> post("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank2", shortcode: "blank2",
filename: "dir/blank.png", filename: "dir/blank.png",
@ -473,37 +488,43 @@ test "add file with not loaded pack", %{admin_conn: admin_conn} do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found" "error" => "pack \"not_loaded\" is not found"
} }
end end
test "remove file with not loaded pack", %{admin_conn: admin_conn} do test "remove file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files", %{shortcode: "blank3"}) |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=blank3")
|> json_response(:bad_request) == %{"error" => "pack \"not_loaded\" is not found"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found"
}
end end
test "remove file with empty shortcode", %{admin_conn: admin_conn} do test "remove file with empty shortcode", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files", %{shortcode: ""}) |> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=")
|> json_response(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name or shortcode cannot be empty" "error" => "pack name or shortcode cannot be empty"
} }
end end
test "update file with not loaded pack", %{admin_conn: admin_conn} do test "update file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/not_loaded/files", %{ |> patch("/api/pleroma/emoji/packs/not_loaded/files", %{
shortcode: "blank4", shortcode: "blank4",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:bad_request) == %{"error" => "pack \"not_loaded\" is not found"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack \"not_loaded\" is not found"
}
end end
test "new with shortcode as file with update", %{admin_conn: admin_conn} do test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank4", shortcode: "blank4",
filename: "dir/blank.png", filename: "dir/blank.png",
@ -512,24 +533,31 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
path: "#{@emoji_path}/test_pack/blank.png" path: "#{@emoji_path}/test_pack/blank.png"
} }
}) })
|> json_response(200) == %{"blank" => "blank.png", "blank4" => "dir/blank.png"} |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png",
"blank4" => "dir/blank.png"
}
assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png") assert File.exists?("#{@emoji_path}/test_pack/dir/blank.png")
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank4", shortcode: "blank4",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(200) == %{"blank3" => "dir_2/blank_3.png", "blank" => "blank.png"} |> json_response_and_validate_schema(200) == %{
"blank3" => "dir_2/blank_3.png",
"blank" => "blank.png"
}
refute File.exists?("#{@emoji_path}/test_pack/dir/") refute File.exists?("#{@emoji_path}/test_pack/dir/")
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files", %{shortcode: "blank3"}) |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3")
|> json_response(200) == %{"blank" => "blank.png"} |> json_response_and_validate_schema(200) == %{"blank" => "blank.png"}
refute File.exists?("#{@emoji_path}/test_pack/dir_2/") refute File.exists?("#{@emoji_path}/test_pack/dir_2/")
@ -546,11 +574,12 @@ test "new with shortcode from url", %{admin_conn: admin_conn} do
end) end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank_url", shortcode: "blank_url",
file: "https://test-blank/blank_url.png" file: "https://test-blank/blank_url.png"
}) })
|> json_response(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank_url" => "blank_url.png", "blank_url" => "blank_url.png",
"blank" => "blank.png" "blank" => "blank.png"
} }
@ -564,40 +593,51 @@ test "new without shortcode", %{admin_conn: admin_conn} do
on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end) on_exit(fn -> File.rm_rf!("#{@emoji_path}/test_pack/shortcode.png") end)
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/test_pack/files", %{
file: %Plug.Upload{ file: %Plug.Upload{
filename: "shortcode.png", filename: "shortcode.png",
path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png"
} }
}) })
|> json_response(200) == %{"shortcode" => "shortcode.png", "blank" => "blank.png"} |> json_response_and_validate_schema(200) == %{
"shortcode" => "shortcode.png",
"blank" => "blank.png"
}
end end
test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files", %{shortcode: "blank2"}) |> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank2")
|> json_response(:bad_request) == %{"error" => "Emoji \"blank2\" does not exist"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank2\" does not exist"
}
end end
test "update non existing emoji", %{admin_conn: admin_conn} do test "update non existing emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank2", shortcode: "blank2",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:bad_request) == %{"error" => "Emoji \"blank2\" does not exist"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank2\" does not exist"
}
end end
test "update with empty shortcode", %{admin_conn: admin_conn} do test "update with empty shortcode", %{admin_conn: admin_conn} do
assert admin_conn assert %{
"error" => "Missing field: new_shortcode."
} =
admin_conn
|> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/test_pack/files", %{
shortcode: "blank", shortcode: "blank",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })
|> json_response(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request)
"error" => "new_shortcode or new_filename cannot be empty"
}
end end
end end
@ -605,7 +645,7 @@ test "update with empty shortcode", %{admin_conn: admin_conn} do
test "creating and deleting a pack", %{admin_conn: admin_conn} do test "creating and deleting a pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created") |> post("/api/pleroma/emoji/packs/test_created")
|> json_response(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_created/pack.json") assert File.exists?("#{@emoji_path}/test_created/pack.json")
@ -616,7 +656,7 @@ test "creating and deleting a pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_created") |> delete("/api/pleroma/emoji/packs/test_created")
|> json_response(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_created/pack.json") refute File.exists?("#{@emoji_path}/test_created/pack.json")
end end
@ -629,7 +669,7 @@ test "if pack exists", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created") |> post("/api/pleroma/emoji/packs/test_created")
|> json_response(:conflict) == %{ |> json_response_and_validate_schema(:conflict) == %{
"error" => "A pack named \"test_created\" already exists" "error" => "A pack named \"test_created\" already exists"
} }
@ -639,20 +679,26 @@ test "if pack exists", %{admin_conn: admin_conn} do
test "with empty name", %{admin_conn: admin_conn} do test "with empty name", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/ ") |> post("/api/pleroma/emoji/packs/ ")
|> json_response(:bad_request) == %{"error" => "pack name cannot be empty"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end end
end end
test "deleting nonexisting pack", %{admin_conn: admin_conn} do test "deleting nonexisting pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/non_existing") |> delete("/api/pleroma/emoji/packs/non_existing")
|> json_response(:not_found) == %{"error" => "Pack non_existing does not exist"} |> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist"
}
end end
test "deleting with empty name", %{admin_conn: admin_conn} do test "deleting with empty name", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/ ") |> delete("/api/pleroma/emoji/packs/ ")
|> json_response(:bad_request) == %{"error" => "pack name cannot be empty"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end end
test "filesystem import", %{admin_conn: admin_conn, conn: conn} do test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
@ -661,15 +707,15 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json") File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
end) end)
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
refute Map.has_key?(resp, "test_pack_for_import") refute Map.has_key?(resp, "test_pack_for_import")
assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/import") |> get("/api/pleroma/emoji/packs/import")
|> json_response(200) == ["test_pack_for_import"] |> json_response_and_validate_schema(200) == ["test_pack_for_import"]
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"} assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"}
File.rm!("#{@emoji_path}/test_pack_for_import/pack.json") File.rm!("#{@emoji_path}/test_pack_for_import/pack.json")
@ -686,9 +732,9 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/import") |> get("/api/pleroma/emoji/packs/import")
|> json_response(200) == ["test_pack_for_import"] |> json_response_and_validate_schema(200) == ["test_pack_for_import"]
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["test_pack_for_import"]["files"] == %{ assert resp["test_pack_for_import"]["files"] == %{
"blank" => "blank.png", "blank" => "blank.png",
@ -712,19 +758,23 @@ test "shows pack.json", %{conn: conn} do
} = } =
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/packs/test_pack")
|> json_response(200) |> json_response_and_validate_schema(200)
end end
test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/non_existing") |> get("/api/pleroma/emoji/packs/non_existing")
|> json_response(:not_found) == %{"error" => "Pack non_existing does not exist"} |> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist"
}
end end
test "error name", %{conn: conn} do test "error name", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/ ") |> get("/api/pleroma/emoji/packs/ ")
|> json_response(:bad_request) == %{"error" => "pack name cannot be empty"} |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty"
}
end end
end end
end end