Skip to content

Commit

Permalink
Initial grlx file testing (#70)
Browse files Browse the repository at this point in the history
* test(ingredients): added test for fileAbsent.go

* refactor: change to types.ErrDeleteRoot instead of custom

* test(files): updates to testing output

* chore(gitignore): updated ignore to not commit coverage.out

* feat(file): updated absent to use MethodPropsSet

- fileAbsent uses MethodPropSet for better argument handling
- added first pass at file validate for this use case

* test(file): initial testing of fileAppend

- adds simple parameter testing

* test(file): added destination testing for caching

- Validates how the caching destination gets handled

* test(file): WIP added cache server testing

- WIP: added test server to help with caching validation, currently does
  not work as expected as there seems to be an issue with file providers

* test(file): initial work on fileContains testing

* test(file): initial testing of fileAppend

- validates parameter validation

* test(file): initial testing of fileDirectory

* refactor(file): move file provider initialization up a level

- Moved individual init functions up a level for easier testing
- Modified sprout to ensure that this functionality gets registered

* test(file): moved test validation to its own file

- moved to its own file for easier reading

* test(file): fixed Cached test to reflect correct behavior after registration

- Change made because local file caching now works since the files are
  registered

* fix(file): fixes issues with error reporting happening incorrectly

* test(file): new fileAppend tests added

- Fixed test with permissions to correctly reflect behavior
- Added tests with empty file and file with content

* test(file): remove invalid check

* fix(file): fix spelling issues

* test(file): updated cache tests for better coverage

* test(file): added more append tests

* test(file): removed extra dest call in test for cached test

* fix(file): updated directory to more correctly report notes

- fixed call to f.undef() to append notes from previous actions
- added TODOs for where this might fail

* test(file): added directory tests for dir_mode

* fix(file): moved test to correctly handle not creating a directory

* fix(test): fixed file utils to fail more gracefully

* test(file): updated dir tests to handle more error cases and tests

* test(file): updated caching configuration
  • Loading branch information
ethanholz authored Oct 17, 2023
1 parent 3c3ea67 commit 7ac33a5
Show file tree
Hide file tree
Showing 20 changed files with 865 additions and 71 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ configs/
etc
.env
*.zip
coverage.out
4 changes: 1 addition & 3 deletions cmd/sprout/include.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package main

import (
_ "github.com/gogrlx/grlx/ingredients/file/http"
_ "github.com/gogrlx/grlx/ingredients/file/local"
_ "github.com/gogrlx/grlx/ingredients/file/s3"
_ "github.com/gogrlx/grlx/ingredients/file"
_ "github.com/gogrlx/grlx/ingredients/service/systemd"
)
47 changes: 40 additions & 7 deletions ingredients/file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,37 @@ func (f File) Parse(id, method string, params map[string]interface{}) (types.Rec
}, nil
}

func (f File) validate() error {
set, err := f.PropertiesForMethod(f.method)
if err != nil {
return err
}
propSet, err := ingredients.PropMapToPropSet(set)
if err != nil {
return err
}
for _, v := range propSet {
if v.IsReq {
if v.Key == "name" {
name, ok := f.params[v.Key].(string)
if !ok {
return types.ErrMissingName
}
if name == "" {
return types.ErrMissingName
}

} else {
// TODO: this might need to be changed to be more deterministic
if _, ok := f.params[v.Key]; !ok {
return fmt.Errorf("missing required property %s", v.Key)
}
}
}
}
return nil
}

