From 06eea425140d8625c38941a63c5866e3035e7151 Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 21:22:40 -0600 Subject: [PATCH 1/8] modernize repo and setup CI --- .github/workflows/ci.yml | 31 ++++ .gitignore | 3 + CHANGELOG.md | 4 +- MifielAPI/.nuget/NuGet.Config | 6 - MifielAPI/.nuget/NuGet.targets | 144 ------------------ MifielAPI/MifielAPI.sln | 14 +- MifielAPI/MifielAPI/ApiClient.cs | 31 +++- .../MifielAPI/Exceptions/MifielException.cs | 7 +- MifielAPI/MifielAPI/MifielAPI.csproj | 97 +++--------- .../MifielAPI/Properties/AssemblyInfo.cs | 36 ----- MifielAPI/MifielAPI/Utils/MifielUtils.cs | 17 +-- MifielAPI/MifielAPI/packages.config | 5 - .../MifielApiTests/MifielApiTests.csproj | 63 +++----- .../MifielApiTests/Properties/AssemblyInfo.cs | 26 ---- MifielAPI/MifielApiTests/packages.config | 4 - README.md | 48 +++++- global.json | 6 + 17 files changed, 163 insertions(+), 379 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 MifielAPI/.nuget/NuGet.Config delete mode 100644 MifielAPI/.nuget/NuGet.targets delete mode 100644 MifielAPI/MifielAPI/Properties/AssemblyInfo.cs delete mode 100644 MifielAPI/MifielAPI/packages.config delete mode 100644 MifielAPI/MifielApiTests/Properties/AssemblyInfo.cs delete mode 100644 MifielAPI/MifielApiTests/packages.config create mode 100644 global.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..46d3cbf --- /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 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + 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 + env: + MIFIEL_APP_ID: ${{ secrets.MIFIEL_APP_ID }} + MIFIEL_APP_SECRET: ${{ secrets.MIFIEL_APP_SECRET }} + MIFIEL_BASE_URL: ${{ secrets.MIFIEL_BASE_URL }} diff --git a/.gitignore b/.gitignore index 3a2238d..7fae868 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,9 @@ publish/ *.nuget.props *.nuget.targets +# Packed artifacts +artifacts/ + # 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/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.sln b/MifielAPI/MifielAPI.sln index 1f60dcf..0a6370c 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", "MifielAPITests\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..8d05212 100644 --- a/MifielAPI/MifielAPI/ApiClient.cs +++ b/MifielAPI/MifielAPI/ApiClient.cs @@ -1,15 +1,33 @@ 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 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; } @@ -67,7 +85,6 @@ 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()) { @@ -109,19 +126,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..87d97bc 100644 --- a/MifielAPI/MifielAPI/MifielAPI.csproj +++ b/MifielAPI/MifielAPI/MifielAPI.csproj @@ -1,96 +1,33 @@ - - - - + - 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 + false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\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.csproj b/MifielAPI/MifielApiTests/MifielApiTests.csproj index 44396e6..a62a99a 100644 --- a/MifielAPI/MifielApiTests/MifielApiTests.csproj +++ b/MifielAPI/MifielApiTests/MifielApiTests.csproj @@ -1,57 +1,30 @@ - - - + - Debug - AnyCPU - {EC120768-5963-4D7E-80F3-FCEC17CE5CB1} - Library + net8.0 MifielAPITests MifielAPITests - 0.0.4 - v4.5 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - false - - - true - bin\Release - prompt - 4 - false + disable + disable + false + latest - - - ..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll - + + + - - + - - - Always + + PreserveNewest - - Always + + PreserveNewest + + + PreserveNewest - - - {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..f480679 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). + +``` +dotnet add package MifielAPIClient +``` + +Or from the Visual Studio Package Manager Console: + +``` +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:** + + ``` + 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. + + ``` + 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: + + ``` + 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: + +``` +dotnet test MifielAPI/MifielAPI.sln --filter Category=Smoke +``` diff --git a/global.json b/global.json new file mode 100644 index 0000000..8eb62be --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMajor" + } +} From 6208aae086ed03458a991996b33c8dfec0b859c2 Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 21:24:53 -0600 Subject: [PATCH 2/8] only build on push --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46d3cbf..3ac7a46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ name: CI on: push: - pull_request: jobs: test: From c236a2a7514c7bfd5e35d978ae2a502335f64ed2 Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 21:28:38 -0600 Subject: [PATCH 3/8] fix ci --- .github/workflows/ci.yml | 4 ++-- MifielAPI/MifielAPITests/MifielApiTests.sln | 17 ----------------- global.json | 2 +- 3 files changed, 3 insertions(+), 20 deletions(-) delete mode 100644 MifielAPI/MifielAPITests/MifielApiTests.sln diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ac7a46..3dee953 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,10 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: "8.0.x" 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/global.json b/global.json index 8eb62be..3fea262 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "8.0.0", - "rollForward": "latestMajor" + "rollForward": "latestFeature" } } From d3e0409f963365779f92c3f964b1b25c8ad57327 Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 21:32:06 -0600 Subject: [PATCH 4/8] Fix CI restore by moving tests to a case-safe path. Linux could not find MifielAPITests/MifielApiTests.csproj because Git stored the project as MifielApiTests. Co-authored-by: Cursor --- .dockerignore | 7 ++ Dockerfile | 15 ++++ .../CsharpDocumentSmokeTests.cs | 78 ++++++++++++++++++ .../DocumentsTests.cs | 0 .../MifielApiTests.csproj | 0 .../csharp-test-pdf.pdf} | Bin .../file_hash.xml | 0 MifielAPI/MifielAPI.Tests/test-pdf.pdf | Bin 0 -> 20419 bytes MifielAPI/MifielAPI.sln | 2 +- 9 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs rename MifielAPI/{MifielApiTests => MifielAPI.Tests}/DocumentsTests.cs (100%) rename MifielAPI/{MifielApiTests => MifielAPI.Tests}/MifielApiTests.csproj (100%) rename MifielAPI/{MifielApiTests/test-pdf.pdf => MifielAPI.Tests/csharp-test-pdf.pdf} (100%) rename MifielAPI/{MifielAPITests => MifielAPI.Tests}/file_hash.xml (100%) create mode 100644 MifielAPI/MifielAPI.Tests/test-pdf.pdf 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/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a0cdb91 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# Build and run the NUnit smoke test with the .NET SDK (this library targets net8.0). +# Usage: +# docker build -t csharp-api-client-smoke . +# docker run --rm csharp-api-client-smoke +# Optional env: MIFIEL_APP_ID, MIFIEL_APP_SECRET, MIFIEL_BASE_URL +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 + +WORKDIR /src +CMD ["dotnet", "test", "MifielAPI/MifielAPI.sln", "--configuration", "Release", "--no-build", "--filter", "Category=Smoke"] diff --git a/MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs b/MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs new file mode 100644 index 0000000..daee1c0 --- /dev/null +++ b/MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NUnit.Framework; +using MifielAPI; +using MifielAPI.Dao; +using MifielAPI.Objects; +using MifielAPI.Utils; + +namespace MifielApiTests +{ + [TestFixture] + [Category("Smoke")] + public class CsharpDocumentSmokeTests + { + const string DefaultAppId = "7c938f8e2ff2f083d454127bffd7d4c8bc2c2dee"; + const string DefaultAppSecret = "1lXeVVMbNe5IH+INgMeQX463fsixeWEMMSLS4WXPwxpR9RwStD3iE4XaNMXbY8YigIxCQP9gb/8xZI3XILN2Rw=="; + const string DefaultBaseUrl = "https://app-sandbox.mifiel.com"; + + private readonly string _currentDirectory = Path.GetFullPath(TestContext.CurrentContext.TestDirectory); + + [Test] + public void CreateDocument_ShouldReturnADocumentWithId() + { + var appId = FirstNonEmpty(Environment.GetEnvironmentVariable("MIFIEL_APP_ID"), DefaultAppId); + var appSecret = FirstNonEmpty(Environment.GetEnvironmentVariable("MIFIEL_APP_SECRET"), DefaultAppSecret); + var baseUrl = FirstNonEmpty(Environment.GetEnvironmentVariable("MIFIEL_BASE_URL"), DefaultBaseUrl); + var pdfFilePath = Path.Combine(_currentDirectory, "csharp-test-pdf.pdf"); + + Assert.IsTrue(File.Exists(pdfFilePath), "Expected fixture PDF at " + pdfFilePath); + + var apiClient = new ApiClient(appId, appSecret) { Url = baseUrl }; + var docs = new Documents(apiClient); + + var document = new Document() + { + File = pdfFilePath, + ManualClose = false, + SendMail = false, + SendInvites = false, + Signatures = new List() + { + new Signature() + { + Email = "smoke-test-" + Guid.NewGuid().ToString("N").Substring(0, 8) + "@mifiel.com", + SignerName = "Smoke Test Signer" + } + } + }; + + document = docs.Save(document); + + Assert.IsNotNull(document); + Assert.IsFalse(string.IsNullOrEmpty(document.Id), "Created document should have an id"); + + var newtonsoft = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(assembly => assembly.GetName().Name == "Newtonsoft.Json"); + Assert.IsNotNull(newtonsoft, "Newtonsoft.Json should be loaded when deserializing the document response"); + Assert.AreEqual(13, newtonsoft.GetName().Version.Major, + "Expected Newtonsoft.Json 13.x after the dependabot bump, got " + newtonsoft.GetName().Version); + + var json = MifielUtils.ConvertObjectToJson(document); + var parsedFromJson = MifielUtils.ConvertJsonToObject(json); + Assert.AreEqual(document.Id, parsedFromJson.Id, "JSON round-trip with Newtonsoft.Json should preserve id"); + StringAssert.Contains("\"" + document.Id + "\"", json); + + Console.WriteLine("CREATED_DOCUMENT_ID=" + parsedFromJson.Id); + Console.WriteLine("NEWTONSOFT_JSON_VERSION=" + newtonsoft.GetName().Version); + TestContext.WriteLine("CREATED_DOCUMENT_ID=" + parsedFromJson.Id); + } + + private static string FirstNonEmpty(string preferred, string fallback) + { + return string.IsNullOrEmpty(preferred) ? fallback : preferred; + } + } +} diff --git a/MifielAPI/MifielApiTests/DocumentsTests.cs b/MifielAPI/MifielAPI.Tests/DocumentsTests.cs similarity index 100% rename from MifielAPI/MifielApiTests/DocumentsTests.cs rename to MifielAPI/MifielAPI.Tests/DocumentsTests.cs diff --git a/MifielAPI/MifielApiTests/MifielApiTests.csproj b/MifielAPI/MifielAPI.Tests/MifielApiTests.csproj similarity index 100% rename from MifielAPI/MifielApiTests/MifielApiTests.csproj rename to MifielAPI/MifielAPI.Tests/MifielApiTests.csproj diff --git a/MifielAPI/MifielApiTests/test-pdf.pdf b/MifielAPI/MifielAPI.Tests/csharp-test-pdf.pdf similarity index 100% rename from MifielAPI/MifielApiTests/test-pdf.pdf rename to MifielAPI/MifielAPI.Tests/csharp-test-pdf.pdf diff --git a/MifielAPI/MifielAPITests/file_hash.xml b/MifielAPI/MifielAPI.Tests/file_hash.xml similarity index 100% rename from MifielAPI/MifielAPITests/file_hash.xml rename to MifielAPI/MifielAPI.Tests/file_hash.xml diff --git a/MifielAPI/MifielAPI.Tests/test-pdf.pdf b/MifielAPI/MifielAPI.Tests/test-pdf.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dd72052991d83500080f5a7ae778274e0888e3f7 GIT binary patch literal 20419 zcmeIabyS#~3Tc}W4 zpoNxtd(SWQ^!T~={oVDw-&)@vcNR>ZdFGitd-mQldyj1)V=WyiptL-#(4)t5OSDu9 zyt2IB&hE4-Dl!IGFANfo4Z)y!fiecXAXzXFDszPwDx=K{l$Qg^$rusdn-ksx<-js} zymq``IS4P|*+ET>7UP9Jhr;`JffgwDTP0c;ls6hpsmbVIJ^V3v866L#Kj!BmGH@+B6{rG4Q&tfOfq`{&zzT{A8d^|okb(wK z5vmDN&;o(fR3IQA3=Gwgg}@+Suq;?dQ&UR|s4WKtD?sGHnmTf74l-~rG$zEv`{4nW%eBTY_dR2WIrg#j9xE}9#O2(A|={sg43+5&DR1X)vAfF{@rFP+`z+7K3wP_yD?Qx7EqELKyR z{FTDMb8>#KOcU$xXNM~Wo@Os*TK;%vrEK{HN+k z_8nnVYX+C9Go*8Qbn_o31>A{8^GqjvK(dd^?k(8sd5HR>VV*b;C>7lO3HMogX^g-CS8 z)jHjdt4`EE&?z;a?P}k*^wb`a)F1uWgnn{hd_NL47RwUiU+Ycoz&|p)&?T)DGkZ%| z&%fzY%9IsJ`=vYcM5{i;IR?RJuh%H4vJ6Vs`gjgj2XxN{JAAl#ZIrW{uZ7m;(Hv-d zS5S-KJ~SRR9NpQBb@bwCum^iBB+QH?m`-qoRDX;FEoiCBfF-mi7LP9<1A-D=L!P0j zdizxqGbDFzFVN+A%(=xFwqI=}5!sU1EKWaWGAv7gZ4&Xp~erz2BVCWdGpx;o91OEY2QJM{tKX%v_x@W$)& z9<(Ie_`#~*uK7<=taOq-a2a1m;(GKcY=$0FC z`}2MRQC^0i2p-O<2ED*V+-RYZ_x`+GY56}2MScP`n%oj_2MG&Cxj7rFCjX!YW;Pxr+ zexJtsM3L8iav^SNAHN)xdA@t}Sf`W_FA-w-$3x%ZujldM@%Q-6!E^ByDUni<{&0m9 zqLkb9wh5euB_g%iHtzDpBDGf5daK13YIF0v<%^!xq4VH({I$eyzhxD7l^EiMZP(Qm^w2N-EtF zia9--8|{um32$C*P7~hHa1%Y3jsfV-&F9@toBuDtjbX!s-Q-KgumeMt@XM|lV^`8oLwTx*Qtk-fEMT~;X81`_fM#yy;K`SRk4t4K zZ%VVTq(3OQ{Cv&jZ2wD9KDJ^3w&IxwA5{#g>3-`sbHE(zx=-4fcF^VSvY!u(;i+@J zQJkxJ(X!E(kG+>HD{jPvT-CPz$Qd+B64I$Z@zlkCC#S<_q4W9t(h9s6r^D$vyX8rU zS3jS}qcPv&N)=UfwdGud8l7Zerzd>nl9GWrQCVp!e10~!K5}F zSTR3@O5{u{JcI6jsBqaXW;1rA&rbuclASTeA1Vkd?{Q9g7>(xW$duHH!J5+xNjQJ{ z=^q8pxz>GhZ^rL0AjEXg`hg|yLnpJ27-@TRK>nj>k!xC>*Fl9K+m~pO&Wfj9IjS5B z37b|G2^+a3$LUA&3-9y;cilb2tzo68uWp#ymF5Yrybo4#2l`iD{GeX1t`H)`-94$itGvO_GLK

