Skip to content
Merged
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
29 changes: 29 additions & 0 deletions test/pg_large_objects/ecto_query_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule PgLargeObjects.EctoQueryTest do
use PgLargeObjects.DataCase, async: true

import Ecto.Query
import PgLargeObjects.EctoQuery

describe "lo_create" do
test "creates a large object via Ecto query" do
query = from(x in fragment("SELECT 1 AS n"), select: lo_create())
assert [oid] = TestRepo.all(query)
assert is_integer(oid) and oid > 0
end
end

describe "lo_unlink" do
test "deletes a large object via Ecto query" do
oid = put_large_object!("test data")

values = [%{oid: oid}]
types = %{oid: :integer}

query = from(x in values(values, types), select: lo_unlink(x.oid))
TestRepo.all(query)

# Verify it's gone
assert {:error, :not_found} = PgLargeObjects.LargeObject.open(TestRepo, oid)
end
end
end
90 changes: 88 additions & 2 deletions test/pg_large_objects/large_object_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ defmodule PgLargeObjects.LargeObjectTest do
assert {:ok, %LargeObject{oid: oid}} = LargeObject.create(TestRepo)
assert %{rows: [[^oid]]} = TestRepo.query!("SELECT oid FROM pg_largeobject_metadata")
end

test "creates an object with custom bufsize" do
assert {:ok, %LargeObject{bufsize: 512}} = LargeObject.create(TestRepo, bufsize: 512)
end

test "creates an object opened for writing" do
{:ok, lob} = LargeObject.create(TestRepo, mode: :write)
assert :ok == LargeObject.write(lob, "test")
end

test "creates an object opened for read_write by default" do
{:ok, lob} = LargeObject.create(TestRepo)
assert :ok == LargeObject.write(lob, "test")
assert {:ok, 0} = LargeObject.seek(lob, 0)
assert {:ok, "test"} == LargeObject.read(lob, 10)
end
end

describe "open/3" do
Expand All @@ -20,6 +36,27 @@ defmodule PgLargeObjects.LargeObjectTest do
test "fails given invalid object ID" do
assert {:error, :not_found} == LargeObject.open(TestRepo, 12_345)
end

test "opens for read_write mode allowing both read and write" do
oid = put_large_object!("ABCDEFG")

TestRepo.transaction(fn ->
{:ok, lob} = LargeObject.open(TestRepo, oid, mode: :read_write)
assert :ok == LargeObject.write(lob, "XYZ")
assert {:ok, 0} = LargeObject.seek(lob, 0)
assert {:ok, "XYZ"} == LargeObject.read(lob, 3)
end)
end

test "raises ArgumentError given invalid mode" do
oid = put_large_object!("test")

assert_raise ArgumentError, ~r/invalid mode/, fn ->
TestRepo.transaction(fn ->
LargeObject.open(TestRepo, oid, mode: :invalid)
end)
end
end
end

describe "remove/2" do
Expand Down Expand Up @@ -64,6 +101,16 @@ defmodule PgLargeObjects.LargeObjectTest do
assert {:error, :not_found} == LargeObject.size(lob)
end)
end

test "preserves read position" do
with_object("ABCDEFGHIJ", fn lob ->
{:ok, "ABC"} = LargeObject.read(lob, 3)
assert {:ok, 3} = LargeObject.tell(lob)
assert {:ok, 10} = LargeObject.size(lob)
assert {:ok, 3} = LargeObject.tell(lob)
assert {:ok, "DEF"} = LargeObject.read(lob, 3)
end)
end
end

describe "write/2" do
Expand Down Expand Up @@ -310,7 +357,22 @@ defmodule PgLargeObjects.LargeObjectTest do
end

describe "Collectable implementation" do
test "raises when writing to a read-only object" do
test "Stream.into writes data to object" do
{:ok, oid} =
TestRepo.transaction(fn ->
{:ok, lob} = LargeObject.create(TestRepo, mode: :write)

["Hello", ", ", "World!"]
|> Stream.into(lob)
|> Stream.run()

lob.oid
end)

