diff --git a/LinkAce.sln b/LinkAce.sln index ec07926..53f80d6 100644 --- a/LinkAce.sln +++ b/LinkAce.sln @@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinkAce.NET", "src\LinkAce. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestClient", "src\TestClient\TestClient.csproj", "{97052387-BAA1-40E8-A861-C412B373FBDC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LinkAce.NET.Tests", "tests\LinkAce.NET.Tests\LinkAce.NET.Tests.csproj", "{671997C6-2DA3-45AB-87C2-4A335C976685}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,10 +35,15 @@ Global {97052387-BAA1-40E8-A861-C412B373FBDC}.Debug|Any CPU.Build.0 = Debug|Any CPU {97052387-BAA1-40E8-A861-C412B373FBDC}.Release|Any CPU.ActiveCfg = Release|Any CPU {97052387-BAA1-40E8-A861-C412B373FBDC}.Release|Any CPU.Build.0 = Release|Any CPU + {671997C6-2DA3-45AB-87C2-4A335C976685}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {671997C6-2DA3-45AB-87C2-4A335C976685}.Debug|Any CPU.Build.0 = Debug|Any CPU + {671997C6-2DA3-45AB-87C2-4A335C976685}.Release|Any CPU.ActiveCfg = Release|Any CPU + {671997C6-2DA3-45AB-87C2-4A335C976685}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {7E05B1B4-5625-4A84-A2B2-4D66C8059904} = {E1115509-DC42-44D8-A031-A207EF90073F} {6C83B6FB-E141-46BA-A446-8346BB11A7E3} = {22C844A6-ACCE-4473-9C7B-D53D613DA802} {97052387-BAA1-40E8-A861-C412B373FBDC} = {22C844A6-ACCE-4473-9C7B-D53D613DA802} + {671997C6-2DA3-45AB-87C2-4A335C976685} = {66EB4E20-E8C9-43B5-9B51-B3BB962AEEF2} EndGlobalSection EndGlobal diff --git a/src/LinkAce.NET/AssemblyInfo.cs b/src/LinkAce.NET/AssemblyInfo.cs new file mode 100644 index 0000000..f928090 --- /dev/null +++ b/src/LinkAce.NET/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("LinkAce.NET.Tests")] diff --git a/tests/LinkAce.NET.Tests/ApiResponses/SearchLinkResponseTests.cs b/tests/LinkAce.NET.Tests/ApiResponses/SearchLinkResponseTests.cs new file mode 100644 index 0000000..7346d2f --- /dev/null +++ b/tests/LinkAce.NET.Tests/ApiResponses/SearchLinkResponseTests.cs @@ -0,0 +1,31 @@ +using LinkAce.NET.ApiResponses; +using LinkAce.NET.Tests.Helpers; + +namespace LinkAce.NET.Tests.ApiResponses; + +[TestClass] +public class SearchLinkResponseTests +{ + [TestMethod] + public void SearchLinkResponse_HasProperties() + { + // Arrange + SearchLinkResponse obj = new(); + const int expectedCount = 12; + + // Assert + Assert.AreEqual(expectedCount, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("CurrentPage")); + Assert.IsTrue(obj.HasProperty("Data")); + Assert.IsTrue(obj.HasProperty("FirstPageUrl")); + Assert.IsTrue(obj.HasProperty("From")); + Assert.IsTrue(obj.HasProperty("LastPage")); + Assert.IsTrue(obj.HasProperty("LastPageUrl")); + Assert.IsTrue(obj.HasProperty("NextPageUrl")); + Assert.IsTrue(obj.HasProperty("Path")); + Assert.IsTrue(obj.HasProperty("PerPage")); + Assert.IsTrue(obj.HasProperty("PreviousPageUrl")); + Assert.IsTrue(obj.HasProperty("To")); + Assert.IsTrue(obj.HasProperty("Total")); + } +} diff --git a/tests/LinkAce.NET.Tests/Entities/TagTests.cs b/tests/LinkAce.NET.Tests/Entities/TagTests.cs new file mode 100644 index 0000000..084cf6a --- /dev/null +++ b/tests/LinkAce.NET.Tests/Entities/TagTests.cs @@ -0,0 +1,40 @@ +using LinkAce.NET.Entites; +using LinkAce.NET.Tests.Helpers; + +namespace LinkAce.NET.Tests.Entities; + +[TestClass] +public class TagTests +{ + [TestMethod] + public void Tag_HasProperties() + { + // Arrange + Tag obj = new(); + const int expectedCount = 8; + + // Act + Assert.AreEqual(expectedCount, obj.PropertyCount()); + Assert.IsTrue(obj.HasProperty("CreatedAt")); + Assert.IsTrue(obj.HasProperty("DeletedAt")); + Assert.IsTrue(obj.HasProperty("Id")); + Assert.IsTrue(obj.HasProperty("IsPrivate")); + Assert.IsTrue(obj.HasProperty("Name")); + Assert.IsTrue(obj.HasProperty("Pivot")); + Assert.IsTrue(obj.HasProperty("UpdatedAt")); + Assert.IsTrue(obj.HasProperty("UserId")); + } + + [TestMethod] + public void ImplicitConversion_String_To_Tag() + { + // Arrange + const string expectedName = "TestTag"; + + // Act + Tag tag = expectedName; + + // Assert + Assert.AreEqual(expectedName, tag.Name); + } +} diff --git a/tests/LinkAce.NET.Tests/Extensions/StringArrayExtensionsTests.cs b/tests/LinkAce.NET.Tests/Extensions/StringArrayExtensionsTests.cs new file mode 100644 index 0000000..ff3b0c7 --- /dev/null +++ b/tests/LinkAce.NET.Tests/Extensions/StringArrayExtensionsTests.cs @@ -0,0 +1,26 @@ +using LinkAce.NET.Entites; +using LinkAce.NET.Extensions; + +namespace LinkAce.NET.Tests.Extensions; + +[TestClass] +public class StringArrayExtensionsTests +{ + + [TestMethod] + public void ToTagArray_Success() + { + // Arrange + string[] expectedNames = { "TestTag1", "TestTag2", "TestTag3" }; + + // Act + Tag[] tags = expectedNames.ToTagArray(); + + // Assert + Assert.AreEqual(expectedNames.Length, tags.Length); + for (int i = 0; i < expectedNames.Length; i++) + { + Assert.AreEqual(expectedNames[i], tags[i].Name); + } + } +} diff --git a/tests/LinkAce.NET.Tests/GlobalUsings.cs b/tests/LinkAce.NET.Tests/GlobalUsings.cs new file mode 100644 index 0000000..540383d --- /dev/null +++ b/tests/LinkAce.NET.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/tests/LinkAce.NET.Tests/Helpers/EntityTests.cs b/tests/LinkAce.NET.Tests/Helpers/EntityTests.cs new file mode 100644 index 0000000..325c58b --- /dev/null +++ b/tests/LinkAce.NET.Tests/Helpers/EntityTests.cs @@ -0,0 +1,23 @@ +using System.Reflection; + +namespace LinkAce.NET.Tests.Helpers; + +public static class EntityTests +{ + public static bool HasMethod(this object obj, string methodName) + { + var type = obj.GetType(); + try + { + return type.GetMethod(methodName) is not null; + } + catch (AmbiguousMatchException) + { + return true; + } + } + public static bool HasProperty(this object obj, string propertyName) + => obj.GetType().GetProperty(propertyName) is not null; + public static int PropertyCount(this object obj) + => obj.GetType().GetProperties().Length; +} diff --git a/tests/LinkAce.NET.Tests/LinkAce.NET.Tests.csproj b/tests/LinkAce.NET.Tests/LinkAce.NET.Tests.csproj new file mode 100644 index 0000000..f3b7e02 --- /dev/null +++ b/tests/LinkAce.NET.Tests/LinkAce.NET.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/tests/LinkAce.NET.Tests/LinkAceClientTests.cs b/tests/LinkAce.NET.Tests/LinkAceClientTests.cs new file mode 100644 index 0000000..b054847 --- /dev/null +++ b/tests/LinkAce.NET.Tests/LinkAceClientTests.cs @@ -0,0 +1,66 @@ +using System.Net; +using LinkAce.NET.Entites; +using LinkAce.NET.Extensions; +using Moq; +using Moq.Protected; + +namespace LinkAce.NET.Tests; + +[TestClass] +public class LinkAceClientTests +{ + private LinkAceClient _client; + private Mock _mockHttpMessageHandler; + [TestInitialize] + public void Init() + { + _mockHttpMessageHandler = new Mock(); + var httpClient = new HttpClient(_mockHttpMessageHandler.Object); + _client = new LinkAceClient("https://links.example.com", TestData.TestApiKey, httpClient); + } + [TestMethod] + public async Task SearchLinksByUrl_Success() + { + // Arrange + _mockHttpMessageHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(TestData.SearchLinkResponseJson) + }); + + // Act + var result = await _client.SearchLinksByUrl("jrgnsn.net"); + + // Assert + Assert.IsNotNull(result); + } + [TestMethod] + public async Task CreateLink_Success() + { + // Arrange + _mockHttpMessageHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(TestData.CreateLinkResponseJson) + }); + + // Act + var result = await _client.CreateLink(new Link() + { + Title = "Test Link", + Url = "https://jrgnsn.net", + Description = "A test link", + Tags = new[]{ "test" }.ToTagArray() + }); + + // Assert + Assert.IsNotNull(result); + + } +} \ No newline at end of file diff --git a/tests/LinkAce.NET.Tests/MetaTests.cs b/tests/LinkAce.NET.Tests/MetaTests.cs new file mode 100644 index 0000000..b8fcc4d --- /dev/null +++ b/tests/LinkAce.NET.Tests/MetaTests.cs @@ -0,0 +1,36 @@ +using System.Net.Http.Headers; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace LinkAce.NET.Tests +{ + [TestClass] + public class MetaTests + { + [TestMethod] + public void UserAgent_ShouldContainCorrectNameAndVersion() + { + // Arrange + var expectedName = "LinkAce.NET"; + var expectedVersion = Assembly.GetEntryAssembly()!.GetName().Version!.ToString(); + var expectedUserAgent = new ProductInfoHeaderValue(expectedName, expectedVersion); + + // Act + var actualUserAgent = Meta.UserAgent; + + // Assert + Assert.AreEqual(expectedUserAgent.Product.Name, actualUserAgent.Product.Name); + Assert.AreEqual(expectedUserAgent.Product.Version, actualUserAgent.Product.Version); + } + + [TestMethod] + public void AssemblyVersion_ShouldNotBeNullOrEmpty() + { + // Act + var assemblyVersion = Meta.AssemblyVersion; + + // Assert + Assert.IsFalse(string.IsNullOrEmpty(assemblyVersion)); + } + } +} diff --git a/tests/LinkAce.NET.Tests/TestData.cs b/tests/LinkAce.NET.Tests/TestData.cs new file mode 100644 index 0000000..a6bafd0 --- /dev/null +++ b/tests/LinkAce.NET.Tests/TestData.cs @@ -0,0 +1,124 @@ +namespace LinkAce.NET.Tests; + +public class TestData +{ + public const string TestApiKey = "testing-token"; + public const string SearchLinkResponseJson = @"{ + ""current_page"": 1, + ""data"": [ + { + ""check_disabled"": false, + ""created_at"": ""2020-09-21T21:39:11.000000Z"", + ""deleted_at"": null, + ""description"": ""Pictures from my Atreus keyboard build"", + ""icon"": ""link"", + ""id"": 1098, + ""is_private"": false, + ""status"": 1, + ""tags"": [ + { + ""created_at"": ""2023-06-09T16:16:36.000000Z"", + ""deleted_at"": null, + ""id"": 603, + ""is_private"": false, + ""name"": ""atreus"", + ""pivot"": { + ""link_id"": 1098, + ""tag_id"": 603 + }, + ""updated_at"": ""2023-06-09T16:16:36.000000Z"", + ""user_id"": 1 + }, + { + ""created_at"": ""2023-06-09T16:07:30.000000Z"", + ""deleted_at"": null, + ""id"": 234, + ""is_private"": false, + ""name"": ""diy"", + ""pivot"": { + ""link_id"": 1098, + ""tag_id"": 234 + }, + ""updated_at"": ""2023-06-09T16:07:30.000000Z"", + ""user_id"": 1 + }, + { + ""created_at"": ""2023-06-09T16:16:37.000000Z"", + ""deleted_at"": null, + ""id"": 605, + ""is_private"": false, + ""name"": ""keyboard"", + ""pivot"": { + ""link_id"": 1098, + ""tag_id"": 605 + }, + ""updated_at"": ""2023-06-09T16:16:37.000000Z"", + ""user_id"": 1 + } + ], + ""thumbnail"": null, + ""title"": ""Classic Atreus Build - jphotos"", + ""updated_at"": ""2023-06-09T16:16:37.000000Z"", + ""url"": ""https://photos.jrgnsn.net/album/classic-atreus-build"", + ""user_id"": 1 + } + ], + ""first_page_url"": ""https://links.fminus.co/api/v1/search/links?page=1"", + ""from"": 1, + ""last_page"": 1, + ""last_page_url"": ""https://links.fminus.co/api/v1/search/links?page=1"", + ""links"": [ + { + ""active"": false, + ""label"": ""« Previous"", + ""url"": null + }, + { + ""active"": true, + ""label"": ""1"", + ""url"": ""https://links.fminus.co/api/v1/search/links?page=1"" + }, + { + ""active"": false, + ""label"": ""Next »"", + ""url"": null + } + ], + ""next_page_url"": null, + ""path"": ""https://links.fminus.co/api/v1/search/links"", + ""per_page"": 24, + ""prev_page_url"": null, + ""to"": 1, + ""total"": 1 +}"; + public const string CreateLinkResponseJson = @"{ + ""check_disabled"": false, + ""created_at"": ""2024-10-04T19:32:31.000000Z"", + ""description"": ""A test link"", + ""icon"": ""link"", + ""id"": 1819, + ""is_private"": false, + ""lists"": [], + ""status"": 0, + ""tags"": [ + { + ""created_at"": ""2023-06-13T23:27:14.000000Z"", + ""deleted_at"": null, + ""id"": 880, + ""is_private"": false, + ""name"": ""test"", + ""pivot"": { + ""link_id"": 1819, + ""tag_id"": 880 + }, + ""updated_at"": ""2023-06-13T23:27:14.000000Z"", + ""user_id"": 1 + } + ], + ""thumbnail"": null, + ""title"": ""Test Link"", + ""updated_at"": ""2024-10-04T19:32:31.000000Z"", + ""url"": ""https://jrgnsn.net"", + ""user_id"": 1 +}"; +}