Skip to content

Commit e1841ee

Browse files
authored
Merge pull request #2 from frerich/upload_writer
Upload writer
2 parents 082266a + 4f0b86c commit e1841ee

9 files changed

Lines changed: 237 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# CHANGELOG
22

3+
## v0.2.0 - 2025-11-13
4+
5+
* Add `PgLargeObject.UploadWriter`
6+
37
## v0.1.3 - 2025-11-13
48

59
* Document difference between `Enum.count/1` and `PgLargeObject.LargeObjects.size/1`

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ PostgreSQL databases.
1010
* Random-access reads and writes to data objects via low-level `PgLargeObjects.LargeObject` API.
1111
* Extensions to Ecto query DSL for interacting with large objects as part of
1212
Ecto queries.
13+
* Ready-made implementation of `Phoenix.LiveView.UploadWriter` for streaming
14+
client uploads straight into the database.
1315

1416
## Why Use Large Objects?
1517

config/test.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,8 @@ config :pg_large_objects, PgLargeObjects.TestRepo,
1111

1212
config :pg_large_objects,
1313
ecto_repos: [PgLargeObjects.TestRepo]
14+
15+
config :pg_large_objects, PgLargeObjects.TestEndpoint,
16+
live_view: [signing_salt: "aaaaaaaa"],
17+
secret_key_base: String.duplicate("a", 64),
18+
pubsub_server: PgLargeObjects.TestPubSub
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
if Code.ensure_loaded?(Phoenix.LiveView.UploadWriter) do
2+
defmodule PgLargeObjects.UploadWriter do
3+
@moduledoc """
4+
LiveView UploadWriter streaming data to Postgres large object.
5+
6+
This module can be used with `Phoenix.LiveView.allow_upload/3` to specify
7+
that file uploads by clients should be streamed straight to large objects in
8+
the database. Pass the `:repo` option in the second element of the tuple
9+
returned by the function passed to `:writer` to indicate which database the
10+
object should be created in, e.g.:
11+
12+
```elixir
13+
socket
14+
|> allow_upload(:avatar,
15+
accept: :any,
16+
writer: fn _name, _entry, _socket ->
17+
{PgLargeObjects.UploadWriter, repo: MyApp.Repo}
18+
end
19+
)
20+
```
21+
22+
The object ID of the uploaded file is available in the meta data available
23+
to the callback given to `Phoenix.LiveView.consume_uploaded_entries/3`:
24+
25+
```elixir
26+
consume_uploaded_entries(socket, :photo, fn meta, _entry ->
27+
%{object_id: object_id} = meta
28+
29+
# Store `object_id` in database to retain handle to uploaded data.
30+
31+
{:ok, nil}
32+
end)
33+
```
34+
35+
See `Phoenix.LiveView.UploadWriter` for further information.
36+
"""
37+
38+
@behaviour Phoenix.LiveView.UploadWriter
39+
40+
alias PgLargeObjects.LargeObject
41+
42+
@impl Phoenix.LiveView.UploadWriter
43+
def init(opts) do
44+
repo = Keyword.fetch!(opts, :repo)
45+
46+
with {:ok, object} <- LargeObject.create(repo) do
47+
{:ok, %{object_id: object.oid, repo: repo}}
48+
end
49+
end
50+
51+
@impl Phoenix.LiveView.UploadWriter
52+
def meta(state), do: state
53+
54+
@impl Phoenix.LiveView.UploadWriter
55+
def write_chunk(data, state) do
56+
%{object_id: object_id, repo: repo} = state
57+
58+
result =
59+
repo.transaction(fn ->
60+
result =
61+
with {:ok, object} <- LargeObject.open(repo, object_id, mode: :append),
62+
:ok <- LargeObject.write(object, data) do
63+
LargeObject.close(object)
64+
end
65+
66+
case result do
67+
:ok -> state
68+
{:error, error} -> repo.rollback(error)
69+
end
70+
end)
71+
72+
case result do
73+
{:ok, state} -> {:ok, state}
74+
{:error, error} -> {:error, error, state}
75+
end
76+
end
77+
78+
@impl Phoenix.LiveView.UploadWriter
79+
def close(state, _reason) do
80+
{:ok, state}
81+
end
82+
end
83+
end

