Files
AUTO-MAS-test/Go_Updater/install/manager_test.go
AoXuan 228e66315c feat(Go_Updater): 添加全新 Go 语言实现的自动更新器
- 新增多个源文件和目录,包括 app.rc、assets、build 脚本等
- 实现了与 MirrorChyan API 交互的客户端逻辑
- 添加了版本检查、更新检测和下载 URL 生成等功能
- 嵌入了配置模板和资源文件系统
- 提供了完整的构建和发布流程
2025-07-20 16:30:14 +08:00

1033 lines
29 KiB
Go

package install
import (
"archive/zip"
"fmt"
"os"
"path/filepath"
"testing"
)
func TestNewManager(t *testing.T) {
manager := NewManager()
if manager == nil {
t.Fatal("NewManager() returned nil")
}
if manager.tempDirs == nil {
t.Fatal("tempDirs slice not initialized")
}
}
func TestCreateTempDir(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
// Verify directory exists
if _, err := os.Stat(tempDir); os.IsNotExist(err) {
t.Fatalf("Temp directory was not created: %s", tempDir)
}
// Verify it's tracked
if len(manager.tempDirs) != 1 || manager.tempDirs[0] != tempDir {
t.Fatalf("Temp directory not properly tracked")
}
// Cleanup
defer manager.CleanupTempDir(tempDir)
}
func TestCleanupTempDir(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
// Create a test file in temp directory
testFile := filepath.Join(tempDir, "test.txt")
if err := os.WriteFile(testFile, []byte("test"), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Cleanup
err = manager.CleanupTempDir(tempDir)
if err != nil {
t.Fatalf("CleanupTempDir() failed: %v", err)
}
// Verify directory is removed
if _, err := os.Stat(tempDir); !os.IsNotExist(err) {
t.Fatalf("Temp directory was not removed: %s", tempDir)
}
// Verify it's no longer tracked
if len(manager.tempDirs) != 0 {
t.Fatalf("Temp directory still tracked after cleanup")
}
}
func TestExtractZip(t *testing.T) {
manager := NewManager()
// Create a temporary ZIP file for testing
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
zipPath := filepath.Join(tempDir, "test.zip")
extractDir := filepath.Join(tempDir, "extract")
// Create test ZIP file
if err := createTestZip(zipPath); err != nil {
t.Fatalf("Failed to create test ZIP: %v", err)
}
// Extract ZIP
err = manager.ExtractZip(zipPath, extractDir)
if err != nil {
t.Fatalf("ExtractZip() failed: %v", err)
}
// Verify extracted files
testFile := filepath.Join(extractDir, "test.txt")
if _, err := os.Stat(testFile); os.IsNotExist(err) {
t.Fatalf("Extracted file not found: %s", testFile)
}
// Verify file contents
content, err := os.ReadFile(testFile)
if err != nil {
t.Fatalf("Failed to read extracted file: %v", err)
}
expected := "Hello, World!"
if string(content) != expected {
t.Fatalf("File content mismatch. Expected: %s, Got: %s", expected, string(content))
}
// Verify directory structure
subDir := filepath.Join(extractDir, "subdir")
if _, err := os.Stat(subDir); os.IsNotExist(err) {
t.Fatalf("Extracted subdirectory not found: %s", subDir)
}
subFile := filepath.Join(subDir, "sub.txt")
if _, err := os.Stat(subFile); os.IsNotExist(err) {
t.Fatalf("Extracted subdirectory file not found: %s", subFile)
}
}
func TestExtractZipInvalidPath(t *testing.T) {
manager := NewManager()
// Test with non-existent ZIP file
err := manager.ExtractZip("nonexistent.zip", "dest")
if err == nil {
t.Fatal("ExtractZip() should fail with non-existent ZIP file")
}
}
func TestExtractZipDirectoryTraversal(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
zipPath := filepath.Join(tempDir, "malicious.zip")
extractDir := filepath.Join(tempDir, "extract")
// Create ZIP with directory traversal attempt
if err := createMaliciousZip(zipPath); err != nil {
t.Fatalf("Failed to create malicious ZIP: %v", err)
}
// Extract should fail or sanitize the path
err = manager.ExtractZip(zipPath, extractDir)
if err != nil {
// This is expected behavior - the extraction should fail
return
}
// If extraction succeeded, verify no files were created outside extract dir
parentDir := filepath.Dir(extractDir)
maliciousFile := filepath.Join(parentDir, "malicious.txt")
if _, err := os.Stat(maliciousFile); !os.IsNotExist(err) {
t.Fatal("Directory traversal attack succeeded - malicious file created outside extract directory")
}
}
// Helper function to create a test ZIP file
func createTestZip(zipPath string) error {
file, err := os.Create(zipPath)
if err != nil {
return err
}
defer file.Close()
writer := zip.NewWriter(file)
defer writer.Close()
// Add a test file
f1, err := writer.Create("test.txt")
if err != nil {
return err
}
_, err = f1.Write([]byte("Hello, World!"))
if err != nil {
return err
}
// Add a subdirectory and file
f2, err := writer.Create("subdir/sub.txt")
if err != nil {
return err
}
_, err = f2.Write([]byte("Subdirectory file"))
if err != nil {
return err
}
return nil
}
func TestProcessChanges(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Test with valid changes.json
changesPath := filepath.Join(tempDir, "changes.json")
changesData := `{
"deleted": ["old_file.txt", "deprecated/module.dll"],
"added": ["new_file.txt", "features/new_module.dll"],
"modified": ["main.exe", "config.ini"]
}`
if err := os.WriteFile(changesPath, []byte(changesData), 0644); err != nil {
t.Fatalf("Failed to create test changes.json: %v", err)
}
changes, err := manager.ProcessChanges(changesPath)
if err != nil {
t.Fatalf("ProcessChanges() failed: %v", err)
}
// Verify parsed data
if len(changes.Deleted) != 2 {
t.Fatalf("Expected 2 deleted files, got %d", len(changes.Deleted))
}
if changes.Deleted[0] != "old_file.txt" {
t.Fatalf("Expected first deleted file to be 'old_file.txt', got '%s'", changes.Deleted[0])
}
if len(changes.Added) != 2 {
t.Fatalf("Expected 2 added files, got %d", len(changes.Added))
}
if len(changes.Modified) != 2 {
t.Fatalf("Expected 2 modified files, got %d", len(changes.Modified))
}
}
func TestProcessChangesNonExistent(t *testing.T) {
manager := NewManager()
// Test with non-existent changes.json
changes, err := manager.ProcessChanges("nonexistent.json")
if err != nil {
t.Fatalf("ProcessChanges() should not fail with non-existent file: %v", err)
}
// Should return empty changes
if len(changes.Deleted) != 0 || len(changes.Added) != 0 || len(changes.Modified) != 0 {
t.Fatalf("Expected empty changes for non-existent file")
}
}
func TestProcessChangesInvalidJSON(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Test with invalid JSON
changesPath := filepath.Join(tempDir, "invalid.json")
invalidData := `{"deleted": ["file1.txt", "file2.txt"` // Missing closing bracket
if err := os.WriteFile(changesPath, []byte(invalidData), 0644); err != nil {
t.Fatalf("Failed to create invalid JSON file: %v", err)
}
_, err = manager.ProcessChanges(changesPath)
if err == nil {
t.Fatal("ProcessChanges() should fail with invalid JSON")
}
}
func TestHandleRunningProcess(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create a test executable file
testExe := filepath.Join(tempDir, "test.exe")
if err := os.WriteFile(testExe, []byte("test executable"), 0755); err != nil {
t.Fatalf("Failed to create test executable: %v", err)
}
// Test handling non-existent process
err = manager.HandleRunningProcess("nonexistent.exe")
if err != nil {
t.Fatalf("HandleRunningProcess() should not fail with non-existent process: %v", err)
}
}
func TestDeleteMarkedFiles(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create test files to be deleted
testFile1 := filepath.Join(tempDir, "file1.txt")
testFile2 := filepath.Join(tempDir, "file2.txt")
if err := os.WriteFile(testFile1, []byte("test1"), 0644); err != nil {
t.Fatalf("Failed to create test file1: %v", err)
}
if err := os.WriteFile(testFile2, []byte("test2"), 0644); err != nil {
t.Fatalf("Failed to create test file2: %v", err)
}
// Create marker files
marker1 := testFile1 + ".delete_on_restart"
marker2 := testFile2 + ".delete_on_restart"
if err := os.WriteFile(marker1, []byte(testFile1), 0644); err != nil {
t.Fatalf("Failed to create marker file1: %v", err)
}
if err := os.WriteFile(marker2, []byte(testFile2), 0644); err != nil {
t.Fatalf("Failed to create marker file2: %v", err)
}
// Delete marked files
err = manager.DeleteMarkedFiles(tempDir)
if err != nil {
t.Fatalf("DeleteMarkedFiles() failed: %v", err)
}
// Verify files are deleted
if _, err := os.Stat(testFile1); !os.IsNotExist(err) {
t.Fatalf("Test file1 should be deleted")
}
if _, err := os.Stat(testFile2); !os.IsNotExist(err) {
t.Fatalf("Test file2 should be deleted")
}
// Verify marker files are deleted
if _, err := os.Stat(marker1); !os.IsNotExist(err) {
t.Fatalf("Marker file1 should be deleted")
}
if _, err := os.Stat(marker2); !os.IsNotExist(err) {
t.Fatalf("Marker file2 should be deleted")
}
}
func TestApplyUpdate(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create source and target directories
sourceDir := filepath.Join(tempDir, "source")
targetDir := filepath.Join(tempDir, "target")
if err := os.MkdirAll(sourceDir, 0755); err != nil {
t.Fatalf("Failed to create source directory: %v", err)
}
if err := os.MkdirAll(targetDir, 0755); err != nil {
t.Fatalf("Failed to create target directory: %v", err)
}
// Create test files in source directory
newFile := filepath.Join(sourceDir, "new_file.txt")
modifiedFile := filepath.Join(sourceDir, "modified_file.txt")
if err := os.WriteFile(newFile, []byte("new content"), 0644); err != nil {
t.Fatalf("Failed to create new file: %v", err)
}
if err := os.WriteFile(modifiedFile, []byte("updated content"), 0644); err != nil {
t.Fatalf("Failed to create modified file: %v", err)
}
// Create existing files in target directory
existingModified := filepath.Join(targetDir, "modified_file.txt")
existingDeleted := filepath.Join(targetDir, "deleted_file.txt")
if err := os.WriteFile(existingModified, []byte("old content"), 0644); err != nil {
t.Fatalf("Failed to create existing modified file: %v", err)
}
if err := os.WriteFile(existingDeleted, []byte("to be deleted"), 0644); err != nil {
t.Fatalf("Failed to create file to be deleted: %v", err)
}
// Define changes
changes := &ChangesInfo{
Added: []string{"new_file.txt"},
Modified: []string{"modified_file.txt"},
Deleted: []string{"deleted_file.txt"},
}
// Apply update
err = manager.ApplyUpdate(sourceDir, targetDir, changes)
if err != nil {
t.Fatalf("ApplyUpdate() failed: %v", err)
}
// Verify new file was added
newTargetFile := filepath.Join(targetDir, "new_file.txt")
if _, err := os.Stat(newTargetFile); os.IsNotExist(err) {
t.Fatalf("New file was not added to target directory")
}
// Verify modified file was updated
content, err := os.ReadFile(existingModified)
if err != nil {
t.Fatalf("Failed to read modified file: %v", err)
}
if string(content) != "updated content" {
t.Fatalf("Modified file content incorrect. Expected: 'updated content', Got: '%s'", string(content))
}
// Verify deleted file was removed
if _, err := os.Stat(existingDeleted); !os.IsNotExist(err) {
t.Fatalf("Deleted file still exists")
}
}
func TestApplyUpdateWithRollback(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create source and target directories
sourceDir := filepath.Join(tempDir, "source")
targetDir := filepath.Join(tempDir, "target")
if err := os.MkdirAll(sourceDir, 0755); err != nil {
t.Fatalf("Failed to create source directory: %v", err)
}
if err := os.MkdirAll(targetDir, 0755); err != nil {
t.Fatalf("Failed to create target directory: %v", err)
}
// Create existing file in target directory
existingFile := filepath.Join(targetDir, "existing_file.txt")
originalContent := "original content"
if err := os.WriteFile(existingFile, []byte(originalContent), 0644); err != nil {
t.Fatalf("Failed to create existing file: %v", err)
}
// Create a source file that will cause a copy failure by making target read-only
sourceFile := filepath.Join(sourceDir, "existing_file.txt")
if err := os.WriteFile(sourceFile, []byte("new content"), 0644); err != nil {
t.Fatalf("Failed to create source file: %v", err)
}
// Make target directory read-only to cause copy failure
readOnlyDir := filepath.Join(targetDir, "readonly")
if err := os.MkdirAll(readOnlyDir, 0755); err != nil {
t.Fatalf("Failed to create readonly directory: %v", err)
}
// Create a file in readonly directory that we'll try to modify
readOnlyFile := filepath.Join(readOnlyDir, "readonly_file.txt")
if err := os.WriteFile(readOnlyFile, []byte("readonly content"), 0644); err != nil {
t.Fatalf("Failed to create readonly file: %v", err)
}
// Create source file for readonly file
sourceReadOnlyFile := filepath.Join(sourceDir, "readonly", "readonly_file.txt")
if err := os.MkdirAll(filepath.Dir(sourceReadOnlyFile), 0755); err != nil {
t.Fatalf("Failed to create source readonly directory: %v", err)
}
if err := os.WriteFile(sourceReadOnlyFile, []byte("new readonly content"), 0644); err != nil {
t.Fatalf("Failed to create source readonly file: %v", err)
}
// Make the readonly directory read-only (Windows specific)
if err := os.Chmod(readOnlyDir, 0444); err != nil {
t.Fatalf("Failed to make directory read-only: %v", err)
}
// Restore permissions after test
defer func() {
os.Chmod(readOnlyDir, 0755)
os.RemoveAll(readOnlyDir)
}()
// Define changes that will cause failure due to read-only directory
changes := &ChangesInfo{
Modified: []string{"existing_file.txt", "readonly/readonly_file.txt"},
}
// Apply update (should fail and rollback)
err = manager.ApplyUpdate(sourceDir, targetDir, changes)
if err == nil {
// On some systems, the read-only test might not work as expected
// Let's just verify the update completed successfully in this case
t.Log("Update completed successfully (read-only test may not work on this system)")
return
}
// Verify rollback occurred - original file should be restored
content, err := os.ReadFile(existingFile)
if err != nil {
t.Fatalf("Failed to read file after rollback: %v", err)
}
if string(content) != originalContent {
t.Fatalf("Rollback failed. Expected: '%s', Got: '%s'", originalContent, string(content))
}
}
func TestCopyFileWithDirs(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create source file
srcFile := filepath.Join(tempDir, "source.txt")
content := "test content"
if err := os.WriteFile(srcFile, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create source file: %v", err)
}
// Copy to destination with nested directories
dstFile := filepath.Join(tempDir, "nested", "dir", "destination.txt")
err = manager.copyFileWithDirs(srcFile, dstFile)
if err != nil {
t.Fatalf("copyFileWithDirs() failed: %v", err)
}
// Verify file was copied
if _, err := os.Stat(dstFile); os.IsNotExist(err) {
t.Fatalf("Destination file was not created")
}
// Verify content
dstContent, err := os.ReadFile(dstFile)
if err != nil {
t.Fatalf("Failed to read destination file: %v", err)
}
if string(dstContent) != content {
t.Fatalf("File content mismatch. Expected: '%s', Got: '%s'", content, string(dstContent))
}
// Verify parent directories were created
parentDir := filepath.Join(tempDir, "nested", "dir")
if _, err := os.Stat(parentDir); os.IsNotExist(err) {
t.Fatalf("Parent directories were not created")
}
}
// Helper function to create a malicious ZIP file with directory traversal
func createMaliciousZip(zipPath string) error {
file, err := os.Create(zipPath)
if err != nil {
return err
}
defer file.Close()
writer := zip.NewWriter(file)
defer writer.Close()
// Add a file with directory traversal path
f1, err := writer.Create("../malicious.txt")
if err != nil {
return err
}
_, err = f1.Write([]byte("This should not be extracted outside the target directory"))
if err != nil {
return err
}
return nil
}
func TestCleanupAllTempDirs(t *testing.T) {
manager := NewManager()
// Create multiple temp directories
tempDir1, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("Failed to create temp dir 1: %v", err)
}
tempDir2, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("Failed to create temp dir 2: %v", err)
}
tempDir3, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("Failed to create temp dir 3: %v", err)
}
// Verify all directories exist
for _, dir := range []string{tempDir1, tempDir2, tempDir3} {
if _, err := os.Stat(dir); os.IsNotExist(err) {
t.Fatalf("Temp directory should exist: %s", dir)
}
}
// Verify manager is tracking all directories
if len(manager.tempDirs) != 3 {
t.Fatalf("Expected 3 tracked temp dirs, got %d", len(manager.tempDirs))
}
// Cleanup all temp directories
err = manager.CleanupAllTempDirs()
if err != nil {
t.Fatalf("CleanupAllTempDirs failed: %v", err)
}
// Verify all directories are removed
for _, dir := range []string{tempDir1, tempDir2, tempDir3} {
if _, err := os.Stat(dir); !os.IsNotExist(err) {
t.Fatalf("Temp directory should be removed: %s", dir)
}
}
// Verify manager is no longer tracking directories
if len(manager.tempDirs) != 0 {
t.Fatalf("Expected 0 tracked temp dirs after cleanup, got %d", len(manager.tempDirs))
}
}
func TestExtractZipWithNestedDirectories(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
zipPath := filepath.Join(tempDir, "nested.zip")
extractDir := filepath.Join(tempDir, "extract")
// Create ZIP with nested directory structure
if err := createNestedZip(zipPath); err != nil {
t.Fatalf("Failed to create nested ZIP: %v", err)
}
// Extract ZIP
err = manager.ExtractZip(zipPath, extractDir)
if err != nil {
t.Fatalf("ExtractZip() failed: %v", err)
}
// Verify nested structure was created
expectedFiles := []string{
"level1/file1.txt",
"level1/level2/file2.txt",
"level1/level2/level3/file3.txt",
}
for _, expectedFile := range expectedFiles {
fullPath := filepath.Join(extractDir, expectedFile)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
t.Fatalf("Expected nested file not found: %s", expectedFile)
}
// Verify file content
content, err := os.ReadFile(fullPath)
if err != nil {
t.Fatalf("Failed to read nested file %s: %v", expectedFile, err)
}
expectedContent := fmt.Sprintf("Content of %s", filepath.Base(expectedFile))
if string(content) != expectedContent {
t.Fatalf("File content mismatch for %s. Expected: %s, Got: %s",
expectedFile, expectedContent, string(content))
}
}
}
func TestProcessChangesWithComplexStructure(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create complex changes.json with nested paths
changesPath := filepath.Join(tempDir, "complex_changes.json")
changesData := `{
"deleted": [
"old/legacy/file1.txt",
"deprecated/module.dll",
"temp/cache.dat"
],
"added": [
"new/features/feature1.dll",
"resources/icons/icon.png",
"config/new_settings.json"
],
"modified": [
"core/main.exe",
"lib/utils.dll",
"data/database.db"
]
}`
if err := os.WriteFile(changesPath, []byte(changesData), 0644); err != nil {
t.Fatalf("Failed to create complex changes.json: %v", err)
}
changes, err := manager.ProcessChanges(changesPath)
if err != nil {
t.Fatalf("ProcessChanges() failed: %v", err)
}
// Verify all changes were parsed correctly
expectedDeleted := []string{"old/legacy/file1.txt", "deprecated/module.dll", "temp/cache.dat"}
expectedAdded := []string{"new/features/feature1.dll", "resources/icons/icon.png", "config/new_settings.json"}
expectedModified := []string{"core/main.exe", "lib/utils.dll", "data/database.db"}
if len(changes.Deleted) != len(expectedDeleted) {
t.Fatalf("Expected %d deleted files, got %d", len(expectedDeleted), len(changes.Deleted))
}
for i, expected := range expectedDeleted {
if changes.Deleted[i] != expected {
t.Errorf("Deleted[%d]: expected %s, got %s", i, expected, changes.Deleted[i])
}
}
if len(changes.Added) != len(expectedAdded) {
t.Fatalf("Expected %d added files, got %d", len(expectedAdded), len(changes.Added))
}
for i, expected := range expectedAdded {
if changes.Added[i] != expected {
t.Errorf("Added[%d]: expected %s, got %s", i, expected, changes.Added[i])
}
}
if len(changes.Modified) != len(expectedModified) {
t.Fatalf("Expected %d modified files, got %d", len(expectedModified), len(changes.Modified))
}
for i, expected := range expectedModified {
if changes.Modified[i] != expected {
t.Errorf("Modified[%d]: expected %s, got %s", i, expected, changes.Modified[i])
}
}
}
func TestApplyUpdateWithNestedPaths(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create source and target directories
sourceDir := filepath.Join(tempDir, "source")
targetDir := filepath.Join(tempDir, "target")
if err := os.MkdirAll(sourceDir, 0755); err != nil {
t.Fatalf("Failed to create source directory: %v", err)
}
if err := os.MkdirAll(targetDir, 0755); err != nil {
t.Fatalf("Failed to create target directory: %v", err)
}
// Create nested source files
nestedFiles := map[string]string{
"level1/new_file.txt": "New file content",
"level1/level2/modified.txt": "Modified content",
"features/feature1/config.json": `{"enabled": true}`,
}
for filePath, content := range nestedFiles {
fullPath := filepath.Join(sourceDir, filePath)
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
t.Fatalf("Failed to create source directory for %s: %v", filePath, err)
}
if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create source file %s: %v", filePath, err)
}
}
// Create existing target files
existingFiles := map[string]string{
"level1/level2/modified.txt": "Old content",
"old/deprecated.txt": "To be deleted",
}
for filePath, content := range existingFiles {
fullPath := filepath.Join(targetDir, filePath)
if err := os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {
t.Fatalf("Failed to create target directory for %s: %v", filePath, err)
}
if err := os.WriteFile(fullPath, []byte(content), 0644); err != nil {
t.Fatalf("Failed to create target file %s: %v", filePath, err)
}
}
// Define changes with nested paths
changes := &ChangesInfo{
Added: []string{"level1/new_file.txt", "features/feature1/config.json"},
Modified: []string{"level1/level2/modified.txt"},
Deleted: []string{"old/deprecated.txt"},
}
// Apply update
err = manager.ApplyUpdate(sourceDir, targetDir, changes)
if err != nil {
t.Fatalf("ApplyUpdate() failed: %v", err)
}
// Verify added files
for _, addedFile := range changes.Added {
targetFile := filepath.Join(targetDir, addedFile)
if _, err := os.Stat(targetFile); os.IsNotExist(err) {
t.Fatalf("Added file not found: %s", addedFile)
}
// Verify content matches source
sourceFile := filepath.Join(sourceDir, addedFile)
sourceContent, _ := os.ReadFile(sourceFile)
targetContent, _ := os.ReadFile(targetFile)
if string(sourceContent) != string(targetContent) {
t.Fatalf("Content mismatch for added file %s", addedFile)
}
}
// Verify modified files
modifiedFile := filepath.Join(targetDir, "level1/level2/modified.txt")
content, err := os.ReadFile(modifiedFile)
if err != nil {
t.Fatalf("Failed to read modified file: %v", err)
}
if string(content) != "Modified content" {
t.Fatalf("Modified file content incorrect. Expected: 'Modified content', Got: '%s'", string(content))
}
// Verify deleted files
deletedFile := filepath.Join(targetDir, "old/deprecated.txt")
if _, err := os.Stat(deletedFile); !os.IsNotExist(err) {
t.Fatalf("Deleted file still exists: %s", deletedFile)
}
}
func TestMarkFileForDeletion(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
// Create a test file
testFile := filepath.Join(tempDir, "test.exe")
if err := os.WriteFile(testFile, []byte("test executable"), 0755); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
// Mark file for deletion
err = manager.markFileForDeletion(testFile)
if err != nil {
t.Fatalf("markFileForDeletion() failed: %v", err)
}
// Verify marker file was created
markerFile := testFile + ".delete_on_restart"
if _, err := os.Stat(markerFile); os.IsNotExist(err) {
t.Fatalf("Marker file was not created: %s", markerFile)
}
// Verify marker file contains correct path
content, err := os.ReadFile(markerFile)
if err != nil {
t.Fatalf("Failed to read marker file: %v", err)
}
if string(content) != testFile {
t.Fatalf("Marker file content incorrect. Expected: %s, Got: %s", testFile, string(content))
}
}
func TestIsFileInUse(t *testing.T) {
// Test with nil error
if isFileInUse(nil) {
t.Error("isFileInUse(nil) should return false")
}
// Test with regular error
regularErr := fmt.Errorf("regular error")
if isFileInUse(regularErr) {
t.Error("isFileInUse with regular error should return false")
}
// Test with file in use error message
fileInUseErr := fmt.Errorf("file is being used by another process")
if !isFileInUse(fileInUseErr) {
t.Error("isFileInUse with 'being used by another process' should return true")
}
// Test with access denied error message
accessDeniedErr := fmt.Errorf("access is denied")
if !isFileInUse(accessDeniedErr) {
t.Error("isFileInUse with 'access is denied' should return true")
}
}
func TestExtractFileEdgeCases(t *testing.T) {
manager := NewManager()
tempDir, err := manager.CreateTempDir()
if err != nil {
t.Fatalf("CreateTempDir() failed: %v", err)
}
defer manager.CleanupTempDir(tempDir)
zipPath := filepath.Join(tempDir, "edge_cases.zip")
extractDir := filepath.Join(tempDir, "extract")
// Create ZIP with edge cases
if err := createEdgeCaseZip(zipPath); err != nil {
t.Fatalf("Failed to create edge case ZIP: %v", err)
}
// Extract ZIP
err = manager.ExtractZip(zipPath, extractDir)
if err != nil {
t.Fatalf("ExtractZip() failed: %v", err)
}
// Verify files with special names were extracted
specialFiles := []string{
"file with spaces.txt",
"file-with-dashes.txt",
"file_with_underscores.txt",
"UPPERCASE.TXT",
}
for _, fileName := range specialFiles {
filePath := filepath.Join(extractDir, fileName)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
t.Fatalf("Special file not extracted: %s", fileName)
}
}
}
// Helper function to create a ZIP with nested directories
func createNestedZip(zipPath string) error {
file, err := os.Create(zipPath)
if err != nil {
return err
}
defer file.Close()
writer := zip.NewWriter(file)
defer writer.Close()
// Create nested structure
files := map[string]string{
"level1/file1.txt": "Content of file1.txt",
"level1/level2/file2.txt": "Content of file2.txt",
"level1/level2/level3/file3.txt": "Content of file3.txt",
}
for filePath, content := range files {
f, err := writer.Create(filePath)
if err != nil {
return err
}
_, err = f.Write([]byte(content))
if err != nil {
return err
}
}
return nil
}
// Helper function to create a ZIP with edge case file names
func createEdgeCaseZip(zipPath string) error {
file, err := os.Create(zipPath)
if err != nil {
return err
}
defer file.Close()
writer := zip.NewWriter(file)
defer writer.Close()
// Create files with special names
files := []string{
"file with spaces.txt",
"file-with-dashes.txt",
"file_with_underscores.txt",
"UPPERCASE.TXT",
}
for _, fileName := range files {
f, err := writer.Create(fileName)
if err != nil {
return err
}
_, err = f.Write([]byte(fmt.Sprintf("Content of %s", fileName)))
if err != nil {
return err
}
}
return nil
}