diff --git a/internal/persistence/fileSaver.go b/internal/persistence/fileSaver.go new file mode 100644 index 0000000..31e4ca9 --- /dev/null +++ b/internal/persistence/fileSaver.go @@ -0,0 +1,62 @@ +package persistence + +import ( + "encoding/json" + "os" + + "github.com/StevanFreeborn/term-tier/internal/core" +) + +type FileSystem interface { + WriteFile(name string, data []byte, perm os.FileMode) error + ReadFile(name string) ([]byte, error) +} + +type osFileSystem struct{} + +func (osFileSystem) WriteFile(name string, data []byte, perm os.FileMode) error { + return os.WriteFile(name, data, perm) +} + +func (osFileSystem) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) +} + +type fileSaver struct { + fs FileSystem +} + +func NewFileSaver() core.Saver { + return &fileSaver{ + fs: osFileSystem{}, + } +} + +func NewFileSaverWithFS(fs FileSystem) core.Saver { + return &fileSaver{ + fs: fs, + } +} + +func (s *fileSaver) Save(filename string, state core.SaveState) error { + data, err := json.MarshalIndent(state, "", " ") + if err != nil { + return err + } + + return s.fs.WriteFile(filename, data, 0644) +} + +func (s *fileSaver) Load(filename string) (core.SaveState, error) { + data, err := s.fs.ReadFile(filename) + if err != nil { + return core.SaveState{}, err + } + + var state core.SaveState + if err := json.Unmarshal(data, &state); err != nil { + return core.SaveState{}, err + } + + return state, nil +} diff --git a/internal/persistence/fileSaver_test.go b/internal/persistence/fileSaver_test.go new file mode 100644 index 0000000..51acf94 --- /dev/null +++ b/internal/persistence/fileSaver_test.go @@ -0,0 +1,124 @@ +package persistence_test + +import ( + "errors" + "os" + "reflect" + "testing" + + "github.com/StevanFreeborn/term-tier/internal/core" + "github.com/StevanFreeborn/term-tier/internal/persistence" +) + +func TestFileSaver_Save(t *testing.T) { + t.Run("Successfully saves content to mock FS", func(t *testing.T) { + mock := newMockFS() + saver := persistence.NewFileSaverWithFS(mock) + + inputState := core.SaveState{} + + filename := "test_tierlist.json" + err := saver.Save(filename, inputState) + + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + if _, exists := mock.files[filename]; !exists { + t.Error("Expected file to be created in mock FS") + } + }) + + t.Run("Returns error on write failure", func(t *testing.T) { + mock := newMockFS() + mock.writeErr = errors.New("disk full") + saver := persistence.NewFileSaverWithFS(mock) + + err := saver.Save("fail.json", core.SaveState{}) + + if err == nil { + t.Error("Expected error due to write failure, got nil") + } + }) +} + +func TestFileSaver_Load(t *testing.T) { + t.Run("Successfully loads existing file", func(t *testing.T) { + mock := newMockFS() + filename := "data.json" + + validJSON := []byte(`{ + "tiers": [] + }`) + + mock.files[filename] = validJSON + + saver := persistence.NewFileSaverWithFS(mock) + result, err := saver.Load(filename) + + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if reflect.ValueOf(result).IsZero() && len(validJSON) == 0 { + t.Error("Loaded data does not match expected content") + } + }) + + t.Run("Returns error when file does not exist", func(t *testing.T) { + mock := newMockFS() + saver := persistence.NewFileSaverWithFS(mock) + + _, err := saver.Load("nonexistent.json") + + if err == nil { + t.Error("Expected error for missing file, got nil") + } + if !errors.Is(err, os.ErrNotExist) { + t.Errorf("Expected os.ErrNotExist, got %v", err) + } + }) + + t.Run("Returns error on invalid JSON", func(t *testing.T) { + mock := newMockFS() + mock.files["bad.json"] = []byte(`{ broken_json: `) + saver := persistence.NewFileSaverWithFS(mock) + + _, err := saver.Load("bad.json") + + if err == nil { + t.Error("Expected JSON unmarshal error, got nil") + } + }) +} + +type mockFS struct { + files map[string][]byte + writeErr error + readErr error +} + +func newMockFS() *mockFS { + return &mockFS{ + files: make(map[string][]byte), + } +} + +func (m *mockFS) WriteFile(name string, data []byte, perm os.FileMode) error { + if m.writeErr != nil { + return m.writeErr + } + m.files[name] = data + return nil +} + +func (m *mockFS) ReadFile(name string) ([]byte, error) { + if m.readErr != nil { + return nil, m.readErr + } + data, exists := m.files[name] + if !exists { + return nil, os.ErrNotExist + } + return data, nil +}