diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 0ca43fdb..35e944cc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,6 +11,7 @@ _Put an `x` in the boxes that apply_ - [ ] New feature (non-breaking change that adds functionality or value) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] **Test fix** (non-breaking change that improves test stability or correctness) +- [ ] Chore/Maintenance (updates to build scripts, dependencies, or GitHub Actions) ## Documentation - [ ] Have you proposed a file change/ PR with Appium to update documentation? diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 1d8400fc..d152e77a 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -48,5 +48,4 @@ version-resolver: default: patch prerelease: false -prerelease-identifier: 'rc' contribution-template: '- $NAME contributed via $PULL_REQUEST' diff --git a/README.md b/README.md index b3d5f66a..0bb398fc 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ ---- [![Functional Tests](https://github.com/appium/dotnet-client/actions/workflows/functional-test.yml/badge.svg)](https://github.com/appium/dotnet-client/actions/workflows/functional-test.yml) -[![Functional Tests (Android)](https://github.com/appium/dotnet-client/actions/workflows/functional-android-test.yml/badge.svg)](https://github.com/appium/dotnet-client/actions/workflows/functional-android-test.yml) -[![Functional Tests (iOS)](https://github.com/appium/dotnet-client/actions/workflows/functional-ios-test.yml/badge.svg)](https://github.com/appium/dotnet-client/actions/workflows/functional-ios-test.yml) [![Unit Tests](https://github.com/appium/dotnet-client/actions/workflows/unit-test.yml/badge.svg)](https://github.com/appium/dotnet-client/actions/workflows/unit-test.yml) ---- diff --git a/src/Appium.Net/Appium.Net.csproj b/src/Appium.Net/Appium.Net.csproj index 869757a2..8c9ced10 100644 --- a/src/Appium.Net/Appium.Net.csproj +++ b/src/Appium.Net/Appium.Net.csproj @@ -43,7 +43,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Appium.Net/Appium/Service/DirectConnect.cs b/src/Appium.Net/Appium/Service/DirectConnect.cs index 6f5cd220..8d6b7dd2 100644 --- a/src/Appium.Net/Appium/Service/DirectConnect.cs +++ b/src/Appium.Net/Appium/Service/DirectConnect.cs @@ -1,4 +1,4 @@ -//Licensed under the Apache License, Version 2.0 (the "License"); +//Licensed under the Apache License, Version 2.0 (the "License"); //you may not use this file except in compliance with the License. //See the NOTICE file distributed with this work for additional //information regarding copyright ownership. @@ -42,21 +42,37 @@ public DirectConnect(Response response) this.Path = GetDirectConnectValue((Dictionary)response.Value, DIRECT_CONNECT_PATH); } + /// + /// Gets a value indicating whether this instance has the required members for attempting URI construction. + /// + private bool IsValid => + this.Protocol != null && + this.Host != null && + this.Port != null && + this.Path != null && + this.Protocol.Equals("https", StringComparison.OrdinalIgnoreCase) && + int.TryParse(this.Port, out int port) && port >= 0 && port <= 65535; + /// /// Returns a URL instance built with members in the DirectConnect instance. /// /// A Uri instance - public Uri GetUri() { - if (this.Protocol == null || this.Host == null || this.Port == null || this.Path == null) { + public Uri GetUri() + { + if (!this.IsValid) + { return null; } - if (this.Protocol != "https") + try + { + var builder = new UriBuilder(this.Protocol, this.Host, int.Parse(this.Port), this.Path); + return builder.Uri; + } + catch (UriFormatException) { return null; } - - return new Uri(this.Protocol + "://" + this.Host + ":" + this.Port + this.Path); } /// diff --git a/test/integration/ImageComparison/ComparisonResultTests.cs b/test/integration/ImageComparison/ComparisonResultTests.cs index bb75c165..7f64d532 100644 --- a/test/integration/ImageComparison/ComparisonResultTests.cs +++ b/test/integration/ImageComparison/ComparisonResultTests.cs @@ -63,13 +63,13 @@ public void TearDown() [Test] public void SaveVisualizationAsFile_NullFileName_ThrowsArgumentNullException() { - Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(null)); + Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(null))); } [Test] public void SaveVisualizationAsFile_EmptyFileName_ThrowsArgumentException() { - var ex = Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile("")); + var ex = Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(""))); Assert.That(ex.Message, Does.Contain("The file name must not be an empty string.")); } @@ -86,7 +86,7 @@ public void SaveVisualizationAsFile_InvalidCharacters_ThrowsArgumentException() } string invalidFileName = "test" + invalidChars[0] + ".png"; - var ex = Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(invalidFileName)); + var ex = Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(invalidFileName))); Assert.That(ex.Message, Does.Contain("The file name contains invalid characters")); } @@ -101,7 +101,7 @@ public void SaveVisualizationAsFile_ReservedDeviceName_ThrowsArgumentException() var invalidNames = new[] { "CON.png", "CON .png", "NUL.", "PRN " }; foreach (var name in invalidNames) { - var ex = Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(name)); + var ex = Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(name))); Assert.That(ex.Message, Does.Contain("reserved device name")); } } @@ -135,14 +135,14 @@ public void SaveVisualizationAsFile_ValidSubdirectoryFileName_SavesFile() public void SaveVisualizationAsFile_PathTraversalParentDirectory_ThrowsIOException() { string fileName = "../traversal_image.png"; - Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(fileName)); + Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(fileName))); } [Test] public void SaveVisualizationAsFile_AbsolutePathOutsideAllowed_ThrowsIOException() { string absolutePath = Path.Combine(Path.GetTempPath(), "absolute_image.png"); - Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(absolutePath)); + Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(absolutePath))); } [Test] @@ -181,7 +181,7 @@ public void SaveVisualizationAsFile_SymlinkTraversal_ThrowsIOException() // Attempt to write a file via the symlink string fileName = Path.Combine("local_sub", "symlink_dir", "image.png"); - var ex = Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(fileName)); + var ex = Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(fileName))); Assert.That(ex.Message, Does.Contain("symbolic link or reparse point")); #else Assert.Ignore("Skipping symlink test: Symbolic links are not supported on this target framework."); @@ -228,7 +228,7 @@ public void SaveVisualizationAsFile_BrokenFileSymlinkTraversal_ThrowsIOException return; } - var ex = Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile("broken_file_link.png")); + var ex = Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile("broken_file_link.png"))); Assert.That(ex.Message, Does.Contain("symbolic link or reparse point")); #else Assert.Ignore("Skipping symlink test: Symbolic links are not supported on this target framework."); @@ -241,7 +241,7 @@ public void SaveVisualizationAsFile_AlternateDataStreamsOnWindows_ThrowsArgument if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { string invalidFileName = "file.png:stream"; - var ex = Assert.Throws(() => _comparisonResult.SaveVisualizationAsFile(invalidFileName)); + var ex = Assert.Throws((Action)(() => _comparisonResult.SaveVisualizationAsFile(invalidFileName))); Assert.That(ex.Message, Does.Contain("alternate data streams")); } else