Skip to content

Commit

Permalink
ResourceIdentifier fixes and enhancements (#15656)
Browse files Browse the repository at this point in the history
- Updated `ResourceIdentifier` to address minor bugs and enhance
compliance with the URI RFC.
- Introduced new public and extension methods to support the file
abstraction migration and minimize changes required in subsequent PRs.
###### Microsoft Reviewers: [Open in
CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/Azure/bicep/pull/15656)
  • Loading branch information
shenglol authored Nov 25, 2024
1 parent 5b8235c commit 9789d36
Show file tree
Hide file tree
Showing 6 changed files with 466 additions and 93 deletions.
51 changes: 51 additions & 0 deletions src/Bicep.IO.UnitTests/Abstraction/ResourceIdentifierExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using Bicep.IO.Abstraction;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Bicep.IO.UnitTests.Abstraction
{
[TestClass]
public class ResourceIdentifierExtensionsTests
{
[DataTestMethod]
[DataRow("/a/b/c.txt", ".txt")]
[DataRow("/a/b/c.tar.gz", ".gz")]
[DataRow("/a/b/c", "")]
[DataRow("/a/b/c.", "")]
[DataRow("/a/b/c.d/e", "")]
public void GetExtension_ValidPaths_ReturnsCorrectExtension(string path, string expectedExtension)
{
// Arrange.
var resourceIdentifier = new ResourceIdentifier("file", "", path);

// Act.
var extension = resourceIdentifier.GetExtension();

// Assert.
extension.ToString().Should().Be(expectedExtension);
}

[DataTestMethod]
[DataRow("/a/b/c.txt", ".bak", "/a/b/c.bak")]
[DataRow("/a/b/c.tar.gz", ".zip", "/a/b/c.tar.zip")]
[DataRow("/a/b/c.tar.gz", "zip", "/a/b/c.tar.zip")]
[DataRow("/a/b/c", ".txt", "/a/b/c.txt")]
[DataRow("/a/b/c.", ".txt", "/a/b/c.txt")]
[DataRow("/a/b/c.d/e", ".txt", "/a/b/c.d/e.txt")]
public void WithExtension_ValidPaths_ReturnsPathWithNewExtension(string path, string newExtension, string expectedPath)
{
// Arrange
var resourceIdentifier = new ResourceIdentifier("file", "", path);

// Act
var newResourceIdentifier = resourceIdentifier.WithExtension(newExtension);

// Assert
newResourceIdentifier.Path.Should().Be(expectedPath);
}
}
}
187 changes: 160 additions & 27 deletions src/Bicep.IO.UnitTests/Abstraction/ResourceIdentifierTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,95 +7,228 @@
using System.Text;
using System.Threading.Tasks;
using Bicep.IO.Abstraction;
using FluentAssertions;