s;#z>*$b5Ap)!&4^gLK4rlC~3yh3U^QDdo|^@$|DW#RjZaw5@%TB zpMLnMV3yRn$)w2YejYy7sqX=&2Qx5waHN8#cmKGv`DysIo#M58c%?(CQ-pdWxOS(7 zTWWnKc_W$qD7yBGu4WG1o6GHb2Vz$haDpMKbnO0Z#(|b2~M}mp0ZmCI{4_T zUdwg4e zMtETG|0l{nZ31Wrydh#BW&r#E5I4;zrqnl$6cW>?&nOqwJWnFpTx={eTfiF<(zb%j zL1rej|qR=|vP}>2uL*A!ON-ak7CZ^RMRePigtHU(kYB zC^+iENBbouI=s}3GzAqejxYd$^D3OF_;vxrf#W7dfuz~@QT;ns^4$k?&6i2g#gF$e zOup&~V0&x6uC;CP&BVP{FEe%V-ZlzGn>-3brNiTVBQ7OvwqkwYf;Ws=IF;Y|W#>~) zVscnEXrj!YuYUva$(d{T>*o&tVORO55_X+oB&zt;u4Xgw8S~tL~}i&fGI7zV8n6cd93kc`(DJFciVFPf~}YIW(Hrf(LqpHV5A9$Y9?7Sjs%bm z6k=D0Pp->EFXEr^*K)U>G2h*nQJX5vNxNQ>?GVceA*+KdgK<|TOf((wG;L5*@eJO} z*aPJ#e9#?!%rPqpVZH7P1U6twxA~)GU08^oiEXi4URFhKwmZyHP8h%+dpn;Ei{A3y zDL?Gk+*~}VSFoIyX0L|Z9MEMJM$oyC>R*jwPfDO&<{PZ{;hhgP=c{!$vV0dAEa%>~ zAUS_1jG6O$HBe_w+B+kBw_q0lpr0R{muLr2EKz_J2Mf3(T~PfV+buRBg4aL?(=qDr zl|D$JHc4zuA8nPCN}2N|is>6imWydJW|ULtn`ZF+T~Y=`?qj(&II#xbFmR*K{Sh2o zJ`+a6vD34=#?cuMAQDk?(u-8dk#(NdMpMxETQ@nb!lL`_-$O@}+=$E43;~pEWM|xk zt$dod2Nh>iBN)PtGa{9!OCzdp^p@Y99bQZZ3v!mbu8V}q^H9lZht4Rdcx4;u;tX{1 zv$^=FaGnZFUiE6akV`;xkRBa*$FIi?3i|^F4Qm zYg3v=b1_K;@#`=tBBfHpeo*1lN&YSuenX1}_%o`i<-D5MILDS-M=~+cI;`I~PQ6-$ zmztqfc4z&XzwxLF4zB9}u!BSNL!~)NHP2#-m zf@<8gYcClT3|rNAN&U0_k~y%6)CvVpG}0NmF0dqjTVMjfChE_uHkoNY-V;j3*}J zhI2)sJ#Qt_NQK8ow{8_LJnFVicF3s2td>`N&Daj|J$1Z>|FD}>ylw97{KJWr145!` zyE*F#u-j`2;ceDA<+AEBidXxhoBC?PA;;RpOLGQfufVgayxU$oX}X7m`PaEGxO0`c zC8fU9r!x}>i(!ob8E8Z?2OS@HeURq)7?RfM=`vE38^dV-@=5GKpXklkgUmGnu<%3L zg}KcZRn2|#gQF$~!8Ry^$I}*OAt^D+y?MbvZPn&QnOCjz$&5!OP6$0eLyAd?ajQnY zDEXdLT7N?zO+cBa$%DYDhxNgkj@4?M70+f1+TXZv+qAiAc}#z8S=^BVQX+<0eA=fA zO9cwWn2)uTHdIi}blhz16#S*45{4%aXfWdqwK%sGbZ!(zhjMfXPCH7j2hAI&?8%eat&8sGn=(1bFudoR(Fg>Q z6#6T+mqBKrM!I0j=uF~Tl+_~mk{SISVr5QNW=m))U(W^Lj8AEvMip;_Ce`rtN|fLi z`T5innayC~sd;v)Gdq>8Qvr)s(gEq7t0tzlhVBqZl^ZCDT(ZZiKY2_uf$}9<&zr0f zm*=*B%T0sSIK22`&A;e)2!;7Ku<=eqmiGFZHO*$eA9yF%W}p>ONl*$W{$!uW(Y9HG z+&3ua_ZJ6ivm%FD%QN_QZkU;Hh8lwhEXS%drOo3ish(_xeSK2E*gR?8R1DOnM$hs~)98kg9 zvGA1ifxn1Px9xJQ2v>kek0ie|XD)l{-ox!xSgAMLjk3q26t#X~8nkW=KHYb|w8X&8 zD$vsPr1SIJGgBcdYTmL@p*D=2n>08$brrW7fERp%K#J*GviysoN=ARG4UQ!}O`)40WhE(Q}^#zcxgq}$NKy*z}X-H3kNwjUV7!jtRN^P-n@-{+qh zjwQmlg0&0atWJ(_5WS@Za_@Scodk{}F|36XmHP7Zi|R_TsW??TuK0>U%T} zK!rSb*uKra)zdt&7x}@ncqTu}>XoG)wlz;|92HGLuuC-t2 zqV-jwo7tFSXdr_WNJkS?V6IP$poG!|7L+iA`fHdmr--bJz@rlFQS*#mJ_FVr51@8v^squ40>|@iJisa28_EFDT3aJl)T3q(x zYVN!WoCV;(tMEw51bRY@q28e35~#$D+Z@SX11w@8Z^iWbArp=8Za7 zt;^OG&yJ*>9oqo$5fE3uM=F*I$^2v%4rx^+P)ij^zaGl^{AD3}fxZLG0%)n8t{uA<&>ax673NeH}c zf6c8if+dr#`vbadu;`YX&VoI>R-1-wce}bn5_|n!85L^dqCf?2D??N9eTG&xe7K!S zMs%e3?`=icF*%J*Y+V>passQw=D5!~_3dYh3-ti=G8dmHBdNzW=X$4_du{^npk&Oblv!R_l2RsXhHJF+!(guSocRi zUf}NRTrRUXpmf)_J)~4@m`##f=nu1H|9nCQ2=u zVfKM`s=8B~&PA^WdKSIGbIEtrg)svhE_xOzOXLu7IsrH{XRf{bnnXe&iM_{z-a#+( z&wr_SJU|!y=>n+3asGvslqg;b5LZ{@P-{7z*HkZ-FCEZ{?ZQ^mMY6r(f?HEEcDYNCE-$UzF98%laA2jSXVM z>L(ntGpBCLZ8azh3@i@?h}vr0PZu=~ug`SRUtjfT%NE?aD}%eWZ z6%(PyW77;nI5n{c)_I@DQW#xS;IX4;dW|x^kKr|+1Z0tdtOAUi?QM-jV~I_VBv!Rk zrS#GWV>{AB3?ZD}KWa3oksqU~Ga9|N#A0iX;X<_HCHb|{g4!X|+IFV(f~Mof&N>!) zSpQ7M8)y3N(V+4yKV-1#>x)M<`-3OfpIqLsu(A)!`jF5=yj8WFc<+(niGd+P@_U|@ z(dxjZEOh!a1;ocn!Mfwc8xfzyYXpOhGZ|jy z)TFx@SQL6_OGTMH8r8P;%!_Rip3%DFX%2))w&YU}yt?%djFmiF#6#YHPTuW)Lenwq zu*>S8Q-*oS=pF9tZsYnruLpr2$0UI!ke7L$!TOH1r;5@58_K?}bY+9_hb3c%cIG4c zZb-(G(a5c`chd;_)w)*ZHyPy;`f&WDp-x(E3#O_11XPeVwA6#PgQ(6YlzsS zcu7gwQ|%0smS;#?MxRRFBj@(ktqXE_!H@1yQ}^Gy!UXy(FSe|^GSsltKdI3FehRGd zLQ8yb6e%Uj;n z=Sp~Nw8+~ZdaLI}9(a#0?yt#h1uI?v&$lk|0qQaNnJEGg4t;EA=j(;5>tG#dFfzT@Gt!k_{q%Qn&3Wt|r#xGSF#{8YWtB-=|WFF{N|RBd&;r0qVTy z_Ut3`H-02l>~?m#6nqfeskDb1#WnDPDurvkf$QZI2>PMxnvD@IFeqok$m1E<1**!( z=4XUS!Sy7LdANuy2~_)C;^WF^rL)QnO_YFR1v#A)fK+!F-Sc6qx z(%I)r=DX*mQ?*Ce!(*$es^#Giif?KsS!?S`u4rp(Yh^iqY9U>bL>M6Fy!wl?h|ENWHYL=oLf7 z@(wHQ=1bNFn19CQ{LvilAunX`QFG$C?)c0|MqG2`>&=%*rG20VmW;jLJAsm%`j;l? zol9QxWmZO+^faN}N%dDbuM#Zuczxn(`r1nk7`)^QYQjA6cZL|^n2PhU7FyK#%_Buf zk!PA4F=5Z|gnzCm2|LXGHpf0t^UcWxT1-Nt>qN7rK23qjd=8OvrE>B|!G=L<9w8jV zT!TfSh9KQ?utlP9b1&mfmW+&^dx5GgbX>yL^9c{0F)@4!2e3zR1s=Z2PO6Z7)^U|P z;)6t%x>Xyu|6aSNUm?(Qj6qxzhHr0)eV%!YUhc?shjh4{9oNfoadcd=o|&p!j<~Pi zXc@9LxIv-urZs#lKXfO`cD^8^+Io|qo4Jv-w;OO3Wy6+r%X2eSg{G$Bkk~bO=Na=5 z1N+bQLShJ?#q$N0&l>R`7;JbLx<7bb#BTF03x1?*zcMjU5R)fCQr+pt(1YiHnzO6& z_G9qs^;v79QMp_91foxQzPg)#2-4?J6>{E?sqtFzw<~2p)}=DesKP6Zqt4u#wnV%U zwkJcjTl)6xLevS-kE%Jm)>^Za?AX}3I>ScR0=lWNj= zm>zEQN50P^rd59&+h0XWF!U%tQP^Zx)GK3-V}U-!lbQiyWa~po@e&3NUQ)Tq>iEQB zsX7Twc5bmS!a~Nwr8j;|Y}ml3i$X6~D|$3+pUfH7)U9apSTfB#M{q#NlM_iFlviaOD8hhzOQMtStS-9c(1S2-j$@)F=vY|7xU!! zWl=%M+H9MldDno+YNv}S@H3w2{+*ja3tz(`bw*-XsrN3;KT?DiDIKp7Y5H+JDiAvK zyE*WZ_QCMwD9uO9ifyG05iac>&!6)z*zWdl6jHT#x_wO6%Bcogh8X2TC7y!HB?IS% z*snbwH%-o#^_}hJapWR`MoA|=;~&pt;8SBPQ56<1+j030H$t=5l`C!C#i*UhR6`sqkb{JhjHxS8dA# z>YPOBpD;?(*?ggEE`|k{S$7{9Zr+o9z)WphF;cfDF4$O*A#s#c6G7FQ7~c%L-HNN5 zGtqZYwp@(Dz}4`g_x+>WLgzcv^*v7y>}(s%-33=Ha$?#1QezwN(^~MFuGicnl2-XI zFZy`Bu5{phQFEWl5XLaFc+|ljIc$P?Nsd__~{Tp^gRQauEde6T27oNnQ&8$&*jm@Cl3c?dVgwM+3qB9+8E zSJ6p3UsYnp_TOlQ>MrZoawcUJ`3WgfEGN#*#cNk-vO3KfX(=`(co+CBNpEv*-o@nm zCMVDVS)^A+qv*h}jDlC!ejwp*)yoVMiGEkd?y`+48A{YfwD}gplIWj`C_lK;KVD3} zO&38$N4b6!K01VL5puNc=b*kr+jzZ7ETd~Uz1`62Zr?4Z!R*L!`b+D^#lX54+E7Rm zPMc$%uslu$VB68W#LBKSx@8ozm1Ddy#P%`dWUV-o0hqP?xI=*Tb)Lc_8C8zZY#k=X z<>4}-=w32))PJ@^@w{ll{J;YDF@Zl%aqjr)*33T07xDf``ZLNfj>@w9j57 zx;c@eRaz&(Wf;nz=pn7Opi6t2ea(IOz{-ew5ssW|xB4KwV>MFuXooCK@X#H;%N~Dv zQZcvm&TMa403Kn9sOkqVh6@W%(SoLdmHzoL6-&(Y)TUoB&tsX5GVcL=P|axJ24>+5v65rZo~YP z#l*xEUlQMZx;Nb)1Eg3w7U(gaT2TnWGBqrma+|rTCr)WA`4f@=pf(p z>70NVV+fw6Of|zQdQFvl;{m%B@#J0pxn4#{IX&8?jXDJ^ts+kQ);>5hLqV;VF@WV( zd+A_(Nn7S*%Yk5p4LK99CYnO?kgpY3>O-X7_u?v@4-Qe_1`tfo0fbL^(oJa4plFY2 z>?tg9bTwk5THK$OsyScsd_+!l;O4p)TTzCE2|GHpqY47L6w)PfCVQ5~d7B?C<3BJ0 zUKrSJN)$Tq8($$eHO;6O`~B_;f%tN3;qRMFiZ&gSJjs-MZzMLtE?C z7mF;Ob`-ysf9ib^v|P1-6Ri)x&)QP&>U)`2S+t7C_llG=pP*@(ODMol3R4h?n(dnE zKq8A@EP&=oL8#!CxOTgLB)k0L-F2DN_Q#oH(UzRytUngCkq(U@Cuxw|eR$a$4o`0v zuOL}*`LvR9tML;U0}+e66_5o>DJrOq=}N-J`>`vf>92|vPn_Mk$|n)g4PXh zz6l>=^^RWb+>+-SXU9B>KTQfQe!nZp$6DppZERQMQhsll#C+d@;$@qX^VZ_cf<@De zcG^Fa#qj~9EryBw{E+51^H86-u^p*C-mZF)Y^ex?_u?~S48tF$=`?VIf%_Y-zPS+ZAAzW!t2Gj7E zbqm+$oEEQ~%ESr-@-vlzLsNY{0e6s|cPc$Pbe!torp~lmUAGl&g0e0nH})4>cc&Pv zgBnMk&`t(l>sZiF;^roP4)vK4mTFlhHLppouV&M(j*gGt$>?>?t)Slcq&XSwD8`C% z{PAV2GgSZWO0FL2K@-=rx(1tKHxE4z9HUMN-+YG@k;_JEg+>{ogAcEO=92t8zMN*L zKOsv{K}N7|DyZk5I!8m-?vtV1GBu-Y2D=dw&$c9bINii6N9y@^nA5lpJ^vBrL6|DI zE&pk>5E2WN{D^|85}OCCN^k@}3eb)0w9wMFNM-cwe9B;76(?Rd6`X+TUtG1XBe~U1 zB7^6bQGS@YZHeLavMZ~3+Gt@H+`qcRT576OBGgH>5OUpcF9Uu(8ys2I)BEDV{b2Tl z<)TWa#aurtulRqPn{avXo>_!7+-=h{Ati>#OPF71+rjDXc0}5t-sk_x;}x)&kstr(V_$xbN9;ew~CNR*5A%Q*Z(MgnaecJ=gD zHG5-l)%2(ei6;IfV*zX9hTJxF8@gUOXGcVV7{z9neR>7=ay7Z!Yq3?rrChk^ivB=h zW*~@kT39FY6YB%CIzga_ST1;o!k%OGDW6lk&i6$^>z~+c20QV;riSC9D8Rhs8P}sg zcQm7~ zgs~<|tJul&O)#VQ@WM&P57~pV(D6@&o#E4ic~LEqC#R!JxSOJgu^0$3*R%jZ#W^mG zGMIxZ2fJl-%TnNHW&`T=R@&V@s~l6buI?5m%%8jJuOqm`*N7>gvCzR&pOC>_18ips zi%h%?=`+RR8ZM=$kF(p$>(-NT_RBVNx$+w-oBr}$Ga+T~40Ao#S{7?`3X&t zgO5j-WFJD=KqGe_%y*Hq+m$=Qb3(f^D03BRx&(VmEHby24A*> zG3NZdz*f;1|N1=-IIpoJBeBGwAhVyvrWS%BD50)D@9av(5SN{!etS;6oxC|bV9C*y##8pPtXy)udA)fiF&^v2zHC9QKB8n@?&YJ_+&o%&v=PIfz?nU-C!fo#(u0>j7SCA^4VPi6s56O_YUT0hKnbxA|biJSk?I(2O!8Y$RA;K&;%9%#^b8QuN>KaMg zQ3(h8maOm(jB)N)0pa4H@IGUoy_08g|AXH=TTCw!TEpM0O5E z4Hs(%H>|Q`KeEiGG=Z~2u7{uo>nQ1KKA3SFBsdpjFxn}pPc|n&W+JoL54F+F=k~&B z(eLIJzt{~Anj{~)+sc+#*GK#)wvF;TdE;GHM4}EbCQ>KI>^UW6pwZxE6TKz#K>NKy zcZEZXS9}&A2#(PiX=$Vnd2y*q7AIhmH=Xz59$!#dX8943O;)AN2B^=1eGmWPkfkU2FYPz;O>`yd(t`wYu+9&t+$S zK!oGe`fl8)9pIu22kppip?c?9CHG+w^7dSaupU0`o@Yv@el|6Nt0z!EIew+Vnk8~} z01?7cWqXkJ5>+$we+t~N282k)DV+k<+$r^I(qqW{&xlI=@Aq9xh z-u6AT^gcdKm`%?}Q3Xxd1U`tPpZB6&4DOdJOOT$8z)Uoo(%E%69(O1{^R-=(F0(Y~ zma^M_Qg}zGv0uK1MW_Cbe?j&@!Vf>#S%n9_5kp*9(4}#RSha3G`j8xhwWU$uhxyrl zpbsNjkl&_mZi^&p*C6ZCCMUVc=#!tHLdpL;IQt1&LQ2B_0ji-8FIxIw>Doqtm}T@D zXvF-tb1Ug*f42liCsm-J@yDV@0wXwJz(Hw(P)AUOX{xb ze97STt8@aCxXt~%w*Gi}4Hi;Sm^<50-dbzZF0_G^9#2>bV&yBI<@5`%X~=H9Ok>-e z%iq+UtsPTMn?qYfT(&UK26#HoMLf;S7m5@LJrq|{vu6@x*-XE^cbtC87N3dIhyqfc z2(+qksoU-s6h(z@t^dF=9+wznoz|!nbKLrFs`G(@Cu3Enc&k?Q+deG+=2v42gDM=Y zr69PETHLl5Hg1~HLCgOq=8(3=fafEOb$qOvytkrPqr_;tNEI~}bexUH^4YylAoXt5g8M|q zdwmjHbexC>uQ^s*&C~n82wDGYQgRnv<1PTZcB;v6^hU{)xy_gFsi_3D6V7P)J<_^% zen!hmF`aNmt1Lt4mA|e;L5l^tyw*w`+0#OD$LH!bX}%;k zc2Kq=ktw--8BMMI@mApCpt8PDwY%1+PF!j|3;o_*%OiQJJh0WHyfpZGf7p<9r-i!` zX@M0dzr|PLp>AYMCzX<1m|z;5udST3l`_CrD2}kK^@lT1>n+EWTjGJV#jd0NJ=6DI z4aH9H?n7Tt@XklvN{rzT>Gy!94StkJxhI32y33yHglD*gLN2FS!zblhpD1e`zBnT6 z#UE!Xlh3z2R&sQ4c)vI7;^W8lmXzL2V_XN8s>!LU z3FM8;v7PMJr|UG+ccB#Hz`HNtl7dg!H}Tvh_$Wca3R>^oRvY5y)%4C&7-_Gdp)ahb zJ&=_0%w#)j0b=~wOkp6YpXtH1VwKlRud=@CRxg62n*Mn)oUR%|Y&Fo{(tkoDgvfF? zb=D;kScJ>YY|k##h)a&Z<1;7qO|wP*G`WoYo!CfY;n3WJ zZm)didHONS+tmgiXwPL(D!qn&llfA) ztlmA*LXx*1?Kk%zor_lYRo|t`wyeAF5{-uI~k+tX&x}eYYO+Bzp`G zX~RZMrC|X&71^=udZcYm)J%Wwdf$5@6yvgAhH7`$2gBV?4)Ky~-OM`6ta;Ef24g*g zPU@1#`VN9Q;`2yKca>9Mp*}mur^snsdpx;%dGWabA8B;41!1Vkh>%s*<}|yIPdC>_ zQco?<$xg>9zB(2)}Uo(DCpuYOI6q^FrlQJ}*nm7gryF=R@+^^KAD zwsm5Ish+Vj^Q{-QJUOPY&K)<5->G@x^H_sjbNE<=Iyb_)9d~*(7k`tJ*>{Xr?W0S) z>f>DSB>x0{r#jLd@@;4y5 zz8RqQaP;m3KZ#Ye%iooemur2h0j1P z)1IuNHtS2$FGl@A9|a24eSAIyw`Sl!t;b63?NdjYYY`6cWwjL$HlJ-`<7?gF&m0hZ z>HW3(5N`Q*2n7#cfe-3S59`8VWFB$is`Jys8mkLcdp+Ap?VXhX_GWYDZ-A;@&al{RPErWjWi` z80Z)u81RI2pNs<85F1#*qMaZ@G@IE>y_Jx;9MiWa)EK{K3K#Cs=oXbr4gO$xd=HD{ zE@xbB<3|Z-jjzVX`m9R?a*`QxNSqqN1wX#{^=R<#mJWZXRJ)5Xty!Sa^I7IjTIh4}7l;ul@9C|PApcZfBf{6XBsOz22~kF$P@l%4!W zrpIx%9GyE!b#3(SVOi*51N-I+MT;_G`N37PDG3FL0C$V2NeMzX#xB*VcfLfvsC8k6 zH$Gf5Qa)49acrUFLAqNEZc3vyZ&eY+;a`S-^dkJxY9BYCswi5%`B_5{>YzOAT}z~h zq0(IZv#xsdp%y*NF61=WZx4VEvx+duhahR%d`H8GksSg$X#X}Fn`Md5zDuf}|qDC>PG={-NPaEEn@%pjTO3Y=6a@(bQG~Ji`utWj;P8P{u z6G~xEq_I3-qprWO6b!CU6R3aDI$3D4+imUH!EXx8c9P7togU!|#(oI9;paRV#KM%( zhJIn=;H73~hYu!KFsu3)nDzF=z&D51C8;3z<>Lg-DXJsQ0cu8`NzO;F74Bv2IBsTB zo1!F}xIJt#l1)qc;e73z>DWR=R@H2&EYdpR)YUau;SfZkD_7s5HgjparqvRQvt6Ba z=NG<_PKlUklWinFvd4KU>o}6?u>o(%#HJH}-RfSF(SmZ(>FiJCA}wc=0zIftJj6uP zy)UiCz9ChQFiGRwT)6fMJFC%&9W5x}io-DvdNTTWUjU|k*!e+aYG$!{Sm|)&R4-+{ zIT^zR>a^_8=kI7$A1vVWi(GzO{&agxc5;$%ch&Y?TO%&i*KeT0wQFNWl$KSgn5LGp z6+UPnFp7BWADQq=9a$Z~QDioafSu{JdL`C9KU*R+4T&8zZ5Fya0%4&%@3jB&+!sq(+Ncb}gZi&o{g zf*8sg`oJ)5SiKND#w^6h92J5?LDBp_s8Oi|D+PP{coHss^9Fl*c=;&>tMVhy@5d++ zp3h&)@biBA#2=^1|Lx)oueG5uFU%W{;e|+pq)@W5Kwfz{X&~Viz8qBY{A#_dj4V_J zq#y;9Q<76q0?Gg4Hh^D^itwm{N4qGQYUzBhj*wF2ck}o6QIe4f3JQ`Al9TqvyUGBe zP^gS7NCpIwB77m`cg@Qm87$@HC-5_nU*KqA{7`tTk3ZJii}xHZ(%Cz}UzMN#yrN&j z_jY;u{92KhpY*pjN~63zWrC4DGC*lrnO`9j3aSujqGafe#=2b7BHSNWBeV)4C95a} zlr;y+Dghx%vLGp0LM*Ey^QSL=tN9xY7{PY{!O6kjzyK@Bf`1R=cVGSq#xJb?J1Bp_ z0+o_QgPo-=-6R4S!E1LI#1Aa(^Td;iVG6 zc>_8Cg~9*5{PBKsTR<3FyaF+Jzw=EE9h4oT7=lE}h|7|;dmQ}(4Lm1m+ejVI? z5!hb`h5suLzcKFr3izLa{bL0;!7G<4e-L5v^Viwe@4OfMjYlXQEaCn)FZdUKTwukYou-{TTp~DGGV}NRZ8GNe z0AwL+e79*KXjW!M!bEoY{j7mlayw}}*~p6+bJnsK7X&LRS+_Qb2u{ORgQ!FQipuZY zs{PNX06_}>78kRhago+Hl$@BJ^MgymSA3d41SfEiyRu+_CKCyh1BqDx2}yuqY|R&b z+Odo;W#(jKPfEU&4KtF#NK|2}Ff+rO55F)Qe61kY@%c)m{ZXuZqFRy0_+;A9;EILM z?3@HbN}OCthmW@s?4TfbWoed Date: Mon, 24 Aug 2026 21:38:36 -0600 Subject: [PATCH 5/8] Stop tests from calling the live API. Stub HTTP in unit tests, drop sandbox credentials, and keep smoke fixtures local-only so CI can run offline. Co-authored-by: Cursor --- .github/workflows/ci.yml | 4 - .gitignore | 5 + Dockerfile | 9 +- .../CsharpDocumentSmokeTests.cs | 78 -------- MifielAPI/MifielAPI.Tests/DocumentsTests.cs | 189 +++++++++--------- .../MifielAPI.Tests/MifielApiTests.csproj | 3 +- .../MifielAPI.Tests/StubHttpMessageHandler.cs | 50 +++++ MifielAPI/MifielAPI.Tests/csharp-test-pdf.pdf | Bin 20419 -> 0 bytes MifielAPI/MifielAPI/ApiClient.cs | 9 +- MifielAPI/MifielAPI/MifielAPI.csproj | 1 + 10 files changed, 159 insertions(+), 189 deletions(-) delete mode 100644 MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs create mode 100644 MifielAPI/MifielAPI.Tests/StubHttpMessageHandler.cs delete mode 100644 MifielAPI/MifielAPI.Tests/csharp-test-pdf.pdf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3dee953..ee7fac1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,3 @@ jobs: - name: Test run: dotnet test MifielAPI/MifielAPI.sln --configuration Release --no-build --verbosity normal - env: - MIFIEL_APP_ID: ${{ secrets.MIFIEL_APP_ID }} - MIFIEL_APP_SECRET: ${{ secrets.MIFIEL_APP_SECRET }} - MIFIEL_BASE_URL: ${{ secrets.MIFIEL_BASE_URL }} diff --git a/.gitignore b/.gitignore index 7fae868..a662e19 100644 --- a/.gitignore +++ b/.gitignore @@ -161,6 +161,11 @@ publish/ # 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/Dockerfile b/Dockerfile index a0cdb91..5b573d2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,7 @@ -# Build and run the NUnit smoke test with the .NET SDK (this library targets net8.0). +# Build and run tests with the .NET SDK (this library targets net8.0). # Usage: -# docker build -t csharp-api-client-smoke . -# docker run --rm csharp-api-client-smoke -# Optional env: MIFIEL_APP_ID, MIFIEL_APP_SECRET, MIFIEL_BASE_URL +# docker build -t csharp-api-client-tests . +# docker run --rm csharp-api-client-tests FROM mcr.microsoft.com/dotnet/sdk:8.0 WORKDIR /src @@ -12,4 +11,4 @@ RUN dotnet restore MifielAPI/MifielAPI.sln \ && dotnet build MifielAPI/MifielAPI.sln --configuration Release --no-restore WORKDIR /src -CMD ["dotnet", "test", "MifielAPI/MifielAPI.sln", "--configuration", "Release", "--no-build", "--filter", "Category=Smoke"] +CMD ["dotnet", "test", "MifielAPI/MifielAPI.sln", "--configuration", "Release", "--no-build"] diff --git a/MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs b/MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs deleted file mode 100644 index daee1c0..0000000 --- a/MifielAPI/MifielAPI.Tests/CsharpDocumentSmokeTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using NUnit.Framework; -using MifielAPI; -using MifielAPI.Dao; -using MifielAPI.Objects; -using MifielAPI.Utils; - -namespace MifielApiTests -{ - [TestFixture] - [Category("Smoke")] - public class CsharpDocumentSmokeTests - { - const string DefaultAppId = "7c938f8e2ff2f083d454127bffd7d4c8bc2c2dee"; - const string DefaultAppSecret = "1lXeVVMbNe5IH+INgMeQX463fsixeWEMMSLS4WXPwxpR9RwStD3iE4XaNMXbY8YigIxCQP9gb/8xZI3XILN2Rw=="; - const string DefaultBaseUrl = "https://app-sandbox.mifiel.com"; - - private readonly string _currentDirectory = Path.GetFullPath(TestContext.CurrentContext.TestDirectory); - - [Test] - public void CreateDocument_ShouldReturnADocumentWithId() - { - var appId = FirstNonEmpty(Environment.GetEnvironmentVariable("MIFIEL_APP_ID"), DefaultAppId); - var appSecret = FirstNonEmpty(Environment.GetEnvironmentVariable("MIFIEL_APP_SECRET"), DefaultAppSecret); - var baseUrl = FirstNonEmpty(Environment.GetEnvironmentVariable("MIFIEL_BASE_URL"), DefaultBaseUrl); - var pdfFilePath = Path.Combine(_currentDirectory, "csharp-test-pdf.pdf"); - - Assert.IsTrue(File.Exists(pdfFilePath), "Expected fixture PDF at " + pdfFilePath); - - var apiClient = new ApiClient(appId, appSecret) { Url = baseUrl }; - var docs = new Documents(apiClient); - - var document = new Document() - { - File = pdfFilePath, - ManualClose = false, - SendMail = false, - SendInvites = false, - Signatures = new List() - { - new Signature() - { - Email = "smoke-test-" + Guid.NewGuid().ToString("N").Substring(0, 8) + "@mifiel.com", - SignerName = "Smoke Test Signer" - } - } - }; - - document = docs.Save(document); - - Assert.IsNotNull(document); - Assert.IsFalse(string.IsNullOrEmpty(document.Id), "Created document should have an id"); - - var newtonsoft = AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(assembly => assembly.GetName().Name == "Newtonsoft.Json"); - Assert.IsNotNull(newtonsoft, "Newtonsoft.Json should be loaded when deserializing the document response"); - Assert.AreEqual(13, newtonsoft.GetName().Version.Major, - "Expected Newtonsoft.Json 13.x after the dependabot bump, got " + newtonsoft.GetName().Version); - - var json = MifielUtils.ConvertObjectToJson(document); - var parsedFromJson = MifielUtils.ConvertJsonToObject(json); - Assert.AreEqual(document.Id, parsedFromJson.Id, "JSON round-trip with Newtonsoft.Json should preserve id"); - StringAssert.Contains("\"" + document.Id + "\"", json); - - Console.WriteLine("CREATED_DOCUMENT_ID=" + parsedFromJson.Id); - Console.WriteLine("NEWTONSOFT_JSON_VERSION=" + newtonsoft.GetName().Version); - TestContext.WriteLine("CREATED_DOCUMENT_ID=" + parsedFromJson.Id); - } - - private static string FirstNonEmpty(string preferred, string fallback) - { - return string.IsNullOrEmpty(preferred) ? fallback : preferred; - } - } -} diff --git a/MifielAPI/MifielAPI.Tests/DocumentsTests.cs b/MifielAPI/MifielAPI.Tests/DocumentsTests.cs index 81da668..05be61c 100644 --- a/MifielAPI/MifielAPI.Tests/DocumentsTests.cs +++ b/MifielAPI/MifielAPI.Tests/DocumentsTests.cs @@ -1,5 +1,6 @@ using System.IO; using System.Collections.Generic; +using System.Net; using NUnit.Framework; using MifielAPI; using MifielAPI.Objects; @@ -10,19 +11,24 @@ 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 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"); - _apiClient = new ApiClient(APP_ID, APP_SECRET); + _handler = new StubHttpMessageHandler(); + _apiClient = new ApiClient("test-app-id", "test-app-secret", _handler) + { + Url = "https://app-sandbox.mifiel.com" + }; _docs = new Documents(_apiClient); } @@ -30,178 +36,161 @@ public void SetUp() 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() { - SetSandboxUrl(); + _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() { - SetSandboxUrl(); - var docId = _docs.FindAll()[0].Id; - var closeDocument = _docs.Close(docId); + _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() { - SetSandboxUrl(); + _handler.EnqueueJson(DocumentJson); var document = new Document() { - File = Path.Combine(_currentDirectory, _pdfFilePath), + File = _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" + 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" + } } }; - 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)); + Assert.AreEqual("doc-1", document.Id); + Assert.AreEqual(1, _handler.SendCount); } [Test] public void Documents__SaveWithOriginalHashAndFileName__ShouldReturnADocument() { - SetSandboxUrl(); - Document document = new Document() + _handler.EnqueueJson(DocumentJson); + var 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" + 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); + Assert.AreEqual("doc-1", document.Id); + Assert.AreEqual(1, _handler.SendCount); } [Test] public void Documents__SaveWithoutRequiredFields__ShouldThrowAnException() { - SetSandboxUrl(); var document = new Document() { CallbackUrl = "http://www.google.com" }; Assert.Throws(() => _docs.Save(document)); - Assert.IsNotNull(document); + Assert.AreEqual(0, _handler.SendCount); } [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"); - } + _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() { - SetSandboxUrl(); - Document document = new Document() - { - OriginalHash = MifielAPI.Utils.MifielUtils.GetDocumentHash(_pdfFilePath), - FileName = "PdfFileName" - }; - - document = _docs.Save(document); - _docs.Delete(document.Id); + _handler.EnqueueJson("{}"); + _docs.Delete("doc-1"); + Assert.AreEqual(1, _handler.SendCount); } [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, + _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() { - SetSandboxUrl(); - Document document = new Document() { File = _pdfFilePath }; - - document = _docs.Save(document); - - _docs.SaveFile(document.Id, Path.Combine(_currentDirectory, "pdf_save_test.pdf")); + _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); } - private void SetSandboxUrl() + [Test] + public void Documents__UnauthorizedResponse__ShouldThrowAnException() { - _apiClient.Url = "https://app-sandbox.mifiel.com"; + _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 index a62a99a..64217ef 100644 --- a/MifielAPI/MifielAPI.Tests/MifielApiTests.csproj +++ b/MifielAPI/MifielAPI.Tests/MifielApiTests.csproj @@ -20,11 +20,12 @@ 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/csharp-test-pdf.pdf b/MifielAPI/MifielAPI.Tests/csharp-test-pdf.pdf deleted file mode 100644 index dd72052991d83500080f5a7ae778274e0888e3f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20419 zcmeIabyS#~3Tc}W4 zpoNxtd(SWQ^!T~={oVDw-&)@vcNR>ZdFGitd-mQldyj1)V=WyiptL-#(4)t5OSDu9 zyt2IB&hE4-Dl!IGFANfo4Z)y!fiecXAXzXFDszPwDx=K{l$Qg^$rusdn-ksx<-js} zymq``IS4P|*+ET>7UP9Jhr;`JffgwDTP0c;ls6hpsmbVIJ^V3v866L#Kj!BmGH@+B6{rG4Q&tfOfq`{&zzT{A8d^|okb(wK z5vmDN&;o(fR3IQA3=Gwgg}@+Suq;?dQ&UR|s4WKtD?sGHnmTf74l-~rG$zEv`{4nW%eBTY_dR2WIrg#j9xE}9#O2(A|={sg43+5&DR1X)vAfF{@rFP+`z+7K3wP_yD?Qx7EqELKyR z{FTDMb8>#KOcU$xXNM~Wo@Os*TK;%vrEK{HN+k z_8nnVYX+C9Go*8Qbn_o31>A{8^GqjvK(dd^?k(8sd5HR>VV*b;C>7lO3HMogX^g-CS8 z)jHjdt4`EE&?z;a?P}k*^wb`a)F1uWgnn{hd_NL47RwUiU+Ycoz&|p)&?T)DGkZ%| z&%fzY%9IsJ`=vYcM5{i;IR?RJuh%H4vJ6Vs`gjgj2XxN{JAAl#ZIrW{uZ7m;(Hv-d zS5S-KJ~SRR9NpQBb@bwCum^iBB+QH?m`-qoRDX;FEoiCBfF-mi7LP9<1A-D=L!P0j zdizxqGbDFzFVN+A%(=xFwqI=}5!sU1EKWaWGAv7gZ4&Xp~erz2BVCWdGpx;o91OEY2QJM{tKX%v_x@W$)& z9<(Ie_`#~*uK7<=taOq-a2a1m;(GKcY=$0FC z`}2MRQC^0i2p-O<2ED*V+-RYZ_x`+GY56}2MScP`n%oj_2MG&Cxj7rFCjX!YW;Pxr+ zexJtsM3L8iav^SNAHN)xdA@t}Sf`W_FA-w-$3x%ZujldM@%Q-6!E^ByDUni<{&0m9 zqLkb9wh5euB_g%iHtzDpBDGf5daK13YIF0v<%^!xq4VH({I$eyzhxD7l^EiMZP(Qm^w2N-EtF zia9--8|{um32$C*P7~hHa1%Y3jsfV-&F9@toBuDtjbX!s-Q-KgumeMt@XM|lV^`8oLwTx*Qtk-fEMT~;X81`_fM#yy;K`SRk4t4K zZ%VVTq(3OQ{Cv&jZ2wD9KDJ^3w&IxwA5{#g>3-`sbHE(zx=-4fcF^VSvY!u(;i+@J zQJkxJ(X!E(kG+>HD{jPvT-CPz$Qd+B64I$Z@zlkCC#S<_q4W9t(h9s6r^D$vyX8rU zS3jS}qcPv&N)=UfwdGud8l7Zerzd>nl9GWrQCVp!e10~!K5}F zSTR3@O5{u{JcI6jsBqaXW;1rA&rbuclASTeA1Vkd?{Q9g7>(xW$duHH!J5+xNjQJ{ z=^q8pxz>GhZ^rL0AjEXg`hg|yLnpJ27-@TRK>nj>k!xC>*Fl9K+m~pO&Wfj9IjS5B z37b|G2^+a3$LUA&3-9y;cilb2tzo68uWp#ymF5Yrybo4#2l`iD{GeX1t`H)`-94$itGvO_GLK

