Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,15 @@ config :ex_ftp,
},
# See "Choose a Storage Connector"
storage_connector: ExFTP.Storage.FileConnector,
storage_config: %{}
storage_config: %{
# Optional: Handler for file transfer completion notifications
# If not configured, logs "Transfer complete: <type> <path>" by default
# Called as: module.function(type, path, connector_state)
# - type: :retrieve (download) or :store (upload)
# - path: the file path
# - connector_state: contains current_working_directory and authenticator_state
on_transfer_complete: {MyApp.TransferHandler, :handle_complete}
}

```

Expand Down Expand Up @@ -446,7 +454,33 @@ This is the out-of-the-box behavior you'd expect from any FTP server.
config :ex_ftp,
#....
storage_connector: ExFTP.Storage.FileConnector,
storage_config: %{}
storage_config: %{
# Optional: Handler for transfer completion notifications
# Default: Logs "Transfer complete: <type> <path>"
on_transfer_complete: {MyApp.TransferHandler, :handle_complete}
}
```

**Example Handler:**

```elixir
defmodule MyApp.TransferHandler do
require Logger

def handle_complete(type, path, connector_state) do
# type is :retrieve or :store
# path is the file path
# connector_state contains authenticator_state with username and other metadata
username = connector_state.authenticator_state.username
Logger.info("User #{username} #{type}: #{path}")

# Trigger custom workflows
case type do
:store -> MyApp.notify_upload(username, path)
:retrieve -> MyApp.track_download(username, path)
end
end
end
```

[^ top](#top)
Expand All @@ -471,7 +505,9 @@ config :ex_ftp,
storage_connector: ExFTP.Storage.S3Connector,
storage_config: %{
# the `/` path of the FTP server will point to s3://{my-storage-bucket}/
storage_bucket: "my-storage-bucket"
storage_bucket: "my-storage-bucket",
# Optional: Handler for transfer completion notifications
on_transfer_complete: {MyApp.TransferHandler, :handle_complete}
}
```

Expand Down Expand Up @@ -513,7 +549,9 @@ config :ex_ftp,
storage_connector: ExFTP.Storage.S3Connector,
storage_config: %{
# the `/` path of the FTP server will point to s3://{my-storage-bucket}/
storage_bucket: "my-storage-bucket"
storage_bucket: "my-storage-bucket",
# Optional: Handler for transfer completion notifications
on_transfer_complete: {MyApp.TransferHandler, :handle_complete}
}
```

Expand Down Expand Up @@ -635,7 +673,7 @@ defmodule MyStorageConnector do
) :: function()
def create_write_func(path, connector_state, opts \\ []) do
# Return a function that will write `stream` to your storage at path
# e.g
# e.g
# fn stream ->
# fs = File.stream!(path)
#
Expand Down
14 changes: 14 additions & 0 deletions lib/ex_ftp/storage/common.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ defmodule ExFTP.Storage.Common do

alias ExFTP.PassiveSocket

require Logger

@directory_action_ok 257
@directory_action_not_taken 521
@file_action_ok 250
Expand Down Expand Up @@ -398,6 +400,7 @@ defmodule ExFTP.Storage.Common do
{:ok, stream} ->
PassiveSocket.write(pasv, stream, close_after_write: true)
send_resp(@closing_connection_success, "Transfer complete.", socket)
notify_transfer_complete(:retrieve, w_path, connector_state)

_ ->
send_resp(@action_aborted, "File not found.", socket)
Expand Down Expand Up @@ -508,6 +511,7 @@ defmodule ExFTP.Storage.Common do
)

ExFTP.Common.send_resp(@closing_connection_success, "Transfer Complete.", socket)
notify_transfer_complete(:store, w_path, connector_state)

connector_state
end
Expand Down Expand Up @@ -693,4 +697,14 @@ defmodule ExFTP.Storage.Common do
{key, val}
end)
end

defp notify_transfer_complete(type, path, connector_state) do
case connector_state[:on_transfer_complete] do
{module, function} when is_atom(module) and is_atom(function) ->
apply(module, function, [type, path, connector_state])

_ ->
Logger.info("Transfer complete: #{type} #{path}")
end
end
end
2 changes: 1 addition & 1 deletion lib/ex_ftp/storage/s3_connector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ defmodule ExFTP.Storage.S3Connector do
* **path** :: `t:ExFTP.StorageConnector.path/0`
* **connector_state** :: `t:ExFTP.StorageConnector.connector_state/0`

#{ExFTP.Doc.returns(success: "{:ok, data}", failure: "{:error, err}")}
#{ExFTP.Doc.returns(success: "{:ok, data}", failure: "{:error, err}")}

#{ExFTP.Doc.related(["`c:ExFTP.StorageConnector.get_content/2`"])}

Expand Down
13 changes: 12 additions & 1 deletion lib/ex_ftp/worker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ defmodule ExFTP.Worker do
connector = env[:storage_connector] || FileConnector
authenticator = env[:authenticator] || PassthroughAuth
server_name = env[:server_name] || :ExFTP
storage_config = env[:storage_config] || %{}
on_transfer_complete = storage_config[:on_transfer_complete]

