2026-02-08 13:25:06 +00:00
|
|
|
package datanode
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"io"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
coreio "github.com/host-uk/core/pkg/io"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Compile-time check: Medium implements io.Medium.
|
|
|
|
|
var _ coreio.Medium = (*Medium)(nil)
|
|
|
|
|
|
|
|
|
|
func TestReadWrite_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
err := m.Write("hello.txt", "world")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
got, err := m.Read("hello.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "world", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestReadWrite_Bad(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
_, err := m.Read("missing.txt")
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
|
|
|
|
|
err = m.Write("", "content")
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNestedPaths_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("a/b/c/deep.txt", "deep"))
|
|
|
|
|
|
|
|
|
|
got, err := m.Read("a/b/c/deep.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "deep", got)
|
|
|
|
|
|
|
|
|
|
assert.True(t, m.IsDir("a"))
|
|
|
|
|
assert.True(t, m.IsDir("a/b"))
|
|
|
|
|
assert.True(t, m.IsDir("a/b/c"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLeadingSlash_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("/leading/file.txt", "stripped"))
|
|
|
|
|
got, err := m.Read("leading/file.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "stripped", got)
|
|
|
|
|
|
|
|
|
|
got, err = m.Read("/leading/file.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "stripped", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestIsFile_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("file.go", "package main"))
|
|
|
|
|
|
|
|
|
|
assert.True(t, m.IsFile("file.go"))
|
|
|
|
|
assert.False(t, m.IsFile("missing.go"))
|
|
|
|
|
assert.False(t, m.IsFile("")) // empty path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEnsureDir_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.EnsureDir("foo/bar/baz"))
|
|
|
|
|
|
|
|
|
|
assert.True(t, m.IsDir("foo"))
|
|
|
|
|
assert.True(t, m.IsDir("foo/bar"))
|
|
|
|
|
assert.True(t, m.IsDir("foo/bar/baz"))
|
|
|
|
|
assert.True(t, m.Exists("foo/bar/baz"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDelete_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("delete-me.txt", "bye"))
|
|
|
|
|
assert.True(t, m.Exists("delete-me.txt"))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Delete("delete-me.txt"))
|
|
|
|
|
assert.False(t, m.Exists("delete-me.txt"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDelete_Bad(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
// Delete non-existent
|
|
|
|
|
assert.Error(t, m.Delete("ghost.txt"))
|
|
|
|
|
|
|
|
|
|
// Delete non-empty dir
|
|
|
|
|
require.NoError(t, m.Write("dir/file.txt", "content"))
|
|
|
|
|
assert.Error(t, m.Delete("dir"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDeleteAll_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("tree/a.txt", "a"))
|
|
|
|
|
require.NoError(t, m.Write("tree/sub/b.txt", "b"))
|
|
|
|
|
require.NoError(t, m.Write("keep.txt", "keep"))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.DeleteAll("tree"))
|
|
|
|
|
|
|
|
|
|
assert.False(t, m.Exists("tree/a.txt"))
|
|
|
|
|
assert.False(t, m.Exists("tree/sub/b.txt"))
|
|
|
|
|
assert.True(t, m.Exists("keep.txt"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRename_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("old.txt", "content"))
|
|
|
|
|
require.NoError(t, m.Rename("old.txt", "new.txt"))
|
|
|
|
|
|
|
|
|
|
assert.False(t, m.Exists("old.txt"))
|
|
|
|
|
got, err := m.Read("new.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "content", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRenameDir_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("src/a.go", "package a"))
|
|
|
|
|
require.NoError(t, m.Write("src/sub/b.go", "package b"))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Rename("src", "dst"))
|
|
|
|
|
|
|
|
|
|
assert.False(t, m.Exists("src/a.go"))
|
|
|
|
|
|
|
|
|
|
got, err := m.Read("dst/a.go")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "package a", got)
|
|
|
|
|
|
|
|
|
|
got, err = m.Read("dst/sub/b.go")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "package b", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestList_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("root.txt", "r"))
|
|
|
|
|
require.NoError(t, m.Write("pkg/a.go", "a"))
|
|
|
|
|
require.NoError(t, m.Write("pkg/b.go", "b"))
|
|
|
|
|
require.NoError(t, m.Write("pkg/sub/c.go", "c"))
|
|
|
|
|
|
|
|
|
|
entries, err := m.List("")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
names := make([]string, len(entries))
|
|
|
|
|
for i, e := range entries {
|
|
|
|
|
names[i] = e.Name()
|
|
|
|
|
}
|
|
|
|
|
assert.Contains(t, names, "root.txt")
|
|
|
|
|
assert.Contains(t, names, "pkg")
|
|
|
|
|
|
|
|
|
|
entries, err = m.List("pkg")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
names = make([]string, len(entries))
|
|
|
|
|
for i, e := range entries {
|
|
|
|
|
names[i] = e.Name()
|
|
|
|
|
}
|
|
|
|
|
assert.Contains(t, names, "a.go")
|
|
|
|
|
assert.Contains(t, names, "b.go")
|
|
|
|
|
assert.Contains(t, names, "sub")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStat_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("stat.txt", "hello"))
|
|
|
|
|
|
|
|
|
|
info, err := m.Stat("stat.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, int64(5), info.Size())
|
|
|
|
|
assert.False(t, info.IsDir())
|
|
|
|
|
|
|
|
|
|
// Root stat
|
|
|
|
|
info, err = m.Stat("")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.True(t, info.IsDir())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOpen_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("open.txt", "opened"))
|
|
|
|
|
|
|
|
|
|
f, err := m.Open("open.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
data, err := io.ReadAll(f)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "opened", string(data))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateAppend_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
// Create
|
|
|
|
|
w, err := m.Create("new.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
w.Write([]byte("hello"))
|
|
|
|
|
w.Close()
|
|
|
|
|
|
|
|
|
|
got, err := m.Read("new.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "hello", got)
|
|
|
|
|
|
|
|
|
|
// Append
|
|
|
|
|
w, err = m.Append("new.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
w.Write([]byte(" world"))
|
|
|
|
|
w.Close()
|
|
|
|
|
|
|
|
|
|
got, err = m.Read("new.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "hello world", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestStreams_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
// WriteStream
|
|
|
|
|
ws, err := m.WriteStream("stream.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ws.Write([]byte("streamed"))
|
|
|
|
|
ws.Close()
|
|
|
|
|
|
|
|
|
|
// ReadStream
|
|
|
|
|
rs, err := m.ReadStream("stream.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
data, err := io.ReadAll(rs)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "streamed", string(data))
|
|
|
|
|
rs.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFileGetFileSet_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.FileSet("alias.txt", "via set"))
|
|
|
|
|
|
|
|
|
|
got, err := m.FileGet("alias.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "via set", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSnapshotRestore_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("a.txt", "alpha"))
|
|
|
|
|
require.NoError(t, m.Write("b/c.txt", "charlie"))
|
|
|
|
|
|
|
|
|
|
snap, err := m.Snapshot()
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.NotEmpty(t, snap)
|
|
|
|
|
|
|
|
|
|
// Restore into a new Medium
|
|
|
|
|
m2, err := FromTar(snap)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
got, err := m2.Read("a.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "alpha", got)
|
|
|
|
|
|
|
|
|
|
got, err = m2.Read("b/c.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "charlie", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRestore_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("original.txt", "before"))
|
|
|
|
|
|
|
|
|
|
snap, err := m.Snapshot()
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// Modify
|
|
|
|
|
require.NoError(t, m.Write("original.txt", "after"))
|
|
|
|
|
require.NoError(t, m.Write("extra.txt", "extra"))
|
|
|
|
|
|
|
|
|
|
// Restore to snapshot
|
|
|
|
|
require.NoError(t, m.Restore(snap))
|
|
|
|
|
|
|
|
|
|
got, err := m.Read("original.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "before", got)
|
|
|
|
|
|
|
|
|
|
assert.False(t, m.Exists("extra.txt"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDataNode_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("test.txt", "borg"))
|
|
|
|
|
|
|
|
|
|
dn := m.DataNode()
|
|
|
|
|
assert.NotNil(t, dn)
|
|
|
|
|
|
|
|
|
|
// Verify we can use the DataNode directly
|
|
|
|
|
f, err := dn.Open("test.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
|
|
data, err := io.ReadAll(f)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "borg", string(data))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOverwrite_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("file.txt", "v1"))
|
|
|
|
|
require.NoError(t, m.Write("file.txt", "v2"))
|
|
|
|
|
|
|
|
|
|
got, err := m.Read("file.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, "v2", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestExists_Good(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
2026-02-09 01:50:57 +00:00
|
|
|
assert.True(t, m.Exists("")) // root
|
2026-02-08 13:25:06 +00:00
|
|
|
assert.False(t, m.Exists("x"))
|
|
|
|
|
|
|
|
|
|
require.NoError(t, m.Write("x", "y"))
|
|
|
|
|
assert.True(t, m.Exists("x"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestReadDir_Ugly(t *testing.T) {
|
|
|
|
|
m := New()
|
|
|
|
|
|
|
|
|
|
// Read from a file path (not a dir) should return empty or error
|
|
|
|
|
require.NoError(t, m.Write("file.txt", "content"))
|
|
|
|
|
_, err := m.Read("file.txt")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
}
|