s;#z>*$b5Ap)!&4^gLK4rlC~3yh3U^QDdo|^@$|DW#RjZaw5@%TB zpMLnMV3yRn$)w2YejYy7sqX=&2Qx5waHN8#cmKGv`DysIo#M58c%?(CQ-pdWxOS(7 zTWWnKc_W$qD7yBGu4WG1o6GHb2Vz$haDpMKbnO0Z#(|b2~M}mp0ZmCI{4_T zUdwg4e zMtETG|0l{nZ31Wrydh#BW&r#E5I4;zrqnl$6cW>?&nOqwJWnFpTx={eTfiF<(zb%j zL1rej|qR=|vP}>2uL*A!ON-ak7CZ^RMRePigtHU(kYB zC^+iENBbouI=s}3GzAqejxYd$^D3OF_;vxrf#W7dfuz~@QT;ns^4$k?&6i2g#gF$e zOup&~V0&x6uC;CP&BVP{FEe%V-ZlzGn>-3brNiTVBQ7OvwqkwYf;Ws=IF;Y|W#>~) zVscnEXrj!YuYUva$(d{T>*o&tVORO55_X+oB&zt;u4Xgw8S~tL~}i&fGI7zV8n6cd93kc`(DJFciVFPf~}YIW(Hrf(LqpHV5A9$Y9?7Sjs%bm z6k=D0Pp->EFXEr^*K)U>G2h*nQJX5vNxNQ>?GVceA*+KdgK<|TOf((wG;L5*@eJO} z*aPJ#e9#?!%rPqpVZH7P1U6twxA~)GU08^oiEXi4URFhKwmZyHP8h%+dpn;Ei{A3y zDL?Gk+*~}VSFoIyX0L|Z9MEMJM$oyC>R*jwPfDO&<{PZ{;hhgP=c{!$vV0dAEa%>~ zAUS_1jG6O$HBe_w+B+kBw_q0lpr0R{muLr2EKz_J2Mf3(T~PfV+buRBg4aL?(=qDr zl|D$JHc4zuA8nPCN}2N|is>6imWydJW|ULtn`ZF+T~Y=`?qj(&II#xbFmR*K{Sh2o zJ`+a6vD34=#?cuMAQDk?(u-8dk#(NdMpMxETQ@nb!lL`_-$O@}+=$E43;~pEWM|xk zt$dod2Nh>iBN)PtGa{9!OCzdp^p@Y99bQZZ3v!mbu8V}q^H9lZht4Rdcx4;u;tX{1 zv$^=FaGnZFUiE6akV`;xkRBa*$FIi?3i|^F4Qm zYg3v=b1_K;@#`=tBBfHpeo*1lN&YSuenX1}_%o`i<-D5MILDS-M=~+cI;`I~PQ6-$ zmztqfc4z&XzwxLF4zB9}u!BSNL!~)NHP2#-m zf@<8gYcClT3|rNAN&U0_k~y%6)CvVpG}0NmF0dqjTVMjfChE_uHkoNY-V;j3*}J zhI2)sJ#Qt_NQK8ow{8_LJnFVicF3s2td>`N&Daj|J$1Z>|FD}>ylw97{KJWr145!` zyE*F#u-j`2;ceDA<+AEBidXxhoBC?PA;;RpOLGQfufVgayxU$oX}X7m`PaEGxO0`c zC8fU9r!x}>i(!ob8E8Z?2OS@HeURq)7?RfM=`vE38^dV-@=5GKpXklkgUmGnu<%3L zg}KcZRn2|#gQF$~!8Ry^$I}*OAt^D+y?MbvZPn&QnOCjz$&5!OP6$0eLyAd?ajQnY zDEXdLT7N?zO+cBa$%DYDhxNgkj@4?M70+f1+TXZv+qAiAc}#z8S=^BVQX+<0eA=fA zO9cwWn2)uTHdIi}blhz16#S*45{4%aXfWdqwK%sGbZ!(zhjMfXPCH7j2hAI&?8%eat&8sGn=(1bFudoR(Fg>Q z6#6T+mqBKrM!I0j=uF~Tl+_~mk{SISVr5QNW=m))U(W^Lj8AEvMip;_Ce`rtN|fLi z`T5innayC~sd;v)Gdq>8Qvr)s(gEq7t0tzlhVBqZl^ZCDT(ZZiKY2_uf$}9<&zr0f zm*=*B%T0sSIK22`&A;e)2!;7Ku<=eqmiGFZHO*$eA9yF%W}p>ONl*$W{$!uW(Y9HG z+&3ua_ZJ6ivm%FD%QN_QZkU;Hh8lwhEXS%drOo3ish(_xeSK2E*gR?8R1DOnM$hs~)98kg9 zvGA1ifxn1Px9xJQ2v>kek0ie|XD)l{-ox!xSgAMLjk3q26t#X~8nkW=KHYb|w8X&8 zD$vsPr1SIJGgBcdYTmL@p*D=2n>08$brrW7fERp%K#J*GviysoN=ARG4UQ!}O`)40WhE(Q}^#zcxgq}$NKy*z}X-H3kNwjUV7!jtRN^P-n@-{+qh zjwQmlg0&0atWJ(_5WS@Za_@Scodk{}F|36XmHP7Zi|R_TsW??TuK0>U%T} zK!rSb*uKra)zdt&7x}@ncqTu}>XoG)wlz;|92HGLuuC-t2 zqV-jwo7tFSXdr_WNJkS?V6IP$poG!|7L+iA`fHdmr--bJz@rlFQS*#mJ_FVr51@8v^squ40>|@iJisa28_EFDT3aJl)T3q(x zYVN!WoCV;(tMEw51bRY@q28e35~#$D+Z@SX11w@8Z^iWbArp=8Za7 zt;^OG&yJ*>9oqo$5fE3uM=F*I$^2v%4rx^+P)ij^zaGl^{AD3}fxZLG0%)n8t{uA<&>ax673NeH}c zf6c8if+dr#`vbadu;`YX&VoI>R-1-wce}bn5_|n!85L^dqCf?2D??N9eTG&xe7K!S zMs%e3?`=icF*%J*Y+V>passQw=D5!~_3dYh3-ti=G8dmHBdNzW=X$4_du{^npk&Oblv!R_l2RsXhHJF+!(guSocRi zUf}NRTrRUXpmf)_J)~4@m`##f=nu1H|9nCQ2=u zVfKM`s=8B~&PA^WdKSIGbIEtrg)svhE_xOzOXLu7IsrH{XRf{bnnXe&iM_{z-a#+( z&wr_SJU|!y=>n+3asGvslqg;b5LZ{@P-{7z*HkZ-FCEZ{?ZQ^mMY6r(f?HEEcDYNCE-$UzF98%laA2jSXVM z>L(ntGpBCLZ8azh3@i@?h}vr0PZu=~ug`SRUtjfT%NE?aD}%eWZ z6%(PyW77;nI5n{c)_I@DQW#xS;IX4;dW|x^kKr|+1Z0tdtOAUi?QM-jV~I_VBv!Rk zrS#GWV>{AB3?ZD}KWa3oksqU~Ga9|N#A0iX;X<_HCHb|{g4!X|+IFV(f~Mof&N>!) zSpQ7M8)y3N(V+4yKV-1#>x)M<`-3OfpIqLsu(A)!`jF5=yj8WFc<+(niGd+P@_U|@ z(dxjZEOh!a1;ocn!Mfwc8xfzyYXpOhGZ|jy z)TFx@SQL6_OGTMH8r8P;%!_Rip3%DFX%2))w&YU}yt?%djFmiF#6#YHPTuW)Lenwq zu*>S8Q-*oS=pF9tZsYnruLpr2$0UI!ke7L$!TOH1r;5@58_K?}bY+9_hb3c%cIG4c zZb-(G(a5c`chd;_)w)*ZHyPy;`f&WDp-x(E3#O_11XPeVwA6#PgQ(6YlzsS zcu7gwQ|%0smS;#?MxRRFBj@(ktqXE_!H@1yQ}^Gy!UXy(FSe|^GSsltKdI3FehRGd zLQ8yb6e%Uj;n z=Sp~Nw8+~ZdaLI}9(a#0?yt#h1uI?v&$lk|0qQaNnJEGg4t;EA=j(;5>tG#dFfzT@Gt!k_{q%Qn&3Wt|r#xGSF#{8YWtB-=|WFF{N|RBd&;r0qVTy z_Ut3`H-02l>~?m#6nqfeskDb1#WnDPDurvkf$QZI2>PMxnvD@IFeqok$m1E<1**!( z=4XUS!Sy7LdANuy2~_)C;^WF^rL)QnO_YFR1v#A)fK+!F-Sc6qx z(%I)r=DX*mQ?*Ce!(*$es^#Giif?KsS!?S`u4rp(Yh^iqY9U>bL>M6Fy!wl?h|ENWHYL=oLf7 z@(wHQ=1bNFn19CQ{LvilAunX`QFG$C?)c0|MqG2`>&=%*rG20VmW;jLJAsm%`j;l? zol9QxWmZO+^faN}N%dDbuM#Zuczxn(`r1nk7`)^QYQjA6cZL|^n2PhU7FyK#%_Buf zk!PA4F=5Z|gnzCm2|LXGHpf0t^UcWxT1-Nt>qN7rK23qjd=8OvrE>B|!G=L<9w8jV zT!TfSh9KQ?utlP9b1&mfmW+&^dx5GgbX>yL^9c{0F)@4!2e3zR1s=Z2PO6Z7)^U|P z;)6t%x>Xyu|6aSNUm?(Qj6qxzhHr0)eV%!YUhc?shjh4{9oNfoadcd=o|&p!j<~Pi zXc@9LxIv-urZs#lKXfO`cD^8^+Io|qo4Jv-w;OO3Wy6+r%X2eSg{G$Bkk~bO=Na=5 z1N+bQLShJ?#q$N0&l>R`7;JbLx<7bb#BTF03x1?*zcMjU5R)fCQr+pt(1YiHnzO6& z_G9qs^;v79QMp_91foxQzPg)#2-4?J6>{E?sqtFzw<~2p)}=DesKP6Zqt4u#wnV%U zwkJcjTl)6xLevS-kE%Jm)>^Za?AX}3I>ScR0=lWNj= zm>zEQN50P^rd59&+h0XWF!U%tQP^Zx)GK3-V}U-!lbQiyWa~po@e&3NUQ)Tq>iEQB zsX7Twc5bmS!a~Nwr8j;|Y}ml3i$X6~D|$3+pUfH7)U9apSTfB#M{q#NlM_iFlviaOD8hhzOQMtStS-9c(1S2-j$@)F=vY|7xU!! zWl=%M+H9MldDno+YNv}S@H3w2{+*ja3tz(`bw*-XsrN3;KT?DiDIKp7Y5H+JDiAvK zyE*WZ_QCMwD9uO9ifyG05iac>&!6)z*zWdl6jHT#x_wO6%Bcogh8X2TC7y!HB?IS% z*snbwH%-o#^_}hJapWR`MoA|=;~&pt;8SBPQ56<1+j030H$t=5l`C!C#i*UhR6`sqkb{JhjHxS8dA# z>YPOBpD;?(*?ggEE`|k{S$7{9Zr+o9z)WphF;cfDF4$O*A#s#c6G7FQ7~c%L-HNN5 zGtqZYwp@(Dz}4`g_x+>WLgzcv^*v7y>}(s%-33=Ha$?#1QezwN(^~MFuGicnl2-XI zFZy`Bu5{phQFEWl5XLaFc+|ljIc$P?Nsd__~{Tp^gRQauEde6T27oNnQ&8$&*jm@Cl3c?dVgwM+3qB9+8E zSJ6p3UsYnp_TOlQ>MrZoawcUJ`3WgfEGN#*#cNk-vO3KfX(=`(co+CBNpEv*-o@nm zCMVDVS)^A+qv*h}jDlC!ejwp*)yoVMiGEkd?y`+48A{YfwD}gplIWj`C_lK;KVD3} zO&38$N4b6!K01VL5puNc=b*kr+jzZ7ETd~Uz1`62Zr?4Z!R*L!`b+D^#lX54+E7Rm zPMc$%uslu$VB68W#LBKSx@8ozm1Ddy#P%`dWUV-o0hqP?xI=*Tb)Lc_8C8zZY#k=X z<>4}-=w32))PJ@^@w{ll{J;YDF@Zl%aqjr)*33T07xDf``ZLNfj>@w9j57 zx;c@eRaz&(Wf;nz=pn7Opi6t2ea(IOz{-ew5ssW|xB4KwV>MFuXooCK@X#H;%N~Dv zQZcvm&TMa403Kn9sOkqVh6@W%(SoLdmHzoL6-&(Y)TUoB&tsX5GVcL=P|axJ24>+5v65rZo~YP z#l*xEUlQMZx;Nb)1Eg3w7U(gaT2TnWGBqrma+|rTCr)WA`4f@=pf(p z>70NVV+fw6Of|zQdQFvl;{m%B@#J0pxn4#{IX&8?jXDJ^ts+kQ);>5hLqV;VF@WV( zd+A_(Nn7S*%Yk5p4LK99CYnO?kgpY3>O-X7_u?v@4-Qe_1`tfo0fbL^(oJa4plFY2 z>?tg9bTwk5THK$OsyScsd_+!l;O4p)TTzCE2|GHpqY47L6w)PfCVQ5~d7B?C<3BJ0 zUKrSJN)$Tq8($$eHO;6O`~B_;f%tN3;qRMFiZ&gSJjs-MZzMLtE?C z7mF;Ob`-ysf9ib^v|P1-6Ri)x&)QP&>U)`2S+t7C_llG=pP*@(ODMol3R4h?n(dnE zKq8A@EP&=oL8#!CxOTgLB)k0L-F2DN_Q#oH(UzRytUngCkq(U@Cuxw|eR$a$4o`0v zuOL}*`LvR9tML;U0}+e66_5o>DJrOq=}N-J`>`vf>92|vPn_Mk$|n)g4PXh zz6l>=^^RWb+>+-SXU9B>KTQfQe!nZp$6DppZERQMQhsll#C+d@;$@qX^VZ_cf<@De zcG^Fa#qj~9EryBw{E+51^H86-u^p*C-mZF)Y^ex?_u?~S48tF$=`?VIf%_Y-zPS+ZAAzW!t2Gj7E zbqm+$oEEQ~%ESr-@-vlzLsNY{0e6s|cPc$Pbe!torp~lmUAGl&g0e0nH})4>cc&Pv zgBnMk&`t(l>sZiF;^roP4)vK4mTFlhHLppouV&M(j*gGt$>?>?t)Slcq&XSwD8`C% z{PAV2GgSZWO0FL2K@-=rx(1tKHxE4z9HUMN-+YG@k;_JEg+>{ogAcEO=92t8zMN*L zKOsv{K}N7|DyZk5I!8m-?vtV1GBu-Y2D=dw&$c9bINii6N9y@^nA5lpJ^vBrL6|DI zE&pk>5E2WN{D^|85}OCCN^k@}3eb)0w9wMFNM-cwe9B;76(?Rd6`X+TUtG1XBe~U1 zB7^6bQGS@YZHeLavMZ~3+Gt@H+`qcRT576OBGgH>5OUpcF9Uu(8ys2I)BEDV{b2Tl z<)TWa#aurtulRqPn{avXo>_!7+-=h{Ati>#OPF71+rjDXc0}5t-sk_x;}x)&kstr(V_$xbN9;ew~CNR*5A%Q*Z(MgnaecJ=gD zHG5-l)%2(ei6;IfV*zX9hTJxF8@gUOXGcVV7{z9neR>7=ay7Z!Yq3?rrChk^ivB=h zW*~@kT39FY6YB%CIzga_ST1;o!k%OGDW6lk&i6$^>z~+c20QV;riSC9D8Rhs8P}sg zcQm7~ zgs~<|tJul&O)#VQ@WM&P57~pV(D6@&o#E4ic~LEqC#R!JxSOJgu^0$3*R%jZ#W^mG zGMIxZ2fJl-%TnNHW&`T=R@&V@s~l6buI?5m%%8jJuOqm`*N7>gvCzR&pOC>_18ips zi%h%?=`+RR8ZM=$kF(p$>(-NT_RBVNx$+w-oBr}$Ga+T~40Ao#S{7?`3X&t zgO5j-WFJD=KqGe_%y*Hq+m$=Qb3(f^D03BRx&(VmEHby24A*> zG3NZdz*f;1|N1=-IIpoJBeBGwAhVyvrWS%BD50)D@9av(5SN{!etS;6oxC|bV9C*y##8pPtXy)udA)fiF&^v2zHC9QKB8n@?&YJ_+&o%&v=PIfz?nU-C!fo#(u0>j7SCA^4VPi6s56O_YUT0hKnbxA|biJSk?I(2O!8Y$RA;K&;%9%#^b8QuN>KaMg zQ3(h8maOm(jB)N)0pa4H@IGUoy_08g|AXH=TTCw!TEpM0O5E z4Hs(%H>|Q`KeEiGG=Z~2u7{uo>nQ1KKA3SFBsdpjFxn}pPc|n&W+JoL54F+F=k~&B z(eLIJzt{~Anj{~)+sc+#*GK#)wvF;TdE;GHM4}EbCQ>KI>^UW6pwZxE6TKz#K>NKy zcZEZXS9}&A2#(PiX=$Vnd2y*q7AIhmH=Xz59$!#dX8943O;)AN2B^=1eGmWPkfkU2FYPz;O>`yd(t`wYu+9&t+$S zK!oGe`fl8)9pIu22kppip?c?9CHG+w^7dSaupU0`o@Yv@el|6Nt0z!EIew+Vnk8~} z01?7cWqXkJ5>+$we+t~N282k)DV+k<+$r^I(qqW{&xlI=@Aq9xh z-u6AT^gcdKm`%?}Q3Xxd1U`tPpZB6&4DOdJOOT$8z)Uoo(%E%69(O1{^R-=(F0(Y~ zma^M_Qg}zGv0uK1MW_Cbe?j&@!Vf>#S%n9_5kp*9(4}#RSha3G`j8xhwWU$uhxyrl zpbsNjkl&_mZi^&p*C6ZCCMUVc=#!tHLdpL;IQt1&LQ2B_0ji-8FIxIw>Doqtm}T@D zXvF-tb1Ug*f42liCsm-J@yDV@0wXwJz(Hw(P)AUOX{xb ze97STt8@aCxXt~%w*Gi}4Hi;Sm^<50-dbzZF0_G^9#2>bV&yBI<@5`%X~=H9Ok>-e z%iq+UtsPTMn?qYfT(&UK26#HoMLf;S7m5@LJrq|{vu6@x*-XE^cbtC87N3dIhyqfc z2(+qksoU-s6h(z@t^dF=9+wznoz|!nbKLrFs`G(@Cu3Enc&k?Q+deG+=2v42gDM=Y zr69PETHLl5Hg1~HLCgOq=8(3=fafEOb$qOvytkrPqr_;tNEI~}bexUH^4YylAoXt5g8M|q zdwmjHbexC>uQ^s*&C~n82wDGYQgRnv<1PTZcB;v6^hU{)xy_gFsi_3D6V7P)J<_^% zen!hmF`aNmt1Lt4mA|e;L5l^tyw*w`+0#OD$LH!bX}%;k zc2Kq=ktw--8BMMI@mApCpt8PDwY%1+PF!j|3;o_*%OiQJJh0WHyfpZGf7p<9r-i!` zX@M0dzr|PLp>AYMCzX<1m|z;5udST3l`_CrD2}kK^@lT1>n+EWTjGJV#jd0NJ=6DI z4aH9H?n7Tt@XklvN{rzT>Gy!94StkJxhI32y33yHglD*gLN2FS!zblhpD1e`zBnT6 z#UE!Xlh3z2R&sQ4c)vI7;^W8lmXzL2V_XN8s>!LU z3FM8;v7PMJr|UG+ccB#Hz`HNtl7dg!H}Tvh_$Wca3R>^oRvY5y)%4C&7-_Gdp)ahb zJ&=_0%w#)j0b=~wOkp6YpXtH1VwKlRud=@CRxg62n*Mn)oUR%|Y&Fo{(tkoDgvfF? zb=D;kScJ>YY|k##h)a&Z<1;7qO|wP*G`WoYo!CfY;n3WJ zZm)didHONS+tmgiXwPL(D!qn&llfA) ztlmA*LXx*1?Kk%zor_lYRo|t`wyeAF5{-uI~k+tX&x}eYYO+Bzp`G zX~RZMrC|X&71^=udZcYm)J%Wwdf$5@6yvgAhH7`$2gBV?4)Ky~-OM`6ta;Ef24g*g zPU@1#`VN9Q;`2yKca>9Mp*}mur^snsdpx;%dGWabA8B;41!1Vkh>%s*<}|yIPdC>_ zQco?<$xg>9zB(2)}Uo(DCpuYOI6q^FrlQJ}*nm7gryF=R@+^^KAD zwsm5Ish+Vj^Q{-QJUOPY&K)<5->G@x^H_sjbNE<=Iyb_)9d~*(7k`tJ*>{Xr?W0S) z>f>DSB>x0{r#jLd@@;4y5 zz8RqQaP;m3KZ#Ye%iooemur2h0j1P z)1IuNHtS2$FGl@A9|a24eSAIyw`Sl!t;b63?NdjYYY`6cWwjL$HlJ-`<7?gF&m0hZ z>HW3(5N`Q*2n7#cfe-3S59`8VWFB$is`Jys8mkLcdp+Ap?VXhX_GWYDZ-A;@&al{RPErWjWi` z80Z)u81RI2pNs<85F1#*qMaZ@G@IE>y_Jx;9MiWa)EK{K3K#Cs=oXbr4gO$xd=HD{ zE@xbB<3|Z-jjzVX`m9R?a*`QxNSqqN1wX#{^=R<#mJWZXRJ)5Xty!Sa^I7IjTIh4}7l;ul@9C|PApcZfBf{6XBsOz22~kF$P@l%4!W zrpIx%9GyE!b#3(SVOi*51N-I+MT;_G`N37PDG3FL0C$V2NeMzX#xB*VcfLfvsC8k6 zH$Gf5Qa)49acrUFLAqNEZc3vyZ&eY+;a`S-^dkJxY9BYCswi5%`B_5{>YzOAT}z~h zq0(IZv#xsdp%y*NF61=WZx4VEvx+duhahR%d`H8GksSg$X#X}Fn`Md5zDuf}|qDC>PG={-NPaEEn@%pjTO3Y=6a@(bQG~Ji`utWj;P8P{u z6G~xEq_I3-qprWO6b!CU6R3aDI$3D4+imUH!EXx8c9P7togU!|#(oI9;paRV#KM%( zhJIn=;H73~hYu!KFsu3)nDzF=z&D51C8;3z<>Lg-DXJsQ0cu8`NzO;F74Bv2IBsTB zo1!F}xIJt#l1)qc;e73z>DWR=R@H2&EYdpR)YUau;SfZkD_7s5HgjparqvRQvt6Ba z=NG<_PKlUklWinFvd4KU>o}6?u>o(%#HJH}-RfSF(SmZ(>FiJCA}wc=0zIftJj6uP zy)UiCz9ChQFiGRwT)6fMJFC%&9W5x}io-DvdNTTWUjU|k*!e+aYG$!{Sm|)&R4-+{ zIT^zR>a^_8=kI7$A1vVWi(GzO{&agxc5;$%ch&Y?TO%&i*KeT0wQFNWl$KSgn5LGp z6+UPnFp7BWADQq=9a$Z~QDioafSu{JdL`C9KU*R+4T&8zZ5Fya0%4&%@3jB&+!sq(+Ncb}gZi&o{g zf*8sg`oJ)5SiKND#w^6h92J5?LDBp_s8Oi|D+PP{coHss^9Fl*c=;&>tMVhy@5d++ zp3h&)@biBA#2=^1|Lx)oueG5uFU%W{;e|+pq)@W5Kwfz{X&~Viz8qBY{A#_dj4V_J zq#y;9Q<76q0?Gg4Hh^D^itwm{N4qGQYUzBhj*wF2ck}o6QIe4f3JQ`Al9TqvyUGBe zP^gS7NCpIwB77m`cg@Qm87$@HC-5_nU*KqA{7`tTk3ZJii}xHZ(%Cz}UzMN#yrN&j z_jY;u{92KhpY*pjN~63zWrC4DGC*lrnO`9j3aSujqGafe#=2b7BHSNWBeV)4C95a} zlr;y+Dghx%vLGp0LM*Ey^QSL=tN9xY7{PY{!O6kjzyK@Bf`1R=cVGSq#xJb?J1Bp_ z0+o_QgPo-=-6R4S!E1LI#1Aa(^Td;iVG6 zc>_8Cg~9*5{PBKsTR<3FyaF+Jzw=EE9h4oT7=lE}h|7|;dmQ}(4Lm1m+ejVI? z5!hb`h5suLzcKFr3izLa{bL0;!7G<4e-L5v^Viwe@4OfMjYlXQEaCn)FZdUKTwukYou-{TTp~DGGV}NRZ8GNe z0AwL+e79*KXjW!M!bEoY{j7mlayw}}*~p6+bJnsK7X&LRS+_Qb2u{ORgQ!FQipuZY zs{PNX06_}>78kRhago+Hl$@BJ^MgymSA3d41SfEiyRu+_CKCyh1BqDx2}yuqY|R&b z+Odo;W#(jKPfEU&4KtF#NK|2}Ff+rO55F)Qe61kY@%c)m{ZXuZqFRy0_+;A9;EILM z?3@HbN}OCthmW@s?4TfbWoedC# SDK for mifiel.com. true false + From dbbf1b8972d20286c52792fcf24010ccabf5fd3d Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 21:38:36 -0600 Subject: [PATCH 6/8] Stop tests from calling the live API. Stub HTTP in unit tests, drop sandbox credentials, and keep smoke fixtures local-only so CI can run offline. Co-authored-by: Cursor --- MifielAPI/MifielAPI/MifielAPI.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MifielAPI/MifielAPI/MifielAPI.csproj b/MifielAPI/MifielAPI/MifielAPI.csproj index 912dfdd..fe3da03 100644 --- a/MifielAPI/MifielAPI/MifielAPI.csproj +++ b/MifielAPI/MifielAPI/MifielAPI.csproj @@ -24,6 +24,9 @@ Through Mifiel’s API, you can easily manage documents and certificates within false + + + From 4c7e2d5c9ac4ef008e84687c844c8585362ddb89 Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 21:51:36 -0600 Subject: [PATCH 7/8] Fix CI restore by moving InternalsVisibleTo into an ItemGroup. MSBuild rejects Include on InternalsVisibleTo inside PropertyGroup. Co-authored-by: Cursor --- MifielAPI/MifielAPI/MifielAPI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/MifielAPI/MifielAPI/MifielAPI.csproj b/MifielAPI/MifielAPI/MifielAPI.csproj index fe3da03..c8dbcae 100644 --- a/MifielAPI/MifielAPI/MifielAPI.csproj +++ b/MifielAPI/MifielAPI/MifielAPI.csproj @@ -22,7 +22,6 @@ Through Mifiel’s API, you can easily manage documents and certificates within

