From d087e9fea30a928ff4fd4f860069c68056e39941 Mon Sep 17 00:00:00 2001 From: ttossavainen Date: Fri, 16 Feb 2024 08:39:40 +0200 Subject: [PATCH 1/6] init --- .../Request_build_and_test_on_main.yml | 18 ++ .../Request_build_and_test_on_push.yml | 18 ++ .github/workflows/Request_release.yml | 13 + .gitignore | 242 ++++++++++++++++++ Frends.ManagementApi.Request/Apache-2.0 | 93 +++++++ Frends.ManagementApi.Request/CHANGELOG.md | 5 + .../Frends.ManagementApi.Request.Tests.csproj | 38 +++ .../GlobalSuppressions.cs | 5 + .../UnitTests.cs | 66 +++++ .../Frends.ManagementApi.Request.sln | 40 +++ .../Definitions/Enums.cs | 15 ++ .../Definitions/Header.cs | 19 ++ .../Definitions/Input.cs | 45 ++++ .../Definitions/Options.cs | 18 ++ .../Definitions/Result.cs | 33 +++ .../Frends.ManagementApi.Request.cs | 110 ++++++++ .../Frends.ManagementApi.Request.csproj | 45 ++++ .../FrendsTaskMetadata.json | 7 + .../GlobalSuppressions.cs | 9 + Frends.ManagementApi.Request/README.md | 34 +++ LICENSE | 21 ++ README.md | 19 +- 22 files changed, 912 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/Request_build_and_test_on_main.yml create mode 100644 .github/workflows/Request_build_and_test_on_push.yml create mode 100644 .github/workflows/Request_release.yml create mode 100644 .gitignore create mode 100644 Frends.ManagementApi.Request/Apache-2.0 create mode 100644 Frends.ManagementApi.Request/CHANGELOG.md create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request.sln create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/FrendsTaskMetadata.json create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs create mode 100644 Frends.ManagementApi.Request/README.md create mode 100644 LICENSE diff --git a/.github/workflows/Request_build_and_test_on_main.yml b/.github/workflows/Request_build_and_test_on_main.yml new file mode 100644 index 0000000..de0ba3a --- /dev/null +++ b/.github/workflows/Request_build_and_test_on_main.yml @@ -0,0 +1,18 @@ +name: Request_build_main + +on: + push: + branches: + - main + paths: + - 'Frends.ManagementApi.Request/**' + workflow_dispatch: + +jobs: + build: + uses: FrendsPlatform/FrendsTasks/.github/workflows/linux_build_main.yml@main + with: + workdir: Frends.ManagementApi.Request + secrets: + badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }} + \ No newline at end of file diff --git a/.github/workflows/Request_build_and_test_on_push.yml b/.github/workflows/Request_build_and_test_on_push.yml new file mode 100644 index 0000000..a4a4dff --- /dev/null +++ b/.github/workflows/Request_build_and_test_on_push.yml @@ -0,0 +1,18 @@ +name: Request_build_test + +on: + push: + branches-ignore: + - main + paths: + - 'Frends.ManagementApi.Request/**' + workflow_dispatch: + +jobs: + build: + uses: FrendsPlatform/FrendsTasks/.github/workflows/linux_build_test.yml@main + with: + workdir: Frends.ManagementApi.Request + secrets: + badge_service_api_key: ${{ secrets.BADGE_SERVICE_API_KEY }} + test_feed_api_key: ${{ secrets.TASKS_TEST_FEED_API_KEY }} diff --git a/.github/workflows/Request_release.yml b/.github/workflows/Request_release.yml new file mode 100644 index 0000000..f9d2275 --- /dev/null +++ b/.github/workflows/Request_release.yml @@ -0,0 +1,13 @@ +name: Request_release + +on: + workflow_dispatch: + +jobs: + build: + uses: FrendsPlatform/FrendsTasks/.github/workflows/release.yml@main + with: + workdir: Frends.ManagementApi.Request + secrets: + feed_api_key: ${{ secrets.TASKS_FEED_API_KEY }} + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8bd702 --- /dev/null +++ b/.gitignore @@ -0,0 +1,242 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +[Xx]64/ +[Xx]86/ +[Bb]uild/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml + +# TODO: Un-comment the next line if you do not want to checkin +# your web deploy settings because they may include unencrypted +# passwords +#*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# LightSwitch generated files +GeneratedArtifacts/ +ModelManifest.xml + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Apache-2.0 b/Frends.ManagementApi.Request/Apache-2.0 new file mode 100644 index 0000000..a20cf5a --- /dev/null +++ b/Frends.ManagementApi.Request/Apache-2.0 @@ -0,0 +1,93 @@ +Apache License 2.0 +SPDX identifier +Apache-2.0 +License text + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Standard License Header + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +Notes + +This license was released January 2004 +SPDX web page + + https://spdx.org/licenses/Apache-2.0.html + +Notice + +This license content is provided by the SPDX project. For more information about licenses.nuget.org, see our documentation. + +Data pulled from spdx/license-list-data on February 9, 2023. diff --git a/Frends.ManagementApi.Request/CHANGELOG.md b/Frends.ManagementApi.Request/CHANGELOG.md new file mode 100644 index 0000000..e6443ef --- /dev/null +++ b/Frends.ManagementApi.Request/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## [1.0.0] - 2024-02-16 +### Changed +- Initial implementation diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj new file mode 100644 index 0000000..364874b --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj @@ -0,0 +1,38 @@ + + + + net6.0 + + false + + + + 1701;1702;SA0001 + + + + 1701;1702;SA0001 + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..987021f --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs @@ -0,0 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines")] diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs new file mode 100644 index 0000000..d48fd8d --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs @@ -0,0 +1,66 @@ +namespace Frends.ManagementApi.Request.Tests; + +using Frends.ManagementApi.Request.Definitions; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +[TestFixture] +internal class UnitTests +{ + /// + /// These cannot be tested in Github. Set your own tenant and token and test locally. + /// + private readonly string tenantUrl = "https://tenant.frendsapp.com/"; + private readonly string token = "token"; + private Input input = new (); + private Options options = new (); + + [SetUp] + public void SetUp() + { + this.input = new Input + { + TenantUrl = this.tenantUrl, + Token = this.token, + ManagementApiVersion = "v0.9", + Path = @"environment-variables", + Method = Methods.GET, + }; + + this.options = new Options + { + ThrowExceptionOnError = true, + }; + } + + [Ignore("Cannot be tested in Github")] + [Test] + public async Task Test_Get() + { + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.That(ret.Success is true); + Assert.That(ret.ErrorMessage is null); + Assert.That(ret.Data != null); + } + + [Ignore("Cannot be tested in Github")] + [Test] + public void Test_Invalid_Token_Throw() + { + this.input.Token = "foo"; + Assert.ThrowsAsync(async () => await ManagementApi.Request(this.input, this.options, default)); + } + + [Ignore("Cannot be tested in Github")] + [Test] + public async Task Test_Invalid_Token_Return() + { + this.input.Token = "foo"; + this.options.ThrowExceptionOnError = false; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.That(ret.Success is false); + Assert.That(ret.Data is null); + Assert.That(ret.ErrorMessage != null); + } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.sln b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.sln new file mode 100644 index 0000000..e441984 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.sln @@ -0,0 +1,40 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.ManagementApi.Request", "Frends.ManagementApi.Request\Frends.ManagementApi.Request.csproj", "{35C305C0-8108-4A98-BB1D-AFE5C926239E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Frends.ManagementApi.Request.Tests", "Frends.ManagementApi.Request.Tests\Frends.ManagementApi.Request.Tests.csproj", "{8CA92187-8E4F-4414-803B-EC899479022E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{78F7F22E-6E20-4BCE-8362-0C558568B729}" + ProjectSection(SolutionItems) = preProject + CHANGELOG.md = CHANGELOG.md + ..\.github\workflows\Request_build_and_test_on_main.yml = ..\.github\workflows\Request_build_and_test_on_main.yml + ..\.github\workflows\Request_build_and_test_on_push.yml = ..\.github\workflows\Request_build_and_test_on_push.yml + ..\.github\workflows\Request_release.yml = ..\.github\workflows\Request_release.yml + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35C305C0-8108-4A98-BB1D-AFE5C926239E}.Release|Any CPU.Build.0 = Release|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8CA92187-8E4F-4414-803B-EC899479022E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {55BC6629-85C9-48D8-8CA2-B0046AF1AF4B} + EndGlobalSection +EndGlobal diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs new file mode 100644 index 0000000..868f475 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs @@ -0,0 +1,15 @@ +namespace Frends.ManagementApi.Request.Definitions; + +/// +/// Type of HTTP Request +/// +public enum Methods +{ +#pragma warning disable CS1591 // Self explanatory + GET, + POST, + PUT, + PATCH, + DELETE, +#pragma warning restore CS1591 // Self explanatory +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs new file mode 100644 index 0000000..6642ca3 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs @@ -0,0 +1,19 @@ +namespace Frends.ManagementApi.Request.Definitions; + +/// +/// Request header. +/// +public class Header +{ + /// + /// Gets or sets name of header. + /// + /// Authorization + public string Name { get; set; } + + /// + /// Gets or sets value of header. + /// + /// Bearer AccessToken123 + public string Value { get; set; } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs new file mode 100644 index 0000000..d3851e3 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs @@ -0,0 +1,45 @@ +namespace Frends.ManagementApi.Request.Definitions; + +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +/// +/// Input parameters. +/// +public class Input +{ + /// + /// HTTP request method. + /// + /// Methods.GET + [DefaultValue(Methods.GET)] + public Methods Method { get; set; } + + /// + /// Tenant URL. + /// + /// https://tenant.frendsapp.com/ + public string TenantUrl { get; set; } + + /// + /// Versions of Management API. + /// + /// v0.9 + [DisplayFormat(DataFormatString = "Text")] + [DefaultValue("v0.9")] + public string ManagementApiVersion { get; set; } + + /// + /// Path. + /// + /// api-management/access/api-keys/{id} + public string Path { get; set; } + + /// + /// Bearer token. + /// + /// abcd123 + [DisplayFormat(DataFormatString = "Text")] + [PasswordPropertyText] + public string Token { get; set; } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs new file mode 100644 index 0000000..09c1dcd --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs @@ -0,0 +1,18 @@ +namespace Frends.ManagementApi.Request.Definitions; + +using System.ComponentModel; + +/// +/// Options parameters. +/// +public class Options +{ + /// + /// Gets or sets a value indicating whether an error should stop the Task and throw an exception. + /// If set to true, an exception will be thrown when an error occurs. + /// If set to false, Task will try to continue and the error message will be added into Result.ErrorMessage and Result.Success will be set to false. + /// + /// true + [DefaultValue(true)] + public bool ThrowExceptionOnError { get; set; } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs new file mode 100644 index 0000000..00f8458 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs @@ -0,0 +1,33 @@ +namespace Frends.ManagementApi.Request.Definitions; + +/// +/// Result class. +/// +public class Result +{ + internal Result(bool success, dynamic data, dynamic errorMessage) + { + Success = success; + Data = data; + ErrorMessage = errorMessage; + } + + /// + /// Gets a value indicating whether the Task was executed successfully and without errors. + /// + /// true + public bool Success { get; private set; } + + /// + /// Result data. + /// + /// {"data":{"id":1,"value":"1-2-3-4-5","modified":"2024-01-25T07:56:27.147","modifier":"user","environment":{"id":51,"displayName":"Development"},"name":"test","rulesetIds":[],"requestLimit":100,"requestLimitPeriod":"Day"}} + public dynamic Data { get; private set; } + + /// + /// Error message. + /// This value is generated when an exception occurs and Options.ThrowExceptionOnError is false. + /// + /// Error occured... + public dynamic ErrorMessage { get; private set; } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs new file mode 100644 index 0000000..81b32fa --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs @@ -0,0 +1,110 @@ +#pragma warning disable SA1503 // Braces should not be omitted +namespace Frends.ManagementApi.Request; + +using Frends.ManagementApi.Request.Definitions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +/// +/// Frends Management API Task. +/// +public static class ManagementApi +{ + /// + /// Task for reading data from Frends Management API. + /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.ManagementApi.Request). + /// + /// Input parameters. + /// Option parameters. + /// Token generated by Frends to stop this Task. + /// Object { bool Success, dynamic Data, dynamic ErrorMessage }. + public static async Task Request([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) + { + InputChecker(input); + var headers = GetHeaderDictionary(input); + var hbody = string.Empty; + try + { + var url = new Uri($"{input.TenantUrl}/api/{input.ManagementApiVersion}/{input.Path}"); + using var responseMessage = await GetHttpRequestResponseAsync(new HttpClient(), input.Method.ToString(), url, headers, cancellationToken); + hbody = await responseMessage.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); + var hstatusCode = (int)responseMessage.StatusCode; + + if (hstatusCode != 200) + { + if (options.ThrowExceptionOnError) + throw new Exception(hbody); + else + return new Result(false, null, hbody); + } + + return new Result(true, hbody, null); + } + catch (Exception ex) + { + if (options.ThrowExceptionOnError) + throw; + return new Result(false, hbody, ex); + } + } + + private static void InputChecker(Input input) + { + if (string.IsNullOrEmpty(input.TenantUrl)) + throw new ArgumentNullException(nameof(input.TenantUrl) + " cannot be empty."); + if (string.IsNullOrEmpty(input.ManagementApiVersion)) + throw new ArgumentNullException(nameof(input.ManagementApiVersion) + " cannot be empty."); + } + + private static IDictionary GetHeaderDictionary(Input inputs) + { + var authHeader = new Header + { + Name = "Authorization", + Value = $"Bearer {inputs.Token}", + }; + var acceptHeader = new Header + { + Name = "Accept", + Value = $"application/json", + }; + var headers = new[] { authHeader, acceptHeader }.ToArray(); + + // Ignore case for headers and key comparison + return headers.ToDictionary(key => key.Name, value => value.Value, StringComparer.InvariantCultureIgnoreCase); + } + + private static async Task GetHttpRequestResponseAsync(HttpClient httpClient, string method, Uri url, IDictionary headers, CancellationToken cancellationToken) + { + HttpResponseMessage response; + cancellationToken.ThrowIfCancellationRequested(); + using var request = new HttpRequestMessage(new HttpMethod(method), url); + var requestHeaderAddedSuccessfully = false; + + foreach (var header in headers) + requestHeaderAddedSuccessfully = request.Headers.TryAddWithoutValidation(header.Key, header.Value); + + try + { + response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + } + catch (TaskCanceledException canceledException) + { + if (cancellationToken.IsCancellationRequested) + throw; // Cancellation is from outside -> Just throw + throw new Exception("HttpRequest was canceled, most likely due to a timeout.", canceledException); // Cancellation is from inside of the request, mostly likely a timeout + } + finally + { + request.Dispose(); + httpClient.Dispose(); + } + + return response; + } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj new file mode 100644 index 0000000..5b2527d --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj @@ -0,0 +1,45 @@ + + + + net6.0 + Latest + 1.0.0 + Frends + Frends + Frends + Frends + Frends + MIT + true + Task for reading data from Frends Management API. + https://frends.com/ + https://github.com/FrendsPlatform/Frends.ManagementApi/tree/main/Frends.ManagementApi.Request + + + + + PreserveNewest + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/FrendsTaskMetadata.json b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/FrendsTaskMetadata.json new file mode 100644 index 0000000..4613a82 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/FrendsTaskMetadata.json @@ -0,0 +1,7 @@ +{ + "Tasks": [ + { + "TaskMethod": "Frends.ManagementApi.Request.ManagementApi.Request" + } + ] +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs new file mode 100644 index 0000000..88fe0fc --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends Tasks guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request")] +[assembly: SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Following Frends guidelines", Scope = "type", Target = "~T:Frends.ManagementApi.Request.ManagementApi")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends guidelines")] diff --git a/Frends.ManagementApi.Request/README.md b/Frends.ManagementApi.Request/README.md new file mode 100644 index 0000000..41cb82c --- /dev/null +++ b/Frends.ManagementApi.Request/README.md @@ -0,0 +1,34 @@ +# Frends.ManagementApi.Request +Task for reading data from Frends Management API. + +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) +[![Build](https://github.com/FrendsPlatform/Frends.ManagementApi/actions/workflows/Request_build_and_test_on_main.yml/badge.svg)](https://github.com/FrendsPlatform/Frends.ManagementApi/actions) +![Coverage](https://app-github-custom-badges.azurewebsites.net/Badge?key=FrendsPlatform/Frends.ManagementApi/Frends.ManagementApi.Request|main) + +## Installing + +You can install the Task via frends UI Task View. + +## Building + +### Clone a copy of the repository + +`git clone https://github.com/FrendsPlatform/Frends.ManagementApi.git` + +### Build the project + +`dotnet build` + +### Run tests + +Run the tests + +`dotnet test` + +### Create a NuGet package + +`dotnet pack --configuration Release` + +### Third party licenses + +StyleCop.Analyzer version (unmodified version 1.1.118) used to analyze code uses Apache-2.0 license, full text and source code can be found in https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c67d3d6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Frends EiPaaS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index bf86d0b..99d86aa 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# Frends.ManagementApi \ No newline at end of file +# Frends.ManagementApi + +Frends Task for Frends Management API related operations. + +# Tasks + +- [Frends.ManagementApi.Request](Frends.ManagementApi.Request/README.md) + +# Contributing +When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. + +1. Fork the repository on GitHub +2. Clone the project to your own machine +3. Commit changes to your own branch +4. Push your work back up to your fork +5. Submit a Pull request so that we can review your changes + +NOTE: Be sure to merge the latest from "upstream" before making a pull request! \ No newline at end of file From 7c85c7e41dec855af10ca5ebfad93485966d808c Mon Sep 17 00:00:00 2001 From: ttossavainen Date: Fri, 16 Feb 2024 09:06:36 +0200 Subject: [PATCH 2/6] Cleaning up --- .../Frends.ManagementApi.Request.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs index 81b32fa..5a2157c 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs @@ -31,7 +31,7 @@ public static async Task Request([PropertyTab] Input input, [PropertyTab try { var url = new Uri($"{input.TenantUrl}/api/{input.ManagementApiVersion}/{input.Path}"); - using var responseMessage = await GetHttpRequestResponseAsync(new HttpClient(), input.Method.ToString(), url, headers, cancellationToken); + using var responseMessage = await GetHttpRequestResponseAsync(input.Method.ToString(), url, headers, cancellationToken); hbody = await responseMessage.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); var hstatusCode = (int)responseMessage.StatusCode; @@ -79,15 +79,15 @@ private static IDictionary GetHeaderDictionary(Input inputs) return headers.ToDictionary(key => key.Name, value => value.Value, StringComparer.InvariantCultureIgnoreCase); } - private static async Task GetHttpRequestResponseAsync(HttpClient httpClient, string method, Uri url, IDictionary headers, CancellationToken cancellationToken) + private static async Task GetHttpRequestResponseAsync(string method, Uri url, IDictionary headers, CancellationToken cancellationToken) { HttpResponseMessage response; cancellationToken.ThrowIfCancellationRequested(); + using var httpClient = new HttpClient(); using var request = new HttpRequestMessage(new HttpMethod(method), url); - var requestHeaderAddedSuccessfully = false; foreach (var header in headers) - requestHeaderAddedSuccessfully = request.Headers.TryAddWithoutValidation(header.Key, header.Value); + request.Headers.TryAddWithoutValidation(header.Key, header.Value); try { @@ -99,11 +99,6 @@ private static async Task GetHttpRequestResponseAsync(HttpC throw; // Cancellation is from outside -> Just throw throw new Exception("HttpRequest was canceled, most likely due to a timeout.", canceledException); // Cancellation is from inside of the request, mostly likely a timeout } - finally - { - request.Dispose(); - httpClient.Dispose(); - } return response; } From 365e8b412e35e2eeb15af375a4a4cc0f1bf6e2ce Mon Sep 17 00:00:00 2001 From: ttossavainen Date: Fri, 16 Feb 2024 09:07:44 +0200 Subject: [PATCH 3/6] Cleaning up --- .../Frends.ManagementApi.Request.Tests/UnitTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs index d48fd8d..f13c6f4 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs @@ -13,8 +13,8 @@ internal class UnitTests /// private readonly string tenantUrl = "https://tenant.frendsapp.com/"; private readonly string token = "token"; - private Input input = new (); - private Options options = new (); + private Input input = new(); + private Options options = new(); [SetUp] public void SetUp() From e09ac96b70cfd6854f932b6aefe58321ce44a7a1 Mon Sep 17 00:00:00 2001 From: ttossavainen Date: Wed, 21 Feb 2024 13:02:49 +0200 Subject: [PATCH 4/6] Using RestSharp and option for multipart. --- .../Frends.ManagementApi.Request.Tests.csproj | 22 +-- .../GlobalSuppressions.cs | 12 +- .../UnitTests.cs | 16 +-- .../Definitions/Enums.cs | 96 +++++++++++-- .../Definitions/Header.cs | 4 +- .../Definitions/Input.cs | 72 +++++++--- .../Definitions/ManualParameters.cs | 32 +++++ .../Definitions/Options.cs | 6 + .../Definitions/Result.cs | 20 ++- .../Definitions/SendFileParameters.cs | 22 +++ .../Frends.ManagementApi.Request.cs | 129 ++++++++++-------- .../Frends.ManagementApi.Request.csproj | 1 + .../GlobalSuppressions.cs | 18 ++- 13 files changed, 326 insertions(+), 124 deletions(-) create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/SendFileParameters.cs diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj index 364874b..6cfef6f 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/Frends.ManagementApi.Request.Tests.csproj @@ -14,22 +14,12 @@ 1701;1702;SA0001 - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers - - + + + + + + diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs index 987021f..0dbcf9f 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/GlobalSuppressions.cs @@ -1,5 +1,13 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Tests")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1400:Access modifier should be declared", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs index f13c6f4..b9a0fee 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs @@ -21,11 +21,9 @@ public void SetUp() { this.input = new Input { - TenantUrl = this.tenantUrl, + Url = this.tenantUrl, Token = this.token, - ManagementApiVersion = "v0.9", - Path = @"environment-variables", - Method = Methods.GET, + Method = Methods.Get, }; this.options = new Options @@ -40,8 +38,8 @@ public async Task Test_Get() { var ret = await ManagementApi.Request(this.input, this.options, default); Assert.That(ret.Success is true); - Assert.That(ret.ErrorMessage is null); - Assert.That(ret.Data != null); + Assert.NotNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); } [Ignore("Cannot be tested in Github")] @@ -59,8 +57,8 @@ public async Task Test_Invalid_Token_Return() this.input.Token = "foo"; this.options.ThrowExceptionOnError = false; var ret = await ManagementApi.Request(this.input, this.options, default); - Assert.That(ret.Success is false); - Assert.That(ret.Data is null); - Assert.That(ret.ErrorMessage != null); + Assert.IsFalse(ret.Success); + Assert.IsNull(ret.Data is null); + Assert.NotNull(ret.ErrorMessage != null); } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs index 868f475..2ebd5d0 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs @@ -1,15 +1,95 @@ namespace Frends.ManagementApi.Request.Definitions; /// -/// Type of HTTP Request +/// Type of HTTP Request. /// public enum Methods { -#pragma warning disable CS1591 // Self explanatory - GET, - POST, - PUT, - PATCH, - DELETE, -#pragma warning restore CS1591 // Self explanatory + /// + /// GET. + /// + Get, + + /// + /// POST. + /// + Post, + + /// + /// PUT. + /// + Put, + + /// + /// PATCH. + /// + Patch, + + /// + /// DELETE. + /// + Delete, +} + +/// +/// File handler. +/// +public enum FileHandler +{ + /// + /// Download file. + /// + Download, + + /// + /// Upload file. + /// + Upload, +} + +/// +/// Selection of file parameter key. +/// +public enum FileParameterKey +{ + /// + /// File. + /// + File, + + /// + /// Content. + /// + Content, +} + +/// +/// Parameter types. +/// +public enum ParameterTypes +{ + /// + /// Parameter that will added to the QueryString for GET, DELETE, OPTIONS and HEAD requests; and form for POST and PUT requests. + /// + GetOrPost, + + /// + /// Parameter that will be added to part of the url by replacing a {placeholder} within the absolute path. + /// + UrlSegment, + + /// + /// Parameter that will be added as a request header. + /// + HttpHeader, + + /// + /// Parameter that will be added to the request body. + /// + RequestBody, + + /// + /// Parameter that will be added to the query string. + /// + QueryString, } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs index 6642ca3..67190f2 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs @@ -8,12 +8,12 @@ public class Header /// /// Gets or sets name of header. /// - /// Authorization + /// Authorization. public string Name { get; set; } /// /// Gets or sets value of header. /// - /// Bearer AccessToken123 + /// Bearer AccessToken123. public string Value { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs index d3851e3..9858249 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs @@ -9,37 +9,73 @@ public class Input { /// - /// HTTP request method. + /// Gets or sets HTTP request method. /// - /// Methods.GET - [DefaultValue(Methods.GET)] + /// Methods.Get. + [DefaultValue(Methods.Get)] public Methods Method { get; set; } /// - /// Tenant URL. + /// Gets or sets URL. /// - /// https://tenant.frendsapp.com/ - public string TenantUrl { get; set; } + /// https://tenant.frendsapp.com/v0.9/api-management/access/api-keys/{id}. + public string Url { get; set; } /// - /// Versions of Management API. + /// Gets or sets Bearer token. /// - /// v0.9 + /// abcd123. [DisplayFormat(DataFormatString = "Text")] - [DefaultValue("v0.9")] - public string ManagementApiVersion { get; set; } + [PasswordPropertyText] + public string Token { get; set; } /// - /// Path. + /// Gets or sets message to be sent with the request. /// - /// api-management/access/api-keys/{id} - public string Path { get; set; } + /// + /// { + /// "agentGroupId": 0, + /// "processes": [ { + /// "processGuid": "00000000-0000-0000-0000-000000000000" + /// ... + /// + [UIHint(nameof(Methods), "", Methods.Post, Methods.Delete, Methods.Patch, Methods.Put)] + public string Message { get; set; } /// - /// Bearer token. + /// Gets or sets a value indicating whether multipart is used. + /// If true, using Content-Type = multipart/form-data instead of application/json. /// - /// abcd123 - [DisplayFormat(DataFormatString = "Text")] - [PasswordPropertyText] - public string Token { get; set; } + /// false. + public bool IsMultipart { get; set; } + + /// + /// Gets or sets how the file resource will be handled. + /// + /// FileHandler.Download. + [UIHint(nameof(IsMultipart), "", true)] + [DefaultValue(FileHandler.Download)] + public FileHandler FileHandler { get; set; } + + /// + /// Gets or sets array of files. + /// + /// { {FileParameterKey = FileParameterKey.File, Fullpath = "C:\temp\file.txt"} }. + [UIHint(nameof(FileHandler), "", FileHandler.Upload)] + public SendFileParameters[] FilePaths { get; set; } + + /// + /// Gets or sets download path. + /// + /// C:\temp\foo.txt. + [UIHint(nameof(FileHandler), "", FileHandler.Download)] + public string DownloadPath { get; set; } + + /// + /// Gets or sets manual parameters. + /// No need to add Content-Type, Accept and Authorization headers. + /// + /// { {Key = foo, Value = bar, ParameterType = ParameterTypes.GetOrPost} }. + [UIHint(nameof(IsMultipart), "", true)] + public ManualParameters[] ManualParameters { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs new file mode 100644 index 0000000..76fc278 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs @@ -0,0 +1,32 @@ +namespace Frends.ManagementApi.Request.Definitions; +using System.ComponentModel; + +/// +/// Manual parameters. +/// +public class ManualParameters +{ + /// + /// Gets or sets name of the parameter. + /// + /// foo. + public string Key { get; set; } + + /// + /// Gets or sets value for the parameter. + /// + /// bar. + public string Value { get; set; } + + /// + /// Gets or sets parameter type. + /// GetOrPost = Parameter that will added to the QueryString for GET, DELETE, OPTIONS and HEAD requests; and form for POST and PUT requests. + /// UrlSegment = Parameter that will be added to part of the url by replacing a {placeholder} within the absolute path. + /// HttpHeader = Parameter that will be added as a request header. + /// RequestBody = Parameter that will be added to the request body. + /// QueryString = Parameter that will be added to the query string. + /// + /// ParameterTypes.GetOrPost. + [DefaultValue(ParameterTypes.GetOrPost)] + public ParameterTypes ParameterType { get; set; } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs index 09c1dcd..d17f0fc 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs @@ -15,4 +15,10 @@ public class Options /// true [DefaultValue(true)] public bool ThrowExceptionOnError { get; set; } + + /// + /// Timeout in seconds. + /// + /// 10 + public int Timeout { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs index 00f8458..e15b854 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Result.cs @@ -5,27 +5,33 @@ /// public class Result { + /// + /// Initializes a new instance of the class. + /// + /// Gets a value indicating whether the Task was executed successfully and without errors. + /// Result data. + /// Error message. internal Result(bool success, dynamic data, dynamic errorMessage) { - Success = success; - Data = data; - ErrorMessage = errorMessage; + this.Success = success; + this.Data = data; + this.ErrorMessage = errorMessage; } /// /// Gets a value indicating whether the Task was executed successfully and without errors. /// - /// true + /// true. public bool Success { get; private set; } /// - /// Result data. + /// Gets result data. /// - /// {"data":{"id":1,"value":"1-2-3-4-5","modified":"2024-01-25T07:56:27.147","modifier":"user","environment":{"id":51,"displayName":"Development"},"name":"test","rulesetIds":[],"requestLimit":100,"requestLimitPeriod":"Day"}} + /// {"data":{"id":1,"value":"1-2-3-4-5","modified":"2024-01-25T07:56:27.147","modifier":"user","environment":{"id":51,"displayName":"Development"},"name":"test","rulesetIds":[],"requestLimit":100,"requestLimitPeriod":"Day"}}. public dynamic Data { get; private set; } /// - /// Error message. + /// Gets error message. /// This value is generated when an exception occurs and Options.ThrowExceptionOnError is false. /// /// Error occured... diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/SendFileParameters.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/SendFileParameters.cs new file mode 100644 index 0000000..3d12478 --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/SendFileParameters.cs @@ -0,0 +1,22 @@ +namespace Frends.ManagementApi.Request.Definitions; + +using System.ComponentModel; + +/// +/// File which will be sent. +/// +public class SendFileParameters +{ + /// + /// Gets or sets key used for file parameter. + /// + /// FileParameterKey.File. + [DefaultValue(FileParameterKey.File)] + public FileParameterKey FileParameterKey { get; set; } + + /// + /// Gets or sets full path to the file to be uploaded. + /// + /// C:\temp\file.txt. + public string Fullpath { get; set; } +} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs index 5a2157c..0d65858 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs @@ -1,12 +1,13 @@ -#pragma warning disable SA1503 // Braces should not be omitted +#pragma warning disable SA1000 // Keywords should be spaced correctly. new() vs new (). +#pragma warning disable SA1503 // Braces should not be omitted namespace Frends.ManagementApi.Request; using Frends.ManagementApi.Request.Definitions; +using RestSharp; +using RestSharp.Authenticators.OAuth2; using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Net.Http; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -26,80 +27,96 @@ public static class ManagementApi public static async Task Request([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) { InputChecker(input); - var headers = GetHeaderDictionary(input); - var hbody = string.Empty; - try - { - var url = new Uri($"{input.TenantUrl}/api/{input.ManagementApiVersion}/{input.Path}"); - using var responseMessage = await GetHttpRequestResponseAsync(input.Method.ToString(), url, headers, cancellationToken); - hbody = await responseMessage.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false); - var hstatusCode = (int)responseMessage.StatusCode; + var responseMessage = await GetRestResponse(input, options.Timeout, cancellationToken); - if (hstatusCode != 200) - { - if (options.ThrowExceptionOnError) - throw new Exception(hbody); - else - return new Result(false, null, hbody); - } + if (responseMessage.ErrorMessage.Length > 0 && options.ThrowExceptionOnError) + throw new Exception(responseMessage.ErrorMessage); - return new Result(true, hbody, null); - } - catch (Exception ex) - { - if (options.ThrowExceptionOnError) - throw; - return new Result(false, hbody, ex); - } + return new Result(responseMessage.ErrorMessage.Length > 0, responseMessage, responseMessage.ErrorMessage); } private static void InputChecker(Input input) { - if (string.IsNullOrEmpty(input.TenantUrl)) - throw new ArgumentNullException(nameof(input.TenantUrl) + " cannot be empty."); - if (string.IsNullOrEmpty(input.ManagementApiVersion)) - throw new ArgumentNullException(nameof(input.ManagementApiVersion) + " cannot be empty."); + if (string.IsNullOrEmpty(input.Url)) + throw new ArgumentNullException(nameof(input.Url) + " cannot be empty."); + if (string.IsNullOrEmpty(input.Token)) + throw new ArgumentNullException(nameof(input.Token) + " cannot be empty."); } - private static IDictionary GetHeaderDictionary(Input inputs) + private static Method GetMethod(Methods methods) { - var authHeader = new Header + return methods switch { - Name = "Authorization", - Value = $"Bearer {inputs.Token}", + Methods.Get => Method.Get, + Methods.Post => Method.Post, + Methods.Put => Method.Put, + Methods.Patch => Method.Patch, + Methods.Delete => Method.Delete, + _ => throw new ArgumentNullException(nameof(methods)), }; - var acceptHeader = new Header + } + + private static ParameterType GetParameterType(ParameterTypes parameterTypes) + { + return parameterTypes switch { - Name = "Accept", - Value = $"application/json", + ParameterTypes.GetOrPost => ParameterType.GetOrPost, + ParameterTypes.UrlSegment => ParameterType.UrlSegment, + ParameterTypes.HttpHeader => ParameterType.HttpHeader, + ParameterTypes.RequestBody => ParameterType.RequestBody, + ParameterTypes.QueryString => ParameterType.QueryString, + _ => throw new ArgumentNullException(nameof(parameterTypes)), }; - var headers = new[] { authHeader, acceptHeader }.ToArray(); - - // Ignore case for headers and key comparison - return headers.ToDictionary(key => key.Name, value => value.Value, StringComparer.InvariantCultureIgnoreCase); } - private static async Task GetHttpRequestResponseAsync(string method, Uri url, IDictionary headers, CancellationToken cancellationToken) + private static async Task GetRestResponse(Input input, int timeout, CancellationToken cancellationToken) { - HttpResponseMessage response; - cancellationToken.ThrowIfCancellationRequested(); - using var httpClient = new HttpClient(); - using var request = new HttpRequestMessage(new HttpMethod(method), url); + RestClientOptions restClientOptions = new() + { + BaseUrl = new Uri(input.Url), + MaxTimeout = (int)TimeSpan.FromSeconds(Convert.ToDouble(timeout)).Ticks, + Authenticator = new OAuth2AuthorizationRequestHeaderAuthenticator(input.Token, "Bearer"), + }; + RestClient restClient = new(restClientOptions); + RestRequest request = null; - foreach (var header in headers) - request.Headers.TryAddWithoutValidation(header.Key, header.Value); + request.Resource = "/"; + request.Method = GetMethod(input.Method); + request.AlwaysMultipartFormData = input.IsMultipart; + request.AddHeader("Content-Type", input.IsMultipart ? "multipart/form-data" : "application/json"); + request.AddHeader("Accept", "application/json"); - try + if (input.ManualParameters.Length > 0) { - response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false); + foreach (var manualParameter in input.ManualParameters) + { + cancellationToken.ThrowIfCancellationRequested(); + request.AddParameter(manualParameter.Key, manualParameter.Value, GetParameterType(manualParameter.ParameterType)); + } } - catch (TaskCanceledException canceledException) + + if (input.FileHandler is FileHandler.Upload) { - if (cancellationToken.IsCancellationRequested) - throw; // Cancellation is from outside -> Just throw - throw new Exception("HttpRequest was canceled, most likely due to a timeout.", canceledException); // Cancellation is from inside of the request, mostly likely a timeout + foreach (var file in input.FilePaths) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!File.Exists(file.Fullpath)) + throw new FileNotFoundException("Input file was not found. File: " + file.Fullpath); + var fileParameterKey = file.FileParameterKey switch + { + FileParameterKey.File => "file", + _ => "content", + }; + request.AddFile(fileParameterKey, file.Fullpath); + } + + return await restClient.ExecuteAsync(request, cancellationToken); } - return response; + request.Resource = "#"; + var fileBytes = restClient.DownloadData(request); + File.WriteAllBytes(Path.GetFullPath(input.DownloadPath), fileBytes); + return null; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj index 5b2527d..4011e60 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj @@ -31,6 +31,7 @@ + diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs index 88fe0fc..a8be3fd 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs @@ -1,9 +1,15 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Definitions")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request.Definitions")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends Tasks guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.ManagementApi.Request")] -[assembly: SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Following Frends guidelines", Scope = "type", Target = "~T:Frends.ManagementApi.Request.ManagementApi")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends Tasks guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] +[assembly: SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Following Frends guidelines", Scope = "type", Target = "~T:Frends.Echo.Execute.Echo")] [assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends guidelines")] +[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] \ No newline at end of file From 4dabdb4bd210f2e5bfbdade1f9449b494dbf0bd5 Mon Sep 17 00:00:00 2001 From: ttossavainen Date: Mon, 18 Mar 2024 10:37:30 +0200 Subject: [PATCH 5/6] Re-init --- Frends.ManagementApi.Request/CHANGELOG.md | 2 +- .../TaskTest.yaml | 87 ++++ .../UnitTests.cs | 418 +++++++++++++++++- .../Definitions/Enums.cs | 16 - .../Definitions/Header.cs | 19 - .../Definitions/Input.cs | 61 ++- .../Definitions/ManualParameters.cs | 2 +- .../Definitions/Options.cs | 6 +- .../Frends.ManagementApi.Request.cs | 155 +++++-- .../Frends.ManagementApi.Request.csproj | 2 +- .../GlobalSuppressions.cs | 24 +- Frends.ManagementApi.Request/README.md | 2 +- 12 files changed, 659 insertions(+), 135 deletions(-) create mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/TaskTest.yaml delete mode 100644 Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs diff --git a/Frends.ManagementApi.Request/CHANGELOG.md b/Frends.ManagementApi.Request/CHANGELOG.md index e6443ef..0f1e8f3 100644 --- a/Frends.ManagementApi.Request/CHANGELOG.md +++ b/Frends.ManagementApi.Request/CHANGELOG.md @@ -1,5 +1,5 @@ # Changelog -## [1.0.0] - 2024-02-16 +## [1.0.0] - 2024-03-18 ### Changed - Initial implementation diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/TaskTest.yaml b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/TaskTest.yaml new file mode 100644 index 0000000..5b0ac0e --- /dev/null +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/TaskTest.yaml @@ -0,0 +1,87 @@ +openapi: 3.0.1 +info: + title: Task test + description: '' + version: 0.0.1 +servers: + - url: /api/tasktesting/v1 +paths: + /customers: + get: + summary: Returns a array of customers. + description: Array of customers. + responses: + '200': + description: A JSON array of customer info + content: + '*/*': + schema: + type: array + items: + $ref: '#/components/schemas/Customer' + post: + requestBody: + $ref: '#/components/requestBodies/CreateCustomerModel' + responses: + '200': + description: ok + security: + - api_key: [ ] + '/customers/{customerId}': + get: + parameters: + - name: customerId + in: path + description: A single customer id to fetch + required: true + schema: + type: string + responses: + '200': + description: ok + content: + '*/*': + schema: + $ref: '#/components/schemas/Customer' +components: + schemas: + Customer: + properties: + id: + type: integer + format: int32 + CompanyName: + type: string + mainContact: + type: string + address: + type: string + phone: + type: string + created: + type: string + format: date-time + CreateCustomerModel: + properties: + companyName: + type: string + mainContact: + type: string + address: + type: string + phone: + type: string + created: + type: string + format: date-time + requestBodies: + CreateCustomerModel: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCustomerModel' + securitySchemes: + api_key: + type: apiKey + name: x-ApiKey + in: header \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs index b9a0fee..e5530d9 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs @@ -1,64 +1,442 @@ namespace Frends.ManagementApi.Request.Tests; using Frends.ManagementApi.Request.Definitions; +using Newtonsoft.Json; using NUnit.Framework; using System; +using System.IO; using System.Threading.Tasks; [TestFixture] internal class UnitTests { /// - /// These cannot be tested in Github. Set your own tenant and token and test locally. + /// These tests cannot be tested in Github. Set your own tenant and token etc. and test locally. /// - private readonly string tenantUrl = "https://tenant.frendsapp.com/"; - private readonly string token = "token"; + private readonly string managementAPIApplicationId = Environment.GetEnvironmentVariable("ManagementAPIApplicationId"); + private readonly string managementAPIClientSecret = Environment.GetEnvironmentVariable("managementAPIClientSecret"); + private readonly string managementAPIApplicationURI = Environment.GetEnvironmentVariable("ManagementAPIApplicationURI"); + private readonly string testTenant = Environment.GetEnvironmentVariable("TestTenant"); + private readonly string downloadPath = @$"C:\temp\ManagementApiTest\{DateTime.Now}\"; private Input input = new(); private Options options = new(); + private readonly string _apiKeyName = $"TaskTestApiKeyName_{Guid.NewGuid()}"; + private static int _apikeyId = 0; + private readonly string _rulesetName = "TaskTestRuleSet"; + private static int _rulesetId = 0; + private readonly string _apiSpecFile = Path.Combine(Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.FullName, "TaskTest.yaml"); + private static int _apiSpecId = 0; [SetUp] public void SetUp() { this.input = new Input { - Url = this.tenantUrl, - Token = this.token, + Url = testTenant, + Token = null, Method = Methods.Get, + DownloadPath = null, + FilePaths = null, + IsMultipart = false, + ManualParameters = null, + Message = null, + ApplicationId = managementAPIApplicationId, + ApplicationUri = managementAPIApplicationURI, + ClientSecret = managementAPIClientSecret, + GeneratedToken = true }; this.options = new Options { ThrowExceptionOnError = true, + Timeout = 10, }; } - [Ignore("Cannot be tested in Github")] + [TearDown] + public async Task OneTimeTearDown() + { + if (Directory.Exists(@$"C:\temp\ManagementApiTest")) + Directory.Delete(@$"C:\temp\ManagementApiTest", true); + + if (_apikeyId > 0) await DeleteApiKey(); + if (_rulesetId > 0) await DeleteRuleset(); + if (_apiSpecId > 0) await DeleteApiSpec(); + } + + [Test] + public async Task Test_Get_apikeys() + { + this.input.Url = testTenant + "api/v1/api-management/access/api-keys/1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_apirulesets() + { + this.input.Url = testTenant + "api/v1/api-management/access/api-rulesets/1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_apikeys_name() + { + this.input.Url = testTenant + "api/v1/api-management/access/api-keys/name/test"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_apirulesets_name() + { + this.input.Url = testTenant + "api/v1/api-management/access/api-rulesets/name/FrendsAcademy"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_api_rulesets() + { + this.input.Url = testTenant + "api/v1/api-management/access/api-rulesets?pagingQuery.pageNumber=1&pagingQuery.pageSize=1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_api_apikeys() + { + this.input.Url = testTenant + "api/v1/api-management/access/api-keys?environmentId=51&pagingQuery.pageNumber=1&pagingQuery.pageSize=1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_api_specifications() + { + this.input.Url = testTenant + "api/v1/api-management/api-specifications/3"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_api_specifications_2() + { + this.input.Url = testTenant + "api/v1/api-management/api-specifications/3/1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_api_specifications_3() + { + this.input.Url = testTenant + "api/v1/api-management/api-specifications?pagingQuery.pageNumber=1&pagingQuery.pageSize=1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_environments() + { + this.input.Url = testTenant + "api/v1/environments"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_agent_groups() + { + this.input.Url = testTenant + "api/v1/agent-groups/51"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_environments_2() + { + this.input.Url = testTenant + "api/v1/environments/51"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_environments_agent_groups2() + { + this.input.Url = testTenant + "api/v1/environments/51/agent-groups"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_environment_variables() + { + this.input.Url = testTenant + "api/v1/environment-variables/1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_environment_variables_2() + { + this.input.Url = testTenant + "api/v1/environment-variables?environmentVariableName=ManagementAPI&pagingQuery.pageNumber=1&pagingQuery.pageSize=1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_processes() + { + this.input.Url = testTenant + "api/v1/processes/1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_processes_export() + { + var path = downloadPath + @$"Test_Get_processes_export_{DateTime.Now}"; + this.input.DownloadPath = path; + this.input.Url = testTenant + "api/v1/processes/1/export"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data.Contains("downloaded")); + Assert.IsTrue(File.Exists(path + ".json_")); + } + + [Test] + public async Task Test_Get_processes_2() + { + this.input.Url = testTenant + "api/v1/processes/6933c9e1-95f4-4494-a630-d3d6b36f7006/versions/2"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + [Test] + public async Task Test_Get_processes_batch_export() + { + var path = downloadPath + @$"Test_Get_processes_export_{DateTime.Now}"; + this.input.DownloadPath = path; + this.input.Url = testTenant + "api/v1/processes/batch-export?ids=1"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data.Contains("downloaded")); + Assert.IsTrue(File.Exists(path + ".json_")); + } + + [Test] + public async Task Test_Post_Create_apikeys() + { + this.input.Method = Methods.Post; + this.input.Url = testTenant + "api/v1/api-management/access/api-keys"; + this.input.Message = $@"{{ + ""name"": ""{_apiKeyName}"", + ""rulesetIds"": [ + 1 + ], + ""requestLimit"": 1, + ""requestLimitPeriod"": ""Minute"", + ""environmentId"": 51 +}}"; + + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + + _apikeyId = JsonConvert.DeserializeObject(ret.Data.ToString()).data.id; + } + + [Test] + public async Task Test_Post_Create_rulesets() + { + this.input.Method = Methods.Post; + this.input.Url = testTenant + "api/v1/api-management/access/api-rulesets"; + this.input.Message = $@"{{ + ""name"": ""{_rulesetName}"", + ""description"": ""Testing Management API Task. Can be deleted."", + ""apiRules"": [ + {{ + ""method"": ""Any"", + ""path"": ""string"" + }} + ] +}}"; + + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + _rulesetId = JsonConvert.DeserializeObject(ret.Data.ToString()).data.id; + } + [Test] - public async Task Test_Get() + public async Task Test_Post_Create_apispecifications_Import() { + this.input.Method = Methods.Post; + this.input.Url = testTenant + "api/v1/api-management/api-specifications/import"; + this.input.IsMultipart = true; + this.input.FilePaths = new[] { new SendFileParameters() { FileParameterKey = FileParameterKey.File, Fullpath = _apiSpecFile } }; + var ret = await ManagementApi.Request(this.input, this.options, default); - Assert.That(ret.Success is true); - Assert.NotNull(ret.ErrorMessage); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); Assert.NotNull(ret.Data); + + _apiSpecId = JsonConvert.DeserializeObject(ret.Data.Content.ToString()).data.id; } - [Ignore("Cannot be tested in Github")] [Test] - public void Test_Invalid_Token_Throw() + public async Task Test_Put_RuleSet_Rename() + { + //_apikeyId = await CreateApiKey(); + _rulesetId = await CreateRuleSet(); + this.input.Method = Methods.Put; + this.input.Url = $@"{testTenant}api/v1/api-management/access/api-rulesets/{_rulesetId}"; + this.input.Message = $@"{{ + ""name"": ""NewName{DateTime.Now}"", + ""description"": ""this is new desc {DateTime.Now}"", + ""apiRules"": [ + {{ + ""method"": ""Any"", + ""path"": ""string"" + }} + ] +}}"; + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); + } + + //[Test] + public async Task Test_Delete_ApiKey_Rename() { - this.input.Token = "foo"; - Assert.ThrowsAsync(async () => await ManagementApi.Request(this.input, this.options, default)); + _apikeyId = await CreateApiKey(); + _rulesetId = await CreateRuleSet(); + this.input.Method = Methods.Post; + this.input.Url = $@"{testTenant}api/v1/api-management/access/api-keys/{_apikeyId}"; + this.input.Message = $@"{{ + ""name"": ""New name{Guid.NewGuid}"", + ""rulesetIds"": [ + {_rulesetId} + ], + ""requestLimit"": 1, + ""requestLimitPeriod"": ""Minute"" +}}"; + + var ret = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(ret.Success); + Assert.IsNull(ret.ErrorMessage); + Assert.NotNull(ret.Data); } - [Ignore("Cannot be tested in Github")] [Test] - public async Task Test_Invalid_Token_Return() + public async Task Test_Delete_Ruleset() + { + var rulesetId = await CreateRuleSet(); + this.input.Method = Methods.Delete; + this.input.Url = $@"{testTenant}api/v1/api-management/access/api-rulesets/{rulesetId}"; + var result = await ManagementApi.Request(this.input, this.options, default); + Assert.IsTrue(result.Success); + Assert.IsNull(result.ErrorMessage); + } + + public async Task CreateApiKey() + { + this.input.Method = Methods.Post; + this.input.Url = testTenant + "api/v1/api-management/access/api-keys"; + this.input.Message = $@"{{ + ""name"": ""{_apiKeyName}"", + ""rulesetIds"": [ + 1 + ], + ""requestLimit"": 1, + ""requestLimitPeriod"": ""Minute"", + ""environmentId"": 51 +}}"; + + var ret = await ManagementApi.Request(this.input, this.options, default); + + return JsonConvert.DeserializeObject(ret.Data.ToString()).data.id; + } + + public async Task CreateRuleSet() { - this.input.Token = "foo"; - this.options.ThrowExceptionOnError = false; + this.input.Method = Methods.Post; + this.input.Url = testTenant + "api/v1/api-management/access/api-rulesets"; + this.input.Message = $@"{{ + ""name"": ""{_rulesetName}{DateTime.Now}"", + ""description"": ""Testing Management API Task. Can be deleted."", + ""apiRules"": [ + {{ + ""method"": ""Any"", + ""path"": ""string"" + }} + ] +}}"; + var ret = await ManagementApi.Request(this.input, this.options, default); - Assert.IsFalse(ret.Success); - Assert.IsNull(ret.Data is null); - Assert.NotNull(ret.ErrorMessage != null); + return JsonConvert.DeserializeObject(ret.Data.ToString()).data.id; + } + + public async Task DeleteApiKey() + { + this.input.Method = Methods.Delete; + this.input.Url = $@"{testTenant}api/v1/api-management/access/api-keys/{_apikeyId}"; + this.input.Message = null; + await ManagementApi.Request(this.input, this.options, default); + } + + public async Task DeleteRuleset() + { + this.input.Method = Methods.Delete; + this.input.Url = $@"{testTenant}api/v1/api-management/access/api-rulesets/{_rulesetId}"; + await ManagementApi.Request(this.input, this.options, default); + } + + public async Task DeleteApiSpec() + { + this.input.Method = Methods.Delete; + this.input.Url = $@"{testTenant}api/v1/api-management/api-specifications/{_apiSpecId}/agent-group/51"; + this.input.IsMultipart = false; + this.input.FilePaths = null; + await ManagementApi.Request(this.input, this.options, default); } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs index 2ebd5d0..19556d8 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Enums.cs @@ -31,22 +31,6 @@ public enum Methods Delete, } -/// -/// File handler. -/// -public enum FileHandler -{ - /// - /// Download file. - /// - Download, - - /// - /// Upload file. - /// - Upload, -} - /// /// Selection of file parameter key. /// diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs deleted file mode 100644 index 67190f2..0000000 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Header.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Frends.ManagementApi.Request.Definitions; - -/// -/// Request header. -/// -public class Header -{ - /// - /// Gets or sets name of header. - /// - /// Authorization. - public string Name { get; set; } - - /// - /// Gets or sets value of header. - /// - /// Bearer AccessToken123. - public string Value { get; set; } -} \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs index 9858249..248ba74 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Input.cs @@ -18,13 +18,45 @@ public class Input /// /// Gets or sets URL. /// - /// https://tenant.frendsapp.com/v0.9/api-management/access/api-keys/{id}. + /// https://tenant.frendsapp.com/api/v1/api-management/access/api-keys/1. public string Url { get; set; } /// - /// Gets or sets Bearer token. + /// Gets or sets a value indicating whether token is generated. + /// + /// false. + [DefaultValue(false)] + public bool GeneratedToken { get; set; } + + /// + /// gets or sets Application URI. + /// + /// api://qwe123-rty456-uio789-1p2i-asd9078. + [UIHint(nameof(GeneratedToken), "", true)] + [DisplayFormat(DataFormatString = "Text")] + public string ApplicationUri { get; set; } + + /// + /// Gets or sets application ID. + /// + /// qwe123-rty456-uio789-1p2i-asd9078. + [UIHint(nameof(GeneratedToken), "", true)] + [DisplayFormat(DataFormatString = "Text")] + public string ApplicationId { get; set; } + + /// + /// Gets or sets client secret. + /// + /// 1r2t3y4u5i6o7p8a9s. + [UIHint(nameof(GeneratedToken), "", true)] + [DisplayFormat(DataFormatString = "Text")] + public string ClientSecret { get; set; } + + /// + /// Gets or sets bearer token. /// /// abcd123. + [UIHint(nameof(GeneratedToken), "", false)] [DisplayFormat(DataFormatString = "Text")] [PasswordPropertyText] public string Token { get; set; } @@ -39,7 +71,6 @@ public class Input /// "processGuid": "00000000-0000-0000-0000-000000000000" /// ... /// - [UIHint(nameof(Methods), "", Methods.Post, Methods.Delete, Methods.Patch, Methods.Put)] public string Message { get; set; } /// @@ -47,35 +78,27 @@ public class Input /// If true, using Content-Type = multipart/form-data instead of application/json. /// /// false. + [DefaultValue(false)] public bool IsMultipart { get; set; } /// - /// Gets or sets how the file resource will be handled. + /// Gets or sets download path where resource will be exported. /// - /// FileHandler.Download. - [UIHint(nameof(IsMultipart), "", true)] - [DefaultValue(FileHandler.Download)] - public FileHandler FileHandler { get; set; } + /// C:\temp\foo.json. + [UIHint(nameof(Method), "", Methods.Get)] + public string DownloadPath { get; set; } /// - /// Gets or sets array of files. + /// Gets or sets array of files to be imported. /// - /// { {FileParameterKey = FileParameterKey.File, Fullpath = "C:\temp\file.txt"} }. - [UIHint(nameof(FileHandler), "", FileHandler.Upload)] + /// { {FileParameterKey = FileParameterKey.File, Fullpath = "C:\temp\file.json"} }. + [UIHint(nameof(Method), "", Methods.Post)] public SendFileParameters[] FilePaths { get; set; } - /// - /// Gets or sets download path. - /// - /// C:\temp\foo.txt. - [UIHint(nameof(FileHandler), "", FileHandler.Download)] - public string DownloadPath { get; set; } - /// /// Gets or sets manual parameters. /// No need to add Content-Type, Accept and Authorization headers. /// /// { {Key = foo, Value = bar, ParameterType = ParameterTypes.GetOrPost} }. - [UIHint(nameof(IsMultipart), "", true)] public ManualParameters[] ManualParameters { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs index 76fc278..55a3b3f 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs @@ -26,7 +26,7 @@ public class ManualParameters /// RequestBody = Parameter that will be added to the request body. /// QueryString = Parameter that will be added to the query string. /// - /// ParameterTypes.GetOrPost. + /// ParameterTypes.GetOrPost [DefaultValue(ParameterTypes.GetOrPost)] public ParameterTypes ParameterType { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs index d17f0fc..4bb2338 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/Options.cs @@ -12,13 +12,13 @@ public class Options /// If set to true, an exception will be thrown when an error occurs. /// If set to false, Task will try to continue and the error message will be added into Result.ErrorMessage and Result.Success will be set to false. /// - /// true + /// true. [DefaultValue(true)] public bool ThrowExceptionOnError { get; set; } /// - /// Timeout in seconds. + /// Gets or sets timeout in seconds. /// - /// 10 + /// 10. public int Timeout { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs index 0d65858..ec078f5 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs @@ -3,6 +3,7 @@ namespace Frends.ManagementApi.Request; using Frends.ManagementApi.Request.Definitions; +using Newtonsoft.Json.Linq; using RestSharp; using RestSharp.Authenticators.OAuth2; using System; @@ -17,7 +18,7 @@ namespace Frends.ManagementApi.Request; public static class ManagementApi { /// - /// Task for reading data from Frends Management API. + /// Task for Frends Management API related operations. /// [Documentation](https://tasks.frends.com/tasks/frends-tasks/Frends.ManagementApi.Request). /// /// Input parameters. @@ -26,21 +27,79 @@ public static class ManagementApi /// Object { bool Success, dynamic Data, dynamic ErrorMessage }. public static async Task Request([PropertyTab] Input input, [PropertyTab] Options options, CancellationToken cancellationToken) { - InputChecker(input); - var responseMessage = await GetRestResponse(input, options.Timeout, cancellationToken); + try + { + InputChecker(input); + var token = input.GeneratedToken ? await GetToken(input.ApplicationId, input.ClientSecret, input.ApplicationUri, cancellationToken) : input.Token; + + RestClientOptions restClientOptions = new() + { + BaseUrl = new Uri(input.Url), + MaxTimeout = (int)TimeSpan.FromSeconds(Convert.ToDouble(options.Timeout)).Ticks, + Authenticator = new OAuth2AuthorizationRequestHeaderAuthenticator(token, "Bearer"), + }; + RestClient restClient = new(restClientOptions); + RestRequest restRequest = GetRestRequest(input, cancellationToken); + + if (input.Method is Methods.Get && !string.IsNullOrEmpty(input.DownloadPath)) + { + var extension = input.Url.Contains("processes") && input.Url.Contains("export") ? ".json_" : ".json"; + var downloadRequest = await DownloadRequest(restRequest, restClient, extension, input.DownloadPath, cancellationToken); + + if (downloadRequest.Contains("There is no data to write to the file.")) + return new Result(false, null, downloadRequest); + + return new Result(true, downloadRequest, null); + } + + if (input.Method is Methods.Post && input.FilePaths != null && input.FilePaths.Length > 0) + { + var postRequest = await SendFileRequest(restClient, restRequest, input.FilePaths, cancellationToken); + if ((int)postRequest.StatusCode >= 200 && (int)postRequest.StatusCode <= 299) + return new Result(true, postRequest, null); + return new Result(false, null, postRequest); + } - if (responseMessage.ErrorMessage.Length > 0 && options.ThrowExceptionOnError) - throw new Exception(responseMessage.ErrorMessage); + var simpleReq = await restClient.ExecuteAsync(restRequest, cancellationToken); + if ((int)simpleReq.StatusCode >= 200 && (int)simpleReq.StatusCode <= 299) + return new Result(true, simpleReq.Content, null); - return new Result(responseMessage.ErrorMessage.Length > 0, responseMessage, responseMessage.ErrorMessage); + return new Result(false, null, simpleReq); + } + catch (Exception ex) + { + if (options.ThrowExceptionOnError) + throw; + + return new Result(false, null, ex); + } } private static void InputChecker(Input input) { if (string.IsNullOrEmpty(input.Url)) - throw new ArgumentNullException(nameof(input.Url) + " cannot be empty."); - if (string.IsNullOrEmpty(input.Token)) - throw new ArgumentNullException(nameof(input.Token) + " cannot be empty."); + throw new ArgumentNullException($"{nameof(input.Url)} cannot be empty."); + if (input.GeneratedToken is false && string.IsNullOrEmpty(input.Token)) + throw new ArgumentNullException($"{nameof(input.Token)} cannot be empty when {nameof(input.GeneratedToken)} is false."); + } + + private static async Task GetToken(string applicationId, string clientSecret, string applicationUri, CancellationToken cancellationToken) + { + RestClientOptions restClientOptions = new() + { + BaseUrl = new Uri("https://login.microsoftonline.com/unthink.onmicrosoft.com/oauth2/token"), + }; + RestClient restClient = new(restClientOptions); + RestRequest restRequest = new() + { + Method = Method.Post, + }; + var obj = @$"client_id={applicationId}&client_secret={clientSecret}&grant_type=client_credentials&resource={applicationUri}"; + restRequest.AddStringBody(obj, contentType: ContentType.Plain); + restRequest.AddHeader("Content-Type", "application/x-www-form-urlencoded"); + var res = await restClient.ExecuteAsync(restRequest, cancellationToken).ConfigureAwait(false); + var token = JToken.Parse(res.Content); + return token.SelectToken("access_token").ToString(); } private static Method GetMethod(Methods methods) @@ -69,54 +128,68 @@ private static ParameterType GetParameterType(ParameterTypes parameterTypes) }; } - private static async Task GetRestResponse(Input input, int timeout, CancellationToken cancellationToken) + private static RestRequest GetRestRequest(Input input, CancellationToken cancellationToken) { - RestClientOptions restClientOptions = new() + RestRequest restRequest = new() { - BaseUrl = new Uri(input.Url), - MaxTimeout = (int)TimeSpan.FromSeconds(Convert.ToDouble(timeout)).Ticks, - Authenticator = new OAuth2AuthorizationRequestHeaderAuthenticator(input.Token, "Bearer"), + AlwaysMultipartFormData = input.IsMultipart, + Method = GetMethod(input.Method), }; - RestClient restClient = new(restClientOptions); - RestRequest request = null; + restRequest.AddHeader("Content-Type", input.IsMultipart ? "multipart/form-data" : "application/json"); + restRequest.AddHeader("Accept", "application/json"); - request.Resource = "/"; - request.Method = GetMethod(input.Method); - request.AlwaysMultipartFormData = input.IsMultipart; - request.AddHeader("Content-Type", input.IsMultipart ? "multipart/form-data" : "application/json"); - request.AddHeader("Accept", "application/json"); + if (!string.IsNullOrEmpty(input.Message)) + restRequest.AddBody(input.Message); - if (input.ManualParameters.Length > 0) + if (input.ManualParameters != null && input.ManualParameters.Length > 0) { foreach (var manualParameter in input.ManualParameters) { cancellationToken.ThrowIfCancellationRequested(); - request.AddParameter(manualParameter.Key, manualParameter.Value, GetParameterType(manualParameter.ParameterType)); + restRequest.AddParameter(manualParameter.Key, manualParameter.Value, GetParameterType(manualParameter.ParameterType)); } } - if (input.FileHandler is FileHandler.Upload) + return restRequest; + } + + private static async Task SendFileRequest(RestClient restClient, RestRequest restRequest, SendFileParameters[] filePaths, CancellationToken cancellationToken) + { + foreach (var file in filePaths) { - foreach (var file in input.FilePaths) - { - cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - if (!File.Exists(file.Fullpath)) - throw new FileNotFoundException("Input file was not found. File: " + file.Fullpath); - var fileParameterKey = file.FileParameterKey switch - { - FileParameterKey.File => "file", - _ => "content", - }; - request.AddFile(fileParameterKey, file.Fullpath); - } + if (!File.Exists(file.Fullpath)) + throw new FileNotFoundException(@$"Input file was not found. File: {file.Fullpath}"); - return await restClient.ExecuteAsync(request, cancellationToken); + var fileParameterKey = file.FileParameterKey switch + { + FileParameterKey.File => "file", + _ => "content", + }; + + restRequest.AddFile(fileParameterKey, file.Fullpath); } - request.Resource = "#"; - var fileBytes = restClient.DownloadData(request); - File.WriteAllBytes(Path.GetFullPath(input.DownloadPath), fileBytes); - return null; + return await restClient.ExecuteAsync(restRequest, cancellationToken); + } + + private static async Task DownloadRequest(RestRequest restRequest, RestClient restClient, string extension, string downloadPath, CancellationToken cancellationToken) + { + var filePath = downloadPath; + + if (new FileInfo(filePath).Extension != null) + filePath += extension; + + var directoryPath = Path.GetDirectoryName(filePath); + if (!Directory.Exists(directoryPath)) + Directory.CreateDirectory(directoryPath); + + var fileBytes = restClient.DownloadData(restRequest); + + if (fileBytes != null) + await File.WriteAllBytesAsync(filePath, fileBytes, cancellationToken); + + return File.Exists(filePath) ? @$"File {filePath} downloaded" : @$"There is no data to write to the file."; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj index 4011e60..42536fb 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.csproj @@ -11,7 +11,7 @@ Frends MIT true - Task for reading data from Frends Management API. + Task for Frends Management API related operations. https://frends.com/ https://github.com/FrendsPlatform/Frends.ManagementApi/tree/main/Frends.ManagementApi.Request diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs index a8be3fd..0dbcf9f 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/GlobalSuppressions.cs @@ -1,15 +1,13 @@ using System.Diagnostics.CodeAnalysis; -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends Tasks guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] -[assembly: SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Following Frends guidelines", Scope = "type", Target = "~T:Frends.Echo.Execute.Echo")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends guidelines")] -[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] -[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1629:Documentation text should end with a period", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Definitions")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:Do not use regions", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] -[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute")] \ No newline at end of file +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "Following Frends documentation guidelines")] +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1400:Access modifier should be declared", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1000:Keywords should be spaced correctly", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:Prefix local calls with this", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1306:Field names should begin with lower-case letter", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] +[assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "Following Frends documentation guidelines", Scope = "namespaceanddescendants", Target = "~N:Frends.Echo.Execute.Tests")] \ No newline at end of file diff --git a/Frends.ManagementApi.Request/README.md b/Frends.ManagementApi.Request/README.md index 41cb82c..ad53368 100644 --- a/Frends.ManagementApi.Request/README.md +++ b/Frends.ManagementApi.Request/README.md @@ -1,5 +1,5 @@ # Frends.ManagementApi.Request -Task for reading data from Frends Management API. +Task for Frends Management API related operations. [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Build](https://github.com/FrendsPlatform/Frends.ManagementApi/actions/workflows/Request_build_and_test_on_main.yml/badge.svg)](https://github.com/FrendsPlatform/Frends.ManagementApi/actions) From 8d7678646ac84a63f211693c693fae30402492af Mon Sep 17 00:00:00 2001 From: ttossavainen Date: Mon, 18 Mar 2024 10:50:14 +0200 Subject: [PATCH 6/6] Cleaning up. --- .../Frends.ManagementApi.Request.Tests/UnitTests.cs | 11 +++++------ .../Definitions/ManualParameters.cs | 2 +- .../Frends.ManagementApi.Request.cs | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs index e5530d9..61bda43 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request.Tests/UnitTests.cs @@ -21,11 +21,11 @@ internal class UnitTests private Input input = new(); private Options options = new(); private readonly string _apiKeyName = $"TaskTestApiKeyName_{Guid.NewGuid()}"; - private static int _apikeyId = 0; + private int _apikeyId = 0; private readonly string _rulesetName = "TaskTestRuleSet"; - private static int _rulesetId = 0; + private int _rulesetId = 0; private readonly string _apiSpecFile = Path.Combine(Directory.GetParent(Environment.CurrentDirectory).Parent.Parent.FullName, "TaskTest.yaml"); - private static int _apiSpecId = 0; + private int _apiSpecId = 0; [SetUp] public void SetUp() @@ -345,15 +345,14 @@ public async Task Test_Put_RuleSet_Rename() Assert.NotNull(ret.Data); } - //[Test] - public async Task Test_Delete_ApiKey_Rename() + public async Task Test_Delete_ApiKey() { _apikeyId = await CreateApiKey(); _rulesetId = await CreateRuleSet(); this.input.Method = Methods.Post; this.input.Url = $@"{testTenant}api/v1/api-management/access/api-keys/{_apikeyId}"; this.input.Message = $@"{{ - ""name"": ""New name{Guid.NewGuid}"", + ""name"": ""New name{Guid.NewGuid()}"", ""rulesetIds"": [ {_rulesetId} ], diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs index 55a3b3f..76fc278 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Definitions/ManualParameters.cs @@ -26,7 +26,7 @@ public class ManualParameters /// RequestBody = Parameter that will be added to the request body. /// QueryString = Parameter that will be added to the query string. /// - /// ParameterTypes.GetOrPost + /// ParameterTypes.GetOrPost. [DefaultValue(ParameterTypes.GetOrPost)] public ParameterTypes ParameterType { get; set; } } \ No newline at end of file diff --git a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs index ec078f5..d99aec8 100644 --- a/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs +++ b/Frends.ManagementApi.Request/Frends.ManagementApi.Request/Frends.ManagementApi.Request.cs @@ -38,7 +38,7 @@ public static async Task Request([PropertyTab] Input input, [PropertyTab MaxTimeout = (int)TimeSpan.FromSeconds(Convert.ToDouble(options.Timeout)).Ticks, Authenticator = new OAuth2AuthorizationRequestHeaderAuthenticator(token, "Bearer"), }; - RestClient restClient = new(restClientOptions); + using var restClient = new RestClient(restClientOptions); RestRequest restRequest = GetRestRequest(input, cancellationToken); if (input.Method is Methods.Get && !string.IsNullOrEmpty(input.DownloadPath)) @@ -89,7 +89,7 @@ private static async Task GetToken(string applicationId, string clientSe { BaseUrl = new Uri("https://login.microsoftonline.com/unthink.onmicrosoft.com/oauth2/token"), }; - RestClient restClient = new(restClientOptions); + using var restClient = new RestClient(restClientOptions); RestRequest restRequest = new() { Method = Method.Post,