From f4d3de98646cb911056ceb5e384cf1a52c172d1d Mon Sep 17 00:00:00 2001 From: H0llyW00dzZ Date: Sat, 9 Dec 2023 22:31:53 +0700 Subject: [PATCH] Fix [Golang] [Package] [Module] Main Command & FileSystem (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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" --- .github/workflows/GopherCI.yml | 5 ++-- filesystem/file_system.go | 14 +++++++-- filesystem/file_system_mock.go | 29 ++++++++++++------- main.go | 52 ++++++++++++++++------------------ main_test.go | 29 ++++++++++++------- 5 files changed, 74 insertions(+), 55 deletions(-) diff --git a/.github/workflows/GopherCI.yml b/.github/workflows/GopherCI.yml index 08e575e..340e2f9 100644 --- a/.github/workflows/GopherCI.yml +++ b/.github/workflows/GopherCI.yml @@ -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: @@ -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: diff --git a/filesystem/file_system.go b/filesystem/file_system.go index cc05f31..8cd8b63 100644 --- a/filesystem/file_system.go +++ b/filesystem/file_system.go @@ -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) } @@ -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) } diff --git a/filesystem/file_system_mock.go b/filesystem/file_system_mock.go index a94381f..120a15c 100644 --- a/filesystem/file_system_mock.go +++ b/filesystem/file_system_mock.go @@ -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. @@ -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) { @@ -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 } @@ -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. diff --git a/main.go b/main.go index 38f5090..97992ba 100644 --- a/main.go +++ b/main.go @@ -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) @@ -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 { @@ -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) @@ -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. diff --git a/main_test.go b/main_test.go index bcd57e8..0ab1d46 100644 --- a/main_test.go +++ b/main_test.go @@ -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() @@ -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) } } @@ -156,6 +157,7 @@ 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) { @@ -163,7 +165,7 @@ func TestRepairJSONDataFromFile(t *testing.T) { 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) } @@ -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. @@ -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") } @@ -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) } }