Skip to content

Commit d5fd057

Browse files
committed
Implement MakeDirectoryArtifactsProcessor
1 parent 2e38e1a commit d5fd057

File tree

8 files changed

+524
-295
lines changed

8 files changed

+524
-295
lines changed

artefactregistry.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,63 @@ import (
88
"time"
99
)
1010

11+
var _ ArtifactRegistry = (*InMemoryArtifactRegistry)(nil)
12+
13+
type InMemoryArtifactRegistry struct {
14+
ArtifactMap map[string][]string
15+
mu sync.RWMutex // Mutex to protect concurrent access
16+
}
17+
18+
func (r *InMemoryArtifactRegistry) Load() error { return nil }
19+
20+
func (r *InMemoryArtifactRegistry) Save() error { return nil }
21+
22+
func (r *InMemoryArtifactRegistry) AddArtifact(processorName string, artifactName string) {
23+
r.mu.Lock()
24+
defer r.mu.Unlock()
25+
if r.ArtifactMap == nil {
26+
r.ArtifactMap = map[string][]string{}
27+
}
28+
29+
if _, ok := r.ArtifactMap[processorName]; !ok {
30+
r.ArtifactMap[processorName] = make([]string, 0)
31+
}
32+
r.ArtifactMap[processorName] = append(r.ArtifactMap[processorName], artifactName)
33+
}
34+
35+
func (r *InMemoryArtifactRegistry) RemoveArtifact(processorName string, artifactName string) {
36+
r.mu.Lock()
37+
defer r.mu.Unlock()
38+
if _, ok := r.ArtifactMap[processorName]; !ok {
39+
return
40+
}
41+
if r.ArtifactMap == nil {
42+
r.ArtifactMap = map[string][]string{}
43+
}
44+
45+
var artifacts []string
46+
for _, file := range r.ArtifactMap[processorName] {
47+
if file != artifactName {
48+
artifacts = append(artifacts, file)
49+
}
50+
}
51+
52+
r.ArtifactMap[processorName] = artifacts
53+
}
54+
55+
func (r *InMemoryArtifactRegistry) Artifacts(processorName string) []string {
56+
if r.ArtifactMap == nil {
57+
r.ArtifactMap = map[string][]string{}
58+
}
59+
60+
values, ok := r.ArtifactMap[processorName]
61+
if !ok {
62+
return nil
63+
}
64+
65+
return values
66+
}
67+
1168
// JSONArtifactRegistry implementation of a ArtifactRegistry that is saved as a JSON file.
1269
type JSONArtifactRegistry struct {
1370
GeneratedAt time.Time `json:"generatedAt"`

filesystem.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package specter
2+
3+
import (
4+
"io/fs"
5+
"os"
6+
"path/filepath"
7+
)
8+
9+
// FileSystem is an abstraction layer over various types of file systems.
10+
// It provides a unified interface to interact with different file system implementations,
11+
// whether they are local, remote, or virtual.
12+
//
13+
// Implementations of this interface allow consumers to perform common file system operations such as
14+
// obtaining absolute paths, retrieving file information, walking through directories, and reading files.
15+
type FileSystem interface {
16+
// Abs converts a relative file path to an absolute one. The implementation may
17+
// differ depending on the underlying file system.
18+
Abs(location string) (string, error)
19+
20+
// StatPath returns file information for the specified location. This typically
21+
// includes details like size, modification time, and whether the path is a file
22+
// or directory.
23+
StatPath(location string) (os.FileInfo, error)
24+
25+
// WalkDir traverses the directory tree rooted at the specified path, calling the
26+
// provided function for each file or directory encountered. This allows for
27+
// efficient processing of large directory structures and can handle errors for
28+
// individual files or directories.
29+
WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error
30+
31+
// ReadFile reads the contents of the specified file and returns it as a byte
32+
// slice. This method abstracts away the specifics of how the file is accessed,
33+
// making it easier to work with files across different types of file systems.
34+
ReadFile(filePath string) ([]byte, error)
35+
36+
// WriteFile writes data to the specified file path with the given permissions.
37+
// If the file exists, it will be overwritten.
38+
WriteFile(path string, data []byte, perm fs.FileMode) error
39+
40+
// Mkdir creates a new directory at the specified path along with any necessary
41+
// parents, and returns nil, If the directory already exists, the implementation
42+
// may either return an error or ignore the request, depending on the file
43+
// system. This method abstracts the underlying mechanism of directory creation
44+
// across different file systems.
45+
Mkdir(dirPath string, mode fs.FileMode) error
46+
47+
// Remove removes the named file or (empty) directory.
48+
// If there is an error, it will be of type *PathError.
49+
Remove(path string) error
50+
}
51+
52+
var _ FileSystem = LocalFileSystem{}
53+
54+
// LocalFileSystem is an implementation of a FileSystem that works on the local file system where this program is running.
55+
type LocalFileSystem struct{}
56+
57+
func (l LocalFileSystem) Remove(path string) error {
58+
return os.Remove(path)
59+
}
60+
61+
func (l LocalFileSystem) WriteFile(path string, data []byte, perm fs.FileMode) error {
62+
return os.WriteFile(path, data, perm)
63+
}
64+
65+
func (l LocalFileSystem) Mkdir(dirPath string, mode fs.FileMode) error {
66+
return os.MkdirAll(dirPath, mode)
67+
}
68+
69+
func (l LocalFileSystem) ReadFile(filePath string) ([]byte, error) {
70+
return os.ReadFile(filePath)
71+
}
72+
73+
func (l LocalFileSystem) WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error {
74+
return filepath.WalkDir(dirPath, f)
75+
}
76+
77+
func (l LocalFileSystem) StatPath(location string) (os.FileInfo, error) {
78+
return os.Stat(location)
79+
}
80+
81+
func (l LocalFileSystem) Abs(location string) (string, error) {
82+
return filepath.Abs(location)
83+
}

filesystem_test.go

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
package specter
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"github.com/stretchr/testify/require"
6+
"io/fs"
7+
"os"
8+
"runtime"
9+
"strings"
10+
"testing"
11+
)
12+
13+
var _ FileSystem = (*mockFileSystem)(nil)
14+
15+
// Mock implementations to use in tests.
16+
type mockFileInfo struct {
17+
os.FileInfo
18+
name string
19+
size int64
20+
mode os.FileMode
21+
modTime int64
22+
isDir bool
23+
}
24+
25+
func (m mockFileInfo) Type() fs.FileMode {
26+
if m.isDir {
27+
return os.ModeDir
28+
}
29+
30+
return os.ModeAppend
31+
}
32+
33+
func (m mockFileInfo) Info() (fs.FileInfo, error) {
34+
return m, nil
35+
}
36+
37+
func (m mockFileInfo) Name() string { return m.name }
38+
func (m mockFileInfo) Size() int64 { return m.size }
39+
func (m mockFileInfo) Mode() os.FileMode { return m.mode }
40+
func (m mockFileInfo) IsDir() bool { return m.isDir }
41+
func (m mockFileInfo) Sys() interface{} { return nil }
42+
43+
type mockFileSystem struct {
44+
files map[string][]byte
45+
dirs map[string]bool
46+
47+
absErr error
48+
statErr error
49+
walkDirErr error
50+
readFileErr error
51+
writeFileErr error
52+
mkdirErr error
53+
rmErr error
54+
}
55+
56+
func (m *mockFileSystem) WriteFile(_ string, _ []byte, _ fs.FileMode) error {
57+
if m.writeFileErr != nil {
58+
return m.writeFileErr
59+
}
60+
return nil
61+
}
62+
63+
func (m *mockFileSystem) Mkdir(dirPath string, _ fs.FileMode) error {
64+
if m.mkdirErr != nil {
65+
return m.mkdirErr
66+
}
67+
68+
m.dirs[dirPath] = true
69+
70+
return nil
71+
}
72+
73+
func (m *mockFileSystem) Remove(path string) error {
74+
if m.rmErr != nil {
75+
return m.rmErr
76+
}
77+
78+
m.dirs[path] = false
79+
delete(m.files, path)
80+
81+
return nil
82+
}
83+
84+
func (m *mockFileSystem) Abs(location string) (string, error) {
85+
if m.absErr != nil {
86+
return "", m.absErr
87+
}
88+
89+
absPaths := make(map[string]bool, len(m.files)+len(m.dirs))
90+
91+
for k := range m.files {
92+
absPaths[k] = true
93+
}
94+
for k := range m.dirs {
95+
absPaths[k] = true
96+
}
97+
98+
if absPath, exists := absPaths[location]; exists && absPath {
99+
return location, nil
100+
}
101+
return "", nil
102+
}
103+
104+
func (m *mockFileSystem) StatPath(location string) (os.FileInfo, error) {
105+
if m.statErr != nil {
106+
return nil, m.statErr
107+
}
108+
109+
if isDir, exists := m.dirs[location]; exists {
110+
return mockFileInfo{name: location, isDir: isDir}, nil
111+
}
112+
113+
if data, exists := m.files[location]; exists {
114+
return mockFileInfo{name: location, size: int64(len(data))}, nil
115+
}
116+
117+
return nil, os.ErrNotExist
118+
}
119+
120+
func (m *mockFileSystem) WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error {
121+
if m.walkDirErr != nil {
122+
return m.walkDirErr
123+
}
124+
125+
for path, isDir := range m.dirs {
126+
if strings.HasPrefix(path, dirPath) {
127+
err := f(path, mockFileInfo{name: path, isDir: isDir}, nil)
128+
if err != nil {
129+
return err
130+
}
131+
}
132+
}
133+
134+
for path := range m.files {
135+
if strings.HasPrefix(path, dirPath) {
136+
err := f(path, mockFileInfo{name: path, isDir: false}, nil)
137+
if err != nil {
138+
return err
139+
}
140+
}
141+
}
142+
143+
return nil
144+
}
145+
146+
func (m *mockFileSystem) ReadFile(filePath string) ([]byte, error) {
147+
if m.readFileErr != nil {
148+
return nil, m.readFileErr
149+
}
150+
151+
if data, exists := m.files[filePath]; exists {
152+
return data, nil
153+
}
154+
155+
return nil, os.ErrNotExist
156+
}
157+
158+
func TestLocalFileSystem_ReadFile(t *testing.T) {
159+
tests := []struct {
160+
name string
161+
given string
162+
then []byte
163+
thenErrContains string
164+
}{
165+
{
166+
name: "GIVEN a file that does not exists THEN return error",
167+
given: "/fake/dir/file1.txt",
168+
thenErrContains: "no such file or directory",
169+
},
170+
}
171+
for _, tt := range tests {
172+
t.Run(tt.name, func(t *testing.T) {
173+
l := LocalFileSystem{}
174+
got, err := l.ReadFile(tt.given)
175+
176+
if tt.thenErrContains != "" {
177+
require.Error(t, err)
178+
assert.ErrorContains(t, err, tt.thenErrContains)
179+
} else {
180+
require.NoError(t, err)
181+
}
182+
assert.Equal(t, tt.then, got)
183+
})
184+
}
185+
}
186+
187+
func TestLocalFileSystem_WalkDir(t *testing.T) {
188+
// Make sure the closure gets called.
189+
lfs := LocalFileSystem{}
190+
closureCalled := false
191+
err := lfs.WalkDir("/fake/dir", func(path string, d fs.DirEntry, err error) error {
192+
closureCalled = true
193+
return nil
194+
})
195+
require.True(t, closureCalled)
196+
require.NoError(t, err)
197+
}
198+
199+
func TestLocalFileSystem_StatPath(t *testing.T) {
200+
lfs := LocalFileSystem{}
201+
_, filename, _, ok := runtime.Caller(0)
202+
require.True(t, ok)
203+
stat, err := lfs.StatPath(filename)
204+
require.NoError(t, err)
205+
assert.NotNil(t, stat)
206+
}

0 commit comments

Comments
 (0)