mix.exs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ defmodule PgLargeObjects.MixProject do
4444
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
4545
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
4646
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
47-
{:benchee, "~> 1.0", only: [:dev, :test]}
47+
{:benchee, "~> 1.0", only: [:dev, :test]},
48+
{:phoenix_live_view, "~> 1.1.17", optional: true},
49+
{:lazy_html, ">= 0.1.0", only: :test}
4850
]
4951
end
5052

mix.lock

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
%{
22
"benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"},
33
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
4+
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
45
"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"},
56
"db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"},
67
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
@@ -9,15 +10,28 @@
910
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
1011
"ecto": {:hex, :ecto, "3.13.4", "27834b45d58075d4a414833d9581e8b7bb18a8d9f264a21e42f653d500dbeeb5", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ad7d1505685dfa7aaf86b133d54f5ad6c42df0b4553741a1ff48796736e88b2"},
1112
"ecto_sql": {:hex, :ecto_sql, "3.13.2", "a07d2461d84107b3d037097c822ffdd36ed69d1cf7c0f70e12a3d1decf04e2e1", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.13.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "539274ab0ecf1a0078a6a72ef3465629e4d6018a3028095dc90f60a19c371717"},
13+
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
1214
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
1315
"ex_doc": {:hex, :ex_doc, "0.39.1", "e19d356a1ba1e8f8cfc79ce1c3f83884b6abfcb79329d435d4bbb3e97ccc286e", [: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", "8abf0ed3e3ca87c0847dfc4168ceab5bedfe881692f1b7c45f4a11b232806865"},
1416
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
17+
"fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"},
1518
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
19+
"lazy_html": {:hex, :lazy_html, "0.1.8", "677a8642e644eef8de98f3040e2520d42d0f0f8bd6c5cd49db36504e34dffe91", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "0d8167d930b704feb94b41414ca7f5779dff9bca7fcf619fcef18de138f08736"},
1620
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
1721
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
1822
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
23+
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
1924
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
25+
"phoenix": {:hex, :phoenix, "1.8.1", "865473a60a979551a4879db79fbfb4503e41cd809e77c85af79716578b6a456d", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "84d77d2b2e77c3c7e7527099bd01ef5c8560cd149c036d6b3a40745f11cd2fb2"},
26+
"phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"},
27+
"phoenix_live_view": {:hex, :phoenix_live_view, "1.1.17", "1d782b5901cf13b137c6d8c56542ff6cb618359b2adca7e185b21df728fa0c6c", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fa82307dd9305657a8236d6b48e60ef2e8d9f742ee7ed832de4b8bcb7e0e5ed2"},
28+
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"},
29+
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
30+
"plug": {:hex, :plug, "1.18.1", "5067f26f7745b7e31bc3368bc1a2b818b9779faa959b49c934c17730efc911cf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "57a57db70df2b422b564437d2d33cf8d33cd16339c1edb190cd11b1a3a546cc2"},
31+
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
2032
"postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"},
2133
"statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"},
2234
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
35+
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
36+
"websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"},
2337
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
defmodule PgLargeObjects.UploadWriterTest do
2+
use PgLargeObjects.ConnCase, async: false
3+
4+
alias Ecto.Adapters.SQL.Sandbox
5+
6+
defmodule PhotoUploadForm do
7+
use Phoenix.LiveView
8+
9+
def mount(_params, session, socket) do
10+
%{"test_pid" => test_pid} = session
11+
12+
{:ok,
13+
socket
14+
|> Phoenix.Component.assign(:test_pid, test_pid)
15+
|> allow_upload(:photo,
16+
accept: ~w(.jpg .jpeg),
17+
writer: fn _name, _entry, _socket ->
18+
{PgLargeObjects.UploadWriter, repo: PgLargeObjects.TestRepo}
19+
end
20+
)}
21+
end
22+
23+
def render(assigns) do
24+
~H"""
25+
<form id="upload-form" phx-change="validate" phx-submit="save">
26+
<.live_file_input upload={@uploads.photo} />
27+
<button type="submit">Upload</button>
28+
</form>
29+
"""
30+
end
31+
32+
def handle_event("save", _params, socket) do
33+
%{test_pid: test_pid} = socket.assigns
34+
35+
[object_id] =
36+
consume_uploaded_entries(socket, :photo, fn meta, _entry ->
37+
%{object_id: object_id} = meta
38+
{:ok, object_id}
39+
end)
40+
41+
send(test_pid, {:large_object_uploaded, object_id})
42+
43+
{:noreply, socket}
44+
end
45+
end
46+
47+
setup do
48+
# Enable shared mode so that LiveView processes (which run the
49+
# `UploadWriter` code) can access the repo.
50+
Sandbox.mode(PgLargeObjects.TestRepo, {:shared, self()})
51+
:ok
52+
end
53+
54+
test "works", %{conn: conn} do
55+
{:ok, view, _html} =
56+
live_isolated(conn, __MODULE__.PhotoUploadForm, session: %{"test_pid" => self()})
57+
58+
fake_data = :crypto.strong_rand_bytes(1_048_576)
59+
60+
view
61+
|> file_input("#upload-form", :photo, [
62+
%{
63+
last_modified: 1_594_171_879_000,
64+
name: "photo.jpeg",
65+
content: fake_data,
66+
size: byte_size(fake_data),
67+
type: "image/jpeg"
68+
}
69+
])
70+
|> render_upload("photo.jpeg")
71+
72+
view
73+
|> element("#upload-form")
74+
|> render_submit(%{})
75+
76+
receive do
77+
{:large_object_uploaded, object_id} ->
78+
assert get_large_object!(object_id) == fake_data
79+
end
80+
end
81+
end

