Skip to content

Commit

Permalink
Fix [Golang] [Package] [Module] Main Command & FileSystem (#22)
Browse files Browse the repository at this point in the history
* Fix [Golang] [Package] [Module] Main Command & FileSystem

- [+] feat(file_system.go): add ReadFile method to FileSystem interface
- [+] fix(file_system.go): change receiver name from fs to rfs in Create and WriteFile methods
- [+] feat(file_system.go): implement ReadFile method in RealFileSystem
- [+] feat(file_system_mock.go): add Files map and ReadFile method to MockFileSystem
- [+] feat(file_system_mock.go): add NewMockFileSystem function
- [+] fix(main.go): change repairJSONData signature to accept FileSystem interface
- [+] fix(main.go): pass RealFileSystem instance to repairJSONData
- [+] fix(main.go): change saveToFile signature to accept FileSystem interface
- [+] fix(main.go): pass FileSystem interface instance to saveToFile
- [+] fix(main_test.go): change mockFS variable to use NewMockFileSystem function
- [+] fix(main_test.go): change repairedPath assertion to include file extension
- [+] fix(main_test.go): change repairedPath assertion to include file extension
- [+] fix(main_test.go): change WriteFileCalled assertion to check if true
- [+] fix(main_test.go): change WriteFilePath assertion to

* Chore CI Gopher Unit Testing 🧪

- [+] chore(GopherCI.yml): add support for Windows in the matrix for both jobs

* Fix [Golang] [Module] Main Command Repairing Data

- [+] fix(main.go): change the repaired file path to prepend "repaired_" to the original file name
- [+] fix(main_test.go): change the name of the repaired file to "repaired_testing.json"
  • Loading branch information
H0llyW00dzZ authored Dec 9, 2023
1 parent c190e0f commit f4d3de9
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 55 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/GopherCI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ jobs:
name: Run Gopher Unit Testing on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
# Temporarily removed `windows-latest` due to some issues with Windows. However, my code is correct lmao.
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ['1.21.5']

steps:
Expand All @@ -51,7 +50,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ['1.21.5']

steps:
Expand Down
14 changes: 11 additions & 3 deletions filesystem/file_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
// FileSystem is an interface that abstracts file system operations such as creating
// files, writing to files, and retrieving file information. This allows for implementations
// that can interact with the file system or provide mock functionality for testing purposes.
// FileSystem interface now includes ReadFile method.
type FileSystem interface {
Create(name string) (*os.File, error)
WriteFile(name string, data []byte, perm fs.FileMode) error
ReadFile(name string) ([]byte, error) // Added ReadFile method
Stat(name string) (os.FileInfo, error)
}

Expand All @@ -23,20 +25,26 @@ type RealFileSystem struct{}

// Create creates a new file with the given name.
// It wraps the os.Create function and returns a pointer to the created file along with any error encountered.
func (fs RealFileSystem) Create(name string) (*os.File, error) {
func (rfs RealFileSystem) Create(name string) (*os.File, error) {
return os.Create(name)
}

// WriteFile writes data to a file named by filename.
// If the file does not exist, WriteFile creates it with permissions perm;
// otherwise WriteFile truncates it before writing.
func (fs RealFileSystem) WriteFile(name string, data []byte, perm fs.FileMode) error {
func (rfs RealFileSystem) WriteFile(name string, data []byte, perm fs.FileMode) error {
return os.WriteFile(name, data, perm)
}

// ReadFile reads the named file and returns the contents.
// It wraps the os.ReadFile function.
func (rfs RealFileSystem) ReadFile(name string) ([]byte, error) {
return os.ReadFile(name)
}

// Stat returns the FileInfo structure describing the file named by the given name.
// It wraps the os.Stat function and returns the FileInfo and any error encountered, for instance,
// if the file does not exist.
func (fs RealFileSystem) Stat(name string) (os.FileInfo, error) {
func (rfs RealFileSystem) Stat(name string) (os.FileInfo, error) {
return os.Stat(name)
}
29 changes: 19 additions & 10 deletions filesystem/file_system_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ type MockFileSystem struct {
WriteFilePath string // Track the path provided to WriteFile.
WriteFileData []byte // Optionally track the data provided to WriteFile.
WriteFilePerm fs.FileMode // Optionally track the file permissions provided to WriteFile.
Files map[string][]byte // Files maps file names to file contents.
ReadFileCalled bool // this field to track if ReadFile has been caled.
ReadFileData []byte // Optionally track the data provided to ReadFile.
ReadFileErr error // Optionally track the error provider to ReadFile.
}

// MockExporter is a mock implementation of the exporter.Exporter interface for testing purposes.
Expand All @@ -44,6 +48,13 @@ func (m *MockExporter) ConvertSessionsToCSV(ctx context.Context, sessions []expo
return m.ErrToReturn
}

// NewMockFileSystem creates a new instance of MockFileSystem with initialized internal structures.
func NewMockFileSystem() *MockFileSystem {
return &MockFileSystem{
FilesCreated: make(map[string]*bytes.Buffer),
}
}

// Stat returns the FileInfo for the given file name if it exists in the mock file system.
// If the file does not exist, it returns an error to simulate the os.Stat behavior.
func (m *MockFileSystem) Stat(name string) (fs.FileInfo, error) {
Expand All @@ -55,21 +66,19 @@ func (m *MockFileSystem) Stat(name string) (fs.FileInfo, error) {
}

// Create simulates the creation of a file by creating a new buffer in the FilesCreated map.
// It returns a dummy *os.File object and nil error to mimic the os.Create function's behavior in tests.
// It returns nil to signify that the file is not meant for actual I/O operations.
func (m *MockFileSystem) Create(name string) (*os.File, error) {
if m.FilesCreated == nil {
m.FilesCreated = make(map[string]*bytes.Buffer)
}
// Now Safe to create a new buffer for the file.
m.FilesCreated[name] = new(bytes.Buffer)
// Return a dummy file object with a fake file descriptor.
return os.NewFile(0, name), nil
// Return nil to indicate that the file is not meant for I/O operations.
return nil, nil
}

// ReadFile simulates reading the content of a file from the FilesCreated map.
// ReadFile simulates reading the content of a file from the Files map.
// If the file exists, it returns the content as a byte slice; otherwise, it returns an error.
func (m *MockFileSystem) ReadFile(name string) ([]byte, error) {
if content, ok := m.FilesCreated[name]; ok {
return content.Bytes(), nil
if content, ok := m.Files[name]; ok {
return content, nil
}
return nil, fs.ErrNotExist
}
Expand All @@ -78,7 +87,7 @@ func (m *MockFileSystem) ReadFile(name string) ([]byte, error) {
// It creates a new buffer with the provided data, simulating a successful write operation.
func (m *MockFileSystem) WriteFile(name string, data []byte, perm fs.FileMode) error {
if m.FilesCreated == nil {
m.FilesCreated = make(map[string]*bytes.Buffer)
m.Files = make(map[string][]byte)
}
m.FilesCreated[name] = bytes.NewBuffer(data)
m.WriteFileCalled = true // Set this to true when WriteFile is called.
Expand Down
52 changes: 24 additions & 28 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ func main() {
}

if strings.ToLower(repairData) == "yes" {
// Attempt to repair the provided JSON data.
// Pass the context to the repairJSONData function.
newFilePath, err := repairJSONData(ctx, jsonFilePath)
// Create an instance of your real file system implementation.
realFS := &filesystem.RealFileSystem{}
// Pass the real file system instance when calling repairJSONData.
newFilePath, err := repairJSONData(realFS, ctx, jsonFilePath)
if err != nil {
fmt.Printf("Error: %s\n", err)
os.Exit(1)
Expand Down Expand Up @@ -195,12 +196,12 @@ func processDatasetOption(fs filesystem.FileSystem, ctx context.Context, reader
os.Exit(1)
}
}
saveToFile(ctx, reader, datasetOutput, "dataset")
saveToFile(fs, ctx, reader, datasetOutput, "dataset")
}

// saveToFile prompts the user to save the provided content to a file of the specified type.
// This function now also accepts a context, allowing file operations to be cancelable.
func saveToFile(ctx context.Context, reader *bufio.Reader, content string, fileType string) {
func saveToFile(fs filesystem.FileSystem, ctx context.Context, reader *bufio.Reader, content string, fileType string) {
saveOutput, err := promptForInput(ctx, reader, fmt.Sprintf("Do you want to save the output to a file? (yes/no)\n"))
if err != nil {
if err == context.Canceled || err == io.EOF {
Expand All @@ -215,10 +216,8 @@ func saveToFile(ctx context.Context, reader *bufio.Reader, content string, fileT
}

if strings.ToLower(saveOutput) == "yes" {
// Create an instance of RealFileSystem to pass to writeContentToFile
realFS := filesystem.RealFileSystem{}
// Now pass this instance as the first argument to writeContentToFile
err = writeContentToFile(realFS, ctx, reader, content, fileType)
// Now pass the provided file system interface instance to writeContentToFile
err = writeContentToFile(fs, ctx, reader, content, fileType)
if err != nil {
// Handle the error
fmt.Printf("Error writing file: %s\n", err)
Expand All @@ -229,33 +228,30 @@ func saveToFile(ctx context.Context, reader *bufio.Reader, content string, fileT

// repairJSONData attempts to repair the JSON data at the provided file path and returns the path to the repaired file.
// This function is not context-aware as it performs a single, typically quick operation.
func repairJSONData(ctx context.Context, jsonFilePath string) (string, error) {
// Check if the context is already done before starting the operation.
select {
case <-ctx.Done():
return "", ctx.Err()
default:
// Continue if the context is not cancelled.
}

oldJSONBytes, err := os.ReadFile(jsonFilePath)
func repairJSONData(fs filesystem.FileSystem, ctx context.Context, jsonFilePath string) (string, error) {
// Read the broken JSON data using the file system interface
data, err := fs.ReadFile(jsonFilePath)
if err != nil {
return "", err
return "", err // Handle the error properly
}

// Simulate a context-aware operation (since os.ReadFile is not context-aware).
newJSONBytes, err := repairdata.RepairSessionData(oldJSONBytes)
if err != nil {
return "", err
// Repair the JSON data (this is where you fix the JSON string)
repairedData, repairErr := repairdata.RepairSessionData(data)
if repairErr != nil {
return "", repairErr // Handle the error properly
}

newFilePath := strings.TrimSuffix(jsonFilePath, ".json") + "_repaired.json"
err = os.WriteFile(newFilePath, newJSONBytes, 0644)
// Define the path for the repaired file
repairedPath := "repaired_" + jsonFilePath

// Write the repaired JSON data using the file system interface
err = fs.WriteFile(repairedPath, repairedData, 0644)
if err != nil {
return "", err
return "", err // Handle the error properly
}

return newFilePath, nil
// Return the path to the repaired file
return repairedPath, nil
}

// executeCSVConversion handles the CSV conversion process based on the user-selected format option.
Expand Down
29 changes: 18 additions & 11 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestProcessCSVOption(t *testing.T) {
defer cancel()

// Create an instance of the mock file system
mockFS := &filesystem.MockFileSystem{}
mockFS := filesystem.NewMockFileSystem()

// Redirect stdout to a pipe where we can capture the output of the function.
r, w, _ := os.Pipe()
Expand Down Expand Up @@ -129,8 +129,9 @@ func TestPromptForInputCancellation(t *testing.T) {
}()

_, err := promptForInput(ctx, reader, "Enter input: ")
if err != nil || err == context.Canceled || err == io.EOF {
t.Fatalf("Expected context.Canceled error, got: %v", err)
// testing for windows now
if err != context.Canceled && err != nil && err != io.EOF {
t.Fatalf("Expected context.Canceled or io.EOF error, got: %v", err)
}
}

Expand All @@ -156,14 +157,15 @@ func TestLoadIncorrectJson(t *testing.T) {
func TestRepairJSONDataFromFile(t *testing.T) {
// Define the path to your testing.json file containing broken JSON for the test.
brokenJSONPath := "testing.json"
realFS := &filesystem.RealFileSystem{}

// Test successful repair of the JSON data.
t.Run("SuccessfulRepair", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

// Attempt to repair the JSON data and expect a valid file path to the repaired JSON.
repairedPath, err := repairJSONData(ctx, brokenJSONPath)
repairedPath, err := repairJSONData(realFS, ctx, brokenJSONPath)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
Expand All @@ -181,7 +183,7 @@ func TestRepairJSONDataFromFile(t *testing.T) {
}

// Clean up by removing the repaired file after the test.
defer os.Remove("testing_repaired.json")
defer os.Remove("repaired_testing.json")
})

// Test repair function with a non-existent file path.
Expand All @@ -190,7 +192,7 @@ func TestRepairJSONDataFromFile(t *testing.T) {
defer cancel()

// Attempt to repair JSON data from a non-existent file and expect an error.
_, err := repairJSONData(ctx, "nonexistent.json")
_, err := repairJSONData(realFS, ctx, "nonexistent.json")
if err == nil {
t.Errorf("Expected an error for a non-existent file path, got nil")
}
Expand All @@ -214,19 +216,24 @@ func TestWriteContentToFile(t *testing.T) {
content := "Test content"

// Create a mock file system.
mockFS := &filesystem.MockFileSystem{}
mockFS := filesystem.NewMockFileSystem()

// Invoke the function to write content to a file with "dataset" as the file type.
writeContentToFile(mockFS, ctx, reader, content, "dataset")

// Verify that the file with the expected name was created in the mock file system.
// Verify that the WriteFile method was called on the mock file system.
if !mockFS.WriteFileCalled {
t.Errorf("WriteFile was not called")
}

// Verify that the WriteFile method was called with the correct parameters.
expectedFileName := "testing.json"
if _, ok := mockFS.FilesCreated[expectedFileName]; !ok {
t.Errorf("Expected file %s to be created, but it does not exist", expectedFileName)
if mockFS.WriteFilePath != expectedFileName {
t.Errorf("WriteFile was called with the wrong file name: got %v, want %v", mockFS.WriteFilePath, expectedFileName)
}

// Check the content written to the mock file system.
if string(mockFS.FilesCreated[expectedFileName].Bytes()) != content {
t.Errorf("Expected file content %q, got %q", content, string(mockFS.FilesCreated[expectedFileName].Bytes()))
t.Errorf("WriteFile was called with the wrong content: got %v, want %v", string(mockFS.FilesCreated[expectedFileName].Bytes()), content)
}
}

0 comments on commit f4d3de9

Please sign in to comment.