diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7192b7a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +**/.vs +**/bin +**/obj +**/packages +**/*.user +**/*.userprefs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c2984da --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,31 @@ +name: CI + +on: + push: + pull_request: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: "8.0.x" + + - name: Restore + run: dotnet restore MifielAPI/MifielAPI.sln + + - name: Build + run: dotnet build MifielAPI/MifielAPI.sln --configuration Release --no-restore + + - name: Test + run: dotnet test MifielAPI/MifielAPI.sln --configuration Release --no-build --verbosity normal diff --git a/.gitignore b/.gitignore index 3a2238d..a662e19 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,14 @@ publish/ *.nuget.props *.nuget.targets +# Packed artifacts +artifacts/ + +# Local pre-release smoke tests +MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs +MifielAPI/MifielAPI.Tests/csharp-test-pdf.pdf +test.sh + # Microsoft Azure Build Output csx/ *.build.csdef diff --git a/CHANGELOG.md b/CHANGELOG.md index f27b186..bdb491d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ - Default API host changed from `https://www.mifiel.com` to `https://app.mifiel.com`. - Sandbox documentation and examples now use `https://app-sandbox.mifiel.com` instead of `https://sandbox.mifiel.com`. - Package version jumped from `0.0.4` to `1.0.0` to mark this default-host breaking change. +- The library now targets **.NET 8 (`net8.0`)** instead of .NET Framework 4.5. .NET Framework and Mono are no longer supported. Consumers should use `dotnet add package MifielAPIClient` on .NET 8 or later. +- Packaging moved from Mono/`msbuild`/`nuget.exe` to the .NET SDK (`dotnet pack`, `dotnet nuget push`). ### Features -- Send a standardized `User-Agent` on API requests, e.g. `DOTNET/4.0.30319.42000 MifielAPIClient/1.0.0 HttpClient/4.0.0.0 (Unix/6.8.0)`. +- Send a standardized `User-Agent` on API requests, e.g. `DOTNET/8.0.0 MifielAPIClient/1.0.0 HttpClient/8.0.0.0 (Unix/24.6.0)`. ### Migration diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1828013 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Build and run tests with the .NET SDK (this library targets net8.0). +# Usage: +# docker build -t csharp-api-client-tests . +# docker run --rm csharp-api-client-tests +FROM mcr.microsoft.com/dotnet/sdk:8.0 + +WORKDIR /src +COPY . /src + +RUN dotnet restore MifielAPI/MifielAPI.sln \ + && dotnet build MifielAPI/MifielAPI.sln --configuration Release --no-restore \ + && chown -R app:app /src + +USER app +CMD ["dotnet", "test", "MifielAPI/MifielAPI.sln", "--configuration", "Release", "--no-build", "--filter", "Category!=Smoke"] diff --git a/MifielAPI/.nuget/NuGet.Config b/MifielAPI/.nuget/NuGet.Config deleted file mode 100644 index 67f8ea0..0000000 --- a/MifielAPI/.nuget/NuGet.Config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MifielAPI/.nuget/NuGet.targets b/MifielAPI/.nuget/NuGet.targets deleted file mode 100644 index 3f8c37b..0000000 --- a/MifielAPI/.nuget/NuGet.targets +++ /dev/null @@ -1,144 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - false - - - false - - - true - - - false - - - - - - - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - - - - - $(SolutionDir).nuget - - - - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config - $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config - - - - $(MSBuildProjectDirectory)\packages.config - $(PackagesProjectConfig) - - - - - $(NuGetToolsPath)\NuGet.exe - @(PackageSource) - - "$(NuGetExePath)" - mono --runtime=v4.0.30319 "$(NuGetExePath)" - - $(TargetDir.Trim('\\')) - - -RequireConsent - -NonInteractive - - "$(SolutionDir) " - "$(SolutionDir)" - - - $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) - $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/MifielAPI/MifielAPI.Tests/DocumentsTests.cs b/MifielAPI/MifielAPI.Tests/DocumentsTests.cs new file mode 100644 index 0000000..05be61c --- /dev/null +++ b/MifielAPI/MifielAPI.Tests/DocumentsTests.cs @@ -0,0 +1,196 @@ +using System.IO; +using System.Collections.Generic; +using System.Net; +using NUnit.Framework; +using MifielAPI; +using MifielAPI.Objects; +using MifielAPI.Dao; + +namespace MifielApiTests +{ + [TestFixture] + public class DocumentsTests + { + private const string DocumentJson = "{\"id\":\"doc-1\",\"name\":\"PdfFileName\"}"; + private const string DocumentsJson = "[{\"id\":\"doc-1\",\"name\":\"PdfFileName\"}]"; + + private readonly string _currentDirectory = Path.GetFullPath(TestContext.CurrentContext.TestDirectory); + private StubHttpMessageHandler _handler; + private ApiClient _apiClient; + private Documents _docs; + private string _pdfFilePath; + + [SetUp] + public void SetUp() + { + _pdfFilePath = Path.Combine(_currentDirectory, "test-pdf.pdf"); + _handler = new StubHttpMessageHandler(); + _apiClient = new ApiClient("test-app-id", "test-app-secret", _handler) + { + Url = "https://app-sandbox.mifiel.com" + }; + _docs = new Documents(_apiClient); + } + + [Test] + public void Documents__WrongUrl__ShouldThrowAnException() + { + Assert.Throws(() => _apiClient.Url = "www.google.com"); + Assert.AreEqual(0, _handler.SendCount); + } + + [Test] + public void Documents__CorrectUrl__ShouldNotThrowAnException() + { + _apiClient.Url = "https://app-sandbox.mifiel.com"; + Assert.AreEqual(0, _handler.SendCount); + } + + [Test] + public void Documents__AppendPDFBase64InOriginalXml__ShouldGenerateNewXML() + { + var pathOriginalXml = Path.Combine(_currentDirectory, "file_hash.xml"); + var pathNewXml = Path.Combine(_currentDirectory, "file_with_hash_and_document.xml"); + MifielAPI.Utils.MifielUtils.AppendPDFBase64InOriginalXml(_pdfFilePath, pathOriginalXml, pathNewXml); + Assert.True(File.Exists(pathNewXml)); + Assert.AreEqual(0, _handler.SendCount); + } + + [Test] + public void Documents__FindAllDocuments__ShouldReturnAList() + { + _handler.EnqueueJson(DocumentsJson); + var allDocuments = _docs.FindAll(); + Assert.IsNotNull(allDocuments); + Assert.AreEqual(1, allDocuments.Count); + Assert.AreEqual("doc-1", allDocuments[0].Id); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__Close__Should_Success() + { + _handler.EnqueueJson("{\"success\":true}"); + var closeDocument = _docs.Close("doc-1"); + Assert.IsTrue(closeDocument.Success); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__SaveWithFilePath__ShouldReturnADocument() + { + _handler.EnqueueJson(DocumentJson); + var document = new Document() + { + File = _pdfFilePath, + ManualClose = false, + CallbackUrl = "https://example.com/callback", + SendMail = true, + SendInvites = true, + Signatures = new List() + { + new Signature() + { + Email = "juan@mifiel.com", + TaxId = "ZAAJ8301061E0", + SignerName = "Juan Antonio Zavala Aguilar" + } + }, + Viewers = new List() + { + new Viewer() + { + Name = "Juan Zavala", + Email = "ja.zavala.aguilar@gmail.com" + } + } + }; + + document = _docs.Save(document); + Assert.IsNotNull(document); + Assert.AreEqual("doc-1", document.Id); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__SaveWithOriginalHashAndFileName__ShouldReturnADocument() + { + _handler.EnqueueJson(DocumentJson); + var document = new Document() + { + OriginalHash = MifielAPI.Utils.MifielUtils.GetDocumentHash(_pdfFilePath), + FileName = "PdfFileName", + ManualClose = false, + Signatures = new List() + { + new Signature() + { + Email = "juan@mifiel.com", + SignerName = "Juan Antonio Zavala Aguilar" + } + } + }; + + document = _docs.Save(document); + Assert.IsNotNull(document); + Assert.AreEqual("doc-1", document.Id); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__SaveWithoutRequiredFields__ShouldThrowAnException() + { + var document = new Document() { CallbackUrl = "http://www.google.com" }; + + Assert.Throws(() => _docs.Save(document)); + Assert.AreEqual(0, _handler.SendCount); + } + + [Test] + public void Documents__Find__ShouldReturnADocument() + { + _handler.EnqueueJson(DocumentJson); + Document doc1 = _docs.Find("doc-1"); + Assert.IsNotNull(doc1); + Assert.AreEqual("doc-1", doc1.Id); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__Delete__ShouldRemoveADocument() + { + _handler.EnqueueJson("{}"); + _docs.Delete("doc-1"); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__RequestSignature__ShouldReturnASignatureResponse() + { + _handler.EnqueueJson("{\"status\":\"ok\",\"message\":\"sent\"}"); + SignatureResponse signatureResponse = _docs.RequestSignature("doc-1", + "enrique@test.com", "enrique2@test.com"); + Assert.IsNotNull(signatureResponse); + Assert.AreEqual("ok", signatureResponse.Status); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__SaveFile__ShouldSaveFileOnSpecifiedPath() + { + _handler.EnqueueBytes(File.ReadAllBytes(_pdfFilePath)); + var savePath = Path.Combine(_currentDirectory, "pdf_save_test.pdf"); + _docs.SaveFile("doc-1", savePath); + Assert.True(File.Exists(savePath)); + Assert.AreEqual(1, _handler.SendCount); + } + + [Test] + public void Documents__UnauthorizedResponse__ShouldThrowAnException() + { + _handler.EnqueueJson("{\"error\":\"unauthorized\"}", HttpStatusCode.Unauthorized); + Assert.Throws(() => _docs.FindAll()); + Assert.AreEqual(1, _handler.SendCount); + } + } +} diff --git a/MifielAPI/MifielAPI.Tests/MifielApiTests.csproj b/MifielAPI/MifielAPI.Tests/MifielApiTests.csproj new file mode 100644 index 0000000..64217ef --- /dev/null +++ b/MifielAPI/MifielAPI.Tests/MifielApiTests.csproj @@ -0,0 +1,31 @@ + + + net8.0 + MifielAPITests + MifielAPITests + disable + disable + false + latest + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/MifielAPI/MifielAPI.Tests/StubHttpMessageHandler.cs b/MifielAPI/MifielAPI.Tests/StubHttpMessageHandler.cs new file mode 100644 index 0000000..759c1b8 --- /dev/null +++ b/MifielAPI/MifielAPI.Tests/StubHttpMessageHandler.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MifielApiTests +{ + internal sealed class StubHttpMessageHandler : HttpMessageHandler + { + private readonly Queue _responses = new Queue(); + + public HttpRequestMessage LastRequest { get; private set; } + + public int SendCount { get; private set; } + + public void EnqueueJson(string json, HttpStatusCode statusCode = HttpStatusCode.OK) + { + _responses.Enqueue(new HttpResponseMessage(statusCode) + { + Content = new StringContent(json ?? "{}", Encoding.UTF8, "application/json") + }); + } + + public void EnqueueBytes(byte[] bytes, HttpStatusCode statusCode = HttpStatusCode.OK) + { + _responses.Enqueue(new HttpResponseMessage(statusCode) + { + Content = new ByteArrayContent(bytes ?? Array.Empty()) + }); + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + LastRequest = request; + SendCount++; + + if (_responses.Count == 0) + { + throw new InvalidOperationException("StubHttpMessageHandler has no queued response. Tests must not make real HTTP calls."); + } + + var response = _responses.Dequeue(); + response.RequestMessage = request; + return Task.FromResult(response); + } + } +} diff --git a/MifielAPI/MifielAPI.Tests/file_hash.xml b/MifielAPI/MifielAPI.Tests/file_hash.xml new file mode 100644 index 0000000..4b28ace --- /dev/null +++ b/MifielAPI/MifielAPI.Tests/file_hash.xml @@ -0,0 +1,9 @@ + + + + + + TEST + + + diff --git a/MifielAPI/MifielApiTests/test-pdf.pdf b/MifielAPI/MifielAPI.Tests/test-pdf.pdf similarity index 100% rename from MifielAPI/MifielApiTests/test-pdf.pdf rename to MifielAPI/MifielAPI.Tests/test-pdf.pdf diff --git a/MifielAPI/MifielAPI.sln b/MifielAPI/MifielAPI.sln index 1f60dcf..15cf3a5 100644 --- a/MifielAPI/MifielAPI.sln +++ b/MifielAPI/MifielAPI.sln @@ -1,11 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MifielAPI", "MifielAPI\MifielAPI.csproj", "{D5C2109D-2E52-41D6-9B32-71FF7E7B5A96}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MifielAPI", "MifielAPI\MifielAPI.csproj", "{D5C2109D-2E52-41D6-9B32-71FF7E7B5A96}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MifielAPITests", "MifielAPITests\MifielAPITests.csproj", "{EC120768-5963-4D7E-80F3-FCEC17CE5CB1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MifielAPITests", "MifielAPI.Tests\MifielApiTests.csproj", "{EC120768-5963-4D7E-80F3-FCEC17CE5CB1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -22,10 +22,4 @@ Global {EC120768-5963-4D7E-80F3-FCEC17CE5CB1}.Release|Any CPU.ActiveCfg = Release|Any CPU {EC120768-5963-4D7E-80F3-FCEC17CE5CB1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - version = 0.0.4 - EndGlobalSection EndGlobal diff --git a/MifielAPI/MifielAPI/ApiClient.cs b/MifielAPI/MifielAPI/ApiClient.cs index 7536cbd..e25b76c 100644 --- a/MifielAPI/MifielAPI/ApiClient.cs +++ b/MifielAPI/MifielAPI/ApiClient.cs @@ -1,20 +1,40 @@ using MifielAPI.Exceptions; using MifielAPI.Utils; using System; -using System.Net.Http; using System.Globalization; +using System.Net.Http; +using System.Reflection; namespace MifielAPI { - public class ApiClient + public class ApiClient : IDisposable { public const string PackageName = "MifielAPIClient"; - public const string PackageVersion = "1.0.0"; + + public static string PackageVersion + { + get + { + var informational = typeof(ApiClient).Assembly + .GetCustomAttribute() + ?.InformationalVersion; + if (string.IsNullOrEmpty(informational)) + { + var version = typeof(ApiClient).Assembly.GetName().Version; + return version != null ? version.ToString(3) : "0.0.0"; + } + + var plus = informational.IndexOf('+'); + return plus >= 0 ? informational.Substring(0, plus) : informational; + } + } public string AppId { get; set; } public string AppSecret { get; set; } private string _apiVersion = "/api/v1/"; private CultureInfo _usCulture = new CultureInfo("en-US"); + private readonly HttpMessageHandler _httpMessageHandler; + private readonly bool _ownsHttpMessageHandler; private string url; public string Url @@ -31,12 +51,32 @@ public string Url } public ApiClient(string appId, string appSecret) + : this(appId, appSecret, new HttpClientHandler(), true) + { + } + + internal ApiClient(string appId, string appSecret, HttpMessageHandler httpMessageHandler) + : this(appId, appSecret, httpMessageHandler, false) + { + } + + private ApiClient(string appId, string appSecret, HttpMessageHandler httpMessageHandler, bool ownsHttpMessageHandler) { AppId = appId; AppSecret = appSecret; + _httpMessageHandler = httpMessageHandler ?? throw new ArgumentNullException(nameof(httpMessageHandler)); + _ownsHttpMessageHandler = ownsHttpMessageHandler; Url = "https://app.mifiel.com"; } + public void Dispose() + { + if (_ownsHttpMessageHandler) + { + _httpMessageHandler.Dispose(); + } + } + public HttpContent Get(string path) { return SendRequest(Rest.HttpMethod.GET, path, new StringContent("")); @@ -67,9 +107,8 @@ private HttpContent SendRequest(Rest.HttpMethod httpMethod, string path, HttpCon string requestUri = url + _apiVersion + path; HttpRequestMessage requestMessage = null; HttpResponseMessage httpResponse = null; - System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; - using (var client = new HttpClient()) + using (var client = new HttpClient(_httpMessageHandler, disposeHandler: false)) { client.Timeout = TimeSpan.FromMinutes(5); using (content) @@ -109,19 +148,21 @@ private HttpContent SendRequest(Rest.HttpMethod httpMethod, string path, HttpCon private void SetAuthentication(Rest.HttpMethod httpMethod, string path, HttpRequestMessage requestMessage) { - string contentType = requestMessage.Content == null ? "" : requestMessage.Content.Headers.ContentType.ToString(); + string contentType = requestMessage.Content == null || requestMessage.Content.Headers.ContentType == null + ? "" + : requestMessage.Content.Headers.ContentType.ToString(); string date = DateTime.Now.ToUniversalTime().ToString("r", _usCulture); string contentMd5 = "";// MifielUtils.CalculateMD5(content); string signature = GetSignature(httpMethod, path, contentMd5, date, contentType); string authorizationHeader = string.Format("APIAuth {0}:{1}", AppId, signature); requestMessage.Headers.Add("Authorization", authorizationHeader); - requestMessage.Headers.Add("Date", date); + requestMessage.Headers.TryAddWithoutValidation("Date", date); requestMessage.Headers.TryAddWithoutValidation("User-Agent", UserAgent()); } /// - /// Example: DOTNET/4.0.30319.42000 MifielAPIClient/1.0.0 HttpClient/4.0.0.0 (Unix/6.8.0) + /// Example: DOTNET/8.0.0 MifielAPIClient/1.0.0 HttpClient/8.0.0.0 (Unix/24.6.0) /// public string UserAgent() { diff --git a/MifielAPI/MifielAPI/Exceptions/MifielException.cs b/MifielAPI/MifielAPI/Exceptions/MifielException.cs index efacd9d..47c3b21 100644 --- a/MifielAPI/MifielAPI/Exceptions/MifielException.cs +++ b/MifielAPI/MifielAPI/Exceptions/MifielException.cs @@ -1,6 +1,5 @@ using MifielAPI.Objects; using System; -using System.Runtime.Serialization; namespace MifielAPI.Exceptions { @@ -21,11 +20,6 @@ public MifielException(string message, Exception innerException) { } - protected MifielException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - public MifielException(string message, string httpResponse) : base(message) { try @@ -36,3 +30,4 @@ public MifielException(string message, string httpResponse) : base(message) } } } + diff --git a/MifielAPI/MifielAPI/MifielAPI.csproj b/MifielAPI/MifielAPI/MifielAPI.csproj index c7abab8..c8dbcae 100644 --- a/MifielAPI/MifielAPI/MifielAPI.csproj +++ b/MifielAPI/MifielAPI/MifielAPI.csproj @@ -1,96 +1,36 @@ - - - - + - Debug - AnyCPU - {D5C2109D-2E52-41D6-9B32-71FF7E7B5A96} - Library - Properties + net8.0 MifielAPI MifielAPI - v4.5 - 512 - + disable + disable + latest MifielAPIClient - 1.0.0 + 1.0.0 Genaro Madrid, Juan Antonio Zavala Aguilar + mifiel.com Mifiel provide a simple and robust RESTful API enabling any service or company operating in Mexico to integrate electronic signatures (using the FIEL) into their workflow. Through Mifiel’s API, you can easily manage documents and certificates within your Mifiel account - true - https://www.mifiel.com/favicon.png - mifiel.com https://github.com/Mifiel/csharp-api-client - mifiel,electronic-signatures + https://github.com/Mifiel/csharp-api-client + mifiel;electronic-signatures + MIT + README.md Mifiel API Client for C# - 1.0.0 - https://github.com/Mifiel/csharp-api-client/blob/master/LICENSE C# SDK for mifiel.com. - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 + true + false - - ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - True - - - - - - - - + - - - - - - - - - - - - - - - - - - - - + - + + - - - - \ No newline at end of file + diff --git a/MifielAPI/MifielAPI/Properties/AssemblyInfo.cs b/MifielAPI/MifielAPI/Properties/AssemblyInfo.cs deleted file mode 100644 index 297fee9..0000000 --- a/MifielAPI/MifielAPI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MifielAPI")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MifielAPI")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d5c2109d-2e52-41d6-9b32-71ff7e7b5a96")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MifielAPI/MifielAPI/Utils/MifielUtils.cs b/MifielAPI/MifielAPI/Utils/MifielUtils.cs index 3dc03d9..f32fe10 100644 --- a/MifielAPI/MifielAPI/Utils/MifielUtils.cs +++ b/MifielAPI/MifielAPI/Utils/MifielUtils.cs @@ -14,7 +14,6 @@ namespace MifielAPI.Utils public static class MifielUtils { private static Regex _rgx = new Regex("/+$"); - private static SHA256 _sha256 = SHA256.Create(); private static UTF8Encoding _utfEncoding = new UTF8Encoding(); internal static bool IsValidUrl(string url) @@ -36,8 +35,8 @@ public static string GetDocumentHash(string path) { using (FileStream stream = File.OpenRead(path)) { - byte[] hashValue = _sha256.ComputeHash(stream); - return BitConverter.ToString(hashValue).Replace("-", string.Empty); + byte[] hashValue = SHA256.HashData(stream); + return Convert.ToHexString(hashValue); } } catch (Exception ex) @@ -51,8 +50,8 @@ public static string CalculateMD5(string content) try { byte[] contentBytes = _utfEncoding.GetBytes(content); - byte[] conetntHash = ((HashAlgorithm)CryptoConfig.CreateFromName("MD5")).ComputeHash(contentBytes); - return BitConverter.ToString(conetntHash).Replace("-", string.Empty); + byte[] contentHash = MD5.HashData(contentBytes); + return Convert.ToHexString(contentHash); } catch (Exception ex) { @@ -77,10 +76,10 @@ public static string CalculateHMAC(string appSecret, string canonicalString) { try { - HMACSHA1 hmacSha1 = new HMACSHA1(Encoding.UTF8.GetBytes(appSecret)); - byte[] byteArray = Encoding.ASCII.GetBytes(canonicalString); - MemoryStream stream = new MemoryStream(byteArray); - return Convert.ToBase64String(hmacSha1.ComputeHash(stream)); + byte[] hash = HMACSHA1.HashData( + Encoding.UTF8.GetBytes(appSecret), + Encoding.ASCII.GetBytes(canonicalString)); + return Convert.ToBase64String(hash); } catch (Exception ex) { diff --git a/MifielAPI/MifielAPI/packages.config b/MifielAPI/MifielAPI/packages.config deleted file mode 100644 index d9a84d8..0000000 --- a/MifielAPI/MifielAPI/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/MifielAPI/MifielAPITests/MifielApiTests.sln b/MifielAPI/MifielAPITests/MifielApiTests.sln deleted file mode 100644 index 383aec3..0000000 --- a/MifielAPI/MifielAPITests/MifielApiTests.sln +++ /dev/null @@ -1,17 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MifielApiTests", "MifielApiTests.csproj", "{EC120768-5963-4D7E-80F3-FCEC17CE5CB1}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EC120768-5963-4D7E-80F3-FCEC17CE5CB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EC120768-5963-4D7E-80F3-FCEC17CE5CB1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EC120768-5963-4D7E-80F3-FCEC17CE5CB1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EC120768-5963-4D7E-80F3-FCEC17CE5CB1}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/MifielAPI/MifielAPITests/file_hash.xml b/MifielAPI/MifielAPITests/file_hash.xml deleted file mode 100644 index 63e17c5..0000000 --- a/MifielAPI/MifielAPITests/file_hash.xml +++ /dev/null @@ -1,109 +0,0 @@ - -MIIF8TCCA9mgAwIBAgICEYcwDQYJKoZIhvcNAQELBQAwXDELMAkGA1UEBhMC -TVgxDzANBgNVBAoMBk1pZmllbDEcMBoGA1UEAwwTTWlmaWVsIEludGVybWVk -aWF0ZTEeMBwGCSqGSIb3DQEJARYPaW5mb0BtaWZpZWwuY29tMB4XDTE5MDMx -NDE4NDA1NloXDTI0MDMxMjE4NDA1NlowgccxHDAaBgNVBAMME0NhcmxvcyBa -YXZhbGEgTG9wZXoxHDAaBgNVBCkME0NhcmxvcyBaYXZhbGEgTG9wZXoxHDAa -BgNVBAoME0NhcmxvcyBaYXZhbGEgTG9wZXoxCzAJBgNVBAYTAk1YMSkwJwYJ -KoZIhvcNAQkBFhpwcnVlYmFwYXJhanVhbnpAcHJ1ZWJhLmNvbTEWMBQGA1UE -LQwNWkFDQTg1MDgwNUpYODEbMBkGA1UEBRMSWkFDQTg1MDgwNUhERlRCRzAx -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3vmM3t28n+sJZwbI -YkqnzJoR7yF6hq2ZE3TNQxw4Y0Elrfx9k/QdecbmQJxA+zAQs7jEWcy5irbm -Hgs6tS2Qx1bMaJJHdKV0NXz1Y/+/nUn4JjcU/ZYhTzpAI7WeVPyt78TctxqG -dkKXLVniPUvUmNuot14PKmDZxNJdEYon39kRoM6HpVZUrYyL66ysEWXpddB7 -kg/AQBtR1vdtBMcKQLLPKSD8QigD+cJn2hAlX48eKiT/U/5w8ecJsrVkgxaw -BJH6h8rdpynpK4jFfiOeN7yjtCdphCO6m2NbhCo6E8QNQ9TsAhFc/kE7bJWP -B8JJm4sI9dmzzQEYPhBBy+n5MQIDAQABo4IBTzCCAUswCQYDVR0TBAIwADAR -BglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4QgENBCYWJE9wZW5TU0wgR2Vu -ZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQU3eonBypoPAf8 -+sf8yOTHZVVuAX8wgbEGA1UdIwSBqTCBpoAUHJcV1V99t7rv4Xm1j7E5ucbU -vMihgYmkgYYwgYMxCzAJBgNVBAYTAk1YMQwwCgYDVQQIDANHREwxDDAKBgNV -BAcMA0dETDEPMA0GA1UECgwGTWlmaWVsMREwDwYDVQQLDAhTZWN1cml0eTEU -MBIGA1UEAwwLTWlmaWVsIE1haW4xHjAcBgkqhkiG9w0BCQEWD2luZm9AbWlm -aWVsLmNvbYICEAEwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUF -BwMBMA0GCSqGSIb3DQEBCwUAA4ICAQCKK3AzDFaFlbNIFglveD15NiGvSk6v -KvgjgaSl1CNa9SElWrMonUOdfnm5DWioaCxQlABJYZtt+E3VCFpMqpYTiGZV -ewopNGiARs309WfhDw3CDysQpttl/13sfaLO8iOrcJia6kIM2RuSHYe/n1+e -rs1LNccVgG4gPiW2QtUdCn5qKtv7gVMAkX1mMTRfVsUJ7So1AjSqJVWhMp5v -igqnEQo//kHN+Y1A2/82zuzVOvu1k0jqmG/Fs4tg28m4bY2JWOr+vcCfJM11 -U0DZjdctceAqNjBSe823DvaRSf6+HB90LGBIf4uYBN9vwMXz5XA7brPWD3M/ -qaWftUGzPY5hIKFAgrkrC+nJKl6x3XKVGveW6okA7E96tS3HNdIzNcGLvKUV -P6ExfwVfPVjtRM+1SuvSLWndZ5OXpA0XadhyPgrnMrlk1nCP9+5SsM7HHg/k -IG16TSe5ul06+8gUa6sM8QR8iFf+Q5dDkHiyLttQ18xHSNp7UvnpTEPr02KC -3nQRHhLKuDTkIrLUS3S/Be3Rl58U/1WA0a0NxapN8XU91OnSskElqXPlUaSn -mTCE5Z++PVuXHBi3GKlF9As82TzFCuakN2/+qpYCrCffJWtbZ7q9/HF67K6u -O7e9A7xZ+iFHGc4PRvvdf+WzmNz3pYrw4vYHrSyz/H9sSmXyNBRhGQ== -BavElaCgQwPDy/WbU6WcATX4omeg5Ct/jgF0hbD/hOGt7+2Ixjohnjnn8EHO -klghGlzaRKqS5DHESb7EDr3WXtoiFNTa+x+a9jFYG6Gkz7vhsQnOz88/+7uL -4/FHBM1O7g9QVPUW2wohW1kjJpctm1rIyLy6WtuRHOfNimyY6H2FtiSTkqMS -NG1fh+b+jQET11H+QVda/PKocqD9tmYqqI5TFGMhIgoMOEUf/FxZoKSgqsmL -Ltv6PjFht/kKyQIjpWbtpbtHhUuobhVV8OcnzLq6B3U5QnmIilSQwN6fd2Jv -k591qWGqO9n+UImbTEgytbVSxl0Yg/fibWtv62ucMw== -LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUlXVENDQmtHZ0F3SUJB -Z0lCTERBTkJna3Foa2lHOXcwQkFRc0ZBRENDQVVVeEZ6QVZCZ05WQkFjVERr -RnMKZG1GeWJ5QlBZbkpsWjI5dU1Sa3dGd1lEVlFRSUV4QkRhWFZrWVdRZ1pH -VWdUV1Y0YVdOdk1Rc3dDUVlEVlFRRwpFd0pOV0RFT01Bd0dBMVVFRVJNRk1E -RXdNekF4S3pBcEJnTlZCQWtUSWtsdWMzVnlaMlZ1ZEdWeklGTjFjaUF4Ck9U -UXdMQ0JEYjJ3dUlFWnNiM0pwWkdFeFJ6QkZCZ05WQkFNVFBrRjFkRzl5YVdS -aFpDQkRaWEowYVdacFkyRmsKYjNKaElGSmhhWG9nVTJWbmRXNWtZU0JrWlNC -VFpXTnlaWFJoY21saElHUmxJRVZqYjI1dmJXbGhNVFF3TWdZRApWUVFMRXl0 -RWFYSmxZMk5wYjI0Z1IyVnVaWEpoYkNCa1pTQk9iM0p0WVhScGRtbGtZV1Fn -VFdWeVkyRnVkR2xzCk1SOHdIUVlEVlFRS0V4WlRaV055WlhSaGNtbGhJR1Js -SUVWamIyNXZiV2xoTVNVd0l3WUpLb1pJaHZjTkFRa0IKRmhaaFkzSXljMlZB -WldOdmJtOXRhV0V1WjI5aUxtMTRNQjRYRFRFM01EZ3lOVEF3TURBd01Gb1hE -VEkzTURneQpOVEF3TURBd01Gb3dnZ0U1TVJjd0ZRWURWUVFIRXc1QmJIWmhj -bThnVDJKeVpXZHZiakVaTUJjR0ExVUVDQk1RClEybDFaR0ZrSUdSbElFMWxl -R2xqYnpFTE1Ba0dBMVVFQmhNQ1RWZ3hEakFNQmdOVkJCRVRCVEF4TWpFd01V -RXcKUHdZRFZRUUpFemhCZGk0Z1UyRnVkR0VnUm1VZ01UY3dMQ0JQWm1samFX -NWhJRE10TWkwd05pNGdRMjlzTGlCTQpiMjFoY3lCa1pTQlRZVzUwWVNCR1pU -RW1NQ1FHQTFVRUF4TWRRV1IyWVc1MFlXZGxJRk5sWTNWeWFYUjVJRkJUClF5 -Qk9UMDB4TlRFeEh6QWRCZ05WQkFzVEZrRmtkbUZ1ZEdGblpTQlRaV04xY21s -MGVTQlFVME14THpBdEJnTlYKQkFvVEprRmtkbUZ1ZEdGblpTQlRaV04xY21s -MGVTd2dVeTRnWkdVZ1VpNU1MaUJrWlNCRExsWXVNU2t3SndZSgpLb1pJaHZj -TkFRa0JGaHB3YzJOQVlXUjJZVzUwWVdkbExYTmxZM1Z5YVhSNUxtTnZiVEND -QWlBd0RRWUpLb1pJCmh2Y05BUUVCQlFBRGdnSU5BRENDQWdnQ2dnSUJBTXc1 -WVA3QWRsYWc0M3dPYno4d2xZSUk0M3VWN3pFYUJLbXUKWTdBZlA0SGJ2SE5k -dG9URmhGWU5iTzVvQXJsT3JMdmFZMDFkZXgwVEFTeEtZM05nWHBrSWRvcyta -UjA1OUtvVwpRMHZ2blJxa3FZL2k0b3AxUVk1MlpVL2QrNG1qMk0zOTRwSlZM -bEUzMnRsUk1WVTZuQ0pGNjllTnE1cTZZSSt5CkpQdlJwaGRvdDloT1M3NUZx -TldhU25qSms1dFoyY1l6M3pkRGFUNktOUFZDQ0VrK1g0Z3hhZkJpcHNyS0FZ -UWYKc2ZCWkZEd0MwNXY3eFIyRW9KcnRFT3ZxcTAyNkwvK2RYeG1FTjJ0Qzd6 -UzFVdzRsd2lvajcrLzdaa0pDZEtEYwpBNXB0ajN2TDNKdnc0OTNDT1VIYzda -Z3NMb1JiazlId2djVytQSGZnTkpOTUI2aVpCQkNkZ1hBRUVtYUswdnhDClc5 -V09sd2E3aFRVVEh1R0FXaFMxampjN0R2bGhIRjFhSHZFMWo3TkNjSThxTWNC -b1h6eDF6ZXpSTnd5UW9LVjIKSHBQSlZsRU1JdjRzbWxrQ2Nna2hSc0tRSDk4 -MmkwNXAxYjhaZ1lkeThFQXVMeGFQbWhueXpJN3UrbGdlYklZeApNUXBRME1C -ZW1vR21wQ3k4aEhWRjhBK3BVTFExRUY5T0czWExmNWNXYmxmb0tWVisvaGc5 -dFBXMVZpNzg5dGhuCno3aXQ5QSs4VVRHTnIzTjhPeHpTeDlqSEFhMGJsaElB -Qk4xLyt2YVVqdWhxU09hREVoYUNrM2hqdlNyZ1Q3ZU8KSkc4NUtmWHB4cWMw -ajRtRHJMcjV2TzM5SlVBSkZ1b3pzTVJqVnlTbXhrT3ZtUnlJcjNXb3oxcXVw -MTZBbmlEZApMdVJGZ2NaaEFnRURvNElCWERDQ0FWZ3dId1lEVlIwakJCZ3dG -b0FVRUlFcEtzRVNEWExOTllLMUlWaFpidmVnClNnWXdIUVlEVlIwT0JCWUVG -RnQ5QWVNWDNhQ0llOVBYeTVWRlFmTng4TDkzTUQwR0NDc0dBUVVGQndFQkJE -RXcKTHpBdEJnZ3JCZ0VGQlFjd0FZWWhhSFIwY0RvdkwyOWpjM0F1WldOdmJt -OXRhV0V1WjI5aUxtMTRPamd3T0RJdgpNQ0VHQTFVZEVRUWFNQmlDRm1Ga2Rt -RnVkR0ZuWlMxelpXTjFjbWwwZVM1amIyMHdRQVlEVlIwZkJEa3dOekExCm9E -T2dNWVl2YUhSMGNITTZMeTkzZDNjdVlXTnlNbk5sTG1WamIyNXZiV2xoTG1k -dllpNXRlQzlsWTI5dWIyMXAKWVM1amNtd3dVUVlEVlIwZ0JFb3dTREJHQmds -Z2cyUmxDb0k4Q2dFd09UQTNCZ2dyQmdFRkJRY0NBUllyYUhSMApjSE02THk5 -M2QzY3VZV055TW5ObExtVmpiMjV2YldsaExtZHZZaTV0ZUM5amNITXVhSFJ0 -YkRBUEJnTlZIUk1CCkFmOEVCVEFEQWdFQU1BNEdBMVVkRHdFQi93UUVBd0lE -NkRBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQWdFQWJzSkQKMWZiZGc4dXVZLzlU -bkFnK2hxM1BIUHgzREZDazU4cDg5ZTh5N2RsVWZwTDdoL3lUUXZmYlhvdHRo -MmJhcU5mZApucGIzZUVsd2Nla2oyemR1ZWxSallPYkg1MkJDbzNhOE44WXll -TUVwcEVBMEZaeGdGYzdwVzhBOWFSMDlOMTRkCnA3L3dzYi9sMmdPcHV3RkJq -TDc3UnhqdFlab3ZSM2FObHBvNTFETE9DK1o5dnRZME5yc2Q2OWI1U21BUlU5 -NE4KTGJqcVI4bnF2Z2lhWEwwaGQ4QXplcThiK1Z5SVhTVjVybTFPSk43QzVX -MXFjZGVHeEZBd1RyajN0T0NNaEhHbApOYTFObEdJTkpuelNCTGxIZmpZNisz -QnNkL0E1TnZZdGhMMGtndEJmTjcyU2RvNjFFL2M1dTcwa01LVHhNWkd4Clhr -aHhhNnFYQ3FyUjNIaDFhbDBTS29odURxSWo1SHNBdzJ6NldMRld6RHVGYmw2 -Q3JwdWpPSmpnODdVTTFFNnIKMFAyS09QT0srZlA2M0gybnZyT2VYYjBvb0Nn -ZTZDaTJ1Wm1BYW9kRCtnQVJZaE5wS1JSckc2N1g5MVd2b1RJaQpOeko3NFVZ -THlmU0x6bU9MYkJxMHVBbGNQdHpBZHlheExUd1A2TFd6Yk44T0NlcmlhWFIw -U1pDTm5LU2xHNHdlCkV1TXVnMkwzWlNaTngzKzk0ZFZxbDdxWXJpa21YQWx4 -WDZUZy9ZMWZSVjNrZUFNNFZoelRFMVFJWk13elVid1UKRnJWWHpMeDNxTG5P -cFovY0RJUndJSzhHVjEwNXplN1MrbUFMQ3lLdU9hS2wvM2ZFdVJqcTgzdGhr -eUxNTGpxVgphSlJKYjN0ellrMm9JN3NhZW9TQjgvNDlGa0JVbDd5VmY2dXRq -U1k9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K -MIIW+zAVAgEAMBAMDk9wZXJhdGlvbiBPa2F5MIIW4AYJKoZIhvcNAQcCoIIW0TCCFs0CAQMxDzANBglghkgBZQMEAgEFADCCAcIGCyqGSIb3DQEJEAEEoIIBsQSCAa0wggGpAgEBBgxgg2RlCoI8AgEBAgEwMTANBglghkgBZQMEAgEFAAQgNkpj0BOu95v3B0UY105jucAR2sUMOCQGQ487y3QdflgCBAF0ygcYEzIwMTgwODA5MjAzODMyLjkzMFowA4ABZKCCAUGkggE9MIIBOTEXMBUGA1UEBxMOQWx2YXJvIE9icmVnb24xGTAXBgNVBAgTEENpdWRhZCBkZSBNZXhpY28xCzAJBgNVBAYTAk1YMQ4wDAYDVQQREwUwMTIxMDFBMD8GA1UECRM4QXYuIFNhbnRhIEZlIDE3MCwgT2ZpY2luYSAzLTItMDYuIENvbC4gTG9tYXMgZGUgU2FudGEgRmUxJjAkBgNVBAMTHUFkdmFudGFnZSBTZWN1cml0eSBQU0MgTk9NMTUxMR8wHQYDVQQLExZBZHZhbnRhZ2UgU2VjdXJpdHkgUFNDMS8wLQYDVQQKEyZBZHZhbnRhZ2UgU2VjdXJpdHksIFMuIGRlIFIuTC4gZGUgQy5WLjEpMCcGCSqGSIb3DQEJARYacHNjQGFkdmFudGFnZS1zZWN1cml0eS5jb22gghC4MIIIWTCCBkGgAwIBAgIBLDANBgkqhkiG9w0BAQsFADCCAUUxFzAVBgNVBAcTDkFsdmFybyBPYnJlZ29uMRkwFwYDVQQIExBDaXVkYWQgZGUgTWV4aWNvMQswCQYDVQQGEwJNWDEOMAwGA1UEERMFMDEwMzAxKzApBgNVBAkTIkluc3VyZ2VudGVzIFN1ciAxOTQwLCBDb2wuIEZsb3JpZGExRzBFBgNVBAMTPkF1dG9yaWRhZCBDZXJ0aWZpY2Fkb3JhIFJhaXogU2VndW5kYSBkZSBTZWNyZXRhcmlhIGRlIEVjb25vbWlhMTQwMgYDVQQLEytEaXJlY2Npb24gR2VuZXJhbCBkZSBOb3JtYXRpdmlkYWQgTWVyY2FudGlsMR8wHQYDVQQKExZTZWNyZXRhcmlhIGRlIEVjb25vbWlhMSUwIwYJKoZIhvcNAQkBFhZhY3Iyc2VAZWNvbm9taWEuZ29iLm14MB4XDTE3MDgyNTAw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 - \ No newline at end of file diff --git a/MifielAPI/MifielApiTests/DocumentsTests.cs b/MifielAPI/MifielApiTests/DocumentsTests.cs deleted file mode 100644 index 81da668..0000000 --- a/MifielAPI/MifielApiTests/DocumentsTests.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System.IO; -using System.Collections.Generic; -using NUnit.Framework; -using MifielAPI; -using MifielAPI.Objects; -using MifielAPI.Dao; - -namespace MifielApiTests -{ - [TestFixture] - public class DocumentsTests - { - const string APP_ID = "7c938f8e2ff2f083d454127bffd7d4c8bc2c2dee"; - const string APP_SECRET = "1lXeVVMbNe5IH+INgMeQX463fsixeWEMMSLS4WXPwxpR9RwStD3iE4XaNMXbY8YigIxCQP9gb/8xZI3XILN2Rw=="; - private static ApiClient _apiClient; - private static Documents _docs; - private static string _pdfFilePath; - - private readonly string _currentDirectory = Path.GetFullPath(TestContext.CurrentContext.TestDirectory); - - [SetUp] - public void SetUp() - { - _pdfFilePath = Path.Combine(_currentDirectory, "test-pdf.pdf"); - _apiClient = new ApiClient(APP_ID, APP_SECRET); - _docs = new Documents(_apiClient); - } - - [Test] - public void Documents__WrongUrl__ShouldThrowAnException() - { - Assert.Throws(() => _apiClient.Url = "www.google.com"); - } - - [Test] - public void Documents__CorrectUrl__ShouldNotThrowAnException() - { - _apiClient.Url = "https://app-sandbox.mifiel.com"; - } - - [Test] - public void Documents__FindAllDocuments__ShouldReturnAList() - { - SetSandboxUrl(); - var allDocuments = _docs.FindAll(); - Assert.IsNotNull(allDocuments); - } - - [Test] - public void Documents__Close__Should_Success() - { - SetSandboxUrl(); - var docId = _docs.FindAll()[0].Id; - var closeDocument = _docs.Close(docId); - Assert.IsTrue(closeDocument.Success); - } - - [Test] - public void Documents__SaveWithFilePath__ShouldReturnADocument() - { - SetSandboxUrl(); - var document = new Document() - { - File = Path.Combine(_currentDirectory, _pdfFilePath), - ManualClose = false, - CallbackUrl = "https://requestb.in/1cuddmz1" - }; - - var signatures = new List(){ - new Signature(){ - Email = "juan@mifiel.com", - TaxId = "ZAAJ8301061E0", - SignerName = "Juan Antonio Zavala Aguilar" - } - }; - - var viewers = new List() { - new Viewer(){ - Name = "Juan Zavala", - Email = "ja.zavala.aguilar@gmail.com" - } - }; - - document.SendMail = true; - document.SendInvites = true; - document.Signatures = signatures; - document.Viewers = viewers; - document = _docs.Save(document); - Assert.IsNotNull(document); - } - - [Test] - public void Documents__AppendPDFBase64InOriginalXml__ShouldGenerateNewXML() - { - var pathOriginalXml = Path.Combine(_currentDirectory, "file_hash.xml"); - var pathNewXml = Path.Combine(_currentDirectory, "file_with_hash_and_document.xml"); - MifielAPI.Utils.MifielUtils.AppendPDFBase64InOriginalXml(_pdfFilePath, pathOriginalXml, pathNewXml); - Assert.True(File.Exists(pathNewXml)); - } - - [Test] - public void Documents__SaveWithOriginalHashAndFileName__ShouldReturnADocument() - { - SetSandboxUrl(); - Document document = new Document() - { - OriginalHash = MifielAPI.Utils.MifielUtils.GetDocumentHash(_pdfFilePath), - FileName = "PdfFileName", - ManualClose = false, - }; - - var signatures = new List(){ - new Signature(){ - Email = "juan@mifiel.com", - SignerName = "Juan Antonio Zavala Aguilar" - } - }; - - var viewers = new List() { - new Viewer(){ - Name = "Juan Zavala", - Email = "ja.zavala.aguilar@gmail.com" - } - }; - - document.Signatures = signatures; - document.Viewers = viewers; - - document = _docs.Save(document); - Assert.IsNotNull(document); - } - - [Test] - public void Documents__SaveWithoutRequiredFields__ShouldThrowAnException() - { - SetSandboxUrl(); - var document = new Document() { CallbackUrl = "http://www.google.com" }; - - Assert.Throws(() => _docs.Save(document)); - Assert.IsNotNull(document); - } - - [Test] - public void Documents__Find__ShouldReturnADocument() - { - SetSandboxUrl(); - Documents__SaveWithOriginalHashAndFileName__ShouldReturnADocument(); - var allDocuments = _docs.FindAll(); - if (allDocuments.Count > 0) - { - Document doc1 = _docs.Find(allDocuments[0].Id); - Assert.IsNotNull(doc1); - } - else - { - throw new MifielAPI.Exceptions.MifielException("No documents found"); - } - } - - [Test] - public void Documents__Delete__ShouldRemoveADocument() - { - SetSandboxUrl(); - Document document = new Document() - { - OriginalHash = MifielAPI.Utils.MifielUtils.GetDocumentHash(_pdfFilePath), - FileName = "PdfFileName" - }; - - document = _docs.Save(document); - _docs.Delete(document.Id); - } - - [Test] - public void Documents__RequestSignature__ShouldReturnASignatureResponse() - { - SetSandboxUrl(); - var document = new Document() - { - OriginalHash = MifielAPI.Utils.MifielUtils.GetDocumentHash(_pdfFilePath), - FileName = "PdfFileName" - }; - - document = _docs.Save(document); - - SignatureResponse signatureResponse = _docs.RequestSignature(document.Id, - "enrique@test.com", "enrique2@test.com"); - Assert.IsNotNull(signatureResponse); - } - - [Test] - public void Documents__SaveFile__ShouldSaveFileOnSpecifiedPath() - { - SetSandboxUrl(); - Document document = new Document() { File = _pdfFilePath }; - - document = _docs.Save(document); - - _docs.SaveFile(document.Id, Path.Combine(_currentDirectory, "pdf_save_test.pdf")); - } - - private void SetSandboxUrl() - { - _apiClient.Url = "https://app-sandbox.mifiel.com"; - } - } -} diff --git a/MifielAPI/MifielApiTests/MifielApiTests.csproj b/MifielAPI/MifielApiTests/MifielApiTests.csproj deleted file mode 100644 index 44396e6..0000000 --- a/MifielAPI/MifielApiTests/MifielApiTests.csproj +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Debug - AnyCPU - {EC120768-5963-4D7E-80F3-FCEC17CE5CB1} - Library - MifielAPITests - MifielAPITests - 0.0.4 - v4.5 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - - - true - bin\Release - prompt - 4 - false - - - - - ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll - - - - - - - - - - Always - - - Always - - - - - {D5C2109D-2E52-41D6-9B32-71FF7E7B5A96} - MifielAPI - - - - \ No newline at end of file diff --git a/MifielAPI/MifielApiTests/Properties/AssemblyInfo.cs b/MifielAPI/MifielApiTests/Properties/AssemblyInfo.cs deleted file mode 100644 index 657726e..0000000 --- a/MifielAPI/MifielApiTests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("MifielAPITests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("${AuthorCopyright}")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/MifielAPI/MifielApiTests/packages.config b/MifielAPI/MifielApiTests/packages.config deleted file mode 100644 index 2f6bc1e..0000000 --- a/MifielAPI/MifielApiTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/README.md b/README.md index edaf9e2..6e9fa82 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,18 @@ C# SDK for [Mifiel](https://www.mifiel.com) API. Please read our [documentation](http://docs.mifiel.com/) for instructions on how to start using the API. ## Installation -TODO + +Requires **.NET 8** or later. The client is published on NuGet as [MifielAPIClient](https://www.nuget.org/packages/MifielAPIClient). + +```shell +dotnet add package MifielAPIClient +``` + +Or from the Visual Studio Package Manager Console: + +```shell +Install-Package MifielAPIClient +``` ## Usage @@ -176,3 +187,38 @@ Certificate methods: Certificates certificates = new Certificates(apiClient); certificates.Delete("id"); ``` + +## Releasing + +This SDK ships as the NuGet package **MifielAPIClient** ([nuget.org/packages/MifielAPIClient](https://www.nuget.org/packages/MifielAPIClient)). It targets `net8.0` and is built with the .NET SDK (`dotnet pack` / `dotnet nuget push`). + +1. **Bump the version** in `MifielAPI/MifielAPI/MifielAPI.csproj` (``) and add a heading in `CHANGELOG.md`. The `User-Agent` package version is read from that assembly attribute; do not hard-code it elsewhere. +2. **Pack:** + + ```shell + dotnet pack MifielAPI/MifielAPI/MifielAPI.csproj -c Release -o artifacts + ``` + + The artifact is `artifacts/MifielAPIClient..nupkg`. +3. **Publish to nuget.org** with an API key from [nuget.org/account/apikeys](https://www.nuget.org/account/apikeys). Versions cannot be overwritten once pushed. + + ```shell + dotnet nuget push artifacts/MifielAPIClient..nupkg \ + --source https://api.nuget.org/v3/index.json \ + --api-key "$NUGET_API_KEY" + ``` +4. **Tag the git commit** and create a GitHub release: + + ```shell + git tag v + git push origin v + gh release create v --title "v" --notes-file CHANGELOG.md + ``` + +The listing usually appears on nuget.org within a few minutes. Confirm at `https://www.nuget.org/packages/MifielAPIClient/`. + +Smoke tests (optional) use the same SDK: + +```shell +dotnet test MifielAPI/MifielAPI.sln --filter Category=Smoke +``` diff --git a/global.json b/global.json new file mode 100644 index 0000000..391ba3c --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestFeature" + } +}