From 782d328b0b69c89f0223f64211fd418ecec5cc9a Mon Sep 17 00:00:00 2001 From: jwillp Date: Thu, 22 Aug 2024 00:52:40 -0400 Subject: [PATCH] Fix JSONArtifact registry tests --- artefactregistry.go | 32 +++++---- artefactregistry_test.go | 136 ++++++++++++++++++++++++--------------- source.go | 5 ++ source_test.go | 24 +++++-- 4 files changed, 130 insertions(+), 67 deletions(-) diff --git a/artefactregistry.go b/artefactregistry.go index 5e25b00..4f2554e 100644 --- a/artefactregistry.go +++ b/artefactregistry.go @@ -10,10 +10,12 @@ import ( // JSONArtifactRegistry implementation of a ArtifactRegistry that is saved as a JSON file. type JSONArtifactRegistry struct { - GeneratedAt time.Time `json:"generatedAt"` - ArtifactMap map[string]*JSONArtifactRegistryProcessor `json:"files"` - FilePath string - mu sync.RWMutex // Mutex to protect concurrent access + GeneratedAt time.Time `json:"generatedAt"` + ArtifactMap map[string]*JSONArtifactRegistryProcessor `json:"files"` + FilePath string `json:"-"` + FileSystem FileSystem `json:"-"` + mu sync.RWMutex // Mutex to protect concurrent access + CurrentTimeProvider func() time.Time } type JSONArtifactRegistryProcessor struct { @@ -21,11 +23,14 @@ type JSONArtifactRegistryProcessor struct { } // NewJSONArtifactRegistry returns a new artifact file registry. -func NewJSONArtifactRegistry(fileName string) *JSONArtifactRegistry { +func NewJSONArtifactRegistry(fileName string, fs FileSystem) *JSONArtifactRegistry { return &JSONArtifactRegistry{ - GeneratedAt: time.Now(), ArtifactMap: nil, FilePath: fileName, + CurrentTimeProvider: func() time.Time { + return time.Now() + }, + FileSystem: fs, } } @@ -33,7 +38,7 @@ func (r *JSONArtifactRegistry) Load() error { r.mu.Lock() defer r.mu.Unlock() - bytes, err := os.ReadFile(r.FilePath) + bytes, err := r.FileSystem.ReadFile(r.FilePath) if err != nil { if os.IsNotExist(err) { @@ -41,6 +46,12 @@ func (r *JSONArtifactRegistry) Load() error { } return errors.WrapWithMessage(err, errors.InternalErrorCode, "failed loading artifact file registry") } + + // empty file is okay + if len(bytes) == 0 { + return nil + } + if err := json.Unmarshal(bytes, r); err != nil { return errors.WrapWithMessage(err, errors.InternalErrorCode, "failed loading artifact file registry") } @@ -52,15 +63,14 @@ func (r *JSONArtifactRegistry) Save() error { r.mu.RLock() defer r.mu.RUnlock() - if r.ArtifactMap == nil { - return nil - } + r.GeneratedAt = r.CurrentTimeProvider() + // Generate a JSON file containing all artifact files for clean up later on js, err := json.MarshalIndent(r, "", " ") if err != nil { return errors.Wrap(err, "failed generating artifact file registry") } - if err := os.WriteFile(r.FilePath, js, os.ModePerm); err != nil { + if err := r.FileSystem.WriteFile(r.FilePath, js, os.ModePerm); err != nil { return errors.Wrap(err, "failed generating artifact file registry") } diff --git a/artefactregistry_test.go b/artefactregistry_test.go index edaa0fc..0cdf581 100644 --- a/artefactregistry_test.go +++ b/artefactregistry_test.go @@ -1,6 +1,8 @@ package specter import ( + "fmt" + "github.com/stretchr/testify/require" "os" "testing" "time" @@ -9,37 +11,55 @@ import ( ) func TestJSONArtifactRegistry_Load(t *testing.T) { - tests := []struct { - name string - fileContent string - expectError bool + type given struct { + jsonFileContent string + } + type then struct { + expectedError error expectedValue *JSONArtifactRegistry + } + tests := []struct { + name string + given given + then then }{ { - name: "Successful Load", - fileContent: `{"generatedAt":"2024-01-01T00:00:00Z","files":{"processor1":{"files":["file1.txt"]}}}`, - expectError: false, - expectedValue: &JSONArtifactRegistry{ - GeneratedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), - ArtifactMap: map[string]*JSONArtifactRegistryProcessor{"processor1": {Artifacts: []string{"file1.txt"}}}, + name: "Successful Load", + given: given{ + jsonFileContent: `{"generatedAt":"2024-01-01T00:00:00Z","files":{"processor1":{"files":["file1.txt"]}}}`, + }, + then: then{ + expectedError: nil, + expectedValue: &JSONArtifactRegistry{ + GeneratedAt: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC), + ArtifactMap: map[string]*JSONArtifactRegistryProcessor{"processor1": {Artifacts: []string{"file1.txt"}}}, + }, }, }, { - name: "File Not Exist", - fileContent: "", - expectError: false, - expectedValue: &JSONArtifactRegistry{ - GeneratedAt: time.Time{}, - ArtifactMap: nil, + name: "File Not Exist", + given: given{ + jsonFileContent: "", + }, + then: then{ + expectedError: nil, + expectedValue: &JSONArtifactRegistry{ + GeneratedAt: time.Time{}, + ArtifactMap: nil, + }, }, }, { - name: "Malformed JSON", - fileContent: `{"files":{`, - expectError: true, - expectedValue: &JSONArtifactRegistry{ - GeneratedAt: time.Time{}, - ArtifactMap: nil, + name: "Malformed JSON", + given: given{ + jsonFileContent: `{"files":{`, + }, + then: then{ + expectedError: fmt.Errorf("failed loading artifact file registry: unexpected end of JSON input"), + expectedValue: &JSONArtifactRegistry{ + GeneratedAt: time.Time{}, + ArtifactMap: nil, + }, }, }, } @@ -48,47 +68,54 @@ func TestJSONArtifactRegistry_Load(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Setup filePath := "test_registry.json" - err := os.WriteFile(filePath, []byte(tt.fileContent), 0644) - if err != nil { - t.Fatalf("Failed to write test file: %v", err) - } - defer os.Remove(filePath) - registry := &JSONArtifactRegistry{ - FilePath: filePath, + fs := &mockFileSystem{} + err := fs.WriteFile(filePath, []byte(tt.given.jsonFileContent), os.ModePerm) + require.NoError(t, err) + + registry := NewJSONArtifactRegistry(filePath, fs) + registry.CurrentTimeProvider = func() time.Time { + return time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) } // Act err = registry.Load() // Assert - if tt.expectError { + if tt.then.expectedError != nil { assert.Error(t, err) } else { assert.NoError(t, err) - assert.Equal(t, tt.expectedValue, registry) + assert.Equal(t, tt.then.expectedValue.GeneratedAt, registry.GeneratedAt) } }) } } func TestJSONArtifactRegistry_Save(t *testing.T) { + type then struct { + expectedJSON string + expectedError error + } + tests := []struct { - name string - initialState *JSONArtifactRegistry - expectedJSON string + name string + given *JSONArtifactRegistry + then then }{ { name: "Successful Save", - initialState: &JSONArtifactRegistry{ + given: &JSONArtifactRegistry{ ArtifactMap: map[string]*JSONArtifactRegistryProcessor{ "processor1": { Artifacts: []string{"file1.txt"}, }, }, }, - expectedJSON: `{ - "generatedAt": "0001-01-01T00:00:00Z", + then: then{ + expectedError: nil, + expectedJSON: `{ + "generatedAt": "2024-01-01T00:00:00Z", "files": { "processor1": { "files": [ @@ -97,14 +124,17 @@ func TestJSONArtifactRegistry_Save(t *testing.T) { } } }`, + }, }, { - name: "Empty Registry", - initialState: &JSONArtifactRegistry{}, - expectedJSON: `{ - "generatedAt": "0001-01-01T00:00:00Z", - "files": {} + name: "Empty Registry", + given: &JSONArtifactRegistry{}, + then: then{ + expectedJSON: `{ + "generatedAt": "2024-01-01T00:00:00Z", + "files": null }`, + }, }, } @@ -112,21 +142,23 @@ func TestJSONArtifactRegistry_Save(t *testing.T) { t.Run(tt.name, func(t *testing.T) { // Setup filePath := "test_registry.json" - registry := &JSONArtifactRegistry{ - FilePath: filePath, + fs := &mockFileSystem{} + registry := NewJSONArtifactRegistry(filePath, fs) + registry.ArtifactMap = tt.given.ArtifactMap + registry.CurrentTimeProvider = func() time.Time { + return time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) } - registry.ArtifactMap = tt.initialState.ArtifactMap - // Act err := registry.Save() - // Assert - assert.NoError(t, err) + if tt.then.expectedError != nil { + require.ErrorIs(t, err, tt.then.expectedError) + } else { + require.NoError(t, err) + } - // Read back and verify - data, err := os.ReadFile(filePath) - assert.NoError(t, err) - assert.JSONEq(t, tt.expectedJSON, string(data)) + actualJSON, err := fs.ReadFile(filePath) + assert.JSONEq(t, tt.then.expectedJSON, string(actualJSON)) }) } } diff --git a/source.go b/source.go index 7603193..d5c9b11 100644 --- a/source.go +++ b/source.go @@ -46,6 +46,7 @@ type FileSystem interface { StatPath(location string) (os.FileInfo, error) WalkDir(dirPath string, f func(path string, d fs.DirEntry, err error) error) error ReadFile(filePath string) ([]byte, error) + WriteFile(filePath string, data []byte, perm fs.FileMode) error } var _ FileSystem = LocalFileSystem{} @@ -53,6 +54,10 @@ var _ FileSystem = LocalFileSystem{} // LocalFileSystem is an implementation of a FileSystem that works on the local file system where this program is running. type LocalFileSystem struct{} +func (l LocalFileSystem) WriteFile(filePath string, data []byte, perm fs.FileMode) error { + return os.WriteFile(filePath, data, perm) +} + func (l LocalFileSystem) ReadFile(filePath string) ([]byte, error) { return os.ReadFile(filePath) } diff --git a/source_test.go b/source_test.go index 42b40ea..17e37cc 100644 --- a/source_test.go +++ b/source_test.go @@ -249,14 +249,30 @@ func (m mockFileInfo) Mode() os.FileMode { return m.mode } func (m mockFileInfo) IsDir() bool { return m.isDir } func (m mockFileInfo) Sys() interface{} { return nil } +var _ FileSystem = (*mockFileSystem)(nil) + type mockFileSystem struct { files map[string][]byte dirs map[string]bool - absErr error - statErr error - walkDirErr error - readFileErr error + absErr error + statErr error + walkDirErr error + readFileErr error + writeFileErr error +} + +func (m *mockFileSystem) WriteFile(filePath string, data []byte, _ fs.FileMode) error { + if m.writeFileErr != nil { + return m.writeFileErr + } + + if m.files == nil { + m.files = map[string][]byte{} + } + + m.files[filePath] = data + return nil } func (m *mockFileSystem) Abs(location string) (string, error) {