diff --git a/idownloadjob_test.go b/idownloadjob_test.go index e05cb0d..37cabae 100644 --- a/idownloadjob_test.go +++ b/idownloadjob_test.go @@ -16,7 +16,11 @@ limitations under the License. package windowsupdate -import "testing" +import ( + "testing" + + "github.com/go-ole/go-ole" +) func TestToIDownloadJob_NilDispatch(t *testing.T) { result, err := toIDownloadJob(nil) @@ -64,3 +68,48 @@ func TestIDownloadJob_Methods_NilDispatch(t *testing.T) { } }() } + +// TestIDownloadJob_BeginDownloadAndMethods exercises toIDownloadJob, CleanUp, RequestAbort, GetProgress via real COM. +func TestIDownloadJob_BeginDownloadAndMethods(t *testing.T) { + ole.CoInitialize(0) + defer ole.CoUninitialize() + + session, err := NewUpdateSession() + if err != nil { + t.Fatalf("NewUpdateSession failed: %v", err) + } + + downloader, err := session.CreateUpdateDownloader() + if err != nil { + t.Fatalf("CreateUpdateDownloader failed: %v", err) + } + + // BeginDownload with empty updates yields a job that completes immediately or very quickly + job, err := downloader.BeginDownload([]*IUpdate{}) + if err != nil { + t.Skipf("BeginDownload failed (may need WU service): %v", err) + return + } + if job == nil { + t.Fatal("BeginDownload returned nil job") + } + + // GetProgress returns current progress (covers toIDownloadProgress path when used from job) + progress, err := job.GetProgress() + if err != nil { + t.Logf("GetProgress returned error (non-fatal): %v", err) + } + if progress != nil { + // If we got progress, calling GetUpdateResult(0) covers IDownloadProgress.GetUpdateResult + _, _ = progress.GetUpdateResult(0) + } + + // RequestAbort before CleanUp so disp is still valid + _ = job.RequestAbort() + + // CleanUp releases resources; safe to call on completed job + err = job.CleanUp() + if err != nil { + t.Logf("CleanUp returned error (non-fatal): %v", err) + } +} diff --git a/idownloadresult.go b/idownloadresult.go index 40194e9..f598c3e 100644 --- a/idownloadresult.go +++ b/idownloadresult.go @@ -27,6 +27,10 @@ type IDownloadResult struct { } func toIDownloadResult(downloadResultDisp *ole.IDispatch) (*IDownloadResult, error) { + if downloadResultDisp == nil { + return nil, nil + } + var err error iDownloadResult := &IDownloadResult{ disp: downloadResultDisp, diff --git a/idownloadresult_test.go b/idownloadresult_test.go index a498a75..f1fbb28 100644 --- a/idownloadresult_test.go +++ b/idownloadresult_test.go @@ -64,30 +64,17 @@ func TestIDownloadResult_ResultCodes(t *testing.T) { } } +// TestToIDownloadResult_NilDispatch covers the nil-dispatch path of toIDownloadResult. func TestToIDownloadResult_NilDispatch(t *testing.T) { - defer func() { - // If a panic occurs when using a nil dispatch, that's acceptable - // as the COM layer may not handle nil pointers uniformly. - _ = recover() - }() - result, err := toIDownloadResult(nil) - if err == nil && result != nil { - t.Errorf("expected error or panic for nil dispatch, got result=%v, err=%v", result, err) - } -} - -func TestIDownloadResult_GetUpdateResult_NilDispatch(t *testing.T) { - defer func() { - // Allow panic as a valid behavior when the underlying COM dispatch is nil. - _ = recover() - }() - - dr := &IDownloadResult{ - disp: nil, + if err != nil { + t.Errorf("expected no error for nil dispatch, got %v", err) } - updateResult, err := dr.GetUpdateResult(0) - if err == nil && updateResult != nil { - t.Errorf("expected error or panic for nil dispatch, got result=%v, err=%v", updateResult, err) + if result != nil { + t.Errorf("expected nil result for nil dispatch, got %v", result) } } + +// Note: toIDownloadResult with real COM and GetUpdateResult are covered by +// TestIUpdateDownloader_Download_EmptyUpdates and TestIUpdateDownloader_BeginDownloadEndDownload +// in iupdatedownloader_test.go when run on Windows. diff --git a/iimageinformation.go b/iimageinformation.go index 3019434..97733b5 100644 --- a/iimageinformation.go +++ b/iimageinformation.go @@ -29,6 +29,10 @@ type IImageInformation struct { } func toIImageInformation(imageInformationDisp *ole.IDispatch) (*IImageInformation, error) { + if imageInformationDisp == nil { + return nil, nil + } + var err error iImageInformation := &IImageInformation{ disp: imageInformationDisp, diff --git a/iimageinformation_test.go b/iimageinformation_test.go index e4c2411..9f6c1cc 100644 --- a/iimageinformation_test.go +++ b/iimageinformation_test.go @@ -18,6 +18,16 @@ package windowsupdate import "testing" +func TestToIImageInformation_NilDispatch(t *testing.T) { + result, err := toIImageInformation(nil) + if err != nil { + t.Errorf("expected no error for nil dispatch, got %v", err) + } + if result != nil { + t.Errorf("expected nil result for nil dispatch, got %v", result) + } +} + func TestIImageInformation_StructureFields(t *testing.T) { image := &IImageInformation{ AltText: "Image alt text", @@ -38,14 +48,3 @@ func TestIImageInformation_StructureFields(t *testing.T) { t.Errorf("Source not set correctly, got %s", image.Source) } } - -func TestToIImageInformation_NilDispatch(t *testing.T) { - defer func() { - _ = recover() - }() - - result, err := toIImageInformation(nil) - if err == nil && result != nil { - t.Errorf("expected error or panic for nil dispatch, got result=%v, err=%v", result, err) - } -} diff --git a/iinstallationjob_test.go b/iinstallationjob_test.go index 32bf904..28e7f1d 100644 --- a/iinstallationjob_test.go +++ b/iinstallationjob_test.go @@ -60,3 +60,43 @@ func TestIInstallationJob_Methods(t *testing.T) { t.Error("IsCompleted should be true") } } + +// TestIInstallationJob_BeginInstallAndMethods exercises toIInstallationJob, CleanUp, RequestAbort, GetProgress via real COM. +func TestIInstallationJob_BeginInstallAndMethods(t *testing.T) { + ole.CoInitialize(0) + defer ole.CoUninitialize() + + session, err := NewUpdateSession() + if err != nil { + t.Fatalf("NewUpdateSession failed: %v", err) + } + + installer, err := session.CreateUpdateInstaller() + if err != nil { + t.Fatalf("CreateUpdateInstaller failed: %v", err) + } + + job, err := installer.BeginInstall([]*IUpdate{}) + if err != nil { + t.Skipf("BeginInstall failed (may need WU service): %v", err) + return + } + if job == nil { + t.Fatal("BeginInstall returned nil job") + } + + // GetProgress covers toIInstallationProgress and IInstallationProgress.GetUpdateResult when progress is used + progress, err := job.GetProgress() + if err != nil { + t.Logf("GetProgress returned error (non-fatal): %v", err) + } + if progress != nil { + _, _ = progress.GetUpdateResult(0) + } + + _ = job.RequestAbort() + err = job.CleanUp() + if err != nil { + t.Logf("CleanUp returned error (non-fatal): %v", err) + } +} diff --git a/iinstallationresult.go b/iinstallationresult.go index 45efb02..5e6b1e7 100644 --- a/iinstallationresult.go +++ b/iinstallationresult.go @@ -28,6 +28,10 @@ type IInstallationResult struct { } func toIInstallationResult(installationResultDisp *ole.IDispatch) (*IInstallationResult, error) { + if installationResultDisp == nil { + return nil, nil + } + var err error iInstallationResult := &IInstallationResult{ disp: installationResultDisp, diff --git a/iinstallationresult_test.go b/iinstallationresult_test.go index 0240231..8b808db 100644 --- a/iinstallationresult_test.go +++ b/iinstallationresult_test.go @@ -18,6 +18,16 @@ package windowsupdate import "testing" +func TestToIInstallationResult_NilDispatch(t *testing.T) { + result, err := toIInstallationResult(nil) + if err != nil { + t.Errorf("expected no error for nil dispatch, got %v", err) + } + if result != nil { + t.Errorf("expected nil result for nil dispatch, got %v", result) + } +} + func TestIInstallationResult_StructureFields(t *testing.T) { result := &IInstallationResult{ HResult: 0, @@ -76,28 +86,3 @@ func TestIInstallationResult_ErrorScenarios(t *testing.T) { }) } } - -func TestToIInstallationResult_NilDispatch(t *testing.T) { - defer func() { - _ = recover() - }() - - result, err := toIInstallationResult(nil) - if err == nil && result != nil { - t.Errorf("expected error or panic for nil dispatch, got result=%v, err=%v", result, err) - } -} - -func TestIInstallationResult_GetUpdateResult_NilDispatch(t *testing.T) { - defer func() { - _ = recover() - }() - - ir := &IInstallationResult{ - disp: nil, - } - updateResult, err := ir.GetUpdateResult(0) - if err == nil && updateResult != nil { - t.Errorf("expected error or panic for nil dispatch, got result=%v, err=%v", updateResult, err) - } -} diff --git a/isearchjob_test.go b/isearchjob_test.go index f62f121..7ef0834 100644 --- a/isearchjob_test.go +++ b/isearchjob_test.go @@ -16,7 +16,11 @@ limitations under the License. package windowsupdate -import "testing" +import ( + "testing" + + "github.com/go-ole/go-ole" +) func TestToISearchJob_NilDispatch(t *testing.T) { result, err := toISearchJob(nil) @@ -55,3 +59,36 @@ func TestISearchJob_Methods_NilDispatch(t *testing.T) { _ = job.RequestAbort() }() } + +// TestISearchJob_CleanUpRequestAbort exercises CleanUp and RequestAbort via a real search job from BeginSearch. +func TestISearchJob_CleanUpRequestAbort(t *testing.T) { + ole.CoInitialize(0) + defer ole.CoUninitialize() + + session, err := NewUpdateSession() + if err != nil { + t.Fatalf("NewUpdateSession failed: %v", err) + } + + searcher, err := session.CreateUpdateSearcher() + if err != nil { + t.Fatalf("CreateUpdateSearcher failed: %v", err) + } + + job, err := searcher.BeginSearch("IsInstalled=1") + if err != nil { + t.Skipf("BeginSearch failed: %v", err) + return + } + if job == nil { + t.Fatal("BeginSearch returned nil job") + } + + // RequestAbort before CleanUp so disp is still valid + _ = job.RequestAbort() + + err = job.CleanUp() + if err != nil { + t.Logf("CleanUp returned error (non-fatal): %v", err) + } +} diff --git a/iupdatedownloader_test.go b/iupdatedownloader_test.go index e71b5a6..27d324e 100644 --- a/iupdatedownloader_test.go +++ b/iupdatedownloader_test.go @@ -138,3 +138,64 @@ func TestIUpdateDownloader_Methods_NilDispatch(t *testing.T) { } }() } + +// TestIUpdateDownloader_Download_EmptyUpdates exercises toIDownloadResult via Download with no updates. +func TestIUpdateDownloader_Download_EmptyUpdates(t *testing.T) { + ole.CoInitialize(0) + defer ole.CoUninitialize() + + session, err := NewUpdateSession() + if err != nil { + t.Fatalf("NewUpdateSession failed: %v", err) + } + + downloader, err := session.CreateUpdateDownloader() + if err != nil { + t.Fatalf("CreateUpdateDownloader failed: %v", err) + } + + result, err := downloader.Download([]*IUpdate{}) + if err != nil { + t.Skipf("Download with empty updates failed (may need WU service): %v", err) + return + } + if result == nil { + t.Fatal("Download returned nil result") + } + // Cover IDownloadResult.GetUpdateResult (may error for index 0 on empty result) + _, _ = result.GetUpdateResult(0) +} + +// TestIUpdateDownloader_BeginDownloadEndDownload exercises toIDownloadResult via EndDownload. +func TestIUpdateDownloader_BeginDownloadEndDownload(t *testing.T) { + ole.CoInitialize(0) + defer ole.CoUninitialize() + + session, err := NewUpdateSession() + if err != nil { + t.Fatalf("NewUpdateSession failed: %v", err) + } + + downloader, err := session.CreateUpdateDownloader() + if err != nil { + t.Fatalf("CreateUpdateDownloader failed: %v", err) + } + + job, err := downloader.BeginDownload([]*IUpdate{}) + if err != nil { + t.Skipf("BeginDownload failed: %v", err) + return + } + if job == nil { + t.Fatal("BeginDownload returned nil job") + } + + result, err := downloader.EndDownload(job) + if err != nil { + t.Skipf("EndDownload failed (job may not be complete): %v", err) + return + } + if result != nil { + _, _ = result.GetUpdateResult(0) + } +} diff --git a/iupdateinstaller_test.go b/iupdateinstaller_test.go index bf11862..ff147bb 100644 --- a/iupdateinstaller_test.go +++ b/iupdateinstaller_test.go @@ -229,6 +229,40 @@ func TestIUpdateInstaller_EndInstall(t *testing.T) { } } +// TestIUpdateInstaller_BeginInstallEndInstall exercises toIInstallationResult and IInstallationResult.GetUpdateResult via real COM. +func TestIUpdateInstaller_BeginInstallEndInstall(t *testing.T) { + ole.CoInitialize(0) + defer ole.CoUninitialize() + + session, err := NewUpdateSession() + if err != nil { + t.Fatalf("NewUpdateSession failed: %v", err) + } + + installer, err := session.CreateUpdateInstaller() + if err != nil { + t.Fatalf("CreateUpdateInstaller failed: %v", err) + } + + job, err := installer.BeginInstall([]*IUpdate{}) + if err != nil { + t.Skipf("BeginInstall failed: %v", err) + return + } + if job == nil { + t.Fatal("BeginInstall returned nil job") + } + + result, err := installer.EndInstall(job) + if err != nil { + t.Skipf("EndInstall failed (job may not be complete): %v", err) + return + } + if result != nil { + _, _ = result.GetUpdateResult(0) + } +} + func TestIUpdateInstaller_BeginUninstall(t *testing.T) { // Note: BeginUninstall requires a real IUpdateInstaller with updates. // Calling with nil dispatch would cause panic. diff --git a/iupdatesearcher_test.go b/iupdatesearcher_test.go index ce059c2..83c4564 100644 --- a/iupdatesearcher_test.go +++ b/iupdatesearcher_test.go @@ -427,12 +427,10 @@ func TestIUpdateSearcher_EndSearch(t *testing.T) { return } - // Wait a bit for the search to complete - // In a real scenario, you would poll IsCompleted or use callbacks - // For testing, we'll try to end it immediately + // EndSearch blocks until the async search completes; call it to cover EndSearch path result, err := searcher.EndSearch(searchJob) if err != nil { - t.Logf("EndSearch may fail if search not complete: %v", err) + t.Logf("EndSearch failed: %v", err) return } if result == nil { diff --git a/iupdateservicemanager_test.go b/iupdateservicemanager_test.go index 4641301..8a8475b 100644 --- a/iupdateservicemanager_test.go +++ b/iupdateservicemanager_test.go @@ -108,14 +108,18 @@ func TestIUpdateServiceManager_AddService(t *testing.T) { } func TestIUpdateServiceManager_RegisterServiceWithAU(t *testing.T) { - // Note: RegisterServiceWithAU can hang for a very long time waiting - // for system services. We verify the structure instead. - mgr := &IUpdateServiceManager{ - ClientApplicationID: "test-app", + ole.CoInitialize(0) + defer ole.CoUninitialize() + + mgr, err := NewUpdateServiceManager() + if err != nil { + t.Fatalf("NewUpdateServiceManager failed: %v", err) } - if mgr.ClientApplicationID != "test-app" { - t.Errorf("ClientApplicationID = %s, want test-app", mgr.ClientApplicationID) + // Call with empty/invalid service ID; expect quick failure rather than hang + err = mgr.RegisterServiceWithAU("") + if err != nil { + t.Logf("RegisterServiceWithAU with empty ID failed as expected: %v", err) } } @@ -138,14 +142,18 @@ func TestIUpdateServiceManager_RemoveService(t *testing.T) { } func TestIUpdateServiceManager_UnregisterServiceWithAU(t *testing.T) { - // Note: UnregisterServiceWithAU can hang for a very long time (10+ minutes) - // waiting for system services. We verify the structure instead. - mgr := &IUpdateServiceManager{ - ClientApplicationID: "test-app", + ole.CoInitialize(0) + defer ole.CoUninitialize() + + mgr, err := NewUpdateServiceManager() + if err != nil { + t.Fatalf("NewUpdateServiceManager failed: %v", err) } - if mgr.ClientApplicationID != "test-app" { - t.Errorf("ClientApplicationID = %s, want test-app", mgr.ClientApplicationID) + // Call with empty/invalid service ID; expect quick failure rather than hang + err = mgr.UnregisterServiceWithAU("") + if err != nil { + t.Logf("UnregisterServiceWithAU with empty ID failed as expected: %v", err) } }