go-io/sqlite/sqlite_test.go

654 lines
14 KiB
Go
Raw Normal View History

package sqlite
import (
goio "io"
"io/fs"
"testing"
core "dappco.re/go/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestMedium(t *testing.T) *Medium {
t.Helper()
m, err := New(Options{Path: ":memory:"})
require.NoError(t, err)
t.Cleanup(func() { m.Close() })
return m
}
// --- Constructor Tests ---
func TestSqlite_New_Good(t *testing.T) {
m, err := New(Options{Path: ":memory:"})
require.NoError(t, err)
defer m.Close()
assert.Equal(t, "files", m.table)
}
func TestSqlite_New_Options_Good(t *testing.T) {
m, err := New(Options{Path: ":memory:", Table: "custom"})
require.NoError(t, err)
defer m.Close()
assert.Equal(t, "custom", m.table)
}
func TestSqlite_New_EmptyPath_Bad(t *testing.T) {
_, err := New(Options{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "database path is required")
}
// --- Read/Write Tests ---
func TestSqlite_ReadWrite_Good(t *testing.T) {
m := newTestMedium(t)
err := m.Write("hello.txt", "world")
require.NoError(t, err)
content, err := m.Read("hello.txt")
require.NoError(t, err)
assert.Equal(t, "world", content)
}
func TestSqlite_ReadWrite_Overwrite_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "first"))
require.NoError(t, m.Write("file.txt", "second"))
content, err := m.Read("file.txt")
require.NoError(t, err)
assert.Equal(t, "second", content)
}
func TestSqlite_ReadWrite_NestedPath_Good(t *testing.T) {
m := newTestMedium(t)
err := m.Write("a/b/c.txt", "nested")
require.NoError(t, err)
content, err := m.Read("a/b/c.txt")
require.NoError(t, err)
assert.Equal(t, "nested", content)
}
func TestSqlite_Read_NotFound_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Read("nonexistent.txt")
assert.Error(t, err)
}
func TestSqlite_Read_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Read("")
assert.Error(t, err)
}
func TestSqlite_Write_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.Write("", "content")
assert.Error(t, err)
}
func TestSqlite_Read_IsDirectory_Bad(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("mydir"))
_, err := m.Read("mydir")
assert.Error(t, err)
}
// --- EnsureDir Tests ---
func TestSqlite_EnsureDir_Good(t *testing.T) {
m := newTestMedium(t)
err := m.EnsureDir("mydir")
require.NoError(t, err)
assert.True(t, m.IsDir("mydir"))
}
func TestSqlite_EnsureDir_EmptyPath_Good(t *testing.T) {
m := newTestMedium(t)
// Root always exists, no-op
err := m.EnsureDir("")
assert.NoError(t, err)
}
func TestSqlite_EnsureDir_Idempotent_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("mydir"))
require.NoError(t, m.EnsureDir("mydir"))
assert.True(t, m.IsDir("mydir"))
}
// --- IsFile Tests ---
func TestSqlite_IsFile_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "content"))
require.NoError(t, m.EnsureDir("mydir"))
assert.True(t, m.IsFile("file.txt"))
assert.False(t, m.IsFile("mydir"))
assert.False(t, m.IsFile("nonexistent"))
assert.False(t, m.IsFile(""))
}
// --- FileGet/FileSet Tests ---
func TestSqlite_FileGetFileSet_Good(t *testing.T) {
m := newTestMedium(t)
err := m.FileSet("key.txt", "value")
require.NoError(t, err)
val, err := m.FileGet("key.txt")
require.NoError(t, err)
assert.Equal(t, "value", val)
}
// --- Delete Tests ---
func TestSqlite_Delete_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("to-delete.txt", "content"))
assert.True(t, m.Exists("to-delete.txt"))
err := m.Delete("to-delete.txt")
require.NoError(t, err)
assert.False(t, m.Exists("to-delete.txt"))
}
func TestSqlite_Delete_EmptyDir_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("emptydir"))
assert.True(t, m.IsDir("emptydir"))
err := m.Delete("emptydir")
require.NoError(t, err)
assert.False(t, m.IsDir("emptydir"))
}
func TestSqlite_Delete_NotFound_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.Delete("nonexistent")
assert.Error(t, err)
}
func TestSqlite_Delete_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.Delete("")
assert.Error(t, err)
}
func TestSqlite_Delete_NotEmpty_Bad(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("mydir"))
require.NoError(t, m.Write("mydir/file.txt", "content"))
err := m.Delete("mydir")
assert.Error(t, err)
}
// --- DeleteAll Tests ---
func TestSqlite_DeleteAll_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("dir/file1.txt", "a"))
require.NoError(t, m.Write("dir/sub/file2.txt", "b"))
require.NoError(t, m.Write("other.txt", "c"))
err := m.DeleteAll("dir")
require.NoError(t, err)
assert.False(t, m.Exists("dir/file1.txt"))
assert.False(t, m.Exists("dir/sub/file2.txt"))
assert.True(t, m.Exists("other.txt"))
}
func TestSqlite_DeleteAll_SingleFile_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "content"))
err := m.DeleteAll("file.txt")
require.NoError(t, err)
assert.False(t, m.Exists("file.txt"))
}
func TestSqlite_DeleteAll_NotFound_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.DeleteAll("nonexistent")
assert.Error(t, err)
}
func TestSqlite_DeleteAll_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.DeleteAll("")
assert.Error(t, err)
}
// --- Rename Tests ---
func TestSqlite_Rename_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("old.txt", "content"))
err := m.Rename("old.txt", "new.txt")
require.NoError(t, err)
assert.False(t, m.Exists("old.txt"))
assert.True(t, m.IsFile("new.txt"))
content, err := m.Read("new.txt")
require.NoError(t, err)
assert.Equal(t, "content", content)
}
func TestSqlite_Rename_Directory_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("olddir"))
require.NoError(t, m.Write("olddir/file.txt", "content"))
err := m.Rename("olddir", "newdir")
require.NoError(t, err)
assert.False(t, m.Exists("olddir"))
assert.False(t, m.Exists("olddir/file.txt"))
assert.True(t, m.IsDir("newdir"))
assert.True(t, m.IsFile("newdir/file.txt"))
content, err := m.Read("newdir/file.txt")
require.NoError(t, err)
assert.Equal(t, "content", content)
}
func TestSqlite_Rename_SourceNotFound_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.Rename("nonexistent", "new")
assert.Error(t, err)
}
func TestSqlite_Rename_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
err := m.Rename("", "new")
assert.Error(t, err)
err = m.Rename("old", "")
assert.Error(t, err)
}
// --- List Tests ---
func TestSqlite_List_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("dir/file1.txt", "a"))
require.NoError(t, m.Write("dir/file2.txt", "b"))
require.NoError(t, m.Write("dir/sub/file3.txt", "c"))
entries, err := m.List("dir")
require.NoError(t, err)
names := make(map[string]bool)
for _, e := range entries {
names[e.Name()] = true
}
assert.True(t, names["file1.txt"])
assert.True(t, names["file2.txt"])
assert.True(t, names["sub"])
assert.Len(t, entries, 3)
}
func TestSqlite_List_Root_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("root.txt", "content"))
require.NoError(t, m.Write("dir/nested.txt", "nested"))
entries, err := m.List("")
require.NoError(t, err)
names := make(map[string]bool)
for _, e := range entries {
names[e.Name()] = true
}
assert.True(t, names["root.txt"])
assert.True(t, names["dir"])
}
func TestSqlite_List_DirectoryEntry_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("dir/sub/file.txt", "content"))
entries, err := m.List("dir")
require.NoError(t, err)
require.Len(t, entries, 1)
assert.Equal(t, "sub", entries[0].Name())
assert.True(t, entries[0].IsDir())
info, err := entries[0].Info()
require.NoError(t, err)
assert.True(t, info.IsDir())
}
// --- Stat Tests ---
func TestSqlite_Stat_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "hello world"))
info, err := m.Stat("file.txt")
require.NoError(t, err)
assert.Equal(t, "file.txt", info.Name())
assert.Equal(t, int64(11), info.Size())
assert.False(t, info.IsDir())
}
func TestSqlite_Stat_Directory_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("mydir"))
info, err := m.Stat("mydir")
require.NoError(t, err)
assert.Equal(t, "mydir", info.Name())
assert.True(t, info.IsDir())
}
func TestSqlite_Stat_NotFound_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Stat("nonexistent")
assert.Error(t, err)
}
func TestSqlite_Stat_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Stat("")
assert.Error(t, err)
}
// --- Open Tests ---
func TestSqlite_Open_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "open me"))
f, err := m.Open("file.txt")
require.NoError(t, err)
defer f.Close()
data, err := goio.ReadAll(f.(goio.Reader))
require.NoError(t, err)
assert.Equal(t, "open me", string(data))
stat, err := f.Stat()
require.NoError(t, err)
assert.Equal(t, "file.txt", stat.Name())
}
func TestSqlite_Open_NotFound_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Open("nonexistent.txt")
assert.Error(t, err)
}
func TestSqlite_Open_IsDirectory_Bad(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("mydir"))
_, err := m.Open("mydir")
assert.Error(t, err)
}
// --- Create Tests ---
func TestSqlite_Create_Good(t *testing.T) {
m := newTestMedium(t)
w, err := m.Create("new.txt")
require.NoError(t, err)
n, err := w.Write([]byte("created"))
require.NoError(t, err)
assert.Equal(t, 7, n)
err = w.Close()
require.NoError(t, err)
content, err := m.Read("new.txt")
require.NoError(t, err)
assert.Equal(t, "created", content)
}
func TestSqlite_Create_Overwrite_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "old content"))
w, err := m.Create("file.txt")
require.NoError(t, err)
_, err = w.Write([]byte("new"))
require.NoError(t, err)
require.NoError(t, w.Close())
content, err := m.Read("file.txt")
require.NoError(t, err)
assert.Equal(t, "new", content)
}
func TestSqlite_Create_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Create("")
assert.Error(t, err)
}
// --- Append Tests ---
func TestSqlite_Append_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("append.txt", "hello"))
w, err := m.Append("append.txt")
require.NoError(t, err)
_, err = w.Write([]byte(" world"))
require.NoError(t, err)
require.NoError(t, w.Close())
content, err := m.Read("append.txt")
require.NoError(t, err)
assert.Equal(t, "hello world", content)
}
func TestSqlite_Append_NewFile_Good(t *testing.T) {
m := newTestMedium(t)
w, err := m.Append("new.txt")
require.NoError(t, err)
_, err = w.Write([]byte("fresh"))
require.NoError(t, err)
require.NoError(t, w.Close())
content, err := m.Read("new.txt")
require.NoError(t, err)
assert.Equal(t, "fresh", content)
}
func TestSqlite_Append_EmptyPath_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.Append("")
assert.Error(t, err)
}
// --- ReadStream Tests ---
func TestSqlite_ReadStream_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("stream.txt", "streaming content"))
reader, err := m.ReadStream("stream.txt")
require.NoError(t, err)
defer reader.Close()
data, err := goio.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, "streaming content", string(data))
}
func TestSqlite_ReadStream_NotFound_Bad(t *testing.T) {
m := newTestMedium(t)
_, err := m.ReadStream("nonexistent.txt")
assert.Error(t, err)
}
func TestSqlite_ReadStream_IsDirectory_Bad(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.EnsureDir("mydir"))
_, err := m.ReadStream("mydir")
assert.Error(t, err)
}
// --- WriteStream Tests ---
func TestSqlite_WriteStream_Good(t *testing.T) {
m := newTestMedium(t)
writer, err := m.WriteStream("output.txt")
require.NoError(t, err)
_, err = goio.Copy(writer, core.NewReader("piped data"))
require.NoError(t, err)
require.NoError(t, writer.Close())
content, err := m.Read("output.txt")
require.NoError(t, err)
assert.Equal(t, "piped data", content)
}
// --- Exists Tests ---
func TestSqlite_Exists_Good(t *testing.T) {
m := newTestMedium(t)
assert.False(t, m.Exists("nonexistent"))
require.NoError(t, m.Write("file.txt", "content"))
assert.True(t, m.Exists("file.txt"))
require.NoError(t, m.EnsureDir("mydir"))
assert.True(t, m.Exists("mydir"))
}
func TestSqlite_Exists_EmptyPath_Good(t *testing.T) {
m := newTestMedium(t)
// Root always exists
assert.True(t, m.Exists(""))
}
// --- IsDir Tests ---
func TestSqlite_IsDir_Good(t *testing.T) {
m := newTestMedium(t)
require.NoError(t, m.Write("file.txt", "content"))
require.NoError(t, m.EnsureDir("mydir"))
assert.True(t, m.IsDir("mydir"))
assert.False(t, m.IsDir("file.txt"))
assert.False(t, m.IsDir("nonexistent"))
assert.False(t, m.IsDir(""))
}
// --- normaliseEntryPath Tests ---
func TestSqlite_NormaliseEntryPath_Good(t *testing.T) {
assert.Equal(t, "file.txt", normaliseEntryPath("file.txt"))
assert.Equal(t, "dir/file.txt", normaliseEntryPath("dir/file.txt"))
assert.Equal(t, "file.txt", normaliseEntryPath("/file.txt"))
assert.Equal(t, "file.txt", normaliseEntryPath("../file.txt"))
assert.Equal(t, "file.txt", normaliseEntryPath("dir/../file.txt"))
assert.Equal(t, "", normaliseEntryPath(""))
assert.Equal(t, "", normaliseEntryPath("."))
assert.Equal(t, "", normaliseEntryPath("/"))
}
// --- Interface Compliance ---
func TestSqlite_InterfaceCompliance_Ugly(t *testing.T) {
m := newTestMedium(t)
// Verify all methods exist by asserting the interface shape.
var _ interface {
Read(string) (string, error)
Write(string, string) error
EnsureDir(string) error
IsFile(string) bool
FileGet(string) (string, error)
FileSet(string, string) error
Delete(string) error
DeleteAll(string) error
Rename(string, string) error
List(string) ([]fs.DirEntry, error)
Stat(string) (fs.FileInfo, error)
Open(string) (fs.File, error)
Create(string) (goio.WriteCloser, error)
Append(string) (goio.WriteCloser, error)
ReadStream(string) (goio.ReadCloser, error)
WriteStream(string) (goio.WriteCloser, error)
Exists(string) bool
IsDir(string) bool
} = m
}
// --- Custom Table ---
func TestSqlite_CustomTable_Good(t *testing.T) {
m, err := New(Options{Path: ":memory:", Table: "my_files"})
require.NoError(t, err)
defer m.Close()
require.NoError(t, m.Write("file.txt", "content"))
content, err := m.Read("file.txt")
require.NoError(t, err)
assert.Equal(t, "content", content)
}