namespace Bicep.IO.UnitTests.Abstraction
{
[TestClass]
public class ResourceIdentifierTests
{
[DataTestMethod]
[DataRow("http", "EXAMPLE.COM", "example.com")]
[DataRow("http", "Example.Com", "example.com")]
[DataRow("http", "example.com", "example.com")]
[DataRow("https", "EXAMPLE.COM:80", "example.com:80")]
[DataRow("https", "Example.Com:443", "example.com:443")]
[DataRow("file", "localhost", "")]
[DataRow("file", "", "")]
[DataRow("file", null, "")]
public void ResourceIdentifier_ByDefault_NormalizesAuthority(string scheme, string? authority, string expectedAuthority)
{
// Arrange & Act.
var resourceIdentifier = new ResourceIdentifier(scheme, authority, "/a/b/c");

// Assert.
resourceIdentifier.Authority.Should().Be(expectedAuthority);
}

[DataTestMethod]
[DataRow("/a/b/c", "/a/b/c")]
[DataRow("/a/b/../c", "/a/c")]
[DataRow("/a/./b/c", "/a/b/c")]
[DataRow("/a/b/c/", "/a/b/c/")]
[DataRow("/a//b/c", "/a/b/c")]
[DataRow("/a/b/c/..", "/a/b")]
public void ResourceIdentifier_ByDefault_CanonicalizePath(string inputPath, string expectedPath)
public void ResourceIdentifier_ByDefault_NormalizesPath(string inputPath, string expectedPath)
{
// Act
// Arrange & Act.
var resourceIdentifier = new ResourceIdentifier("http", "example.com", inputPath);

// Assert
Assert.AreEqual(expectedPath, resourceIdentifier.Path);
// Assert.
resourceIdentifier.Path.Should().Be(expectedPath);
}

[DataTestMethod]
[DataRow("https", "")]
[DataRow("https", null)]
[DataRow("http", "")]
[DataRow("http", null)]
public void ResourceIdentifier_NullOrEmptyHttpOrHttpsAuthority_ThrowsArgumentException(string scheme, string? authority)
{
FluentActions
.Invoking(() => new ResourceIdentifier(scheme, authority, "/a/b/c"))
.Should().Throw<ArgumentException>();
}

[DataTestMethod]
[DataRow("http", "example.com", "a/b/c")]
[DataRow("http", null, "//a/b/c")]
[DataRow("file", null, "a/b/c")]
public void ResourceIdentifier_InvalidPath_ThrowsArgumentException(string scheme, string? authorty, string path)
{
FluentActions
.Invoking(() => new ResourceIdentifier(scheme, authorty, path))
.Should().Throw<ArgumentException>();
}

[DataTestMethod]
[DataRow("http", "example.com", "/a/b/c", "http://example.com/a/b/c")]
[DataRow("https", "example.com", "/a/b/c", "https://example.com/a/b/c")]
[DataRow("file", "", "/a/b/c", "/a/b/c")]
public void ResourceIdentifier_ToString_ReturnsCorrectUriOrPath(string scheme, string authority, string path, string expectedOutput)
public void ToString_ByDefault_ReturnsUriOrLocalFilePath(string scheme, string authority, string path, string expectedOutput)
{
// Act
// Arrange & Act.
var resourceIdentifier = new ResourceIdentifier(scheme, authority, path);

// Assert
Assert.AreEqual(expectedOutput, resourceIdentifier.ToString());
resourceIdentifier.ToString().Should().Be(expectedOutput);
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ResourceIdentifier_InvalidPath_ThrowsArgumentException()
{
// Act
var resourceIdentifier = new ResourceIdentifier("http", "example.com", "a/b/c");
}

[TestMethod]
public void ResourceIdentifier_Equals_ReturnsTrueForIdenticalIdentifiers()
public void Equals_IdenticalIdentifiers_ReturnsTrue()
{
// Arrange
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b/c");
var identifier2 = new ResourceIdentifier("http", "example.com", "/a/b/c");

// Act & Assert
Assert.IsTrue(identifier1.Equals(identifier2));
Assert.IsTrue(identifier1 == identifier2);
Assert.IsFalse(identifier1 != identifier2);
identifier1.Equals(identifier2).Should().BeTrue();
(identifier1 == identifier2).Should().BeTrue();
(identifier1 != identifier2).Should().BeFalse();
}

[TestMethod]
public void ResourceIdentifier_Equals_ReturnsFalseForDifferentIdentifiers()
public void Equals_DifferentIdentifiers_ReturnsFalse()
{
// Arrange
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b/c");
var identifier2 = new ResourceIdentifier("http", "example.com", "/a/b/d");

// Act & Assert
Assert.IsFalse(identifier1.Equals(identifier2));
Assert.IsFalse(identifier1 == identifier2);
Assert.IsTrue(identifier1 != identifier2);
identifier1.Equals(identifier2).Should().BeFalse();
(identifier1 == identifier2).Should().BeFalse();
(identifier1 != identifier2).Should().BeTrue();
}

[TestMethod]
public void ResourceIdentifier_GetHashCode_ReturnsConsistentHashCode()
public void GetHashCode_IdenticalIdentifiers_ReturnsConsistentHashCode()
{
// Arrange
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b/c");
var identifier2 = new ResourceIdentifier("http", "example.com", "/a/b/c");

// Act & Assert
Assert.AreEqual(identifier1.GetHashCode(), identifier2.GetHashCode());
identifier1.GetHashCode().Should().Be(identifier2.GetHashCode());
}

[TestMethod]
public void ResourceIdentifier_GetHashCode_ReturnsDifferentHashCodesForDifferentIdentifiers()
public void GetHashCode_DifferentIdentifiers_ReturnsDifferentHashCodes()
{
// Arrange
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b/c");
var identifier2 = new ResourceIdentifier("http", "example.com", "/a/b/d");

// Act & Assert
Assert.AreNotEqual(identifier1.GetHashCode(), identifier2.GetHashCode());
identifier1.GetHashCode().Should().NotBe(identifier2.GetHashCode());
}

[DataTestMethod]
[DataRow("http", "example.com", "/a/b/c", "/a/b", "c")]
[DataRow("http", "example.com", "/a/b/c/", "/a/b", "c/")]
[DataRow("http", "example.com", "/a/b/c", "/a/b/c", "")]
[DataRow("http", "example.com", "/a/b/c", "/a/b/d", "../c")]
[DataRow("http", "example.com", "/a/b/c/d", "/a/b", "c/d")]
[DataRow("http", "example.com", "/a/b/c", "/a/b/c/d", "..")]
public void GetPathRelativeTo_ValidPaths_ReturnsCorrectRelativePath(string scheme, string authority, string path, string otherPath, string expectedRelativePath)
{
// Arrange.
var identifier = new ResourceIdentifier(scheme, authority, path);
var otherIdentifier = new ResourceIdentifier(scheme, authority, otherPath);

// Act.
var relativePath = identifier.GetPathRelativeTo(otherIdentifier);

// Assert.
relativePath.Should().Be(expectedRelativePath);
}

[TestMethod]
public void GetPathRelativeTo_DifferentSchemes_ThrowsInvalidOperationException()
{
// Arrange.
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b/c");
var identifier2 = new ResourceIdentifier("https", "example.com", "/a/b/c");

// Act.
Action act = () => identifier1.GetPathRelativeTo(identifier2);

// Assert.
act.Should().Throw<InvalidOperationException>();
}

[TestMethod]
public void GetPathRelativeTo_DifferentAuthorities_ThrowsInvalidOperationException()
{
// Arrange.
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b/c");
var identifier2 = new ResourceIdentifier("http", "example.org", "/a/b/c");

// Act.
Action act = () => identifier1.GetPathRelativeTo(identifier2);

// Assert.
act.Should().Throw<InvalidOperationException>();
}

[DataTestMethod]
[DataRow("http", "example.com", "/a/b", "/a/b/c", true)]
[DataRow("http", "example.com", "/a/b", "/a/b/c/d", true)]
[DataRow("http", "example.com", "/a/b", "/a/b", true)]
[DataRow("http", "example.com", "/a/b", "/a/c", false)]
[DataRow("http", "example.com", "/a/b", "/a/bc", false)]
[DataRow("http", "example.com", "/a/b", "/a/bc/d", false)]
[DataRow("http", "example.com", "/a/b", "/a/b/../c", false)]
[DataRow("http", "example.com", "/a/b", "/a/b/./c", true)]
[DataRow("http", "example.com", "/a/b", "/a/b/c/..", true)]
public void IsBaseOf_ValidPaths_ReturnsExpectedResult(string scheme, string authority, string basePath, string otherPath, bool expectedResult)
{
// Arrange.
var baseIdentifier = new ResourceIdentifier(scheme, authority, basePath);
var otherIdentifier = new ResourceIdentifier(scheme, authority, otherPath);

// Act.
var result = baseIdentifier.IsBaseOf(otherIdentifier);

// Assert.
result.Should().Be(expectedResult);
}

[TestMethod]
public void IsBaseOf_DifferentSchemes_ReturnsFalse()
{
// Arrange.
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b");
var identifier2 = new ResourceIdentifier("https", "example.com", "/a/b/c");

// Act.
var result = identifier1.IsBaseOf(identifier2);

// Assert.
result.Should().BeFalse();
}

[TestMethod]
public void IsBaseOf_DifferentAuthorities_ReturnsFalse()
{
// Arrange.
var identifier1 = new ResourceIdentifier("http", "example.com", "/a/b");
var identifier2 = new ResourceIdentifier("http", "example.org", "/a/b/c");

// Act.
var result = identifier1.IsBaseOf(identifier2);

// Assert.
result.Should().BeFalse();
}
}
}
Loading

0 comments on commit 9789d36

Please sign in to comment.