test/support/conn_case.ex

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
defmodule PgLargeObjects.ConnCase do
2+
@moduledoc """
3+
Convenience module for creating test cases interacting with a LiveView UI.
4+
"""
5+
use ExUnit.CaseTemplate
6+
7+
alias Ecto.Adapters.SQL.Sandbox
8+
9+
using do
10+
quote do
11+
# Import conveniences for testing with connections
12+
import Plug.Conn
13+
import Phoenix.ConnTest
14+
import Phoenix.LiveViewTest
15+
16+
import PgLargeObjects.ConnCase
17+
import PgLargeObjects.Fixtures
18+
19+
alias PgLargeObjects.TestRepo
20+
21+
# The default endpoint for testing
22+
@endpoint PgLargeObjects.TestEndpoint
23+
end
24+
end
25+
26+
setup tags do
27+
opts = [shared: not tags[:async]]
28+
29+
pid = Sandbox.start_owner!(PgLargeObjects.TestRepo, opts)
30+
on_exit(fn -> Sandbox.stop_owner(pid) end)
31+
32+
start_supervised!({Phoenix.PubSub, name: PgLargeObjects.TestPubSub})
33+
start_supervised!(PgLargeObjects.TestEndpoint)
34+
35+
conn = Phoenix.ConnTest.build_conn()
36+
37+
{:ok, conn: conn}
38+
end
39+
end

test/support/test_endpoint.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
defmodule PgLargeObjects.TestEndpoint do
2+
@moduledoc """
3+
Dummy endpoint for testing purposes.
4+
"""
5+
use Phoenix.Endpoint, otp_app: :pg_large_objects
6+
end

0 commit comments

Comments
 (0)