{:ok, host} =
ftp_addr
Expand All @@ -49,13 +51,22 @@ defmodule ExFTP.Worker do

send_resp(220, "Hello from #{server_name}.", socket)

connector_state = %{current_working_directory: "/"}

connector_state =
if on_transfer_complete do
Map.put(connector_state, :on_transfer_complete, on_transfer_complete)
else
connector_state
end

%Worker{
socket: socket,
host: host,
pasv_socket: nil,
type: :ascii,
storage_connector: connector,
connector_state: %{current_working_directory: "/"},
connector_state: connector_state,
authenticator: authenticator,
authenticator_state: %{}
}
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule ExFTP.MixProject do
alias ExFTP.Storage.S3ConnectorConfig

@source_url "https://github.com/camatcode/ex_ftp"
@version "1.2.0"
@version "1.3.0"

def project do
[
Expand Down
10 changes: 5 additions & 5 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
"cachex": {:hex, :cachex, "4.1.1", "574c5cd28473db313a0a76aac8c945fe44191659538ca6a1e8946ec300b1a19f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d6b7449ff98d6bb92dda58bd4fc3189cae9f99e7042054d669596f56dc503cd8"},
"certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"},
"configparser_ex": {:hex, :configparser_ex, "4.0.0", "17e2b831cfa33a08c56effc610339b2986f0d82a9caa0ed18880a07658292ab6", [:mix], [], "hexpm", "02e6d1a559361a063cba7b75bc3eb2d6ad7e62730c551cc4703541fd11e65e5b"},
"credo": {:hex, :credo, "1.7.13", "126a0697df6b7b71cd18c81bc92335297839a806b6f62b61d417500d1070ff4e", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "47641e6d2bbff1e241e87695b29f617f1a8f912adea34296fb10ecc3d7e9e84f"},
"credo": {:hex, :credo, "1.7.15", "283da72eeb2fd3ccf7248f4941a0527efb97afa224bcdef30b4b580bc8258e1c", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "291e8645ea3fea7481829f1e1eb0881b8395db212821338e577a90bf225c5607"},
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex_aws": {:hex, :ex_aws, "2.5.9", "8e2455172f0e5cbe2f56dd68de514f0dae6bb26d6b6e2f435a06434cf9dbb412", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbdb6ffb0e6c6368de05ed8641fe1376298ba23354674428e5b153a541f23359"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.8", "5ee7407bc8252121ad28fba936b3b293f4ecef93753962351feb95b8a66096fa", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "84e512ca2e0ae6a6c497036dff06d4493ffb422cfe476acc811d7c337c16691c"},
"ex_doc": {:hex, :ex_doc, "0.38.4", "ab48dff7a8af84226bf23baddcdda329f467255d924380a0cf0cee97bb9a9ede", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "f7b62346408a83911c2580154e35613eb314e0278aeea72ed7fedef9c1f165b2"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.9", "862b7792f2e60d7010e2920d79964e3fab289bc0fd951b0ba8457a3f7f9d1199", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "a480d2bb2da64610014021629800e1e9457ca5e4a62f6775bffd963360c2bf90"},
"ex_doc": {:hex, :ex_doc, "0.39.3", "519c6bc7e84a2918b737aec7ef48b96aa4698342927d080437f61395d361dcee", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "0590955cf7ad3b625780ee1c1ea627c28a78948c6c0a9b0322bd976a079996e1"},
"ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"},
"ex_license": {:hex, :ex_license, "0.1.1", "bbdaba704f861894da3ae80e4399984379f19de1cca4ecf7d799616c832b8821", [:mix], [], "hexpm", "dd95aaaba0c9c6f0e9be2db3e9fac0bf69784557e04f700f94e90629f5e24e1d"},
"ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
Expand Down Expand Up @@ -38,12 +38,12 @@
"poison": {:hex, :poison, "6.0.0", "9bbe86722355e36ffb62c51a552719534257ba53f3271dacd20fbbd6621a583a", [:mix], [{:decimal, "~> 2.1", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "bb9064632b94775a3964642d6a78281c07b7be1319e0016e1643790704e739a2"},
"proper_case": {:hex, :proper_case, "1.3.1", "5f51cabd2d422a45f374c6061b7379191d585b5154456b371432d0fa7cb1ffda", [:mix], [], "hexpm", "6cc715550cc1895e61608060bbe67aef0d7c9cf55d7ddb013c6d7073036811dd"},
"quokka": {:hex, :quokka, "2.11.2", "2856118154425f18547720d997199be54febed771a740ba3c988a17762328287", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "8208f5d814007cb35a2eb278462464d083fca8c463f62517ab94eef982f181cc"},
"req": {:hex, :req, "0.5.15", "662020efb6ea60b9f0e0fac9be88cd7558b53fe51155a2d9899de594f9906ba9", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "a6513a35fad65467893ced9785457e91693352c70b58bbc045b47e5eb2ef0c53"},
"req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"},
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"thousand_island": {:hex, :thousand_island, "1.4.2", "735fa783005d1703359bbd2d3a5a3a398075ba4456e5afe3c5b7cf4666303d36", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1c7637f16558fc1c35746d5ee0e83b18b8e59e18d28affd1f2fa1645f8bc7473"},
"thousand_island": {:hex, :thousand_island, "1.4.3", "2158209580f633be38d43ec4e3ce0a01079592b9657afff9080d5d8ca149a3af", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6e4ce09b0fd761a58594d02814d40f77daff460c48a7354a15ab353bb998ea0b"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.1", "a48703a25c170eedadca83b11e88985af08d35f37c6f664d6dcfb106a97782fc", [:rebar3], [], "hexpm", "b3a917854ce3ae233619744ad1e0102e05673136776fb2fa76234f3e03b23642"},
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
}
150 changes: 150 additions & 0 deletions test/ex_ftp/storage/transfer_complete_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
defmodule ExFTP.Storage.TransferCompleteTest do
@moduledoc false

use ExUnit.Case

import ExFTP.StorageTester
import ExFTP.TestHelper
import ExUnit.CaptureLog

alias ExFTP.Auth.PassthroughAuth
alias ExFTP.Storage.FileConnector

defmodule TestHandler do
require Logger

def handle_complete(type, path, _connector_state) do
Logger.info("Custom handler called: #{type} #{path}")
:ok
end
end

describe "transfer completion with custom handler" do
setup do
Application.put_env(:ex_ftp, :authenticator, PassthroughAuth)
Application.put_env(:ex_ftp, :authenticator_config, %{})
Application.put_env(:ex_ftp, :storage_connector, FileConnector)

Application.put_env(:ex_ftp, :storage_config, %{
on_transfer_complete: {TestHandler, :handle_complete}
})

socket = get_socket()
username = Faker.Internet.user_name()
password = Faker.Internet.slug()

socket
|> send_and_expect("USER", [username], 331, "User name okay, need password")
|> send_and_expect("PASS", [password], 230, "Welcome.")

on_exit(fn ->
Application.put_env(:ex_ftp, :storage_config, %{})
end)

%{socket: socket, username: username}
end

test "calls custom handler on STOR (upload)", state do
w_dir = Path.join(System.tmp_dir!(), Faker.Internet.slug())
on_exit(fn -> File.rm_rf!(w_dir) end)

files_to_store =
File.cwd!()
|> File.ls!()
|> Enum.filter(fn file -> File.cwd!() |> Path.join(file) |> File.regular?() end)
|> Enum.take(1)

refute Enum.empty?(files_to_store)

log =
capture_log(fn ->
test_stor(state, w_dir, files_to_store)
end)

# Verify custom handler was called (not default)
assert log =~ "Custom handler called: store"
refute log =~ "Transfer complete: store"
end

test "calls custom handler on RETR (download)", state do
w_dir = File.cwd!()

paths_to_download =
w_dir
|> File.ls!()
|> Enum.filter(fn file -> w_dir |> Path.join(file) |> File.regular?() end)
|> Enum.take(1)

refute Enum.empty?(paths_to_download)

log =
capture_log(fn ->
test_retr(state, w_dir, paths_to_download)
end)

# Verify custom handler was called (not default)
assert log =~ "Custom handler called: retrieve"
refute log =~ "Transfer complete: retrieve"
end
end

describe "transfer completion with default behavior" do
setup do
Application.put_env(:ex_ftp, :authenticator, PassthroughAuth)
Application.put_env(:ex_ftp, :authenticator_config, %{})
Application.put_env(:ex_ftp, :storage_connector, FileConnector)
Application.put_env(:ex_ftp, :storage_config, %{})

socket = get_socket()
username = Faker.Internet.user_name()
password = Faker.Internet.slug()

socket
|> send_and_expect("USER", [username], 331, "User name okay, need password")
|> send_and_expect("PASS", [password], 230, "Welcome.")

%{socket: socket}
end

test "logs default message on RETR when no handler configured", state do
w_dir = File.cwd!()

paths_to_download =
w_dir
|> File.ls!()
|> Enum.filter(fn file -> w_dir |> Path.join(file) |> File.regular?() end)
|> Enum.take(1)

refute Enum.empty?(paths_to_download)

log =
capture_log(fn ->
test_retr(state, w_dir, paths_to_download)
end)

# Verify default log message appears
assert log =~ "Transfer complete: retrieve"
end

test "logs default message on STOR when no handler configured", state do
w_dir = Path.join(System.tmp_dir!(), Faker.Internet.slug())
on_exit(fn -> File.rm_rf!(w_dir) end)

files_to_store =
File.cwd!()
|> File.ls!()
|> Enum.filter(fn file -> File.cwd!() |> Path.join(file) |> File.regular?() end)
|> Enum.take(1)

refute Enum.empty?(files_to_store)

log =
capture_log(fn ->
test_stor(state, w_dir, files_to_store)
end)

# Verify default log message appears
assert log =~ "Transfer complete: store"
end
end
end
Loading