Files
ConfigKit/internal/engine/resource_context_test.go

134 lines
4.7 KiB
Go

package engine
import (
"errors"
"io/fs"
"os"
"path/filepath"
"testing"
"testing/fstest"
)
type trackedHostRoot struct {
root *os.Root
closed *int
failOpen bool
}
func (r *trackedHostRoot) Open(name string) (*os.File, error) {
if r.failOpen {
return nil, fs.ErrPermission
}
return r.root.Open(name)
}
func (r *trackedHostRoot) Close() error {
(*r.closed)++
return r.root.Close()
}
func TestRootedHostFSClosesRootOnSuccessfulAndFailedOpen(t *testing.T) {
rootPath := t.TempDir()
if err := os.WriteFile(filepath.Join(rootPath, "resource.txt"), []byte("resource"), 0o600); err != nil {
t.Fatal(err)
}
opened, closed := 0, 0
failOpen := false
filesystem := rootedHostFS{root: rootPath, openRoot: func(name string) (hostResourceRoot, error) {
root, err := os.OpenRoot(name)
if err != nil {
return nil, err
}
opened++
return &trackedHostRoot{root: root, closed: &closed, failOpen: failOpen}, nil
}}
if _, err := filesystem.Open("../resource.txt"); !errors.Is(err, fs.ErrInvalid) || opened != 0 {
t.Fatalf("invalid path = %v; roots opened %d", err, opened)
}
if _, err := filesystem.Open("missing.txt"); !errors.Is(err, fs.ErrNotExist) || opened != 1 || closed != 1 {
t.Fatalf("missing resource = %v; opened %d, closed %d", err, opened, closed)
}
failOpen = true
if _, err := filesystem.Open("resource.txt"); !errors.Is(err, fs.ErrPermission) || opened != 2 || closed != 2 {
t.Fatalf("failed inner open = %v; opened %d, closed %d", err, opened, closed)
}
failOpen = false
file, err := filesystem.Open("resource.txt")
if err != nil || opened != 3 || closed != 2 {
t.Fatalf("resource open = %v; opened %d, closed %d", err, opened, closed)
}
if err := file.Close(); err != nil || closed != 3 {
t.Fatalf("resource close = %v; opened %d, closed %d", err, opened, closed)
}
if _, err := file.Read(make([]byte, 1)); err == nil {
t.Fatal("closed resource file remained readable")
}
directory, err := filesystem.Open(".")
if err != nil {
t.Fatal(err)
}
reader, ok := directory.(fs.ReadDirFile)
if !ok {
t.Fatal("rooted host directory did not expose ReadDirFile")
}
entries, err := reader.ReadDir(-1)
if err != nil || len(entries) != 1 || entries[0].Name() != "resource.txt" {
t.Fatalf("directory entries = %#v, %v", entries, err)
}
if err := directory.Close(); err != nil || opened != 4 || closed != 4 {
t.Fatalf("directory close = %v; opened %d, closed %d", err, opened, closed)
}
}
func TestNativeResourceViewIsBorrowedAndRooted(t *testing.T) {
filesystem := &observedFilesystem{files: fstest.MapFS{
"bundle/config/resource.txt": &fstest.MapFile{Data: []byte("content")},
}}
view := rootedNativeFS{filesystem: filesystem, root: "bundle"}
if filesystem.opened != 0 {
t.Fatal("constructing a native resource view opened files")
}
data, err := fs.ReadFile(view, "config/resource.txt")
if err != nil || string(data) != "content" {
t.Fatalf("resource = %q, %v", data, err)
}
filesystem.assertClosed(t)
if _, err := fs.ReadFile(view, "../outside.txt"); !errors.Is(err, fs.ErrInvalid) {
t.Fatalf("resource escape = %v", err)
}
}
func TestLoadingClosesNativeFilesAcrossPipelineOutcomes(t *testing.T) {
type config struct {
Value string `yaml:"value"`
Version string `yaml:"version"`
}
for _, test := range []struct {
name string
main string
child string
failRead string
options Options[config]
wantErr bool
}{
{name: "success", main: "value: retained\n"},
{name: "acquisition", main: "value: retained\n", failRead: "main.yml", wantErr: true},
{name: "parse", main: "value: [broken\n", wantErr: true},
{name: "composition", main: "configkit: {imports: [{path: child.yml}]}\nvalue: first\n", child: "value: second\n", options: Options[config]{Imports: true}, wantErr: true},
{name: "version", main: "version: wrong\n", options: Options[config]{Definition: Definition[config]{Version: &VersionRule{Path: Keys("version"), Check: func(Version, *Validation) error { return fs.ErrInvalid }}}}, wantErr: true},
{name: "validation", main: "value: retained\n", options: Options[config]{Definition: Definition[config]{Validate: func(_ *config, validation *Validation) { validation.Error(Keys("value"), "rejected") }}}, wantErr: true},
} {
t.Run(test.name, func(t *testing.T) {
files := fstest.MapFS{"main.yml": &fstest.MapFile{Data: []byte(test.main)}}
if test.child != "" {
files["child.yml"] = &fstest.MapFile{Data: []byte(test.child)}
}
filesystem := &observedFilesystem{files: files, failRead: test.failRead}
_, err := testLoad(filesystemFile(newNativeNamespace("bundle", filesystem), "main.yml"), test.options)
if (err != nil) != test.wantErr {
t.Fatalf("load error = %v, want failure %v", err, test.wantErr)
}
filesystem.assertClosed(t)
})
}
}