assert get_large_object!(oid) == "Hello, World!"
end

test "raises on write error when streaming into read-only object" do
oid = put_large_object!("hello")

TestRepo.transaction(fn ->
Expand Down Expand Up @@ -344,7 +406,31 @@ defmodule PgLargeObjects.LargeObjectTest do
end

describe "Enumerable implementation" do
test "raises on read error" do
test "Enum.to_list returns chunks of data" do
with_object("ABCDEFGHIJ", [bufsize: 4], fn lob ->
assert Enum.to_list(lob) == ["ABCD", "EFGH", "IJ"]
end)
end

test "Enum.at returns the nth chunk" do
with_object("ABCDEFGHIJ", [bufsize: 4], fn lob ->
assert Enum.at(lob, 1) == "EFGH"
end)
end

test "Enum.count returns the number of chunks" do
with_object("ABCDEFGHIJ", [bufsize: 4], fn lob ->
assert Enum.count(lob) == 3
end)
end

test "Enum.slice returns a subset of chunks" do
with_object("ABCDEFGHIJKL", [bufsize: 4], fn lob ->
assert Enum.slice(lob, 1..2) == ["EFGH", "IJKL"]
end)
end

test "raises on read error when enumerating a closed object" do
oid = put_large_object!("hello")

TestRepo.transaction(fn ->
Expand Down
36 changes: 36 additions & 0 deletions test/pg_large_objects/repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,40 @@ defmodule PgLargeObjects.RepoTest do
assert {"", ^data} = StringIO.contents(pid)
end
end

describe "create_large_object/1" do
test "creates and opens a large object" do
TestRepo.transaction(fn ->
assert {:ok, %PgLargeObjects.LargeObject{}} = TestRepo.create_large_object()
end)
end
end

describe "open_large_object/2" do
test "opens an existing large object" do
oid = put_large_object!("test data")

TestRepo.transaction(fn ->
assert {:ok, %PgLargeObjects.LargeObject{oid: ^oid}} =
TestRepo.open_large_object(oid)
end)
end

test "returns error for invalid oid" do
TestRepo.transaction(fn ->
assert {:error, :not_found} = TestRepo.open_large_object(12_345)
end)
end
end

describe "remove_large_object/1" do
test "removes an existing large object" do
oid = put_large_object!("test data")
assert :ok == TestRepo.remove_large_object(oid)
end

test "returns error for invalid oid" do
assert {:error, :not_found} = TestRepo.remove_large_object(12_345)
end
end
end
35 changes: 35 additions & 0 deletions test/pg_large_objects_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ defmodule PgLargeObjectsTest do
assert data == get_large_object!(oid)
end

test "can import binary with custom bufsize" do
data = :crypto.strong_rand_bytes(256)

{:ok, oid} =
TestRepo.transaction(fn ->
{:ok, oid} = PgLargeObjects.import(TestRepo, data, bufsize: 64)
oid
end)

assert data == get_large_object!(oid)
end

test "can import large binary spanning multiple chunks" do
# 200KB with 64KB bufsize = 4 chunks (tests multi-chunk path)
data = :crypto.strong_rand_bytes(200_000)

{:ok, oid} =
TestRepo.transaction(fn ->
{:ok, oid} = PgLargeObjects.import(TestRepo, data, bufsize: 65_536)
oid
end)

assert data == get_large_object!(oid)
end

test "can import from enumerable" do
data = :crypto.strong_rand_bytes(Enum.random(0..1024))

Expand Down Expand Up @@ -67,5 +92,15 @@ defmodule PgLargeObjectsTest do
assert {:error, :not_found} = PgLargeObjects.export(TestRepo, 12_345)
end)
end

test "handles invalid object IDs with collectable" do
{:ok, pid} = StringIO.open("", encoding: :latin1)
stream = IO.binstream(pid, 3)

TestRepo.transaction(fn ->
assert {:error, :not_found} =
PgLargeObjects.export(TestRepo, 12_345, into: stream)
end)
end
end
end
Loading