jx/internal/resource/file_test.go
Matthew Rich 8feb7b8d56
Some checks failed
Lint / golangci-lint (push) Failing after 10m1s
Declarative Tests / test (push) Failing after 14s
add support of import search paths [doublejynx/jx#7]
2024-10-16 10:26:42 -07:00

660 lines
15 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.
package resource
import (
"context"
_ "encoding/json"
"fmt"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"io"
_ "log"
_ "net/http"
_ "net/http/httptest"
_ "net/url"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
"os/user"
"io/fs"
"decl/internal/codec"
"decl/internal/data"
"decl/internal/folio"
"log/slog"
)
func TestNewFileResource(t *testing.T) {
f := NewFile()
assert.NotEqual(t, nil, f)
}
func TestNewFileNormalized(t *testing.T) {
indirectFile := fmt.Sprintf("%s/%s", string(TempDir), "bar/../fooread.txt")
absFilePath,_ := filepath.Abs(indirectFile)
f := NewNormalizedFile()
assert.NotNil(t, f)
f.Path = indirectFile
assert.Nil(t, f.Init(nil))
assert.NotEqual(t, indirectFile, f.Path)
assert.Equal(t, absFilePath, f.Path)
assert.NotEqual(t, "file://" + indirectFile, f.URI())
assert.Equal(t, "file://" + absFilePath, f.URI())
}
func TestApplyResourceTransformation(t *testing.T) {
f := NewFile()
assert.NotEqual(t, nil, f)
//e := f.Apply()
//assert.Equal(t, nil, e)
}
func TestReadFile(t *testing.T) {
ctx := context.Background()
file, _ := filepath.Abs(TempDir.FilePath("fooread.txt"))
expectedTime, timeErr := time.Parse(time.RFC3339Nano, "2001-12-15T01:01:01.000000001Z")
assert.Nil(t, timeErr)
expectedTimestamp := expectedTime.Local().Format(time.RFC3339Nano)
declarationAttributes := `
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
atime: %s
ctime: %s
mtime: %s
content: |-
test line 1
test line 2
sha256: f2082f984f1bf1a7886e2af32ccc9ca474fbff3553d131204b070c438114dd51
size: 23
filetype: "regular"
state: present
`
decl := fmt.Sprintf(declarationAttributes, file, ProcessTestUserName, ProcessTestGroupName, expectedTimestamp, expectedTimestamp, expectedTimestamp)
testFile := NewFile()
e := testFile.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, "present", testFile.Common.State)
assert.Equal(t, file, testFile.Common.Path)
applyErr := testFile.Apply()
assert.Nil(t, applyErr)
assert.FileExists(t, file)
f := NewFile()
assert.NotNil(t, f)
f.Path = file
assert.Nil(t, f.Init(nil))
r, e := f.Read(ctx)
assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
info, statErr := os.Stat(file)
assert.Nil(t, statErr)
stat, ok := info.Sys().(*syscall.Stat_t)
assert.True(t, ok)
cTime := time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec))
expected := fmt.Sprintf(declarationAttributes, file, ProcessTestUserName, ProcessTestGroupName, expectedTimestamp, cTime.Local().Format(time.RFC3339Nano), expectedTimestamp)
assert.YAMLEq(t, expected, string(r))
}
func TestUseConfig(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("missingfile.txt"))
f := NewFile()
assert.NotNil(t, f)
f.UseConfig(MockConfig(func(key string) (any, error) {
if key == "prefix" {
return "/tmp", nil
}
return nil, data.ErrUnknownConfigurationKey
}))
uri := folio.URI(fmt.Sprintf("file://%s", file))
assert.Nil(t, f.Init(uri.Parse()))
assert.Equal(t, filepath.Join("/tmp", file), f.FilePath())
}
func TestReadFileError(t *testing.T) {
ctx := context.Background()
file, _ := filepath.Abs(TempDir.FilePath("missingfile.txt"))
f := NewFile()
assert.NotNil(t, f)
f.Path = file
_, e := f.Read(ctx)
assert.ErrorIs(t, e, fs.ErrNotExist)
assert.Equal(t, "absent", f.State)
}
func TestCreateFile(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("foo.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
content: |-
test line 1
test line 2
state: present
`, file, ProcessTestUserName, ProcessTestGroupName)
f := NewFile()
e := f.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
applyErr := f.Apply()
assert.Equal(t, nil, applyErr)
assert.FileExists(t, file, nil)
s, e := os.Stat(file)
assert.Equal(t, nil, e)
assert.Greater(t, s.Size(), int64(0))
f.State = "absent"
assert.Nil(t, f.Apply())
assert.NoFileExists(t, file, nil)
}
func TestLoadFile(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("foo.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
content: |-
test line 1
test line 2
state: present
`, file, ProcessTestUserName, ProcessTestGroupName)
f := NewFile()
assert.Nil(t, f.Load([]byte(decl), codec.FormatYaml))
assert.Equal(t, ProcessTestUserName, f.Owner)
assert.Greater(t, f.Size, int64(0))
reader := io.NopCloser(strings.NewReader(decl))
fr := NewFile()
assert.Nil(t, fr.LoadReader(reader, codec.FormatYaml))
assert.Equal(t, ProcessTestUserName, f.Owner)
assert.Greater(t, f.Size, int64(0))
contentReaderTransport, trErr := fr.ContentReaderStream()
assert.ErrorContains(t, trErr, "Cannot provide transport reader for string content")
assert.Nil(t, contentReaderTransport)
contentWriterTransport, trErr := fr.ContentWriterStream()
assert.ErrorContains(t, trErr, "Cannot provide transport writer for string content")
assert.Nil(t, contentWriterTransport)
}
func TestFileType(t *testing.T) {
fileType := []byte(`
filetype: "directory"
`)
var testFile File
err := yaml.Unmarshal(fileType, &testFile)
assert.Nil(t, err)
}
func TestFileDirectory(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("testdir"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0700"
filetype: "directory"
state: present
`, file, ProcessTestUserName, ProcessTestGroupName)
f := NewFile()
e := f.LoadDecl(decl)
assert.Equal(t, nil, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
applyErr := f.Apply()
assert.Equal(t, nil, applyErr)
assert.DirExists(t, file)
f.State = "absent"
deleteErr := f.Apply()
assert.Nil(t, deleteErr)
assert.NoDirExists(t, file)
}
func TestFileTimes(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("testtimes.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mtime: 2001-12-15T01:01:01.1Z
mode: "0600"
filtetype: "regular"
state: "present"
`, file, ProcessTestUserName, ProcessTestGroupName)
expectedTime, timeErr := time.Parse(time.RFC3339, "2001-12-15T01:01:01.1Z")
assert.Nil(t, timeErr)
f := NewFile()
e := f.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
assert.True(t, f.Mtime.Equal(expectedTime))
}
func TestFileSetURI(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("testuri.txt"))
f := NewFile()
assert.NotNil(t, f)
uri := folio.URI("file://" + file).Parse()
assert.Nil(t, f.Init(uri))
assert.Equal(t, "file", f.Type())
assert.Equal(t, file, f.Path)
}
func TestFileNormalizePath(t *testing.T) {
absFile, absFilePathErr := filepath.Abs(TempDir.FilePath("./testuri.txt"))
assert.Nil(t, absFilePathErr)
file := TempDir.FilePath("./testuri.txt")
f := NewFile()
assert.NotNil(t, f)
f.Path = file
e := f.NormalizePath()
assert.Nil(t, e)
assert.Equal(t, absFile, f.Path)
}
func TestFileUpdateAttributesFromFileInfo(t *testing.T) {
f := NewFile()
assert.NotNil(t, f)
info, e := os.Lstat(string(TempDir))
assert.Nil(t, e)
updateAttributesErr := f.UpdateAttributesFromFileInfo(info)
assert.Nil(t, updateAttributesErr)
assert.Equal(t, DirectoryFile, f.FileType)
assert.ErrorIs(t, f.SetFileInfo(nil), ErrInvalidFileInfo)
}
func TestFileReadStat(t *testing.T) {
ctx := context.Background()
link := TempDir.FilePath("link.txt")
linkTargetFile := TempDir.FilePath("testuri.txt")
f := NewFile()
assert.NotNil(t, f)
f.Path = linkTargetFile
f.PathNormalization(true)
assert.Nil(t, f.Init(nil))
statErr := f.ReadStat()
assert.Error(t, statErr)
f.Owner = ProcessTestUserName
f.Group = ProcessTestGroupName
f.State = "present"
assert.Nil(t, f.Apply())
assert.Nil(t, f.ReadStat())
l := NewFile()
assert.NotNil(t, l)
l.PathNormalization(true)
assert.Nil(t, l.Init(nil))
l.FileType = SymbolicLinkFile
l.Path = link
l.Target = linkTargetFile
l.State = "present"
slog.Info("TestFileReadStat()", "file", f, "link", l)
applyErr := l.Apply()
assert.Nil(t, applyErr)
readStatErr := l.ReadStat()
assert.Nil(t, readStatErr)
testRead := NewFile()
testRead.Path = link
assert.Nil(t, testRead.Init(nil))
_,testReadErr := testRead.Read(ctx)
assert.Nil(t, testReadErr)
assert.Equal(t, linkTargetFile, testRead.Target)
}
func TestFileResourceFileInfo(t *testing.T) {
testFile := TempDir.FilePath("testuri.txt")
f := NewFile()
assert.NotNil(t, f)
f.Path = testFile
f.Mode = "0600"
f.Content = "some test data"
f.State = "present"
assert.Nil(t, f.Init(nil))
assert.Nil(t, f.Apply())
_, readErr := f.Read(context.Background())
assert.Nil(t, readErr)
fi := f.FileInfo()
assert.Equal(t, os.FileMode(0600), fi.Mode().Perm())
assert.Equal(t, testFile, fi.Name())
assert.False(t, fi.IsDir())
assert.Nil(t, fi.Sys())
assert.Greater(t, time.Now(), fi.ModTime())
assert.Greater(t, fi.Size(), int64(0))
}
func TestFileClone(t *testing.T) {
ctx := context.Background()
testFile := TempDir.FilePath("testorig.txt")
testCloneFile := TempDir.FilePath("testclone.txt")
f := NewFile()
assert.NotNil(t, f)
f.Path = testFile
assert.Nil(t, f.Init(nil))
f.Mode = "0600"
f.State = "present"
assert.Nil(t, f.Apply())
origin := time.Now()
_,readErr := f.Read(ctx)
assert.Nil(t, readErr)
time.Sleep(100 * time.Millisecond)
assert.Greater(t, origin, f.Mtime)
clone := f.Clone().(*File)
assert.Equal(t, f.Common.Path, clone.Common.Path)
assert.Equal(t, f.Common.absPath, clone.Common.absPath)
assert.Equal(t, f.Common.parsedURI, clone.Common.parsedURI)
assert.Equal(t, f.Common.exttype, clone.Common.exttype)
assert.Equal(t, f.Common.fileext, clone.Common.fileext)
assert.Equal(t, f.Common.State, clone.Common.State)
assert.Equal(t, f.Size, clone.Size)
assert.Equal(t, f.Owner, clone.Owner)
assert.Equal(t, f.Group, clone.Group)
assert.Equal(t, f.Mode, clone.Mode)
assert.Equal(t, f.Atime, clone.Atime)
assert.Equal(t, f.Mtime, clone.Mtime)
assert.Equal(t, f.Ctime, clone.Ctime)
assert.Equal(t, f.Content, clone.Content)
assert.Equal(t, f.Sha256, clone.Sha256)
clone.Mtime = time.Now()
clone.Path = testCloneFile
assert.Nil(t, clone.Init(nil))
assert.NotEqual(t, f.absPath, clone.absPath)
slog.Info("TestFileClone", "clone", clone)
assert.Nil(t, clone.Apply())
slog.Info("TestFileClone - applied mtime change", "clone", clone)
_,updateReadErr := f.Read(ctx)
assert.Nil(t, updateReadErr)
_, cloneReadErr := clone.Read(ctx)
assert.Nil(t, cloneReadErr)
slog.Info("TestFileClone - read mtime change", "orig", f.Mtime, "clone", clone.Mtime)
fmt.Printf("file %#v\n %#v\nclone %#v\n %#v\n", f, f.Common, clone, clone.Common)
assert.NotEqual(t, f.Mtime, clone.Mtime)
}
func TestFileErrors(t *testing.T) {
//ctx := context.Background()
testFile := TempDir.FilePath("testerr.txt")
f := NewFile()
assert.NotNil(t, f)
stater := f.StateMachine()
f.Path = testFile
assert.Nil(t, f.Init(nil))
f.Mode = "631"
assert.Nil(t, stater.Trigger("create"))
assert.FileExists(t, f.Path)
read := NewFile()
readStater := read.StateMachine()
read.Path = testFile
assert.Nil(t, read.Init(nil))
assert.Nil(t, readStater.Trigger("read"))
assert.Equal(t, FileMode("0631"), read.Mode)
f.Mode = "900"
assert.ErrorAs(t, stater.Trigger("create"), &ErrInvalidFileMode, "Apply should fail with NumError when converting invalid octal")
assert.Nil(t, readStater.Trigger("read"))
assert.Equal(t, FileMode("0631"), read.Mode)
f.Mode = "0631"
f.Owner = "bar"
uidErr := f.Apply()
var UnknownUser user.UnknownUserError
assert.Error(t, uidErr, UnknownUser)
}
func TestFileDelete(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("foo.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
content: |-
test line 1
test line 2
state: present
`, file, ProcessTestUserName, ProcessTestGroupName)
f := NewFile()
stater := f.StateMachine()
e := f.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
assert.Nil(t, stater.Trigger("create"))
assert.FileExists(t, file, nil)
s, e := os.Stat(file)
assert.Nil(t, e)
assert.Greater(t, s.Size(), int64(0))
assert.Nil(t, stater.Trigger("delete"))
assert.NoFileExists(t, file, nil)
}
func TestFileContentRef(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("src.txt"))
copyFile, _ := filepath.Abs(TempDir.FilePath("copy.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
content: |-
test line 1
test line 2
state: present
`, file, ProcessTestUserName, ProcessTestGroupName)
contentRef := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
sourceref: "file://%s"
state: present
`, copyFile, ProcessTestUserName, ProcessTestGroupName, file)
f := NewFile()
stater := f.StateMachine()
e := f.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
assert.Nil(t, stater.Trigger("create"))
assert.FileExists(t, file, nil)
s, e := os.Stat(file)
assert.Nil(t, e)
assert.Greater(t, s.Size(), int64(0))
fr := NewFile()
loadErr := fr.LoadDecl(contentRef)
assert.Nil(t, loadErr)
assert.Equal(t, ProcessTestUserName, fr.Owner)
assert.Nil(t, fr.StateMachine().Trigger("create"))
assert.FileExists(t, file, nil)
_, statErr := os.Stat(file)
assert.Nil(t, statErr)
assert.Nil(t, stater.Trigger("delete"))
assert.NoFileExists(t, file, nil)
}
func TestFilePathURI(t *testing.T) {
// file, _ := filepath.Abs(filepath.Join(TempDir, "foo.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
content: |-
test line 1
test line 2
`, "", ProcessTestUserName, ProcessTestGroupName)
f := NewFile()
e := f.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, "", f.FilePath())
// assert.ErrorContains(t, f.Validate(), "path: String length must be greater than or equal to 1")
assert.ErrorContains(t, f.Validate(), "path is required")
}
func TestFileAbsent(t *testing.T) {
file, _ := filepath.Abs(TempDir.FilePath("testabsentstate.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
filetype: "regular"
`, file, ProcessTestUserName, ProcessTestGroupName)
f := NewFile()
stater := f.StateMachine()
e := f.LoadDecl(decl)
assert.Nil(t, e)
assert.Equal(t, ProcessTestUserName, f.Owner)
err := stater.Trigger("read")
assert.Nil(t, err)
assert.Equal(t, "absent", f.State)
}
func TestFileReader(t *testing.T) {
expected := "datatoreadusinganio.readers"
dataReader := strings.NewReader(expected)
file, _ := filepath.Abs(TempDir.FilePath("testabsentstate.txt"))
decl := fmt.Sprintf(`
path: "%s"
owner: "%s"
group: "%s"
mode: "0600"
content: "%s"
filetype: "regular"
`, file, ProcessTestUserName, ProcessTestGroupName, expected)
f := NewFile()
assert.Nil(t, f.LoadString(decl, codec.FormatYaml))
assert.Nil(t, f.SetContent(dataReader))
reader, err := f.GetContent(nil)
assert.Nil(t, err)
value, ioErr := io.ReadAll(reader)
assert.Nil(t, ioErr)
assert.Equal(t, expected, string(value))
var writer strings.Builder
_, writerErr := f.GetContent(&writer)
assert.Nil(t, writerErr)
assert.Equal(t, expected, writer.String())
}
func TestFileSetURIError(t *testing.T) {
file := TempDir.FilePath("fooread.txt")
f := NewFile()
assert.NotNil(t, f)
uri := folio.URI("foo://" + file).Parse()
e := f.Init(uri)
assert.ErrorIs(t, e, ErrInvalidResourceURI)
}
func TestFileContentType(t *testing.T) {
file := TempDir.FilePath("fooread.txt")
f := NewFile()
assert.NotNil(t, f)
uri := folio.URI("file://" + file).Parse()
assert.Nil(t, f.Init(uri))
assert.Equal(t, "txt", f.ContentType())
}