diff --git a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs index e7b8b9307bf..9fc4106c0bb 100644 --- a/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs @@ -965,60 +965,65 @@ internal async void ExecutePackageDownload(string name, PackageVersion package, } // Determine if there are any dependencies that have a newer dynamo version, (this includes the root package). - // We assume this means this package is compatibile with that dynamo version, but we should warn the user it + // We assume this means this package is compatible with that dynamo version, but we should warn the user it // may not work with the current version of Dynamo. - //wrap in try catch as it's possible version info could be missing. If so, we install the package and log an error. - try + // If the compatibility matrix already indicates compatibility, skip the engine_version heuristic. + // Only perform engine_version based warnings when compatibility is not explicitly true. + if (compatible != true) { - var dynamoVersion = Version.Parse(DynamoModel.Version); - var futureDeps = newPackageHeaders.Where(dep => Version.Parse(dep.engine_version) > dynamoVersion); - // also identify packages that have a dynamo engine version less than 3.x as a special case, - // as Dynamo 3.x uses .net8 and older versions used .net framework - these packages may not be compatible. - // This check will return empty if the current major version is not 3. - var preDYN4Deps = newPackageHeaders.Where(dep => dynamoVersion.Major == 4 && Version.Parse(dep.engine_version).Major < dynamoVersion.Major); - - // If any of the required packages use a newer version of Dynamo, show a dialog to the user - // allowing them to cancel the package download - if (futureDeps.Any()) + //wrap in try catch as it's possible version info could be missing. If so, we install the package and log an error. + try { - var res = MessageBoxService.Show(ViewModelOwner, - $"{string.Format(Resources.MessagePackageNewerDynamo, DynamoViewModel.BrandingResourceProvider.ProductName)} {Resources.MessagePackOlderDynamoLink}", - string.Format(Resources.PackageUseNewerDynamoMessageBoxTitle, DynamoViewModel.BrandingResourceProvider.ProductName), + var dynamoVersion = Version.Parse(DynamoModel.Version); + var futureDeps = newPackageHeaders.Where(dep => Version.Parse(dep.engine_version) > dynamoVersion); + // also identify packages that have a Dynamo engine version from a different major version as a special case, + // as different major Dynamo versions may use different runtime frameworks - these packages may not be compatible. + // This check will return empty if the current major version does not require this validation. + var preDYNDeps = newPackageHeaders.Where(dep => Version.Parse(dep.engine_version).Major < dynamoVersion.Major); + + // If any of the required packages use a newer version of Dynamo, show a dialog to the user + // allowing them to cancel the package download + if (futureDeps.Any()) + { + var res = MessageBoxService.Show(ViewModelOwner, + $"{string.Format(Resources.MessagePackageNewerDynamo, DynamoViewModel.BrandingResourceProvider.ProductName)} {Resources.MessagePackOlderDynamoLink}", + string.Format(Resources.PackageUseNewerDynamoMessageBoxTitle, DynamoViewModel.BrandingResourceProvider.ProductName), + //this message has a url link so we use the rich text box version of the message box. + showRichTextBox: true, + MessageBoxButton.OKCancel, + MessageBoxImage.Warning); + if (res == MessageBoxResult.Cancel || res == MessageBoxResult.None) + { + return; + } + } + + //if any of the required packages use a pre 3.x version of Dynamo, show a dialog to the user + //allowing them to cancel the package download + if (preDYNDeps.Any()) + { + var res = MessageBoxService.Show(ViewModelOwner, + $"{string.Format(Resources.MessagePackageOlderDynamo, DynamoViewModel.BrandingResourceProvider.ProductName)} {Resources.MessagePackOlderDynamoLink}", + string.Format(Resources.PackageUseOlderDynamoMessageBoxTitle, DynamoViewModel.BrandingResourceProvider.ProductName), //this message has a url link so we use the rich text box version of the message box. showRichTextBox: true, MessageBoxButton.OKCancel, MessageBoxImage.Warning); - if (res == MessageBoxResult.Cancel || res == MessageBoxResult.None) - { - return; + if (res == MessageBoxResult.Cancel || res == MessageBoxResult.None) + { + return; + } } } - - //if any of the required packages use a pre 3.x version of Dynamo, show a dialog to the user - //allowing them to cancel the package download - if (preDYN4Deps.Any()) + catch (ArgumentException ex) { - var res = MessageBoxService.Show(ViewModelOwner, - $"{string.Format(Resources.MessagePackageOlderDynamo, DynamoViewModel.BrandingResourceProvider.ProductName)} {Resources.MessagePackOlderDynamoLink}", - string.Format(Resources.PackageUseOlderDynamoMessageBoxTitle, DynamoViewModel.BrandingResourceProvider.ProductName), - //this message has a url link so we use the rich text box version of the message box. - showRichTextBox: true, - MessageBoxButton.OKCancel, - MessageBoxImage.Warning); - if (res == MessageBoxResult.Cancel || res == MessageBoxResult.None) - { - return; - } + DynamoConsoleLogger.OnLogMessageToDynamoConsole($"exception while trying to compare version info between package and dynamo {ex}"); + } + catch (FormatException ex) + { + DynamoConsoleLogger.OnLogMessageToDynamoConsole($"exception while trying to compare version info between package and dynamo {ex}"); } - } - catch (ArgumentException ex) - { - DynamoConsoleLogger.OnLogMessageToDynamoConsole($"exception while trying to compare version info between package and dynamo {ex}"); - } - catch (FormatException ex) - { - DynamoConsoleLogger.OnLogMessageToDynamoConsole($"exception while trying to compare version info between package and dynamo {ex}"); } // add custom path to custom package folder list diff --git a/test/DynamoCoreWpf2Tests/PackageManager/PackageManagerUITests.cs b/test/DynamoCoreWpf2Tests/PackageManager/PackageManagerUITests.cs index b1f9616853f..b571b69ecb6 100644 --- a/test/DynamoCoreWpf2Tests/PackageManager/PackageManagerUITests.cs +++ b/test/DynamoCoreWpf2Tests/PackageManager/PackageManagerUITests.cs @@ -1626,6 +1626,152 @@ public void PackageManagerShowsWarningWhenDownloadingLegacyPackage() MessageBoxButton.OKCancel,MessageBoxImage.Exclamation), Times.Exactly(1)); } + + [Test] + [Description("If the package compatibility matrix explicitly marks the package as compatible, do NOT show engine_version newer/older warnings even when engine_version differs.")] + public void PackageManagerDoesNotShowEngineVersionWarningWhenCompatibilityMatrixIsCompatible() + { + var mockGreg = new Mock(); + var clientMock = new Mock(mockGreg.Object, MockMaker.Empty(), string.Empty, false); + var pmVmMock = new Mock(ViewModel, clientMock.Object); + var pmMock = new Mock(); + + // When we attempt a download, return a valid download path so we never hit the real Greg client. + pmVmMock.Setup(x => x.Download(It.IsAny())).Returns(h => Task.Factory.StartNew(() => (h, h.Name))); + pmVmMock.Setup(x => x.InstallPackage(It.IsAny(), It.IsAny(), It.IsAny())); + + var pkgVer = new Version(1, 0, 0).ToString(); + // An engine_version that would normally trip the older/newer Dynamo warning heuristic. + var pkgEngineVersion = "1.0.0"; + var id =Guid.NewGuid().ToString(); + var name = "test-123-name"; + var deps = new List() { new Dependency() { _id = id, name = name } }; + var depVersions = new List() { pkgVer }; + + // A compatibility matrix that explicitly marks the package as compatible with the current Dynamo version. + var compatibilityMatrix = new List + { + new Greg.Responses.Compatibility { name = "Dynamo", min = "1.0", max = "100.0" } + }; + + var pkgVersionObject = new PackageVersion() + { + version = pkgVer, + engine_version = pkgEngineVersion, + name = name, + id = id, + full_dependency_ids = deps, + full_dependency_versions = depVersions, + compatibility_matrix = compatibilityMatrix + }; + + // When headers are retrieved for the package, return the correct header (carrying the compatibility matrix). + clientMock.Setup(x => x.GetPackageVersionHeader(It.IsAny(), It.IsAny())).Returns((i, v) => + { + return i == id ? pkgVersionObject : null; + }); + + // Wire up the PackageManagerExtension so CheckIfPackagesTargetOtherHosts can resolve known hosts. + clientMock.Setup(x => x.GetKnownHosts()).Returns(new List() { "Revit", "Civil 3D", "FormIt" }); + pmMock.Setup(x => x.PackageManagerClient).Returns(clientMock.Object); + pmMock.Setup(x => x.Host).Returns(""); + pmVmMock.Setup(x => x.PackageManagerExtension).Returns(pmMock.Object); + var dlgMock = new Mock(); + + // Click OK on the download consent dialog. + dlgMock.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), + It.Is(x => x == MessageBoxButton.OKCancel || x == MessageBoxButton.OK), It.IsAny())) + .Returns(MessageBoxResult.OK); + dlgMock.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns(MessageBoxResult.OK); + MessageBoxService.OverrideMessageBoxDuringTests(dlgMock.Object); + + var productName = ViewModel.BrandingResourceProvider.ProductName; + var newerDynamoMessage = $"{string.Format(Dynamo.Wpf.Properties.Resources.MessagePackageNewerDynamo, productName)} {Dynamo.Wpf.Properties.Resources.MessagePackOlderDynamoLink}"; + var olderDynamoMessage = $"{string.Format(Dynamo.Wpf.Properties.Resources.MessagePackageOlderDynamo, productName)} {Dynamo.Wpf.Properties.Resources.MessagePackOlderDynamoLink}"; + + // Actually perform the download & install operations. + pmVmMock.Object.ExecutePackageDownload(id, pkgVersionObject, ""); + + // Because the compatibility matrix marks the package as compatible (compatible == true), + // the engine_version heuristic is skipped entirely, so neither + // the "newer Dynamo" nor the "older/legacy Dynamo" warning should be shown. + dlgMock.Verify(x => x.Show(It.IsAny(), newerDynamoMessage, It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + dlgMock.Verify(x => x.Show(It.IsAny(), olderDynamoMessage, It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + } + + [Test] + [Description("If the package compatibility matrix is null, engine_version warnings should still appear when engine_version differs from the current Dynamo version.")] + public void PackageManagerShowsEngineVersionWarningWhenCompatibilityMatrixIsNull() + { + var mockGreg = new Mock(); + var clientMock = new Mock(mockGreg.Object, MockMaker.Empty(), string.Empty, false); + var pmVmMock = new Mock(ViewModel, clientMock.Object); + var pmMock = new Mock(); + + // When we attempt a download, return a valid download path so we never hit the real Greg client. + pmVmMock.Setup(x => x.Download(It.IsAny())).Returns(h => Task.Factory.StartNew(() => (h, h.Name))); + pmVmMock.Setup(x => x.InstallPackage(It.IsAny(), It.IsAny(), It.IsAny())); + + var pkgVer = new Version(1, 0, 0).ToString(); + // An engine_version that would normally trip the older/newer Dynamo warning heuristic. + var pkgEngineVersion = "1.0.0"; + var id = Guid.NewGuid().ToString(); + var name = "test-123-name"; + var deps = new List() { new Dependency() { _id = id, name = name } }; + var depVersions = new List() { pkgVer }; + + // No compatibility matrix — the engine_version heuristic should still fire. + var pkgVersionObject = new PackageVersion() + { + version = pkgVer, + engine_version = pkgEngineVersion, + name = name, + id = id, + full_dependency_ids = deps, + full_dependency_versions = depVersions, + compatibility_matrix = null + }; + + // When headers are retrieved for the package, return the correct header (carrying the compatibility matrix). + clientMock.Setup(x => x.GetPackageVersionHeader(It.IsAny(), It.IsAny())).Returns((i, v) => + { + return i == id ? pkgVersionObject : null; + }); + + // Wire up the PackageManagerExtension so CheckIfPackagesTargetOtherHosts can resolve known hosts. + clientMock.Setup(x => x.GetKnownHosts()).Returns(new List() { "Revit", "Civil 3D", "FormIt" }); + pmMock.Setup(x => x.PackageManagerClient).Returns(clientMock.Object); + pmMock.Setup(x => x.Host).Returns(""); + pmVmMock.Setup(x => x.PackageManagerExtension).Returns(pmMock.Object); + var dlgMock = new Mock(); + + // Click OK on the download consent dialog. + dlgMock.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), + It.Is(x => x == MessageBoxButton.OKCancel || x == MessageBoxButton.OK), It.IsAny())) + .Returns(MessageBoxResult.OK); + dlgMock.Setup(m => m.Show(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())) + .Returns(MessageBoxResult.OK); + MessageBoxService.OverrideMessageBoxDuringTests(dlgMock.Object); + + var productName = ViewModel.BrandingResourceProvider.ProductName; + var newerDynamoMessage = $"{string.Format(Dynamo.Wpf.Properties.Resources.MessagePackageNewerDynamo, productName)} {Dynamo.Wpf.Properties.Resources.MessagePackOlderDynamoLink}"; + var olderDynamoMessage = $"{string.Format(Dynamo.Wpf.Properties.Resources.MessagePackageOlderDynamo, productName)} {Dynamo.Wpf.Properties.Resources.MessagePackOlderDynamoLink}"; + + // Actually perform the download & install operations. + pmVmMock.Object.ExecutePackageDownload(id, pkgVersionObject, ""); + + // Because the compatibility matrix is null, the engine_version heuristic is NOT skipped, + // so either the "newer Dynamo" or the "older/legacy Dynamo" warning should be shown exactly once. + var newerShown = dlgMock.Invocations.Any(i => i.Method.Name == "Show" && i.Arguments.Count > 1 && i.Arguments[1]?.ToString() == newerDynamoMessage); + var olderShown = dlgMock.Invocations.Any(i => i.Method.Name == "Show" && i.Arguments.Count > 1 && i.Arguments[1]?.ToString() == olderDynamoMessage); + + Assert.IsTrue(newerShown || olderShown, "Neither the newer-Dynamo or older-Dynamo engine_version warning were shown when compatibility_matrix is null."); + } #endregion #region PackageManagerSearchView