611 lines
14 KiB
Go
611 lines
14 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"
|
|
"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)
|
|
assert.Nil(t, f.SetURI("file://" + indirectFile))
|
|
|
|
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)
|
|
applyErr := testFile.Apply()
|
|
assert.Nil(t, applyErr)
|
|
|
|
f := NewFile()
|
|
assert.NotEqual(t, nil, f)
|
|
|
|
f.Path = file
|
|
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
|
|
}))
|
|
|
|
assert.Nil(t, f.SetURI(fmt.Sprintf("file://%s", file)))
|
|
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)
|
|
e := f.SetURI("file://" + file)
|
|
assert.Nil(t, e)
|
|
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
|
|
e := f.NormalizePath()
|
|
assert.Nil(t, e)
|
|
|
|
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)
|
|
|
|
assert.Nil(t, l.NormalizePath())
|
|
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
|
|
_,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.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
|
|
f.Mode = "0600"
|
|
f.State = "present"
|
|
assert.Nil(t, f.Apply())
|
|
|
|
_,readErr := f.Read(ctx)
|
|
assert.Nil(t, readErr)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
clone := f.Clone().(*File)
|
|
assert.Equal(t, f, clone)
|
|
clone.Mtime = time.Time{}
|
|
clone.Path = testCloneFile
|
|
assert.Nil(t, clone.Apply())
|
|
|
|
_,updateReadErr := f.Read(ctx)
|
|
assert.Nil(t, updateReadErr)
|
|
|
|
_, cloneReadErr := clone.Read(ctx)
|
|
assert.Nil(t, cloneReadErr)
|
|
|
|
fmt.Printf("file %#v\nclone %#v\n", f, clone)
|
|
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
|
|
f.Mode = "631"
|
|
assert.Nil(t, stater.Trigger("create"))
|
|
|
|
read := NewFile()
|
|
readStater := read.StateMachine()
|
|
read.Path = testFile
|
|
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")
|
|
}
|
|
|
|
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)
|
|
e := f.SetURI("foo://" + file)
|
|
assert.NotNil(t, e)
|
|
assert.ErrorIs(t, e, ErrInvalidResourceURI)
|
|
}
|
|
|
|
func TestFileContentType(t *testing.T) {
|
|
file := TempDir.FilePath("fooread.txt")
|
|
f := NewFile()
|
|
assert.NotNil(t, f)
|
|
e := f.SetURI("file://" + file)
|
|
assert.Nil(t, e)
|
|
assert.Equal(t, "txt", f.ContentType())
|
|
}
|
|
|
|
|