// this is a helper func to replace fallthroughs so I can keep the
// cases sorted alphabetically. It's not exported and won't stick around.
// TODO remove undef func
Expand Down Expand Up @@ -389,7 +420,9 @@ func (f File) Apply(ctx context.Context) (types.Result, error) {
func (f File) PropertiesForMethod(method string) (map[string]string, error) {
switch f.method {
case "absent":
return map[string]string{"name": "string"}, nil
return ingredients.MethodPropsSet{
ingredients.MethodProps{Key: "name", Type: "string", IsReq: true},
}.ToMap(), nil
case "append":
return map[string]string{
"name": "string", "text": "[]string", "makedirs": "bool",
Expand All @@ -398,9 +431,12 @@ func (f File) PropertiesForMethod(method string) (map[string]string, error) {
"source_hashes": "[]string",
}, nil
case "cached":
return map[string]string{
"source": "string", "source_hash": "string",
}, nil
return ingredients.MethodPropsSet{
ingredients.MethodProps{Key: "name", Type: "string", IsReq: true},
ingredients.MethodProps{Key: "source", Type: "string", IsReq: true},
ingredients.MethodProps{Key: "hash", Type: "string", IsReq: false},
ingredients.MethodProps{Key: "skip_verify", Type: "bool", IsReq: false},
}.ToMap(), nil
case "contains":
return map[string]string{
"name": "string", "text": "[]string",
Expand Down Expand Up @@ -443,9 +479,6 @@ func (f File) PropertiesForMethod(method string) (map[string]string, error) {
"name": "string",
}, nil
case "symlink":
// return ingredients.MethodPropsSet{
// ingredients.MethodProps{Key: "name", Val: "string", IsReq: true},
// }.ToMap(), nil
return map[string]string{
"name": "string", "target": "string", "force": "bool", "backupname": "string",
"makedirs": "bool", "user": "string", "group": "string", "mode": "string",
Expand Down
14 changes: 5 additions & 9 deletions ingredients/file/fileAbsent.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,20 @@ import (

func (f File) absent(ctx context.Context, test bool) (types.Result, error) {
var notes []fmt.Stringer
name, ok := f.params["name"].(string)
if !ok {
err := f.validate()
if err != nil {
return types.Result{
Succeeded: false, Failed: true, Notes: notes,
}, types.ErrMissingName
}, err
}
name := f.params["name"].(string)
name = filepath.Clean(name)
if name == "" {
return types.Result{
Succeeded: false, Failed: true, Notes: notes,
}, types.ErrMissingName
}
if name == "/" {
return types.Result{
Succeeded: false, Failed: true, Notes: notes,
}, types.ErrDeleteRoot
}
_, err := os.Stat(name)
_, err = os.Stat(name)
if errors.Is(err, os.ErrNotExist) {
notes = append(notes, types.Snprintf("%v is already absent", name))
return types.Result{
Expand Down
118 changes: 118 additions & 0 deletions ingredients/file/fileAbsent_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package file

import (
"context"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/gogrlx/grlx/types"
)

func TestAbsent(t *testing.T) {
tempDir := t.TempDir()
existingFile := filepath.Join(tempDir, "there-is-a-file-here")
os.Create(existingFile)
sampleDir := filepath.Join(tempDir, "there-is-a-dir-here")
os.Mkdir(sampleDir, 0o755)
file := filepath.Join(sampleDir, "there-is-a-file-here")
os.Create(file)
tests := []struct {
name string
params map[string]interface{}
expected types.Result
error error
test bool
}{
{
name: "IncorrectFilename",
params: map[string]interface{}{
"name": 1,
},
expected: types.Result{
Succeeded: false,
Failed: true,
Notes: []fmt.Stringer{},
},
error: types.ErrMissingName,
},
{
name: "AbsentRoot",
params: map[string]interface{}{
"name": "/",
},
expected: types.Result{
Succeeded: false,
Failed: true,
Notes: []fmt.Stringer{},
},
error: types.ErrDeleteRoot,
},
{
name: "AbsentNonExistent",
params: map[string]interface{}{
"name": filepath.Join(tempDir, "there-isnt-a-file-here"),
},
expected: types.Result{
Succeeded: true,
Failed: false,
Changed: false,
Notes: []fmt.Stringer{types.Snprintf("%s is already absent", filepath.Join(tempDir, "there-isnt-a-file-here"))},
},
error: nil,
},
{
name: "AbsentTestRun",
params: map[string]interface{}{
"name": existingFile,
},
expected: types.Result{
Succeeded: true,
Failed: false,
Changed: true,
Notes: []fmt.Stringer{types.Snprintf("%s would be deleted", existingFile)},
},
test: true,
},
{
name: "AbsentTestActual",
params: map[string]interface{}{
"name": existingFile,
},
expected: types.Result{
Succeeded: true,
Failed: false,
Changed: true,
Notes: []fmt.Stringer{types.Snprintf("%s has been deleted", existingFile)},
},
},
{
name: "AbesentDeletePopulatedDirs",
params: map[string]interface{}{
"name": sampleDir,
},
expected: types.Result{
Succeeded: false,
Failed: true,
Changed: false,
Notes: []fmt.Stringer{},
},
error: &os.PathError{Op: "remove", Path: sampleDir, Err: fmt.Errorf("directory not empty")},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
f := File{
id: "",
method: "absent",
params: test.params,
}
result, err := f.absent(context.TODO(), test.test)
if test.error != nil && err.Error() != test.error.Error() {
t.Errorf("expected error %v, got %v", test.error, err)
}
compareResults(t, result, test.expected)
})
}
}
6 changes: 1 addition & 5 deletions ingredients/file/fileAppend.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ func (f File) append(ctx context.Context, test bool) (types.Result, error) {
}, types.ErrMissingName
}
name = filepath.Clean(name)
if name == "" {
return types.Result{
Succeeded: false, Failed: true, Notes: notes,
}, types.ErrMissingName
}
if name == "/" {
return types.Result{
Succeeded: false, Failed: true, Notes: notes,
Expand Down Expand Up @@ -65,6 +60,7 @@ func (f File) append(ctx context.Context, test bool) (types.Result, error) {
}
if errors.Is(err, types.ErrMissingContent) {
f, err := os.OpenFile(name, os.O_APPEND|os.O_WRONLY, 0o644)
// TODO: Bug consider muxing errors to make this more descriptive of the issue that occurred
if err != nil {
return types.Result{
Succeeded: false, Failed: true, Notes: notes,
Expand Down
Loading

0 comments on commit 7ac33a5

Please sign in to comment.