C# SDK for mifiel.com. true false - From 08f29215eb752335b5397c536f55d35a180db0e6 Mon Sep 17 00:00:00 2001 From: Genaro Madrid Date: Mon, 24 Aug 2026 22:03:49 -0600 Subject: [PATCH 8/8] Address review comments on CI, Docker, disposal, and fixtures. Harden the workflow token, pin a valid SDK feature band, run container tests as app, dispose owned HTTP handlers, and drop real identity data from the XML fixture. Co-authored-by: Cursor --- .github/workflows/ci.yml | 5 + Dockerfile | 7 +- MifielAPI/MifielAPI.Tests/file_hash.xml | 116 ++---------------------- MifielAPI/MifielAPI/ApiClient.cs | 19 +++- README.md | 12 +-- global.json | 2 +- 6 files changed, 41 insertions(+), 120 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee7fac1..c2984da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,14 +2,19 @@ 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 diff --git a/Dockerfile b/Dockerfile index 5b573d2..1828013 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,8 @@ WORKDIR /src COPY . /src RUN dotnet restore MifielAPI/MifielAPI.sln \ - && dotnet build MifielAPI/MifielAPI.sln --configuration Release --no-restore + && dotnet build MifielAPI/MifielAPI.sln --configuration Release --no-restore \ + && chown -R app:app /src -WORKDIR /src -CMD ["dotnet", "test", "MifielAPI/MifielAPI.sln", "--configuration", "Release", "--no-build"] +USER app +CMD ["dotnet", "test", "MifielAPI/MifielAPI.sln", "--configuration", "Release", "--no-build", "--filter", "Category!=Smoke"] diff --git a/MifielAPI/MifielAPI.Tests/file_hash.xml b/MifielAPI/MifielAPI.Tests/file_hash.xml index 63e17c5..4b28ace 100644 --- a/MifielAPI/MifielAPI.Tests/file_hash.xml +++ b/MifielAPI/MifielAPI.Tests/file_hash.xml @@ -1,109 +1,9 @@ -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 + + + + + TEST + + + diff --git a/MifielAPI/MifielAPI/ApiClient.cs b/MifielAPI/MifielAPI/ApiClient.cs index 81e72f9..e25b76c 100644 --- a/MifielAPI/MifielAPI/ApiClient.cs +++ b/MifielAPI/MifielAPI/ApiClient.cs @@ -7,7 +7,7 @@ namespace MifielAPI { - public class ApiClient + public class ApiClient : IDisposable { public const string PackageName = "MifielAPIClient"; @@ -34,6 +34,7 @@ public static string PackageVersion 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 @@ -50,18 +51,32 @@ public string Url } public ApiClient(string appId, string appSecret) - : this(appId, appSecret, new HttpClientHandler()) + : 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("")); diff --git a/README.md b/README.md index f480679..6e9fa82 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ Please read our [documentation](http://docs.mifiel.com/) for instructions on how 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 ``` @@ -195,21 +195,21 @@ This SDK ships as the NuGet package **MifielAPIClient** ([nuget.org/packages/Mif 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 @@ -219,6 +219,6 @@ The listing usually appears on nuget.org within a few minutes. Confirm at `https Smoke tests (optional) use the same SDK: -``` +```shell dotnet test MifielAPI/MifielAPI.sln --filter Category=Smoke ``` diff --git a/global.json b/global.json index 3fea262..391ba3c 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.0", + "version": "8.0.100", "rollForward": "latestFeature" } }