- 新增多个源文件和目录,包括 app.rc、assets、build 脚本等 - 实现了与 MirrorChyan API 交互的客户端逻辑 - 添加了版本检查、更新检测和下载 URL 生成等功能 - 嵌入了配置模板和资源文件系统 - 提供了完整的构建和发布流程
1033 lines
29 KiB
Go
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
|
|
} |