diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 54e1f177..ed9d9bce 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -126,7 +126,7 @@ jobs: - name: Upload Code Coverage Report (Codecov.io) continue-on-error: true - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: name: ${{ runner.os }}-codecov-${{ matrix.framework }} flags: ${{ runner.os }},${{ env.TARGET_FRAMEWORK }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 03b79b9f..2faee75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,18 @@ The format is based on [Keep a Changelog](http://keepachangelog.com) and this project adheres to [Semantic Versioning](http://semver.org). ## [Unreleased] - TBD + +## [1.3.0] - 2024-12-18 ### Added - Resend Invite feature [#153](https://github.com/signnow/SignNow.NET/issues/153) - Options for Embedded invite [#160](https://github.com/signnow/SignNow.NET/issues/160) +- DocumentGroup feature [#161](https://github.com/signnow/SignNow.NET/issues/161) + +### Changed +- `DownloadDocumentResponse` model extended with `MediaType` property + +### Fixed +- Fixed force logout after user details update ## [1.2.3] - 2023-10-15 @@ -214,7 +223,9 @@ and this project adheres to [Semantic Versioning](http://semver.org). [create freeform invite]: https://github.com/signnow/SignNow.NET/blob/develop/README.md#create-freeform-invite -[Unreleased]: https://github.com/signnow/SignNow.NET/compare/1.2.2...HEAD +[Unreleased]: https://github.com/signnow/SignNow.NET/compare/1.3.0...HEAD +[1.3.0]: https://github.com/signnow/SignNow.NET/compare/1.2.3...1.3.0 +[1.2.3]: https://github.com/signnow/SignNow.NET/compare/1.2.2...1.2.3 [1.2.2]: https://github.com/signnow/SignNow.NET/compare/1.2.1...1.2.2 [1.2.1]: https://github.com/signnow/SignNow.NET/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/signnow/SignNow.NET/compare/1.1.1...1.2.0 diff --git a/README.md b/README.md index a07f8da7..9f63945d 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,11 @@ Get your account at 4. [Documentation](#documentation) 5. [Features](#features) - [Authorization](#authorization) - - [Request Access Token](#get-token) + - [Request Access Token][request_access_token example] - [Verify Access Token][verify_access_token example] - [Refresh Access Token][refresh_access_token example] - [User](#user) - - [Creates an account for a user](#create-user) + - [Creates an account for a user][create_user example] - [Retrieve User Information][get_user_info example] - [Sends verification email to a user][send_verification example] - [Change User details][update_user example] @@ -32,24 +32,33 @@ Get your account at - [Get modified documents][get_modified_docs example] - [Get user documents][get_user_docs example] - [Document](#document) - - [Upload a document to signNow](#upload-document) + - [Upload a document to signNow][upload_document example] - [Upload a document & Extract Fields][upload_doc_extract example] - - [Download a document from signNow](#download-document) + - [Download a document from signNow][download_signed_doc example] - [Retrieve a document resource][get_document example] - - [Merge two or more signNow documents into one](#merge-documents) - - [Create a signing link to the document for signature](#create-signing-link) - - [Create a freeform invite to the document for signature](#create-freeform-invite) - - [Create a role-based invite to the document for signature](#create-role-based-invite) - - [Create embedded signing invite to the document for signature](#create-embedded-invite) - - [Create a one-time link to download the document as a PDF](#share-document-via-link) - - [Get the history of a document](#document-history) + - [Merge two or more signNow documents into one][merge_documents example] + - [Create a signing link to the document for signature][create_sign_lnk example] + - [Create a freeform invite to the document for signature][create_ff_invite example] + - [Create a role-based invite to the document for signature][create_rb_invite example] + - [Create embedded signing invite to the document for signature][generate_embedded_link example] + - [Create a one-time link to download the document as a PDF][create_one_time_link example] + - [Get the history of a document][document_history example] - [Check the status of the document][check_sign_status example] - [Move document into specified folder][move_document example] - [Edit document][edit_document example] - [Prefill document text fields][prefill_text_field example] - [Template](#template) - - [Create a template by flattening an existing document](#create-template) + - [Create a template by flattening an existing document][create_template example] - [Create document from the template][create_document example] + - [Document Group](#document-group) + - [Create a document group](#create-document-group) + - [Delete document group][doc_group_operations example] + - [Rename document group][doc_group_operations example] + - [Create a copy of document group][doc_group_operations example] + - [Download document group][doc_group_operations example] + - [Move document group][doc_group_operations example] + - [Get document group info][doc_group_operations example] + - [Get all document groups the user owns][doc_group_operations example] - [Folders](#folders) - [Get all folders](#get-all-folders) - [Get folder by Id][get_folder example] @@ -68,7 +77,7 @@ To start using the API you will need an API key. You can get one here | | | Entry page: | | | @@ -548,6 +557,53 @@ public static class DocumentExamples More examples: [Create a template by flattening an existing document][create_template example], [Create document from the template][create_document example] + +## Document Group + +### Create document group + +Creates a document group from a list of document ids + +All documents: + +- Must be owned by the person creating the document group. +- Cannot be templates. +- Cannot already be a part of another document group (delete document group first to add them). +- At least one of the documents must have fields. + +```csharp +public async Task CreateDocumentGroupAsync() +{ + // using token from the Authorization step + var signNowContext = new SignNowContext(token); + + // Upload test documents + await using var fileStream = File.OpenRead("./PdfWithSignatureField.pdf"); + + var documents = new List(); + + for (int i = 0; i < 2; i++) + { + var upload = await signNowContext.Documents + .UploadDocumentAsync(fileStream, $"ForDocumentGroupFile-{i}.pdf"); + var doc = await signNowContext.Documents.GetDocumentAsync(upload.Id).ConfigureAwait(false); + documents.Add(doc); + } + + // Create document group from uploaded documents + var documentGroup = await signNowContext.DocumentGroup + .CreateDocumentGroupAsync("CreateDocumentGroupExample", documents) + .ConfigureAwait(false); + + // Get document group by id + return await signNowContext.DocumentGroup + .GetDocumentGroupInfoAsync(documentGroup.Id) + .ConfigureAwait(false); +} +``` +More examples: [Document group operations][doc_group_operations example] + + ## Folders ### Get all folders @@ -573,6 +629,7 @@ public static class FolderExamples More examples: [Get all folders][get_all_folders example], [Get folder][get_folder example], [Create folder][create_folder example] + ## Contribution guidelines ### XML doc generation @@ -594,6 +651,7 @@ Thanks to all contributors who got interested in this project. We're excited to - When suggesting new functionality, give as many details as possible. Add a test or code example if you can. - When reporting a bug, please, provide full system information. If possible, add a test that helps us reproduce the bug. It will speed up the fix significantly. + ## License This SDK is distributed under the MIT License, see [LICENSE][license link] for more information. @@ -618,46 +676,51 @@ If you have questions about the signNow API, please visit [signNow API Reference [license badge]: https://img.shields.io/github/license/signnow/SignNow.NET?style=flat-square "signNow .Net SDK License" [license link]: https://github.com/signnow/SignNow.NET/blob/develop/LICENSE [api docs link]: https://docs.signnow.com -[api reference link]: https://docs.signnow.com/sn/ref +[api reference link]: https://docs.signnow.com/docs/signnow/reference -[request_access_token example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Authentication/RequestAccessToken.cs#L16 -[verify_access_token example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Authentication/RequestAccessToken.cs#39 -[refresh_access_token example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Authentication/RequestAccessToken.cs#54 +[request_access_token example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/OAuth2/GenerateAccessToken.cs +[verify_access_token example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/OAuth2/VerifyAccessToken.cs +[refresh_access_token example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/OAuth2/RefreshAccessToken.cs [create_user example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/CreateSignNowUser.cs -[get_user_info example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/CreateSignNowUser.cs#42 -[send_verification example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/SendVerificationEmailToUser.cs -[update_user example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/ChangeUserDetails.cs#18 -[reset_password example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/ChangeUserDetails.cs#40 -[get_modified_docs example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/GetUserModifiedDocuments.cs#15 -[get_user_docs example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/GetUserDocuments.cs#15 +[get_user_info example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/ChangeUserDetails.cs +[send_verification example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/CreateSignNowUser.cs +[update_user example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/ChangeUserDetails.cs +[reset_password example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/ChangeUserDetails.cs +[get_modified_docs example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/GetUserModifiedDocuments.cs +[get_user_docs example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Users/GetUserDocuments.cs -[upload_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/UploadDocument.cs#33 -[upload_doc_extract example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/UploadDocument.cs#14 -[upload_document_complex_tags]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/ExamplesRunner.cs#L364 +[upload_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/UploadDocument.cs +[upload_doc_extract example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/UploadDocumentWithTags.cs +[upload_document_complex_tags]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/UploadDocumentWithComplexTags.cs [download_signed_doc example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/DownloadSignedDocument.cs [get_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/CheckTheStatusOfTheDocument.cs [merge_documents example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/MergeTwoDocuments.cs [create_sign_lnk example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/CreateSigningLinkToTheDocument.cs -[create_ff_invite example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/CreateFreeformInviteToSignTheDocument.cs -[create_rb_invite example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/CreateRoleBasedInviteToSignTheDocument.cs -[generate_embedded_link example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/GenerateLinkForEmbeddedInvite.cs -[cancel_embedded_invite example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/CancelEmbeddedInvite.cs [check_sign_status example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/CheckTheStatusOfTheDocument.cs [create_one_time_link example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/CreateOneTimeLinkToDownloadTheDocument.cs [document_history example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/GetTheDocumentHistory.cs [move_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/MoveTheDocumentToFolder.cs -[edit_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/EditDocumentTextFields.cs +[edit_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/PrefillTextFields.cs [prefill_text_field example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/PrefillTextFields.cs + +[create_ff_invite example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/CreateFreeformInviteToSignTheDocument.cs +[create_rb_invite example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/CreateRoleBasedInviteToSignTheDocument.cs +[generate_embedded_link example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/GCreateEmbeddedSigningInviteToSignTheDocument.cs +[cancel_embedded_invite example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Invites/CreateEmbeddedSigningInviteToSignTheDocument.cs + [create_template example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/CreateTemplateFromTheDocument.cs [create_document example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Documents/CreateDocumentFromTheTemplate.cs + +[doc_group_operations example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Document%20group/DocumentGroupOperations.cs + [get_all_folders example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Folders/GetAllFolders.cs [get_folder example]: https://github.com/signnow/SignNow.NET/blob/develop/SignNow.Net.Examples/Folders/GetFolder.cs diff --git a/SignNow.Net.Examples/Authentication/RequestAccessToken.cs b/SignNow.Net.Examples/Authentication/RequestAccessToken.cs deleted file mode 100644 index 9ffc5079..00000000 --- a/SignNow.Net.Examples/Authentication/RequestAccessToken.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Threading.Tasks; -using SignNow.Net.Model; -using SignNow.Net.Service; -using SignNow.Net.Test.Context; - -namespace SignNow.Net.Examples.Authentication -{ - public static class AuthenticationExamples - { - /// - /// An example of obtaining an access token via OAuth 2.0 service. - /// - /// signNow API base URL. Sandbox: "https://api-eval.signnow.com", Production: "https://api.signnow.com" - /// with Application Client ID, Client Secret, Login and Password - public static async Task RequestAccessToken(Uri apiBase, CredentialModel credentials) - { - Uri apiBaseUrl = apiBase; - - string clientId = credentials.ClientId; - string clientSecret = credentials.ClientSecret; - - string userLogin = credentials.Login; - string userPassword = credentials.Password; - - var oauth = new OAuth2Service(apiBaseUrl, clientId, clientSecret) - { - ExpirationTime = 60 - }; - - return await oauth.GetTokenAsync(userLogin, userPassword, Scope.All) - .ConfigureAwait(false); - } - - /// - /// Verify access token example - /// - /// signNow API base URL. Sandbox: "https://api-eval.signnow.com", Production: "https://api.signnow.com" - /// with Application Client ID and Client Secret - /// Access token - /// - public static async Task VerifyAccessToken(Uri apiBaseUrl, CredentialModel clientInfo, Token token) - { - var oauth2 = new OAuth2Service(apiBaseUrl, clientInfo.Login, clientInfo.Password); - - return await oauth2.ValidateTokenAsync(token) - .ConfigureAwait(false); - } - - /// - /// Refresh access token example - /// - /// signNow API base URL. Sandbox: "https://api-eval.signnow.com", Production: "https://api.signnow.com" - /// with Application Client ID and Client Secret - /// Access token - /// - public static async Task RefreshAccessToken(Uri apiBaseUrl, CredentialModel clientInfo, Token token) - { - var oauth2 = new OAuth2Service(apiBaseUrl, clientInfo.Login, clientInfo.Password); - - return await oauth2.RefreshTokenAsync(token) - .ConfigureAwait(false); - } - } -} diff --git a/SignNow.Net.Examples/Document group/DocumentGroupOperations.cs b/SignNow.Net.Examples/Document group/DocumentGroupOperations.cs new file mode 100644 index 00000000..d8acd84c --- /dev/null +++ b/SignNow.Net.Examples/Document group/DocumentGroupOperations.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Model.Requests.DocumentGroup; +using UnitTests; +using DownloadType = SignNow.Net.Model.Requests.DocumentGroup.DownloadType; + +namespace SignNow.Net.Examples +{ + [TestClass] + public partial class DocumentGroupOperations : ExamplesBase + { + [TestMethod] + public async Task BasicOperationsWithDocumentGroupAsync() + { + // Upload test documents + await using var fileStream = File.OpenRead(PdfWithSignatureField); + + var documents = new List(); + + for (int i = 0; i < 2; i++) + { + var upload = await testContext.Documents + .UploadDocumentAsync(fileStream, $"ForDocumentGroupFile-{i}.pdf"); + var doc = await testContext.Documents.GetDocumentAsync(upload.Id).ConfigureAwait(false); + documents.Add(doc); + } + + // Create document group from uploaded documents + var documentGroup = await testContext.DocumentGroup + .CreateDocumentGroupAsync("CreateDocumentGroupTest", documents) + .ConfigureAwait(false); + + // Get document group by id + var createdDocumentGroup = await testContext.DocumentGroup.GetDocumentGroupInfoAsync(documentGroup.Id).ConfigureAwait(false); + + // Check if document group was created + Assert.IsTrue(documentGroup.Id.Length == 40); + Assert.AreEqual("CreateDocumentGroupTest", createdDocumentGroup.Data.Name); + Assert.AreEqual(documents.Count, createdDocumentGroup.Data.Documents.Count); + Console.WriteLine("Created document group: {0} with name {1}", documentGroup.Id, createdDocumentGroup.Data.Name); + + // rename document group + await testContext.DocumentGroup.RenameDocumentGroupAsync("renamedDocumentGroup", documentGroup.Id).ConfigureAwait(false); + + // Get document group by id + var renamedDocumentGroup = await testContext.DocumentGroup.GetDocumentGroupInfoAsync(documentGroup.Id).ConfigureAwait(false); + + // check if document group was renamed + Assert.AreEqual("renamedDocumentGroup", renamedDocumentGroup.Data.Name); + Console.WriteLine("Document group was renamed: {0} => {1}", createdDocumentGroup.Data.Name, renamedDocumentGroup.Data.Name); + + // Download document group files + var pdfFiles = await testContext.DocumentGroup + .DownloadDocumentGroupAsync(documentGroup.Id, new DownloadOptions {DownloadType = DownloadType.MergedPdf}) + .ConfigureAwait(false); + var zipFile = await testContext.DocumentGroup + .DownloadDocumentGroupAsync(documentGroup.Id, new DownloadOptions {DownloadType = DownloadType.Zip}) + .ConfigureAwait(false); + + // Check if downloaded files are PDF and ZIP + Assert.That.StreamIsPdf(pdfFiles.Document); + Assert.That.StreamIsZip(zipFile.Document); + Assert.IsTrue(pdfFiles.Length > 0); + Assert.IsTrue(zipFile.Length > 0); + Assert.AreEqual("application/pdf", pdfFiles.MediaType); + Assert.AreEqual("application/zip", zipFile.MediaType); + Console.WriteLine("Document group downloades as: {0} with name {1} and size {2} bytes", pdfFiles.MediaType, pdfFiles.Filename, pdfFiles.Length); + Console.WriteLine("Document group downloades as: {0} with name {1} and size {2} bytes", zipFile.MediaType, zipFile.Filename, zipFile.Length); + + // Clean up + await testContext.DocumentGroup.DeleteDocumentGroupAsync(documentGroup.Id).ConfigureAwait(false); + foreach (var document in documents) + { + DeleteTestDocument(document.Id); + } + } + } +} diff --git a/SignNow.Net.Examples/Documents/CheckTheStatusOfTheDocument.cs b/SignNow.Net.Examples/Documents/CheckTheStatusOfTheDocument.cs index 72fe506e..e3ea680d 100644 --- a/SignNow.Net.Examples/Documents/CheckTheStatusOfTheDocument.cs +++ b/SignNow.Net.Examples/Documents/CheckTheStatusOfTheDocument.cs @@ -1,28 +1,31 @@ +using System.IO; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Check the Status of the Document - /// Can be one of: - /// - /// - /// - /// - /// - /// - /// Identity of the document - /// signNow container with services. - /// - public static async Task CheckTheStatusOfTheDocument(string documentId, SignNowContext signNowContext) + [TestMethod] + public async Task CheckTheStatusOfTheDocumentAsync() { - var document = await signNowContext.Documents - .GetDocumentAsync(documentId).ConfigureAwait(false); + // upload a document with a signature field + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "CheckTheStatusOfTheDocument.pdf") + .ConfigureAwait(false); - return document.Status; + // get the document + var documentStatus = await testContext.Documents + .GetDocumentAsync(document?.Id) + .ConfigureAwait(false); + + // check the status of the document + Assert.AreEqual(DocumentStatus.NoInvite, documentStatus.Status); + + // delete the test document + DeleteTestDocument(document?.Id); } } } diff --git a/SignNow.Net.Examples/Documents/CreateDocumentFromTheTemplate.cs b/SignNow.Net.Examples/Documents/CreateDocumentFromTheTemplate.cs deleted file mode 100644 index 37b14e13..00000000 --- a/SignNow.Net.Examples/Documents/CreateDocumentFromTheTemplate.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Threading.Tasks; -using SignNow.Net.Model; -using SignNow.Net.Model.Responses; - -namespace SignNow.Net.Examples.Documents -{ - public static partial class DocumentExamples - { - /// - /// Creates document from template. - /// - /// Identity of the template - /// The name of new document - /// signNow container with services. - /// New document ID - public static async Task CreateDocumentFromTheTemplate(string templateId, string documentName, SignNowContext signNowContext) - { - return await signNowContext.Documents - .CreateDocumentFromTemplateAsync(templateId, documentName) - .ConfigureAwait(false); - } - } -} diff --git a/SignNow.Net.Examples/Documents/CreateOneTimeLinkToDownloadTheDocument.cs b/SignNow.Net.Examples/Documents/CreateOneTimeLinkToDownloadTheDocument.cs index a99e65c4..3271d84d 100644 --- a/SignNow.Net.Examples/Documents/CreateOneTimeLinkToDownloadTheDocument.cs +++ b/SignNow.Net.Examples/Documents/CreateOneTimeLinkToDownloadTheDocument.cs @@ -1,22 +1,30 @@ +using System.IO; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Create a one-time use URL for anyone to download the document as a PDF. - /// - /// Identity of the document - /// signNow container with services. - /// - public static async Task - CreateOneTimeLinkToDownloadTheDocument(string documentId, SignNowContext signNowContext) + [TestMethod] + public async Task CreateOneTimeLinkToDownloadTheDocumentAsync() { - return await signNowContext.Documents - .CreateOneTimeDownloadLinkAsync(documentId) + // Upload a document with a signature field + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "CreateOneTimeLinkToDownloadTheDocumentTest.pdf") .ConfigureAwait(false); + + // Create a one-time use URL for anyone to download the document as a PDF + var oneTimeLink = await testContext.Documents + .CreateOneTimeDownloadLinkAsync(document?.Id) + .ConfigureAwait(false); + + // Check if the URL is valid + StringAssert.Contains(oneTimeLink.Url.Host, "signnow.com"); + + // Clean up + DeleteTestDocument(document?.Id); } } } diff --git a/SignNow.Net.Examples/Documents/CreateSigningLinkToTheDocument.cs b/SignNow.Net.Examples/Documents/CreateSigningLinkToTheDocument.cs index cbc54981..77870130 100644 --- a/SignNow.Net.Examples/Documents/CreateSigningLinkToTheDocument.cs +++ b/SignNow.Net.Examples/Documents/CreateSigningLinkToTheDocument.cs @@ -1,26 +1,35 @@ +using System; +using System.IO; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Create a signing link to the document for signature. - /// - /// Identity of the document you’d like to have signed - /// signNow container with services. - /// - /// Response with: - /// to sign the document via web browser using signNow credentials. - /// to sign the document via web browser without signNow credentials. - /// - public static async Task CreateSigningLinkToTheDocument(string documentId, SignNowContext signNowContext) + [TestMethod] + public async Task CreateSigningLinkToTheDocumentAsync() { - // using `documentId` from the Upload document step - return await signNowContext.Documents - .CreateSigningLinkAsync(documentId) + // Upload a document with a signature field + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "CreateSigningLinkToTheDocument.pdf") .ConfigureAwait(false); + + // Create a signing link to the document for signature + var signingLink = await testContext.Documents + .CreateSigningLinkAsync(document?.Id) + .ConfigureAwait(false); + + // Validate the response + Assert.IsNotNull(signingLink.Url); + Assert.IsNotNull(signingLink.AnonymousUrl); + Assert.IsInstanceOfType(signingLink.Url, typeof(Uri)); + Assert.AreEqual("https", signingLink.Url.Scheme); + Assert.IsFalse(string.IsNullOrEmpty(signingLink.Url.OriginalString)); + + // Clean up + DeleteTestDocument(document?.Id); } } } diff --git a/SignNow.Net.Examples/Documents/CreateTemplateFromTheDocument.cs b/SignNow.Net.Examples/Documents/CreateTemplateFromTheDocument.cs deleted file mode 100644 index 17a7a8e9..00000000 --- a/SignNow.Net.Examples/Documents/CreateTemplateFromTheDocument.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Threading.Tasks; -using SignNow.Net.Model; -using SignNow.Net.Model.Responses; - -namespace SignNow.Net.Examples.Documents -{ - public static partial class DocumentExamples - { - /// - /// Creates a template by flattening an existing document. - /// - /// Identity of the document which is the source of a template - /// The new template name - /// signNow container with services. - /// Returns a new template ID - public static async Task CreateTemplateFromTheDocument(string documentId, string templateName, SignNowContext signNowContext) - { - return await signNowContext.Documents - .CreateTemplateFromDocumentAsync(documentId, templateName) - .ConfigureAwait(false); - } - } -} diff --git a/SignNow.Net.Examples/Documents/DownloadSignedDocument.cs b/SignNow.Net.Examples/Documents/DownloadSignedDocument.cs index 50de52c6..3bd527a8 100644 --- a/SignNow.Net.Examples/Documents/DownloadSignedDocument.cs +++ b/SignNow.Net.Examples/Documents/DownloadSignedDocument.cs @@ -1,20 +1,32 @@ +using System.IO; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Downloads signed document - /// - /// ID of signed document - /// signNow container with services. - public static async Task DownloadSignedDocument(string documentId, SignNowContext signNowContext) + [TestMethod] + public async Task DownloadSignedDocumentAsync() { - return await signNowContext.Documents - .DownloadDocumentAsync(documentId, DownloadType.PdfCollapsed) + // Upload document + await using var fileStream = File.OpenRead(PdfWithoutFields); + var document = await testContext.Documents + .UploadDocumentAsync(fileStream, "SignedDocumentTest.pdf") .ConfigureAwait(false); + + // Download signed document + var documentSigned = await testContext.Documents + .DownloadDocumentAsync(document.Id, DownloadType.PdfCollapsed) + .ConfigureAwait(false); + + // Check if document is downloaded + Assert.AreEqual("SignedDocumentTest.pdf", documentSigned.Filename); + Assert.IsInstanceOfType(documentSigned.Document, typeof(Stream)); + + // Clean up + DeleteTestDocument(document.Id); } } } diff --git a/SignNow.Net.Examples/Documents/EditDocumentTextFields.cs b/SignNow.Net.Examples/Documents/EditDocumentTextFields.cs deleted file mode 100644 index b3c635a4..00000000 --- a/SignNow.Net.Examples/Documents/EditDocumentTextFields.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using SignNow.Net.Interfaces; -using SignNow.Net.Model; -using SignNow.Net.Model.EditFields; -using SignNow.Net.Model.Responses; - -namespace SignNow.Net.Examples.Documents -{ - // public class EditDocumentTextFields - public static partial class DocumentExamples - { - /// - /// Updates a document by adding/overwriting fields or elements (texts, checks, signatures, hyperlinks, attachments) - /// - /// Identity of the document to add/edit fields for. - /// Collection of the fields - /// signNow container with services. - /// - public static async Task EditDocumentTextFields(string documentId, IEnumerable fields, SignNowContext signNowContext) - { - return await signNowContext.Documents - .EditDocumentAsync(documentId, fields) - .ConfigureAwait(false); - } - } -} diff --git a/SignNow.Net.Examples/Documents/GetTheDocumentHistory.cs b/SignNow.Net.Examples/Documents/GetTheDocumentHistory.cs index e609bdf3..d2186b3b 100644 --- a/SignNow.Net.Examples/Documents/GetTheDocumentHistory.cs +++ b/SignNow.Net.Examples/Documents/GetTheDocumentHistory.cs @@ -1,23 +1,33 @@ -using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Get Document History example - /// - /// Identity of the document - /// signNow container with services. - /// - public static async Task> - GetTheDocumentHistory(string documentId, SignNowContext signNowContext) + [TestMethod] + public async Task GetTheDocumentHistoryAsync() { - return await signNowContext.Documents - .GetDocumentHistoryAsync(documentId) + // upload a document with a signature field + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "GetTheDocumentHistory.pdf") .ConfigureAwait(false); + + // get the document history + var documentHistory = await testContext.Documents + .GetDocumentHistoryAsync(document?.Id) + .ConfigureAwait(false); + + // check the document history + Assert.IsTrue(documentHistory.All(item => item.DocumentId == document?.Id)); + Assert.IsTrue(documentHistory.Any(item => item.Origin == "original")); + Assert.IsTrue(documentHistory.All(item => item.Email == credentials.Login)); + + // Clean up + DeleteTestDocument(document?.Id); } } } diff --git a/SignNow.Net.Examples/Documents/MergeTwoDocuments.cs b/SignNow.Net.Examples/Documents/MergeTwoDocuments.cs index b8b79566..39c50754 100644 --- a/SignNow.Net.Examples/Documents/MergeTwoDocuments.cs +++ b/SignNow.Net.Examples/Documents/MergeTwoDocuments.cs @@ -1,23 +1,44 @@ using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Merge two documents into one final document - /// - /// New Document name with extension - /// List of the documents to be merged - /// signNow container with services. - /// - public static async Task MergeTwoDocuments(string documentName, IEnumerable documentsList, SignNowContext signNowContext) + [TestMethod] + public async Task MergeTwoDocumentsAsync() { - return await signNowContext.Documents - .MergeDocumentsAsync(documentName, documentsList) + // upload two documents + await using var file1Stream = File.OpenRead(PdfWithSignatureField); + await using var file2Stream = File.OpenRead(PdfWithoutFields); + + var doc1 = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(file1Stream, "MergeTwoDocumentsTest1.pdf") + .ConfigureAwait(false); + var doc2 = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(file2Stream, "MergeTwoDocumentsTest2.pdf") .ConfigureAwait(false); + + // get uploaded documents + var document1 = await testContext.Documents.GetDocumentAsync(doc1.Id).ConfigureAwait(false); + var document2 = await testContext.Documents.GetDocumentAsync(doc2.Id).ConfigureAwait(false); + + // merge two documents + var documents = new List { document1, document2 }; + var finalDocument = await testContext.Documents + .MergeDocumentsAsync("MergeTwoDocumentsTestResult.pdf", documents) + .ConfigureAwait(false); + + // check merged document + Assert.AreEqual("MergeTwoDocumentsTestResult.pdf", finalDocument.Filename); + Assert.IsInstanceOfType(finalDocument.Document, typeof(Stream) ); + + // clean up + await testContext.Documents.DeleteDocumentAsync(doc1.Id); + await testContext.Documents.DeleteDocumentAsync(doc2.Id); } } } diff --git a/SignNow.Net.Examples/Documents/MoveTheDocumentToFolder.cs b/SignNow.Net.Examples/Documents/MoveTheDocumentToFolder.cs index 62ad5672..b7d073dd 100644 --- a/SignNow.Net.Examples/Documents/MoveTheDocumentToFolder.cs +++ b/SignNow.Net.Examples/Documents/MoveTheDocumentToFolder.cs @@ -1,21 +1,45 @@ +using System.IO; +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Move document to specified folder example - /// - /// Id of the document to move - /// Id of the folder where you'd like to keep this document - /// signNow container with services. - public static async Task MoveTheDocumentToFolder(string documentId, string folderId, SignNowContext signNowContext) + [TestMethod] + public async Task MoveDocumentToFolderAsync() { - await signNowContext.Documents - .MoveDocumentAsync(documentId, folderId) + // Upload test document + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var testDocument = await testContext.Documents + .UploadDocumentAsync(fileStream, "MoveDocumentTest.pdf"); + + // Create new Folder where you'd like to keep test document + var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); + var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); + var folderToMove = await testContext.Folders + .CreateFolderAsync("FolderToMoveDocument", documentsFolder?.Id) + .ConfigureAwait(false); + + // Move test document to folder created with previous step + await testContext.Documents + .MoveDocumentAsync(testDocument.Id, folderToMove.Id) .ConfigureAwait(false); + + // Get folder with updated document + var folderToMoveUpdated = await testContext.Folders + .GetFolderAsync(folderToMove.Id) + .ConfigureAwait(false); + + // Check if test document has been moved + Assert.AreEqual("FolderToMoveDocument", folderToMoveUpdated.Name); + Assert.AreEqual(1, folderToMoveUpdated.TotalDocuments); + Assert.AreEqual(testDocument.Id, folderToMoveUpdated.Documents.FirstOrDefault(d => d.Id == testDocument.Id)?.Id); + + // Finally - delete document and folder + await testContext.Documents.DeleteDocumentAsync(testDocument.Id).ConfigureAwait(false); + await testContext.Folders.DeleteFolderAsync(folderToMoveUpdated.Id).ConfigureAwait(false); } } } diff --git a/SignNow.Net.Examples/Documents/PrefillTextFields.cs b/SignNow.Net.Examples/Documents/PrefillTextFields.cs index 0e23ff32..d0bb5982 100644 --- a/SignNow.Net.Examples/Documents/PrefillTextFields.cs +++ b/SignNow.Net.Examples/Documents/PrefillTextFields.cs @@ -1,25 +1,76 @@ using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Interfaces; using SignNow.Net.Model.EditFields; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Adds values to fields that the Signers can later edit when they receive the document for signature. - /// Works only with Text field types. - /// - /// Identity of the document to prefill values for. - /// Collection of the fields - /// signNow container with services. - /// - public static async Task PrefillTextFields(string documentId, IEnumerable fields, SignNowContext signNowContext) + [TestMethod] + public async Task PrefillTextFieldsAsync() { - await signNowContext.Documents - .PrefillTextFieldsAsync(documentId, fields) + // Upload test document + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var testDocument = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "PrefillDocumentTest.pdf") .ConfigureAwait(false); + + // get uploaded document + var documentUploaded = await testContext.Documents.GetDocumentAsync(testDocument.Id).ConfigureAwait(false); + Assert.IsNull(documentUploaded.Fields.FirstOrDefault()?.JsonAttributes.PrefilledText); + + // Add simple text field which will be prefilled next. + var editFields = new List + { + new TextField + { + PageNumber = 0, + Name = "Text_1", + Height = 40, + Width = 200, + X = 10, + Y = 40, + Role = "Signer 1" + } + }; + + var editDocument = await testContext.Documents + .EditDocumentAsync(testDocument.Id, editFields) + .ConfigureAwait(false); + + // Get edited document + var documentEdited = await testContext.Documents.GetDocumentAsync(editDocument.Id).ConfigureAwait(false); + + // Check that document has fields + Assert.IsNull(documentEdited.Fields.FirstOrDefault()?.JsonAttributes.PrefilledText); + Assert.AreEqual("Text_1", documentEdited.Fields.FirstOrDefault()?.JsonAttributes.Name); + + // Prefill text field + var fields = new List + { + new TextField + { + Name = "Text_1", + PrefilledText = "Test Prefill" + } + }; + + await testContext.Documents.PrefillTextFieldsAsync(testDocument.Id, fields).ConfigureAwait(false); + + // Get final document + var documentFinal = await testContext.Documents.GetDocumentAsync(testDocument.Id).ConfigureAwait(false); + + // Check that document has fields + Assert.AreEqual("Test Prefill", documentFinal.Fields.FirstOrDefault()?.JsonAttributes.PrefilledText); + + // clean up + DeleteTestDocument(documentUploaded.Id); + DeleteTestDocument(documentEdited.Id); + DeleteTestDocument(documentFinal.Id); } } } diff --git a/SignNow.Net.Examples/Documents/UploadDocument.cs b/SignNow.Net.Examples/Documents/UploadDocument.cs index 0dd3e87e..42293d37 100644 --- a/SignNow.Net.Examples/Documents/UploadDocument.cs +++ b/SignNow.Net.Examples/Documents/UploadDocument.cs @@ -1,47 +1,31 @@ using System.IO; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Documents +namespace SignNow.Net.Examples { - public static partial class DocumentExamples + public partial class DocumentExamples { - /// - /// Uploads a PDF with fillable field (Signature field) - /// - /// Full qualified path to your PDF with field tags. - /// signNow container with services. - public static async Task UploadDocumentWithFieldExtract(string pdfFilePath, SignNowContext signNowContext) + [TestMethod] + public async Task UploadDocumentAsync() { - await using var fileStream = File.OpenRead(pdfFilePath); - // Upload the document with field extract - var uploadResponse = signNowContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "DocumentSampleWithSignatureTextTag.pdf").Result; - - var documentId = uploadResponse.Id; - - return await signNowContext.Documents.GetDocumentAsync(documentId); - } - - /// - /// Uploads a PDF document to signNow and returns SignNowDocument object. - /// - /// Full qualified path to your PDF file. - /// signNow container with services. - public static async Task UploadDocument(string pdfFilePath, SignNowContext signNowContext) - { - var pdfFileName = "document-example.pdf"; - - await using var fileStream = File.OpenRead(pdfFilePath); + await using var fileStream = File.OpenRead(PdfWithoutFields); // Upload the document - var uploadResponse = signNowContext.Documents - .UploadDocumentAsync(fileStream, pdfFileName).Result; + var uploadResponse = await testContext.Documents + .UploadDocumentAsync(fileStream, "document-example.pdf") + .ConfigureAwait(false); + + // Gets document after from successful upload + var uploadedDocument = await testContext.Documents + .GetDocumentAsync(uploadResponse.Id) + .ConfigureAwait(false); - // Gets document ID from successful response - var documentId = uploadResponse.Id; + // Check uploaded document + Assert.AreEqual(uploadResponse.Id, uploadedDocument.Id); - return await signNowContext.Documents.GetDocumentAsync(documentId); + // clean up + DeleteTestDocument(uploadedDocument.Id); } } } diff --git a/SignNow.Net.Examples/Documents/UploadDocumentWithComplexTags.cs b/SignNow.Net.Examples/Documents/UploadDocumentWithComplexTags.cs new file mode 100644 index 00000000..971438dd --- /dev/null +++ b/SignNow.Net.Examples/Documents/UploadDocumentWithComplexTags.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Model.ComplexTags; + +namespace SignNow.Net.Examples +{ + public partial class DocumentExamples + { + [TestMethod] + public async Task UploadDocumentWithComplexTagsAsync() + { + // Create complex tags collection based on tag name in {{ }} inside the document example + var complexTags = new ComplexTextTags(); + complexTags.Properties.Add(new AttachmentTag + { + TagName = "AttachmentTagExample", + Label = "Attach your files here", + Role = "Signer 1", + Required = true, + Width = 200, + Height = 20 + }); + complexTags.Properties.Add(new CheckBoxTag + { + TagName = "CheckboxTagExample", + Role = "Signer 1", + Required = true, + Width = 12, + Height = 12 + }); + complexTags.Properties.Add(new DateValidatorTag + { + TagName = "DateValidatorTagExample", + Label = "Date of sign", + Role = "Signer 1", + Required = false, + Width = 200, + Height = 20, + LockSigningDate = true, + Validator = DataValidator.DateUS + }); + complexTags.Properties.Add(new DropdownTag + { + TagName = "DropdownTagExample", + Label = "Select item", + Role = "Signer 1", + Required = false, + Width = 200, + Height = 20, + EnumerationOptions = new List{"All", "None", "Custom"} + }); + complexTags.Properties.Add(new HyperlinkTag + { + TagName = "HyperlinkTagExample", + PageNumber = 0, + Label = "signNow API documentation", + Hint = "Click to view", + Link = new Uri("https://docs.signnnow.com"), + Role = "Signer 1", + Required = false, + Width = 200, + Height = 20 + }); + + var radioButton = new RadioButtonTag("my_options") + { + TagName = "RadioButtonTagExample", + PageNumber = 0, + Label = "Radio button label", + Role = "Signer 1", + Required = false, + X = 72, + Y = 658, + Width = 200, + Height = 20 + }; + radioButton.AddOption("value-1", 0, 0); + radioButton.AddOption("value-2", 0, 14, 1); + radioButton.AddOption("value-3", 0, 28); + complexTags.Properties.Add(radioButton); + + complexTags.Properties.Add(new InitialsTag + { + TagName = "InitialsTagExample", + Role = "Signer 1", + Required = true, + Width = 100, + Height = 20 + }); + complexTags.Properties.Add(new SignatureTag + { + TagName = "SignatureTagExample", + Role = "Signer 1", + Required = true, + Width = 200, + Height = 20 + }); + + // Upload test document + await using var fileStream = File.OpenRead(PdfWithComplexTags); + var testDocument = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "ComplexTextTags.pdf", complexTags) + .ConfigureAwait(false); + + // Get document to check if all fields are present + var actualDoc = await testContext.Documents.GetDocumentAsync(testDocument.Id).ConfigureAwait(false); + + // Dropdown tag may not be present as field if it's not allowed on Organization level settings + Assert.IsTrue(actualDoc.Fields.Count >= complexTags.Properties.Count - 1); + + var documentFields = (List)actualDoc.Fields; + + // Check all fields are present in the document + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.Attachment)?.Type == FieldType.Attachment, + "Attachment field is not present in the document"); + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.Signature)?.Type == FieldType.Signature, + "Signature field is not present in the document"); + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.Initials)?.Type == FieldType.Initials, + "Initials field is not present in the document"); + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.Checkbox)?.Type == FieldType.Checkbox, + "Checkbox field is not present in the document"); + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.Enumeration)?.Type == FieldType.Enumeration, + "Dropdown field is not present in the document"); + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.RadioButton)?.Type == FieldType.RadioButton, + "Radiobutton field is not present in the document"); + Assert.IsTrue( + documentFields.Find(t => t.Type == FieldType.Text)?.Type == FieldType.Text, + "Text field is not present in the document"); + + // clean up + DeleteTestDocument(actualDoc.Id); + } + } +} diff --git a/SignNow.Net.Examples/Documents/UploadDocumentWithTags.cs b/SignNow.Net.Examples/Documents/UploadDocumentWithTags.cs new file mode 100644 index 00000000..ebb9efbe --- /dev/null +++ b/SignNow.Net.Examples/Documents/UploadDocumentWithTags.cs @@ -0,0 +1,37 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; + +namespace SignNow.Net.Examples +{ + [TestClass] + public partial class DocumentExamples : ExamplesBase + { + [TestMethod] + public async Task UploadDocumentWithTagsAsync() + { + await using var fileStream = File.OpenRead(PdfWithSignatureField); + + // Upload the document with field extract + var documentWithFields = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "DocumentSampleWithSignatureTextTag.pdf") + .ConfigureAwait(false); + + // Get uploaded document + var uploadedDocument = await testContext.Documents + .GetDocumentAsync(documentWithFields.Id) + .ConfigureAwait(false); + + // Check that document has fields + using var documentFields = uploadedDocument?.Fields.GetEnumerator(); + documentFields?.MoveNext(); + + Assert.AreEqual(FieldType.Text, documentFields?.Current?.Type); + Assert.IsTrue(uploadedDocument?.Fields.Count > 0); + + // clean up + DeleteTestDocument(uploadedDocument.Id); + } + } +} diff --git a/SignNow.Net.Examples/ExamplesBase.cs b/SignNow.Net.Examples/ExamplesBase.cs new file mode 100644 index 00000000..7e8d362c --- /dev/null +++ b/SignNow.Net.Examples/ExamplesBase.cs @@ -0,0 +1,82 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Test.Context; + +namespace SignNow.Net.Examples +{ + [TestClass] + public abstract class ExamplesBase + { + protected DateTime UnixEpoch => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// + /// Base path to the `TestExamples` directory. + /// Path should use Unix-like directory separator char. It requires for cross-platform path compatibility. + /// + private static readonly string BaseTestExamplesPath = "../../../TestExamples/" + .Replace('/', Path.DirectorySeparatorChar); + + protected static readonly string PdfWithSignatureField = Path.Combine(BaseTestExamplesPath, "DocumentWithSignatureFieldTag.pdf"); + protected static readonly string PdfWithoutFields = Path.Combine(BaseTestExamplesPath, "SignAndDate.pdf"); + protected static readonly string PdfWithComplexTags = Path.Combine(BaseTestExamplesPath, "ComplexTags.pdf"); + + /// + /// Contains application clientId, clientSecret and user credentials + /// + protected static CredentialModel credentials = new CredentialLoader(ApiBaseUrl).GetCredentials(); + + /// + /// signNow service container used for ExampleRunner + /// + protected static SignNowContext testContext; + + /// + /// signNow API base Url (sandbox) + /// + public static Uri ApiBaseUrl => new Uri("https://api-eval.signnow.com/"); + + /// + /// Delete test document after test. + /// + protected void DeleteTestDocument(string disposableDocumentId) + { + if (string.IsNullOrEmpty(disposableDocumentId)) + { + return; + } + + var documentTask = testContext.Documents + .DeleteDocumentAsync(disposableDocumentId); + + Task.WaitAll(documentTask); + + Assert.IsFalse(documentTask.IsFaulted); + } + + [AssemblyInitialize] + public static void AssemblyInitialize(TestContext context) + { + // If you want to use your own credentials just for simple and fast test + // uncomment next lines bellow and replace placeholders with your credentials: + + // credentials = new CredentialModel + // { + // Login = "user_eamail@noemail.com", + // Password = "your-secret-password", + // ClientId = "your-application-client-id", + // ClientSecret = "your-application-client-secret" + // }; + + var client = new HttpClient(); + + // Create signNow context with all the services and Authorization + testContext = new SignNowContext(ApiBaseUrl, null, client); + testContext.SetAppCredentials(credentials.ClientId, credentials.ClientSecret); + testContext.GetAccessToken(credentials.Login, credentials.Password, Scope.All); + } + } +} diff --git a/SignNow.Net.Examples/ExamplesRunner.cs b/SignNow.Net.Examples/ExamplesRunner.cs deleted file mode 100644 index 99719a7c..00000000 --- a/SignNow.Net.Examples/ExamplesRunner.cs +++ /dev/null @@ -1,933 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using SignNow.Net.Examples.Authentication; -using SignNow.Net.Examples.Documents; -using SignNow.Net.Examples.Folders; -using SignNow.Net.Examples.Invites; -using SignNow.Net.Examples.Users; -using SignNow.Net.Interfaces; -using SignNow.Net.Model; -using SignNow.Net.Model.ComplexTags; -using SignNow.Net.Model.EditFields; -using SignNow.Net.Model.Requests; -using SignNow.Net.Model.Requests.EventSubscriptionBase; -using SignNow.Net.Model.Requests.GetFolderQuery; -using SignNow.Net.Test.Context; -using UnitTests; - -namespace SignNow.Net.Examples -{ - /// - /// This Test class contains all tests for Code Samples. - /// - /// To run single test from console: - /// # For example we want to run only RequestAccessTokenTest - /// dotnet test SignNow.Net.Examples --filter RequestAccessTokenTest - /// - [TestClass] - public class ExamplesRunner - { - private DateTime UnixEpoch => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - /// - /// Base path to the `TestExamples` directory. - /// Path should use Unix-like directory separator char. It requires for cross platform path compatibility. - /// - private static readonly string BaseTestExamplesPath = "../../../TestExamples/" - .Replace('/', Path.DirectorySeparatorChar); - - private static readonly string PdfWithSignatureField = Path.Combine(BaseTestExamplesPath, "DocumentWithSignatureFieldTag.pdf"); - private static readonly string PdfWithoutFields = Path.Combine(BaseTestExamplesPath, "SignAndDate.pdf"); - private static readonly string PdfWithComplexTags = Path.Combine(BaseTestExamplesPath, "ComplexTags.pdf"); - - /// - /// Contains application clientId, clientSecret and user credentials - /// - private static CredentialModel credentials = new CredentialLoader(ApiBaseUrl).GetCredentials(); - - /// - /// signNow service container used for ExampleRunner - /// - private SignNowContext testContext; - - /// - /// signNow API base Url (sandbox) - /// - private static Uri ApiBaseUrl => new Uri("https://api-eval.signnow.com/"); - - public ExamplesRunner() - { - // If you want to use your own credentials just for simple and fast test - // uncomment next lines bellow and replace placeholders with your credentials: - - // credentials = new CredentialModel - // { - // Login = "user_eamail@noemail.com", - // Password = "your-secret-password", - // ClientId = "your-application-client-id", - // ClientSecret = "your-application-client-secret" - // }; - - var client = new HttpClient(); - - // Create signNow context with all the services and Authorization - testContext = new SignNowContext(ApiBaseUrl, null, client); - testContext.SetAppCredentials(credentials.ClientId, credentials.ClientSecret); - testContext.GetAccessToken(credentials.Login, credentials.Password, Scope.All); - } - - /// - /// Delete test document after test. - /// - private void DeleteTestDocument(string disposableDocumentId) - { - if (string.IsNullOrEmpty(disposableDocumentId)) - { - return; - } - - var documentTask = testContext.Documents - .DeleteDocumentAsync(disposableDocumentId); - - Task.WaitAll(documentTask); - - Assert.IsFalse(documentTask.IsFaulted); - } - - #region Authentication Examples - - /// - /// Run test for example: - /// - [TestMethod] - public async Task RequestAccessTokenTest() - { - var requestAccessToken = await AuthenticationExamples - .RequestAccessToken(ApiBaseUrl, credentials) - .ConfigureAwait(false); - - Assert.IsNotNull(requestAccessToken); - Assert.IsFalse(string.IsNullOrEmpty(requestAccessToken.AccessToken)); - Assert.IsFalse(string.IsNullOrEmpty(requestAccessToken.RefreshToken)); - } - - #endregion - - #region Documents Examples - - /// - /// Run test for example: - /// - [TestMethod] - public async Task UploadDocumentWithFieldExtractTest() - { - var documentWithFields = await DocumentExamples - .UploadDocumentWithFieldExtract(PdfWithSignatureField, testContext) - .ConfigureAwait(false); - - using var documentFields = documentWithFields?.Fields.GetEnumerator(); - documentFields?.MoveNext(); - - Assert.AreEqual(FieldType.Text, documentFields?.Current?.Type); - Assert.IsTrue(documentWithFields?.Fields.Count > 0); - DeleteTestDocument(documentWithFields.Id); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task DownloadSignedDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithoutFields); - var document = await testContext.Documents - .UploadDocumentAsync(fileStream, "SignedDocumentTest.pdf").ConfigureAwait(false); - - var documentSigned = await DocumentExamples - .DownloadSignedDocument(document.Id, testContext).ConfigureAwait(false); - - Assert.AreEqual("SignedDocumentTest.pdf", documentSigned.Filename); - Assert.IsInstanceOfType(documentSigned.Document, typeof(Stream)); - DeleteTestDocument(document.Id); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CreateSigningLinkToTheDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "CreateSigningLinkToTheDocument.pdf") - .ConfigureAwait(false); - - var signingLink = await DocumentExamples - .CreateSigningLinkToTheDocument(document?.Id, testContext).ConfigureAwait(false); - - Assert.IsNotNull(signingLink.Url); - Assert.IsNotNull(signingLink.AnonymousUrl); - Assert.IsInstanceOfType(signingLink.Url, typeof(Uri)); - Assert.AreEqual("https", signingLink.Url.Scheme); - Assert.IsFalse(string.IsNullOrEmpty(signingLink.Url.OriginalString)); - DeleteTestDocument(document?.Id); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CheckTheStatusOfTheDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "CheckTheStatusOfTheDocument.pdf") - .ConfigureAwait(false); - - var documentStatus = await DocumentExamples - .CheckTheStatusOfTheDocument(document?.Id, testContext) - .ConfigureAwait(false); - - Assert.AreEqual(DocumentStatus.NoInvite, documentStatus); - DeleteTestDocument(document?.Id); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task MergeTwoDocumentsTest() - { - await using var file1Stream = File.OpenRead(PdfWithSignatureField); - await using var file2Stream = File.OpenRead(PdfWithoutFields); - - var doc1 = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(file1Stream, "MergeTwoDocumentsTest1.pdf") - .ConfigureAwait(false); - var doc2 = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(file2Stream, "MergeTwoDocumentsTest2.pdf") - .ConfigureAwait(false); - - var document1 = await testContext.Documents.GetDocumentAsync(doc1.Id).ConfigureAwait(false); - var document2 = await testContext.Documents.GetDocumentAsync(doc2.Id).ConfigureAwait(false); - - var documents = new List { document1, document2 }; - var finalDocument = await DocumentExamples - .MergeTwoDocuments("MergeTwoDocumentsTestResult.pdf", documents, testContext) - .ConfigureAwait(false); - - await testContext.Documents.DeleteDocumentAsync(doc1.Id); - await testContext.Documents.DeleteDocumentAsync(doc2.Id); - - Assert.AreEqual("MergeTwoDocumentsTestResult.pdf", finalDocument.Filename); - Assert.IsInstanceOfType(finalDocument.Document, typeof(Stream) ); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task GetTheDocumentHistoryTest() - { - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "GetTheDocumentHistory.pdf") - .ConfigureAwait(false); - - var documentHistory = await DocumentExamples - .GetTheDocumentHistory(document?.Id, testContext) - .ConfigureAwait(false); - - Assert.IsTrue(documentHistory.All(item => item.DocumentId == document?.Id)); - Assert.IsTrue(documentHistory.Any(item => item.Origin == "original")); - Assert.IsTrue(documentHistory.All(item => item.Email == credentials.Login)); - DeleteTestDocument(document?.Id); - } - - /// - /// Run the test for example: - /// - [TestMethod] - public async Task CreateOneTimeLinkToDownloadTheDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "CreateOneTimeLinkToDownloadTheDocumentTest.pdf") - .ConfigureAwait(false); - - var oneTimeLink = await DocumentExamples - .CreateOneTimeLinkToDownloadTheDocument(document?.Id, testContext) - .ConfigureAwait(false); - - StringAssert.Contains(oneTimeLink.Url.Host, "signnow.com"); - DeleteTestDocument(document?.Id); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task MoveDocumentTest() - { - // Upload test document - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var testDocument = await testContext.Documents - .UploadDocumentAsync(fileStream, "MoveDocumentTest.pdf"); - - // Create new Folder where you'd like to keep test document - var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); - var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); - var folderToMove = await testContext.Folders - .CreateFolderAsync("FolderToMoveDocument", documentsFolder?.Id) - .ConfigureAwait(false); - - // Move test document to folder created with previous step - await DocumentExamples - .MoveTheDocumentToFolder(testDocument.Id, folderToMove.Id, testContext) - .ConfigureAwait(false); - - // Check if test document has been moved - var folderToMoveUpdated = await testContext.Folders - .GetFolderAsync(folderToMove.Id) - .ConfigureAwait(false); - Assert.AreEqual("FolderToMoveDocument", folderToMoveUpdated.Name); - Assert.AreEqual(1, folderToMoveUpdated.TotalDocuments); - Assert.AreEqual(testDocument.Id, folderToMoveUpdated.Documents.FirstOrDefault(d => d.Id == testDocument.Id)?.Id); - - // Finally - delete document and folder - await testContext.Documents.DeleteDocumentAsync(testDocument.Id).ConfigureAwait(false); - await testContext.Folders.DeleteFolderAsync(folderToMoveUpdated.Id).ConfigureAwait(false); - } - - /// - /// Run test for example: - /// Run test for example: - /// - [TestMethod] - public async Task PrefillTextFieldsTest() - { - // Upload test document - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var testDocument = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "PrefillDocumentTest.pdf") - .ConfigureAwait(false); - - var documentUploaded = await testContext.Documents.GetDocumentAsync(testDocument.Id).ConfigureAwait(false); - Assert.IsNull(documentUploaded.Fields.FirstOrDefault()?.JsonAttributes.PrefilledText); - - // Add simple text field which will be prefilled next. - var editFields = new List - { - new TextField - { - PageNumber = 0, - Name = "Text_1", - Height = 40, - Width = 200, - X = 10, - Y = 40, - Role = "Signer 1" - } - }; - - var editDocument = await DocumentExamples - .EditDocumentTextFields(testDocument.Id, editFields, testContext) - .ConfigureAwait(false); - - var documentEdited = await testContext.Documents.GetDocumentAsync(editDocument.Id).ConfigureAwait(false); - Assert.IsNull(documentEdited.Fields.FirstOrDefault()?.JsonAttributes.PrefilledText); - Assert.AreEqual("Text_1", documentEdited.Fields.FirstOrDefault()?.JsonAttributes.Name); - - var fields = new List - { - new TextField - { - Name = "Text_1", - PrefilledText = "Test Prefill" - } - }; - - await DocumentExamples.PrefillTextFields(testDocument.Id, fields, testContext).ConfigureAwait(false); - - var documentFinal = await testContext.Documents.GetDocumentAsync(testDocument.Id).ConfigureAwait(false); - Assert.AreEqual("Test Prefill", documentFinal.Fields.FirstOrDefault()?.JsonAttributes.PrefilledText); - - DeleteTestDocument(documentUploaded.Id); - DeleteTestDocument(documentEdited.Id); - DeleteTestDocument(documentFinal.Id); - } - - [TestMethod] - public async Task UploadDocumentWithComplexTags() - { - // Create complex tags collection based on tag name in {{ }} inside the document example - var complexTags = new ComplexTextTags(); - complexTags.Properties.Add(new AttachmentTag - { - TagName = "AttachmentTagExample", - Label = "Attach your files here", - Role = "Signer 1", - Required = true, - Width = 200, - Height = 20 - }); - complexTags.Properties.Add(new CheckBoxTag - { - TagName = "CheckboxTagExample", - Role = "Signer 1", - Required = true, - Width = 12, - Height = 12 - }); - complexTags.Properties.Add(new DateValidatorTag - { - TagName = "DateValidatorTagExample", - Label = "Date of sign", - Role = "Signer 1", - Required = false, - Width = 200, - Height = 20, - LockSigningDate = true, - Validator = DataValidator.DateUS - }); - complexTags.Properties.Add(new DropdownTag - { - TagName = "DropdownTagExample", - Label = "Select item", - Role = "Signer 1", - Required = false, - Width = 200, - Height = 20, - EnumerationOptions = new List{"All", "None", "Custom"} - }); - complexTags.Properties.Add(new HyperlinkTag - { - TagName = "HyperlinkTagExample", - PageNumber = 0, - Label = "signNow API documentation", - Hint = "Click to view", - Link = new Uri("https://docs.signnnow.com"), - Role = "Signer 1", - Required = false, - Width = 200, - Height = 20 - }); - - var radioButton = new RadioButtonTag("my_options") - { - TagName = "RadioButtonTagExample", - PageNumber = 0, - Label = "Radio button label", - Role = "Signer 1", - Required = false, - X = 72, - Y = 658, - Width = 200, - Height = 20 - }; - radioButton.AddOption("value-1", 0, 0); - radioButton.AddOption("value-2", 0, 14, 1); - radioButton.AddOption("value-3", 0, 28); - complexTags.Properties.Add(radioButton); - - complexTags.Properties.Add(new InitialsTag - { - TagName = "InitialsTagExample", - Role = "Signer 1", - Required = true, - Width = 100, - Height = 20 - }); - complexTags.Properties.Add(new SignatureTag - { - TagName = "SignatureTagExample", - Role = "Signer 1", - Required = true, - Width = 200, - Height = 20 - }); - - // Upload test document - await using var fileStream = File.OpenRead(PdfWithComplexTags); - var testDocument = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "ComplexTextTags.pdf", complexTags) - .ConfigureAwait(false); - - var actualDoc = await testContext.Documents.GetDocumentAsync(testDocument.Id).ConfigureAwait(false); - - // Dropdown tag may not be present as field if it's not allowed on Organization level settings - Assert.IsTrue(actualDoc.Fields.Count >= complexTags.Properties.Count - 1); - var documentFields = (List)actualDoc.Fields; - // Check all fields are present in the document - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.Attachment)?.Type == FieldType.Attachment, - "Attachment field is not present in the document"); - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.Signature)?.Type == FieldType.Signature, - "Signature field is not present in the document"); - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.Initials)?.Type == FieldType.Initials, - "Initials field is not present in the document"); - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.Checkbox)?.Type == FieldType.Checkbox, - "Checkbox field is not present in the document"); - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.Enumeration)?.Type == FieldType.Enumeration, - "Dropdown field is not present in the document"); - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.RadioButton)?.Type == FieldType.RadioButton, - "Radiobutton field is not present in the document"); - Assert.IsTrue( - documentFields.Find(t => t.Type == FieldType.Text)?.Type == FieldType.Text, - "Text field is not present in the document"); - - DeleteTestDocument(actualDoc.Id); - } - - #endregion - - #region Invites Examples - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CreateFreeformInviteToSignTheDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithoutFields); - var document = await testContext.Documents - .UploadDocumentAsync(fileStream, "CreateFreeformInviteToSignTheDocument.pdf") - .ConfigureAwait(false); - - var signNowDoc = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); - Assert.AreEqual(DocumentStatus.NoInvite, signNowDoc.Status); - - var inviteResponse = await InviteExamples - .CreateFreeformInviteToSignTheDocument(signNowDoc, "noreply@signnow.com", testContext) - .ConfigureAwait(false); - - Assert.IsFalse(string.IsNullOrEmpty(inviteResponse.Id)); - - var documentWithInvite = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); - var createdInvite = documentWithInvite.InvitesStatus.FirstOrDefault(); - - Assert.AreEqual("noreply@signnow.com", createdInvite?.SignerEmail); - Assert.AreEqual(inviteResponse.Id, createdInvite?.Id); - Assert.AreEqual(InviteStatus.Pending, createdInvite?.Status); - Assert.AreEqual(DocumentStatus.Pending, documentWithInvite.Status); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CreateRoleBasedInviteToSignTheDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "CreateRoleBasedInviteToSignTheDocument.pdf") - .ConfigureAwait(false); - - var signNowDoc = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); - Assert.AreEqual(DocumentStatus.NoInvite, signNowDoc.Status); - - var inviteResponse = await InviteExamples - .CreateRoleBasedInviteToSignTheDocument(signNowDoc, "noreply@signnow.com", testContext) - .ConfigureAwait(false); - - Assert.IsNull(inviteResponse.Id,"Successful Role-Based invite response doesnt contains Invite ID."); - - var documentWithInvite = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); - var createdInvite = documentWithInvite.FieldInvites.FirstOrDefault(); - - var fieldInvite = documentWithInvite.Fields.FirstOrDefault(); - Assert.IsNotNull(fieldInvite?.FieldRequestId); - - await testContext.Invites - .ResendEmailInviteAsync(fieldInvite?.FieldRequestId) - .ConfigureAwait(false); - - Assert.AreEqual("noreply@signnow.com", createdInvite?.SignerEmail); - Assert.AreEqual("Signer 1", createdInvite?.RoleName, "Signer role mismatch."); - Assert.AreEqual(InviteStatus.Pending, createdInvite?.Status); - Assert.AreEqual(DocumentStatus.Pending, documentWithInvite.Status); - } - - /// - /// Run test for example: - /// - /// - /// - [TestMethod] - public async Task CreateEmbeddedSigningInviteToSignTheDocumentTest() - { - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "CreateEmbeddedSigningInviteToSignTheDocument.pdf") - .ConfigureAwait(false); - - var signNowDoc = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); - - // Create Embedded Signing Invite - var embeddedInviteResponse = await InviteExamples - .CreateEmbeddedSigningInviteToSignTheDocument(signNowDoc, "testemail@signnow.com", testContext) - .ConfigureAwait(false); - - Assert.AreEqual(1, embeddedInviteResponse.InviteData.Count); - Assert.AreEqual(1, embeddedInviteResponse.InviteData[0].Order); - Assert.AreEqual("testemail@signnow.com", embeddedInviteResponse.InviteData[0].Email); - Assert.AreEqual("Pending", embeddedInviteResponse.InviteData[0].Status.ToString()); - - - var documentWithEmbed = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); - Assert.IsTrue(documentWithEmbed.FieldInvites.First().IsEmbedded); - - // Generate link for Embedded Signing Invite - var embeddedLink = await InviteExamples - .GenerateLinkForEmbeddedInvite(documentWithEmbed, 30, testContext).ConfigureAwait(false); - - Assert.IsInstanceOfType(embeddedLink.Link, typeof(Uri)); - Console.WriteLine($@"Embedded link: {embeddedLink.Link.AbsoluteUri}"); - - // Cancel embedded invite - await InviteExamples.CancelEmbeddedInvite(documentWithEmbed, testContext).ConfigureAwait(false); - DeleteTestDocument(document.Id); - DeleteTestDocument(documentWithEmbed.Id); - } - - #endregion - - #region User Examples - - /// - /// Run test for and - /// - [TestMethod] - public async Task CreateSignNowUserTest() - { - var timestamp = (long)(DateTime.Now - UnixEpoch).TotalSeconds; - - var createUserResponse = await UserExamples.CreateSignNowUser( - "John", - $"Sample{timestamp}", - $"signnow.tutorial+sample_test{timestamp}@gmail.com", - "secretPassword", - testContext - ).ConfigureAwait(false); - - Assert.AreEqual($"signnow.tutorial+sample_test{timestamp}@gmail.com", createUserResponse.Email); - Assert.IsFalse(createUserResponse.Verified); - - // Finally - send verification email to User - await UserExamples.SendVerificationEmailToUser(createUserResponse.Email, testContext).ConfigureAwait(false); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task GetUserModifiedDocumentsTest() - { - var perPage = 25; - var SignNowDocumentsAsync = await UserExamples - .GetUserModifiedDocuments(perPage, testContext) - .ConfigureAwait(false); - - var modifiedDocuments = SignNowDocumentsAsync.ToList(); - foreach (var document in modifiedDocuments) - { - Assert.AreEqual(credentials.Login, document.Owner); - } - - Assert.IsNotNull(modifiedDocuments.Count); - Console.WriteLine($@"Total modified documents: {modifiedDocuments.Count}"); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task GetUserDocumentsTest() - { - var perPage = 25; - var SignNowDocumentsAsync = await UserExamples - .GetUserDocuments(perPage, testContext) - .ConfigureAwait(false); - - var userDocuments = SignNowDocumentsAsync.ToList(); - foreach (var document in userDocuments) - { - Assert.AreEqual(credentials.Login, document.Owner); - } - - Assert.IsNotNull(userDocuments.Count); - Console.WriteLine($@"Total modified documents: {userDocuments.Count}"); - } - - #endregion - - #region Templates Examples - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CreateTemplateFromDocumentTest() - { - var document = await DocumentExamples - .UploadDocumentWithFieldExtract(PdfWithSignatureField, testContext).ConfigureAwait(false); - - const string templateName = "Template Name"; - var result = await DocumentExamples.CreateTemplateFromTheDocument(document?.Id, templateName, testContext).ConfigureAwait(false); - var template = await testContext.Documents.GetDocumentAsync(result.Id).ConfigureAwait(false); - - Assert.IsFalse(document?.IsTemplate); - Assert.IsNotNull(template?.Id); - Assert.AreEqual(templateName, template.Name); - Assert.IsTrue(template.IsTemplate); - - await testContext.Documents.DeleteDocumentAsync(template.Id).ConfigureAwait(false); - DeleteTestDocument(document?.Id); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CreateDocumentFromTemplateTest() - { - var testDocument = await DocumentExamples - .UploadDocumentWithFieldExtract(PdfWithSignatureField, testContext) - .ConfigureAwait(false); - - var template = await testContext.Documents - .CreateTemplateFromDocumentAsync(testDocument.Id, "TemplateName") - .ConfigureAwait(false); - var documentName = "Document Name"; - var result = await DocumentExamples - .CreateDocumentFromTheTemplate(template.Id, documentName, testContext) - .ConfigureAwait(false); - var document = await testContext.Documents - .GetDocumentAsync(result.Id) - .ConfigureAwait(false); - - Assert.IsNotNull(document?.Id); - Assert.IsFalse(document.IsTemplate); - Assert.AreEqual(documentName, document.Name); - - await testContext.Documents.DeleteDocumentAsync(document.Id).ConfigureAwait(false); - await testContext.Documents.DeleteDocumentAsync(template.Id).ConfigureAwait(false); - DeleteTestDocument(testDocument?.Id); - } - - #endregion - - #region Folder Examples - - /// - /// Run test for example: - /// - [TestMethod] - public async Task GetAllFoldersTest() - { - var folders = await FolderExamples - .GetAllFolders(testContext) - .ConfigureAwait(false); - - Assert.IsInstanceOfType(folders, typeof(SignNowFolders)); - Assert.AreEqual("Root", folders.Name); - Assert.IsTrue(folders.SystemFolder); - - Assert.IsTrue(folders.Folders.Any(f => f.Name == "Documents")); - Assert.IsTrue(folders.Folders.Any(f => f.Name == "Archive")); - Assert.IsTrue(folders.Folders.Any(f => f.Name == "Templates")); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task GetFolderTest() - { - var folders = await FolderExamples.GetAllFolders(testContext).ConfigureAwait(false); - var folderId = folders.Folders.FirstOrDefault(f => f.Name == "Documents")?.Id; - - var filterBySigningStatus = new GetFolderOptions - { - Filters = new FolderFilters(SigningStatus.Pending) - }; - - var folder = await FolderExamples - .GetFolder(folderId, filterBySigningStatus, testContext) - .ConfigureAwait(false); - - Assert.IsTrue(folders.Documents.All(d => d.Status == DocumentStatus.Pending)); - Assert.AreEqual(folders.TotalDocuments, folders.Documents.Count); - Assert.IsTrue(folder.SystemFolder); - Assert.AreEqual(folderId, folder.Id); - Assert.AreEqual(folders.Id, folder.ParentId); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task CreateFolderTest() - { - // Get Root folder and Documents folder - var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); - var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); - - var timestamp = (long)(DateTime.Now - UnixEpoch).TotalSeconds; - // Note: You should use different folder name for each example run - var myFolderName = $"CreateFolderExample_{timestamp}"; - - // Creating new folder - var createNewFolder = await FolderExamples - .CreateFolder(myFolderName, documentsFolder?.Id, testContext) - .ConfigureAwait(false); - - Assert.IsNotNull(createNewFolder.Id); - - // Check if new folder exists - var checkNewFolderExists = await testContext.Folders - .GetFolderAsync(documentsFolder?.Id, new GetFolderOptions {IncludeDocumentsSubfolder = false}) - .ConfigureAwait(false); - - var myFolder = checkNewFolderExists.Folders.FirstOrDefault(f => f.Name == myFolderName); - Assert.AreEqual(myFolderName, myFolder?.Name); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task RenameFolderTest() - { - // Creates folder inside Documents folder for test - var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); - var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); - var folderForRename = await testContext.Folders - .CreateFolderAsync("noname", documentsFolder?.Id) - .ConfigureAwait(false); - - // Rename previously created folder - var renameFolder = await FolderExamples - .RenameFolder("ItsRenamedFolder", folderForRename.Id, testContext) - .ConfigureAwait(false); - - var renamed = await testContext.Folders.GetFolderAsync(renameFolder.Id).ConfigureAwait(false); - - // Check if folder renamed - Assert.AreEqual("ItsRenamedFolder", renamed.Name); - Assert.AreEqual(folderForRename.Id, renamed.Id); - - // Finally - delete test folder - await testContext.Folders.DeleteFolderAsync(renamed.Id).ConfigureAwait(false); - } - - /// - /// Run test for example: - /// - [TestMethod] - public async Task DeleteFolderTest() - { - // Create some folder for test inside the Documents folder - var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); - var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); - var folderToDelete = await testContext.Folders - .CreateFolderAsync("DeleteMe", documentsFolder?.Id) - .ConfigureAwait(false); - - // Check if test folder exists - var createdFolder = await testContext.Folders - .GetFolderAsync(folderToDelete.Id) - .ConfigureAwait(false); - Assert.AreEqual(folderToDelete.Id, createdFolder.Id); - - // Delete folder - await FolderExamples.DeleteFolder(folderToDelete.Id, testContext).ConfigureAwait(false); - - // Check if test folder has been deleted - var folders = await testContext.Folders - .GetFolderAsync(documentsFolder?.Id, new GetFolderOptions {IncludeDocumentsSubfolder = false}) - .ConfigureAwait(false); - - Assert.IsFalse(folders.Folders.Any(f => f.Name == "DeleteMe")); - } - - #endregion - - #region WebHooks Examples - - /// - /// Allows to subscribe an external service(callback_url) to a specific event of user or document. - /// As soon as a certain selected event from the List of event types occurs, SignNow sends a notification about it. - /// - /// - [TestMethod] - public async Task CreateEventSubscription() - { - // Upload document with fields - await using var fileStream = File.OpenRead(PdfWithSignatureField); - var document = await testContext.Documents - .UploadDocumentWithFieldExtractAsync(fileStream, "DocumentForEventSubscriptionCreate.pdf") - .ConfigureAwait(false); - - // Using signNowContext lets create event subscription - var myCallbackUrl = new Uri("https://signnow.com/callbackHandler"); - await testContext.Events - .CreateEventSubscriptionAsync( - new CreateEventSubscription(EventType.DocumentComplete, document.Id, myCallbackUrl)) - .ConfigureAwait(false); - - // Check for successful created event subscription - // Gets information about all subscriptions to events made with a specific application - // - var eventSubscriptionList = await testContext.Events - .GetEventSubscriptionsAsync(new PagePaginationOptions { Page = 1, PerPage = 1}) - .ConfigureAwait(false); - - // Determining events total to get the latest event from whole events list - var latestPage = eventSubscriptionList.Meta.Pagination.Total; - - // Getting event details from Events list - var myLatestCreatedEvent = await testContext.Events - .GetEventSubscriptionsAsync(new PagePaginationOptions { Page = latestPage, PerPage = 1}) - .ConfigureAwait(false); - - var myLatestEvent = myLatestCreatedEvent.Data.First(); - Assert.AreEqual(EventType.DocumentComplete, myLatestEvent.Event); - Assert.AreEqual(myCallbackUrl, myLatestEvent.JsonAttributes.CallbackUrl); - - // Changing an existing event subscription - // - var eventForUpdate = myLatestCreatedEvent.Data.First(); - eventForUpdate.JsonAttributes.CallbackUrl = new Uri("https://signnow.com/myNewCallbackHandler"); - - var changedEvent = await testContext.Events - .UpdateEventSubscriptionAsync(new UpdateEventSubscription(eventForUpdate)) - .ConfigureAwait(false); - - var updatedEvent = await testContext.Events - .GetEventSubscriptionInfoAsync(changedEvent.Id) - .ConfigureAwait(false); - - Assert.AreEqual(myLatestEvent.Id, updatedEvent.Id); - Assert.AreEqual("https://signnow.com/myNewCallbackHandler", updatedEvent.JsonAttributes.CallbackUrl.AbsoluteUri); - - // Unsubscribes an external service (callback_url) from specific events of user or document - // - await testContext.Events - .DeleteEventSubscriptionAsync(myLatestEvent.Id) - .ConfigureAwait(false); - } - - #endregion - } -} diff --git a/SignNow.Net.Examples/Folders/CreateFolder.cs b/SignNow.Net.Examples/Folders/CreateFolder.cs index 67008959..a59ea8ac 100644 --- a/SignNow.Net.Examples/Folders/CreateFolder.cs +++ b/SignNow.Net.Examples/Folders/CreateFolder.cs @@ -1,23 +1,41 @@ +using System; +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; -using SignNow.Net.Model.Responses; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model.Requests; -namespace SignNow.Net.Examples.Folders +namespace SignNow.Net.Examples { - public static partial class FolderExamples + public partial class FolderExamples: ExamplesBase { - /// - /// Creates a folder for the user - /// - /// Name of a new folder - /// Identifier for the parent folder that contains this folder - /// signNow container with services. - /// - public static async Task CreateFolder(string name, string parentId, SignNowContext signNowContext) + [TestMethod] + public async Task CreateFolderAsync() { - return await signNowContext.Folders - .CreateFolderAsync(name, parentId) + // Get Root folder and Documents folder + var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); + var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); + + var timestamp = (long)(DateTime.Now - UnixEpoch).TotalSeconds; + // Note: You should use different folder name for each example run + var myFolderName = $"CreateFolderExample_{timestamp}"; + + // Creating new folder + var createNewFolder = await testContext.Folders + .CreateFolderAsync(myFolderName, documentsFolder?.Id) .ConfigureAwait(false); + + Assert.IsNotNull(createNewFolder.Id); + + // Check if new folder exists + var checkNewFolderExists = await testContext.Folders + .GetFolderAsync(documentsFolder?.Id, new GetFolderOptions {IncludeDocumentsSubfolder = false}) + .ConfigureAwait(false); + + var myFolder = checkNewFolderExists.Folders.FirstOrDefault(f => f.Name == myFolderName); + Assert.AreEqual(myFolderName, myFolder?.Name); + + // clean up + await testContext.Folders.DeleteFolderAsync(myFolder?.Id).ConfigureAwait(false); } } } diff --git a/SignNow.Net.Examples/Folders/DeleteFolder.cs b/SignNow.Net.Examples/Folders/DeleteFolder.cs index 3780dd8b..289a89fa 100644 --- a/SignNow.Net.Examples/Folders/DeleteFolder.cs +++ b/SignNow.Net.Examples/Folders/DeleteFolder.cs @@ -1,21 +1,37 @@ +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model.Requests; -namespace SignNow.Net.Examples.Folders +namespace SignNow.Net.Examples { - public static partial class FolderExamples + public partial class FolderExamples { - /// - /// Delete folder example - /// - /// Id of the folder to delete - /// signNow container with services. - /// - public static async Task DeleteFolder(string folderId, SignNowContext signNowContext) + [TestMethod] + public async Task DeleteFolderAsync() { - await signNowContext.Folders - .DeleteFolderAsync(folderId) + // Create some folder for test inside the Documents folder + var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); + var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); + var folderToDelete = await testContext.Folders + .CreateFolderAsync("DeleteMe", documentsFolder?.Id) .ConfigureAwait(false); + + // Check if test folder exists + var createdFolder = await testContext.Folders + .GetFolderAsync(folderToDelete.Id) + .ConfigureAwait(false); + Assert.AreEqual(folderToDelete.Id, createdFolder.Id); + + // Delete folder + await testContext.Folders.DeleteFolderAsync(folderToDelete.Id).ConfigureAwait(false); + + // Check if test folder has been deleted + var folders = await testContext.Folders + .GetFolderAsync(documentsFolder?.Id, new GetFolderOptions {IncludeDocumentsSubfolder = false}) + .ConfigureAwait(false); + + Assert.IsFalse(folders.Folders.Any(f => f.Name == "DeleteMe")); } } } diff --git a/SignNow.Net.Examples/Folders/GetAllFolders.cs b/SignNow.Net.Examples/Folders/GetAllFolders.cs index 1659e530..250cbf74 100644 --- a/SignNow.Net.Examples/Folders/GetAllFolders.cs +++ b/SignNow.Net.Examples/Folders/GetAllFolders.cs @@ -1,20 +1,28 @@ +using System.Linq; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; -namespace SignNow.Net.Examples.Folders +namespace SignNow.Net.Examples { - public static partial class FolderExamples + public partial class FolderExamples { - /// - /// Get all folders of a user. - /// - /// signNow container with services. - /// Returns all information about user's folders. - public static async Task GetAllFolders(SignNowContext signNowContext) + [TestMethod] + public async Task GetAllFoldersAsync() { - return await signNowContext.Folders + // get all folders + var folders = await testContext.Folders .GetAllFoldersAsync() .ConfigureAwait(false); + + // check if the root folder contains the default folders + Assert.IsInstanceOfType(folders, typeof(SignNowFolders)); + Assert.AreEqual("Root", folders.Name); + Assert.IsTrue(folders.SystemFolder); + + Assert.IsTrue(folders.Folders.Any(f => f.Name == "Documents")); + Assert.IsTrue(folders.Folders.Any(f => f.Name == "Archive")); + Assert.IsTrue(folders.Folders.Any(f => f.Name == "Templates")); } } } diff --git a/SignNow.Net.Examples/Folders/GetFolder.cs b/SignNow.Net.Examples/Folders/GetFolder.cs index 6ffadd84..5f404917 100644 --- a/SignNow.Net.Examples/Folders/GetFolder.cs +++ b/SignNow.Net.Examples/Folders/GetFolder.cs @@ -1,23 +1,39 @@ +using System.Linq; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; using SignNow.Net.Model.Requests; +using SignNow.Net.Model.Requests.GetFolderQuery; -namespace SignNow.Net.Examples.Folders +namespace SignNow.Net.Examples { - public static partial class FolderExamples + public partial class FolderExamples { - /// - /// Get all details of a specific folder including a list of all documents in that folder. - /// - /// ID of the folder to get details of - /// Options like: sort, filter, limit, etc... - /// signNow container with services. - /// - public static async Task GetFolder(string folderId, GetFolderOptions options, SignNowContext signNowContext) + [TestMethod] + public async Task GetFolderAsync() { - return await signNowContext.Folders - .GetFolderAsync(folderId, options) + // Get all folders + var folders = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); + + // Get folderId of the "Documents" folder + var folderId = folders.Folders.FirstOrDefault(f => f.Name == "Documents")?.Id; + + var filterBySigningStatus = new GetFolderOptions + { + Filters = new FolderFilters(SigningStatus.Pending) + }; + + // Get all details of a specific folder including a list of all documents in that folder + var folder = await testContext.Folders + .GetFolderAsync(folderId, filterBySigningStatus) .ConfigureAwait(false); + + // Check if folder contains only pending documents + Assert.IsTrue(folders.Documents.All(d => d.Status == DocumentStatus.Pending)); + Assert.AreEqual(folders.TotalDocuments, folders.Documents.Count); + Assert.IsTrue(folder.SystemFolder); + Assert.AreEqual(folderId, folder.Id); + Assert.AreEqual(folders.Id, folder.ParentId); } } } diff --git a/SignNow.Net.Examples/Folders/RenameFolder.cs b/SignNow.Net.Examples/Folders/RenameFolder.cs index 221e804c..bf9da9ff 100644 --- a/SignNow.Net.Examples/Folders/RenameFolder.cs +++ b/SignNow.Net.Examples/Folders/RenameFolder.cs @@ -1,23 +1,38 @@ +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; -using SignNow.Net.Model.Responses; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Folders +namespace SignNow.Net.Examples { - public static partial class FolderExamples + public partial class FolderExamples { - /// - /// Rename folder example - /// - /// A new folder's name - /// Id of the folder to rename - /// signNow container with services. - /// - public static async Task RenameFolder(string name, string folderId, SignNowContext signNowContext) + [TestMethod] + public async Task RenameFolderAsync() { - return await signNowContext.Folders - .RenameFolderAsync(name, folderId) + // Creates folder inside Documents folder for test + var root = await testContext.Folders.GetAllFoldersAsync().ConfigureAwait(false); + + // Get Documents folder + var documentsFolder = root.Folders.FirstOrDefault(f => f.Name == "Documents"); + + // create a folder for test + var folderForRename = await testContext.Folders + .CreateFolderAsync("noname", documentsFolder?.Id) + .ConfigureAwait(false); + + // Rename previously created folder + var renameFolder = await testContext.Folders + .RenameFolderAsync("ItsRenamedFolder", folderForRename.Id) .ConfigureAwait(false); + + var renamed = await testContext.Folders.GetFolderAsync(renameFolder.Id).ConfigureAwait(false); + + // Check if folder renamed + Assert.AreEqual("ItsRenamedFolder", renamed.Name); + Assert.AreEqual(folderForRename.Id, renamed.Id); + + // Finally - delete test folder + await testContext.Folders.DeleteFolderAsync(renamed.Id).ConfigureAwait(false); } } } diff --git a/SignNow.Net.Examples/Invites/CancelEmbeddedInvite.cs b/SignNow.Net.Examples/Invites/CancelEmbeddedInvite.cs deleted file mode 100644 index 49949f43..00000000 --- a/SignNow.Net.Examples/Invites/CancelEmbeddedInvite.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using SignNow.Net.Model; - -namespace SignNow.Net.Examples.Invites -{ - public static partial class InviteExamples - { - /// - /// Cancel an embedded signing invite - /// - /// signNow document you’d like to cancel embedded invite - /// signNow container with services. - /// - public static async Task CancelEmbeddedInvite(SignNowDocument document, SignNowContext signNowContext) - { - await signNowContext.Invites - .CancelEmbeddedInviteAsync(document.Id) - .ConfigureAwait(false); - } - } -} diff --git a/SignNow.Net.Examples/Invites/CreateEmbeddedSigningInviteToSignTheDocument.cs b/SignNow.Net.Examples/Invites/CreateEmbeddedSigningInviteToSignTheDocument.cs index d5ed5d36..c1adb414 100644 --- a/SignNow.Net.Examples/Invites/CreateEmbeddedSigningInviteToSignTheDocument.cs +++ b/SignNow.Net.Examples/Invites/CreateEmbeddedSigningInviteToSignTheDocument.cs @@ -1,34 +1,76 @@ +using System; +using System.IO; +using System.Linq; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; +using SignNow.Net.Model.Requests; -namespace SignNow.Net.Examples.Invites +namespace SignNow.Net.Examples { - public static partial class InviteExamples + [TestClass] + public partial class InviteExamples : ExamplesBase { - /// - /// Create an embedded signing invite to the document for signature. - /// - /// signNow document you'd like to have signed - /// The email of the invitee. - /// signNow container with services. - /// - /// which contains an invite data. - /// - public static async Task - CreateEmbeddedSigningInviteToSignTheDocument(SignNowDocument document, string email, SignNowContext signNowContext) + [TestMethod] + public async Task CreateEmbeddedSigningInviteToSignTheDocumentAsync() { - // create embedded signing invite - var invite = new EmbeddedSigningInvite(document); + // Upload document with signature field + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "CreateEmbeddedSigningInviteToSignTheDocument.pdf") + .ConfigureAwait(false); + + // Get document with signature field + var signNowDoc = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); + + // Create Embedded Signing Invite + var invite = new EmbeddedSigningInvite(signNowDoc); invite.AddEmbeddedSigningInvite( new EmbeddedInvite { - Email = email, - RoleId = document.Roles[0].Id, + Email = "testemail@signnow.com", + RoleId = signNowDoc.Roles[0].Id, SigningOrder = 1 }); + var embeddedInviteResponse = await testContext.Invites + .CreateInviteAsync(signNowDoc.Id, invite) + .ConfigureAwait(false); + + // Check if invite was created + Assert.AreEqual(1, embeddedInviteResponse.InviteData.Count); + Assert.AreEqual(1, embeddedInviteResponse.InviteData[0].Order); + Assert.AreEqual("testemail@signnow.com", embeddedInviteResponse.InviteData[0].Email); + Assert.AreEqual("Pending", embeddedInviteResponse.InviteData[0].Status.ToString()); + + // get document with embedded invite + var documentWithEmbed = await testContext.Documents + .GetDocumentAsync(document.Id) + .ConfigureAwait(false); + + // Check if invite is embedded + Assert.IsTrue(documentWithEmbed.FieldInvites.First().IsEmbedded); + + // Generate link for Embedded Signing Invite + var options = new CreateEmbedLinkOptions + { + FieldInvite = documentWithEmbed.FieldInvites.First(), + LinkExpiration = 30 + }; - return await signNowContext.Invites.CreateInviteAsync(document.Id, invite) + var embeddedLink = await testContext.Invites + .GenerateEmbeddedInviteLinkAsync(documentWithEmbed.Id, options) .ConfigureAwait(false); + + // Check if link is generated + Assert.IsInstanceOfType(embeddedLink.Link, typeof(Uri)); + Console.WriteLine($@"Embedded link: {embeddedLink.Link.AbsoluteUri}"); + + // Cancel embedded invite to delete test document with embedded invite + await testContext.Invites.CancelEmbeddedInviteAsync(documentWithEmbed.Id).ConfigureAwait(false); + + // clean up + DeleteTestDocument(document.Id); + DeleteTestDocument(documentWithEmbed.Id); } } } diff --git a/SignNow.Net.Examples/Invites/CreateFreeformInviteToSignTheDocument.cs b/SignNow.Net.Examples/Invites/CreateFreeformInviteToSignTheDocument.cs index b4019faf..c98c953e 100644 --- a/SignNow.Net.Examples/Invites/CreateFreeformInviteToSignTheDocument.cs +++ b/SignNow.Net.Examples/Invites/CreateFreeformInviteToSignTheDocument.cs @@ -1,32 +1,57 @@ +using System.IO; +using System.Linq; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; -namespace SignNow.Net.Examples.Invites +namespace SignNow.Net.Examples { - public static partial class InviteExamples + public partial class InviteExamples { - /// - /// Create a freeform invite to the document for signature. - /// - /// signNow document you’d like to have signed - /// The email of the invitee. - /// signNow container with services. - /// - /// which contains an Identity of invite request. - /// - public static async Task CreateFreeformInviteToSignTheDocument(SignNowDocument document, string email, SignNowContext signNowContext) + [TestMethod] + public async Task CreateFreeformInviteToSignTheDocumentAsync() { - // Create freeform invite - var invite = new FreeFormSignInvite(email) + // Upload the document + await using var fileStream = File.OpenRead(PdfWithoutFields); + var document = await testContext.Documents + .UploadDocumentAsync(fileStream, "CreateFreeformInviteToSignTheDocument.pdf") + .ConfigureAwait(false); + + // get the document + var signNowDoc = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); + + // Check the document doesn't have any invites + Assert.AreEqual(DocumentStatus.NoInvite, signNowDoc.Status); + + // Create an free form invite + var emailTo = "noreply@signnow.com"; + var invite = new FreeFormSignInvite(emailTo) { - Message = $"{email} invited you to sign the document {document.Name}", + Message = $"{emailTo} invited you to sign the document {signNowDoc.Name}", Subject = "The subject of the Email" }; - - // Creating Invite request - return await signNowContext.Invites - .CreateInviteAsync(document.Id, invite) + var inviteResponse = await testContext.Invites + .CreateInviteAsync(signNowDoc.Id, invite) .ConfigureAwait(false); + + // Check the invite was created + Assert.IsFalse(string.IsNullOrEmpty(inviteResponse.Id)); + + // get document with invite + var documentWithInvite = await testContext.Documents.GetDocumentAsync(signNowDoc.Id).ConfigureAwait(false); + var createdInvite = documentWithInvite.InvitesStatus.FirstOrDefault(); + + // Check the invite was added to the document + Assert.AreEqual("noreply@signnow.com", createdInvite?.SignerEmail); + Assert.AreEqual(inviteResponse.Id, createdInvite?.Id); + Assert.AreEqual(InviteStatus.Pending, createdInvite?.Status); + Assert.AreEqual(DocumentStatus.Pending, documentWithInvite.Status); + + // cancel free form invite + await testContext.Invites.CancelInviteAsync((FreeformInvite)createdInvite).ConfigureAwait(false); + + // clean up + DeleteTestDocument(document.Id); } } } diff --git a/SignNow.Net.Examples/Invites/CreateRoleBasedInviteToSignTheDocument.cs b/SignNow.Net.Examples/Invites/CreateRoleBasedInviteToSignTheDocument.cs index 66a69543..0e299b43 100644 --- a/SignNow.Net.Examples/Invites/CreateRoleBasedInviteToSignTheDocument.cs +++ b/SignNow.Net.Examples/Invites/CreateRoleBasedInviteToSignTheDocument.cs @@ -1,24 +1,33 @@ +using System.IO; using System.Linq; using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model; -namespace SignNow.Net.Examples.Invites +namespace SignNow.Net.Examples { - public static partial class InviteExamples + public partial class InviteExamples { - /// - /// Create a role-based invite to the document for signature. - /// - /// signNow document with fields you’d like to have signed - /// The email of the invitee. - /// signNow container with services. - /// without any Identity of invite request. - public static async Task CreateRoleBasedInviteToSignTheDocument(SignNowDocument document, string email, SignNowContext signNowContext) + [TestMethod] + public async Task CreateRoleBasedInviteToSignTheDocumentAsync() { - // Create role-based invite - var invite = new RoleBasedInvite(document) + // Upload a document with a signature field + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "CreateRoleBasedInviteToSignTheDocument.pdf") + .ConfigureAwait(false); + + // Get the document by Id + var signNowDoc = await testContext.Documents.GetDocumentAsync(document.Id).ConfigureAwait(false); + + // check if the document doesn't have any invites + Assert.AreEqual(DocumentStatus.NoInvite, signNowDoc.Status); + + // Create a role-based invite to the document for signature + var email = "noreply@signnow.com"; + var invite = new RoleBasedInvite(signNowDoc) { - Message = $"{email} invited you to sign the document {document.Name}", + Message = $"{email} invited you to sign the document {signNowDoc.Name}", Subject = "The subject of the Email" }; @@ -34,9 +43,39 @@ public static async Task CreateRoleBasedInviteToSignTheDocument( invite.AddRoleBasedInvite(signer); // Creating Invite request - return await signNowContext.Invites - .CreateInviteAsync(document.Id, invite) + var inviteResponse = await testContext.Invites + .CreateInviteAsync(signNowDoc.Id, invite) .ConfigureAwait(false); + + // check if the invite has been created successfully + Assert.IsNull(inviteResponse.Id,"Successful Role-Based invite response doesnt contains Invite ID."); + + // Get the document by Id to check the status of the invite + var documentWithInvite = await testContext.Documents.GetDocumentAsync(signNowDoc.Id).ConfigureAwait(false); + var createdInvite = documentWithInvite.FieldInvites.FirstOrDefault(); + + // check if the document has an invite + var fieldInvite = documentWithInvite.Fields.FirstOrDefault(); + Assert.IsNotNull(fieldInvite?.FieldRequestId); + + // Resend the invite - just for the sake of the example + await testContext.Invites + .ResendEmailInviteAsync(fieldInvite?.FieldRequestId) + .ConfigureAwait(false); + + // check if the document has an invite + Assert.AreEqual("noreply@signnow.com", createdInvite?.SignerEmail); + Assert.AreEqual("Signer 1", createdInvite?.RoleName, "Signer role mismatch."); + Assert.AreEqual(InviteStatus.Pending, createdInvite?.Status); + Assert.AreEqual(DocumentStatus.Pending, documentWithInvite.Status); + + // cancel the invite to delete the document + await testContext.Invites + .CancelInviteAsync(documentWithInvite.Id) + .ConfigureAwait(false); + + // clean up + DeleteTestDocument(signNowDoc.Id); } } } diff --git a/SignNow.Net.Examples/Invites/GenerateLinkForEmbeddedInvite.cs b/SignNow.Net.Examples/Invites/GenerateLinkForEmbeddedInvite.cs deleted file mode 100644 index f6bd5d61..00000000 --- a/SignNow.Net.Examples/Invites/GenerateLinkForEmbeddedInvite.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using SignNow.Net.Model; -using SignNow.Net.Model.Requests; - -namespace SignNow.Net.Examples.Invites -{ - public static partial class InviteExamples - { - /// - /// Create Link for Embedded Signing Invite - /// - /// signNow document you'd like to have signed - /// In how many minutes the link expires, ranges from 15 to 45 minutes or null - /// signNow container with services. - /// - public static async Task - GenerateLinkForEmbeddedInvite(SignNowDocument document, int expires, SignNowContext signNowContext) - { - var options = new CreateEmbedLinkOptions - { - FieldInvite = document.FieldInvites.First(), - LinkExpiration = (uint)expires - }; - - return await signNowContext.Invites - .GenerateEmbeddedInviteLinkAsync(document.Id, options).ConfigureAwait(false); - } - - } -} diff --git a/SignNow.Net.Examples/OAuth2/GenerateAccessToken.cs b/SignNow.Net.Examples/OAuth2/GenerateAccessToken.cs new file mode 100644 index 00000000..6c2bee1f --- /dev/null +++ b/SignNow.Net.Examples/OAuth2/GenerateAccessToken.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Service; + +namespace SignNow.Net.Examples +{ + [TestClass] + public partial class OAuth2Examples : ExamplesBase + { + /// + /// An example of obtaining an access token via OAuth 2.0 service. + /// + [TestMethod] + public async Task GenerateAccessTokenAsync() + { + var clientId = credentials.ClientId; + var clientSecret = credentials.ClientSecret; + + var userLogin = credentials.Login; + var userPassword = credentials.Password; + + var oauth = new OAuth2Service(ApiBaseUrl, clientId, clientSecret) + { + ExpirationTime = 60 + }; + + var response = await oauth.GetTokenAsync(userLogin, userPassword, Scope.All) + .ConfigureAwait(false); + + Assert.IsNotNull(response); + Assert.IsFalse(string.IsNullOrEmpty(response.AccessToken)); + Assert.IsFalse(string.IsNullOrEmpty(response.RefreshToken)); + } + } +} diff --git a/SignNow.Net.Examples/OAuth2/RefreshAccessToken.cs b/SignNow.Net.Examples/OAuth2/RefreshAccessToken.cs new file mode 100644 index 00000000..c3c1d709 --- /dev/null +++ b/SignNow.Net.Examples/OAuth2/RefreshAccessToken.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Service; + +namespace SignNow.Net.Examples +{ + public partial class OAuth2Examples + { + /// + /// Refresh access token example + /// + [TestMethod] + public async Task RefreshAccessTokenAsync() + { + var oauth = new OAuth2Service(ApiBaseUrl, credentials.ClientId, credentials.ClientSecret); + + // Get a valid token + var validToken = await oauth.GetTokenAsync(credentials.Login, credentials.Password, Scope.All) + .ConfigureAwait(false); + + // Refresh the token + var refreshedToken = await oauth.RefreshTokenAsync(validToken) + .ConfigureAwait(false); + + Assert.AreNotEqual(refreshedToken, validToken); + } + } +} diff --git a/SignNow.Net.Examples/OAuth2/VerifyAccessToken.cs b/SignNow.Net.Examples/OAuth2/VerifyAccessToken.cs new file mode 100644 index 00000000..1ebc956c --- /dev/null +++ b/SignNow.Net.Examples/OAuth2/VerifyAccessToken.cs @@ -0,0 +1,33 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Service; + +namespace SignNow.Net.Examples +{ + public partial class OAuth2Examples + { + /// + /// An example of validating an access token via OAuth 2.0 service. + /// + [TestMethod] + public async Task VerifyAccessTokenAsync() + { + var oauth = new OAuth2Service(ApiBaseUrl, credentials.Login, credentials.Password); + + var dummyToken = new Token + { + AccessToken = "dummyAccessToken", + AppToken = "dummyAppToken", + ExpiresIn = 100, + Scope = "*", + TokenType = TokenType.Bearer + }; + + var dummyTokenResponse = await oauth.ValidateTokenAsync(dummyToken) + .ConfigureAwait(false); + + Assert.IsFalse(dummyTokenResponse); + } + } +} diff --git a/SignNow.Net.Examples/Template/CreateDocumentFromTheTemplate.cs b/SignNow.Net.Examples/Template/CreateDocumentFromTheTemplate.cs new file mode 100644 index 00000000..54cb1491 --- /dev/null +++ b/SignNow.Net.Examples/Template/CreateDocumentFromTheTemplate.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SignNow.Net.Examples +{ + [TestClass] + public partial class TemplateExamples: ExamplesBase + { + [TestMethod] + public async Task CreateDocumentFromTemplateAsync() + { + await using var fileStream = File.OpenRead(PdfWithSignatureField); + + // Upload a document with a signature field + var testDocument = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "DocumentWithSignatureTextTag.pdf") + .ConfigureAwait(false); + + // Create a template from the uploaded document + var template = await testContext.Documents + .CreateTemplateFromDocumentAsync(testDocument.Id, "TemplateName") + .ConfigureAwait(false); + + // Creates a new document copy out of template + var documentName = "Document Name"; + var result = await testContext.Documents + .CreateDocumentFromTemplateAsync(template.Id, documentName) + .ConfigureAwait(false); + + // Get the new document created from template + var document = await testContext.Documents + .GetDocumentAsync(result.Id) + .ConfigureAwait(false); + + // Check that the document is not a template + Assert.IsNotNull(document?.Id); + Assert.IsFalse(document.IsTemplate); + Assert.AreEqual(documentName, document.Name); + + // clean up + await testContext.Documents.DeleteDocumentAsync(document.Id).ConfigureAwait(false); + await testContext.Documents.DeleteDocumentAsync(template.Id).ConfigureAwait(false); + DeleteTestDocument(testDocument?.Id); + } + } +} diff --git a/SignNow.Net.Examples/Template/CreateTemplateFromTheDocument.cs b/SignNow.Net.Examples/Template/CreateTemplateFromTheDocument.cs new file mode 100644 index 00000000..e7b353de --- /dev/null +++ b/SignNow.Net.Examples/Template/CreateTemplateFromTheDocument.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace SignNow.Net.Examples +{ + public partial class TemplateExamples + { + [TestMethod] + public async Task CreateTemplateFromDocumentAsync() + { + await using var fileStream = File.OpenRead(PdfWithSignatureField); + + // Upload a document with a signature field + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "DocumentWithSignatureTextTag.pdf") + .ConfigureAwait(false); + + // create a template from the uploaded document + const string templateName = "Template Name"; + var result = await testContext.Documents + .CreateTemplateFromDocumentAsync(document?.Id, templateName) + .ConfigureAwait(false); + + // Get the new template created from the document + var template = await testContext.Documents + .GetDocumentAsync(result.Id) + .ConfigureAwait(false); + + // Check that the document is a template + Assert.IsNotNull(template?.Id); + Assert.AreEqual(templateName, template.Name); + Assert.IsTrue(template.IsTemplate); + + // clean up + await testContext.Documents.DeleteDocumentAsync(template.Id).ConfigureAwait(false); + DeleteTestDocument(document?.Id); + } + } +} diff --git a/SignNow.Net.Examples/Users/ChangeUserDetails.cs b/SignNow.Net.Examples/Users/ChangeUserDetails.cs index 369b0ddc..32f52465 100644 --- a/SignNow.Net.Examples/Users/ChangeUserDetails.cs +++ b/SignNow.Net.Examples/Users/ChangeUserDetails.cs @@ -1,43 +1,77 @@ +using System; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model.Requests; -namespace SignNow.Net.Examples.Users +namespace SignNow.Net.Examples { - public static partial class UserExamples + public partial class UserExamples { - /// - /// Updates user information i.e. first name, last name - /// - /// new User firstname - /// new User lastname - /// Old User password - /// New User password - /// signNow container with services. - /// - public static async Task ChangeUserDetails(string firstname, string lastname, string oldPwd, string pwd, SignNowContext signNowContext) + [TestMethod] + public async Task ChangeUserDetailsAsync() { - var userUpdateOptions = new UpdateUserOptions + // Get current user details + var currentUser = await testContext.Users + .GetCurrentUserAsync() + .ConfigureAwait(false); + + Console.WriteLine("Current user is: '{0}' '{1}'", currentUser.FirstName, currentUser.LastName); + + // Update user details + var update = new UpdateUserOptions { - FirstName = firstname, - LastName = lastname, - OldPassword = oldPwd, - Password = pwd + FirstName = "signNow", + LastName = currentUser.LastName + "Updated", + OldPassword = credentials.Password, + Password = credentials.Password, + LogOutAll = false }; - return await signNowContext.Users.UpdateUserAsync(userUpdateOptions) + var updateUserDetails = await testContext.Users + .UpdateUserAsync(update) .ConfigureAwait(false); - } - /// - /// Sends password reset link via email example - /// - /// User email - /// signNow container with services. - /// - public static async Task SendsPasswordResetLink(string email, SignNowContext signNowContext) - { - await signNowContext.Users.SendPasswordResetLinkAsync(email) + // get updated user details + var updatedUser = await testContext.Users + .GetCurrentUserAsync() + .ConfigureAwait(false); + + Console.WriteLine("Updated user is: '{0}' '{1}'", updatedUser.FirstName, updatedUser.LastName); + + // check if user details were updated + Assert.AreEqual(currentUser.FirstName, updateUserDetails.FirstName); + Assert.AreNotEqual(currentUser.LastName, updateUserDetails.LastName); + Assert.AreEqual(currentUser.LastName + "Updated", updateUserDetails.LastName); + + // Revert user details back + var revert = new UpdateUserOptions + { + FirstName = currentUser.FirstName, + LastName = currentUser.LastName, + OldPassword = credentials.Password, + Password = credentials.Password, + LogOutAll = false + }; + + var revertedUser = await testContext.Users + .UpdateUserAsync(revert) + .ConfigureAwait(false); + + // get last user details + var lastUserInfo = await testContext.Users + .GetCurrentUserAsync() + .ConfigureAwait(false); + + Console.WriteLine("Reverted user is: '{0}' '{1}'", lastUserInfo.FirstName, lastUserInfo.LastName); + + // check if user details were reverted + Assert.AreEqual(currentUser.FirstName, lastUserInfo.FirstName); + Assert.AreEqual(currentUser.LastName, lastUserInfo.LastName); + Assert.AreEqual(currentUser.LastName, revertedUser.LastName); + + // send user password reset email + await testContext.Users + .SendPasswordResetLinkAsync(currentUser.Email) .ConfigureAwait(false); } } diff --git a/SignNow.Net.Examples/Users/CreateSignNowUser.cs b/SignNow.Net.Examples/Users/CreateSignNowUser.cs index 25596c9c..feb35a57 100644 --- a/SignNow.Net.Examples/Users/CreateSignNowUser.cs +++ b/SignNow.Net.Examples/Users/CreateSignNowUser.cs @@ -1,45 +1,39 @@ +using System; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SignNow.Net.Model.Requests; -namespace SignNow.Net.Examples.Users +namespace SignNow.Net.Examples { - public static partial class UserExamples + [TestClass] + public partial class UserExamples : ExamplesBase { - /// - /// Creates an account for a user example - /// - /// User firstname - /// User lastname - /// User email - /// User password - /// signNow container with services. - /// - /// Response with: User identity, email - /// - public static async Task CreateSignNowUser(string firstname, string lastname, string email, string password, SignNowContext signNowContext) + [TestMethod] + public async Task CreateSignNowUserAsync() { + var timestamp = (long)(DateTime.Now - UnixEpoch).TotalSeconds; + var email = $"signnow.tutorial+create_user_test{timestamp}@gmail.com"; + var userRequest = new CreateUserOptions { Email = email, - FirstName = firstname, - LastName = lastname, - Password = password + FirstName = "John", + LastName = "Wick", + Password = "password" }; - return await signNowContext.Users + // Create a new user + var createUserResponse = await testContext.Users .CreateUserAsync(userRequest) .ConfigureAwait(false); - } - /// - /// Retrieve User Information example - /// - /// signNow container with services. - /// - public static async Task RetrieveUserInformation(SignNowContext signNowContext) - { - return await signNowContext.Users.GetCurrentUserAsync() + // Check if the user was created and not verified + Assert.AreEqual(email, createUserResponse.Email); + Assert.IsFalse(createUserResponse.Verified); + + // Finally - send verification email to User + await testContext.Users + .SendVerificationEmailAsync(email) .ConfigureAwait(false); } } diff --git a/SignNow.Net.Examples/Users/GetUserDocuments.cs b/SignNow.Net.Examples/Users/GetUserDocuments.cs index b7f7ef02..aa016316 100644 --- a/SignNow.Net.Examples/Users/GetUserDocuments.cs +++ b/SignNow.Net.Examples/Users/GetUserDocuments.cs @@ -1,22 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Users +namespace SignNow.Net.Examples { - public static partial class UserExamples + public partial class UserExamples { - /// - /// Get User documents that were not modified yet example - /// - /// How many document objects to display per page in response. - /// Access token. - /// - public static async Task> GetUserDocuments(int perPage, SignNowContext signNowContext) + [TestMethod] + public async Task GetUserDocumentsAsync() { - return await signNowContext.Users - .GetUserDocumentsAsync(perPage) + // Get user documents, first 25 documents + var signNowDocuments = await testContext.Users + .GetUserDocumentsAsync(perPage:25) .ConfigureAwait(false); + + // Check if the documents are owned by the user + var userDocuments = signNowDocuments.ToList(); + foreach (var document in userDocuments) + { + Assert.AreEqual(credentials.Login, document.Owner); + } + + Assert.IsNotNull(userDocuments.Count); + Console.WriteLine($@"Total modified documents: {userDocuments.Count}"); } } } diff --git a/SignNow.Net.Examples/Users/GetUserModifiedDocuments.cs b/SignNow.Net.Examples/Users/GetUserModifiedDocuments.cs index 5a54fcf7..fef398cf 100644 --- a/SignNow.Net.Examples/Users/GetUserModifiedDocuments.cs +++ b/SignNow.Net.Examples/Users/GetUserModifiedDocuments.cs @@ -1,22 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Linq; using System.Threading.Tasks; -using SignNow.Net.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; -namespace SignNow.Net.Examples.Users +namespace SignNow.Net.Examples { - public static partial class UserExamples + public partial class UserExamples { - /// - /// Get User modified documents example - /// - /// How many document objects to display per page in response. - /// Access token. - /// - public static async Task> GetUserModifiedDocuments(int perPage, SignNowContext signNowContext) + [TestMethod] + public async Task GetUserModifiedDocumentsAsync() { - return await signNowContext.Users - .GetModifiedDocumentsAsync(perPage) + // get user modified documents + var signNowDocuments = await testContext.Users + .GetModifiedDocumentsAsync(perPage:25) .ConfigureAwait(false); + + // check if user is the owner of the modified documents + var modifiedDocuments = signNowDocuments.ToList(); + foreach (var document in modifiedDocuments) + { + Assert.AreEqual(credentials.Login, document.Owner); + } + + Assert.IsNotNull(modifiedDocuments.Count); + Console.WriteLine($@"Total modified documents: {modifiedDocuments.Count}"); } } } diff --git a/SignNow.Net.Examples/Users/SendVerificationEmailToUser.cs b/SignNow.Net.Examples/Users/SendVerificationEmailToUser.cs deleted file mode 100644 index 694f4bce..00000000 --- a/SignNow.Net.Examples/Users/SendVerificationEmailToUser.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading.Tasks; -using SignNow.Net.Model; - -namespace SignNow.Net.Examples.Users -{ - public static partial class UserExamples - { - /// - /// Sends verification email to a user example - /// - /// User email - /// signNow container with services. - /// - public static async Task SendVerificationEmailToUser(string email, SignNowContext signNowContext) - { - await signNowContext.Users - .SendVerificationEmailAsync(email) - .ConfigureAwait(false); - } - } -} diff --git a/SignNow.Net.Examples/Webhooks/CreateEventSubscriptionForDocument.cs b/SignNow.Net.Examples/Webhooks/CreateEventSubscriptionForDocument.cs new file mode 100644 index 00000000..d35018ac --- /dev/null +++ b/SignNow.Net.Examples/Webhooks/CreateEventSubscriptionForDocument.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Model.Requests; + +namespace SignNow.Net.Examples +{ + [TestClass] + public partial class CreateEventSubscriptionForDocument : ExamplesBase + { + /// + /// Allows to subscribe an external service(callback_url) to a specific event of user or document. + /// As soon as a certain selected event from the List of event types occurs, SignNow sends a notification about it. + /// + /// + [TestMethod] + public async Task CreateEventSubscriptionForDocumentAsync() + { + // Upload document with fields + await using var fileStream = File.OpenRead(PdfWithSignatureField); + var document = await testContext.Documents + .UploadDocumentWithFieldExtractAsync(fileStream, "DocumentForEventSubscriptionCreate.pdf") + .ConfigureAwait(false); + + // Using signNowContext lets create event subscription + var myCallbackUrl = new Uri("https://signnow.com/callbackHandler"); + await testContext.Events + .CreateEventSubscriptionAsync(new CreateEventSubscription(EventType.DocumentComplete, document.Id, myCallbackUrl)) + .ConfigureAwait(false); + + // Check for successful created event subscription + // Gets information about all subscriptions to events made with a specific application + // + var eventSubscriptionList = await testContext.Events + .GetEventSubscriptionsAsync(new PagePaginationOptions { Page = 1, PerPage = 1}) + .ConfigureAwait(false); + + // Determining events total to get the latest event from whole events list + var latestPage = eventSubscriptionList.Meta.Pagination.Total; + + // Getting event details from Events list + var myLatestCreatedEvent = await testContext.Events + .GetEventSubscriptionsAsync(new PagePaginationOptions { Page = latestPage, PerPage = 1}) + .ConfigureAwait(false); + + var myLatestEvent = myLatestCreatedEvent.Data.First(); + Assert.AreEqual(EventType.DocumentComplete, myLatestEvent.Event); + Assert.AreEqual(myCallbackUrl, myLatestEvent.JsonAttributes.CallbackUrl); + + // Changing an existing event subscription + // + var eventForUpdate = myLatestCreatedEvent.Data.First(); + eventForUpdate.JsonAttributes.CallbackUrl = new Uri("https://signnow.com/myNewCallbackHandler"); + + var changedEvent = await testContext.Events + .UpdateEventSubscriptionAsync(new UpdateEventSubscription(eventForUpdate)) + .ConfigureAwait(false); + + var updatedEvent = await testContext.Events + .GetEventSubscriptionInfoAsync(changedEvent.Id) + .ConfigureAwait(false); + + Assert.AreEqual(myLatestEvent.Id, updatedEvent.Id); + Assert.AreEqual("https://signnow.com/myNewCallbackHandler", updatedEvent.JsonAttributes.CallbackUrl.AbsoluteUri); + + // Unsubscribes an external service (callback_url) from specific events of user or document + // + await testContext.Events + .DeleteEventSubscriptionAsync(myLatestEvent.Id) + .ConfigureAwait(false); + + // clean up + DeleteTestDocument(document.Id); + } + } +} diff --git a/SignNow.Net.Test/AssertExtensions.cs b/SignNow.Net.Test/AssertExtensions.cs index b953851d..790649f4 100644 --- a/SignNow.Net.Test/AssertExtensions.cs +++ b/SignNow.Net.Test/AssertExtensions.cs @@ -39,10 +39,8 @@ private static string PrettifyJson(string json) using var stringReader = new StringReader(json); using var stringWriter = new StringWriter(); using var jsonReader = new JsonTextReader(stringReader); - using var jsonWriter = new JsonTextWriter(stringWriter) - { - Formatting = Formatting.Indented - }; + using var jsonWriter = new JsonTextWriter(stringWriter); + jsonWriter.Formatting = Formatting.Indented; jsonWriter.WriteToken(jsonReader); return stringWriter.ToString(); diff --git a/SignNow.Net.Test/UnitTests/Requests/CopyDocumentGroupRequestTest.cs b/SignNow.Net.Test/UnitTests/Requests/CopyDocumentGroupRequestTest.cs new file mode 100644 index 00000000..fbd3a6ae --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Requests/CopyDocumentGroupRequestTest.cs @@ -0,0 +1,24 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Internal.Helpers.Converters; +using SignNow.Net.Model.Requests.DocumentGroup; + +namespace UnitTests.Requests +{ + [TestClass] + public class CopyDocumentGroupRequestTest + { + [TestMethod] + public void ShouldBeSerializedAsWithDefaultTimestampTest() + { + var request = new CopyDocumentGroupRequest + { + DocumentGroupName = "test name" + }; + var actualTime = UnixTimeStampConverter.ToUnixTimestamp(request.ClientTimestamp); + + var expectedJson = $@"{{""document_group_name"":""test name"",""client_timestamp"":""{actualTime}""}}"; + + Assert.That.JsonEqual(expectedJson, request); + } + } +} diff --git a/SignNow.Net.Test/UnitTests/Requests/DownloadOptionsTest.cs b/SignNow.Net.Test/UnitTests/Requests/DownloadOptionsTest.cs new file mode 100644 index 00000000..4dc224e1 --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Requests/DownloadOptionsTest.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; +using SignNow.Net.Model.Requests.DocumentGroup; + +namespace UnitTests.Requests +{ + [TestClass] + public class DownloadOptionsTest + { + [TestMethod] + public void DownloadOptionsWithDefaultsTest() + { + var downloadOptions = new DownloadOptions(); + + Assert.AreEqual(DownloadType.Zip, downloadOptions.DownloadType); + Assert.AreEqual(DocumentHistoryType.NoHistory, downloadOptions.WithHistory); + Assert.AreEqual(0, downloadOptions.DocumentOrder.Count); + } + + [TestMethod] + public void Serialize_ShouldIncludeAllPropertiesTest() + { + var options = new DownloadOptions + { + DownloadType = DownloadType.PdfWithCertificate, + WithHistory = DocumentHistoryType.AfterEachDocument, + DocumentOrder = new List { "03c74b3083f34ebf8ef40a3039dfb32c85a08437", "03739a736d324f9794c2e93ec7c5bda817af3f7f" } + }; + + var expectedJson = "{\"type\":\"certificate\",\"with_history\":\"after_each_document\",\"document_order\":[\"03c74b3083f34ebf8ef40a3039dfb32c85a08437\",\"03739a736d324f9794c2e93ec7c5bda817af3f7f\"]}"; + + Assert.That.JsonEqual(options, expectedJson); + } + + [TestMethod] + public void Serialize_ShouldIgnoreNullDocumentOrderTest() + { + var options = new DownloadOptions + { + DownloadType = DownloadType.ZipForEmail, + WithHistory = DocumentHistoryType.AfterMergedPdf, + DocumentOrder = null + }; + + var expectedJson = "{\"type\":\"email\",\"with_history\":\"after_merged_pdf\"}"; + + Assert.That.JsonEqual(options, expectedJson); + } + + [TestMethod] + public void Deserialize_ShouldSetAllPropertiesTest() + { + var json = "{\"type\":\"merged\",\"with_history\":\"no\",\"document_order\":[\"03c74b3083f34ebf8ef40a3039dfb32c85a08437\",\"03c74b3083f34ebf8ef40a3039dfb32c85a08438\"]}"; + var options = JsonConvert.DeserializeObject(json); + + Assert.AreEqual(DownloadType.MergedPdf, options.DownloadType); + Assert.AreEqual(DocumentHistoryType.NoHistory, options.WithHistory); + CollectionAssert.AreEqual(new List { "03c74b3083f34ebf8ef40a3039dfb32c85a08437", "03c74b3083f34ebf8ef40a3039dfb32c85a08438" }, options.DocumentOrder); + } + + } +} diff --git a/SignNow.Net.Test/UnitTests/Requests/LimitOffsetOptionsTest.cs b/SignNow.Net.Test/UnitTests/Requests/LimitOffsetOptionsTest.cs new file mode 100644 index 00000000..b0ee17a5 --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Requests/LimitOffsetOptionsTest.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model.Requests; + +namespace UnitTests.Requests +{ + [TestClass] + public class LimitOffsetOptionsTest + { + [TestMethod] + public void BuildQuery() + { + var emptyOptions = new LimitOffsetOptions(); + var fullOptions = new LimitOffsetOptions + { + Limit = 2, + Offset = 3 + }; + + Assert.AreEqual(String.Empty, emptyOptions.ToQueryString()); + Assert.AreEqual("limit=2&offset=3", fullOptions.ToQueryString()); + } + } +} diff --git a/SignNow.Net.Test/UnitTests/Requests/UpdateUserOptionsTest.cs b/SignNow.Net.Test/UnitTests/Requests/UpdateUserOptionsTest.cs new file mode 100644 index 00000000..6149b0b5 --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Requests/UpdateUserOptionsTest.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTests.Requests +{ + [TestClass] + public class UpdateUserOptionsTest : SignNowTestBase + { + [TestMethod] + public void ShouldSerializeAllProperties() + { + var updateUserOptions = new SignNow.Net.Model.Requests.UpdateUserOptions + { + FirstName = "signNow", + LastName = "SDK", + OldPassword = "old-password", + Password = "new-password", + LogOutAll = false + }; + + var expected = @"{ + ""first_name"": ""signNow"", + ""last_name"": ""SDK"", + ""password"": ""new-password"", + ""old_password"": ""old-password"", + ""logout_all"": ""false"" + }"; + + Assert.That.JsonEqual(updateUserOptions, expected); + } + } +} diff --git a/SignNow.Net.Test/UnitTests/Responses/DocumentGroupInfoResponseTest.cs b/SignNow.Net.Test/UnitTests/Responses/DocumentGroupInfoResponseTest.cs new file mode 100644 index 00000000..c2e61837 --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Responses/DocumentGroupInfoResponseTest.cs @@ -0,0 +1,84 @@ +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model.Responses; + +namespace UnitTests.Responses +{ + [TestClass] + public class DocumentGroupInfoResponseTest + { + [TestMethod] + public void ShouldProperDeserialize() + { + var jsonResponse = @"{ + ""data"": { + ""id"": ""03c74b3083f34ebf8ef40a3039dfb32c85a08437"", + ""name"": ""CreateDocumentGroupTest"", + ""created"": 1729535107, + ""updated"": 1729535107, + ""invite_id"": null, + ""pending_step_id"": null, + ""state"": ""created"", + ""sign_as_merged"": null, + ""last_invite_id"": null, + ""owner_email"": ""signnow@gmail.com"", + ""folder_id"": ""e1d8d63ba51c4009ab9941f279c908a0fd5a5e48"", + ""documents"": [ + { + ""roles"": [], + ""document_name"": ""ForDocumentGroupFile-1"", + ""page_count"": 1, + ""id"": ""66974a4b421546a69167ba342d1ae94af56ce351"", + ""updated"": 1729535105, + ""folder_id"": ""e1d8d63ba51c4009ab9941f279c908a0fd5a5e48"", + ""owner"": { + ""id"": ""50204b3344984768bb16d61f8550f8b5edfd719a"", + ""email"": ""signnow@gmail.com"" + }, + ""thumbnail"": { + ""small"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce351/thumbnail?size=small"", + ""medium"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce351/thumbnail?size=medium"", + ""large"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce351/thumbnail?size=large"" + }, + ""origin_document_id"": null, + ""has_unassigned_field"": false, + ""has_credit_card_number"": false, + ""field_invites"": [], + ""shared_with_team"": null, + ""settings"": [], + ""allow_to_remove"": true + } + ], + ""owner"": { + ""id"": ""50204b3344984768bb16d61f8550f8b5edfd719a"", + ""email"": ""signnow@gmail.com"", + ""organization"": { + ""id"": ""44f2dc3eef1c4924a8aea48d238968402abc745f"" + } + }, + ""cc_emails"": [], + ""freeform_invite"": { + ""id"": null, + ""last_id"": null + }, + ""originator_organization_settings"": [ + { + ""setting"": ""mobileweb_option"", + ""value"": ""app_or_mobileweb_choice"" + } + ], + ""mail_provider"": null + } + }"; + + var response = TestUtils.DeserializeFromJson(jsonResponse); + + Assert.AreEqual("03c74b3083f34ebf8ef40a3039dfb32c85a08437", response.Data.Id); + Assert.AreEqual("CreateDocumentGroupTest", response.Data.Name); + Assert.AreEqual(1, response.Data.Documents.Count); + Assert.AreEqual("ForDocumentGroupFile-1", response.Data.Documents.FirstOrDefault()?.Name); + Assert.AreEqual("50204b3344984768bb16d61f8550f8b5edfd719a", response.Data.Owner.Id); + Assert.AreEqual("44f2dc3eef1c4924a8aea48d238968402abc745f", response.Data.Owner.Organization.Id); + } + } +} diff --git a/SignNow.Net.Test/UnitTests/Responses/DocumentGroupsResponseTests.cs b/SignNow.Net.Test/UnitTests/Responses/DocumentGroupsResponseTests.cs new file mode 100644 index 00000000..73b597fa --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Responses/DocumentGroupsResponseTests.cs @@ -0,0 +1,65 @@ +using System.Linq; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model.Responses; + +namespace UnitTests.Responses +{ + [TestClass] + public class DocumentGroupsResponseTests + { + [TestMethod] + public void ShouldProperDeserialize() + { + var jsonResponse = @"{ + ""document_groups"": [ + { + ""folder_id"": null, + ""last_updated"": ""1729535107"", + ""group_id"": ""03c74b3083f34ebf8ef40a3039dfb32c85a08333"", + ""group_name"": ""CreateDocumentGroupTest"", + ""invite_id"": null, + ""invite_status"": null, + ""documents"": [ + { + ""id"": ""66974a4b421546a69167ba342d1ae94af56ce333"", + ""name"": ""ForDocumentGroupFile-1"", + ""page_count"": 1, + ""thumbnail"": { + ""small"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce333/thumbnail?size=small"", + ""medium"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce333/thumbnail?size=medium"", + ""large"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce333/thumbnail?size=large"" + }, + ""roles"": [], + ""settings"": { + ""advanced_signing_flow"": false + }, + ""has_credit_card_number"": false + } + ], + ""is_full_declined"": false, + ""is_embedded"": false, + ""freeform_invite"": { + ""id"": null + }, + ""state"": ""created"", + ""has_guest_signer"": false, + ""has_signing_group"": false + } + ], + ""document_group_total_count"": 1 + }"; + + var response = TestUtils.DeserializeFromJson(jsonResponse); + + Assert.AreEqual(1, response.TotalCount); + Assert.AreEqual(1, response.Data.Count); + + var group = response.Data.First(); + Assert.AreEqual("03c74b3083f34ebf8ef40a3039dfb32c85a08333", group.GroupId); + Assert.AreEqual("CreateDocumentGroupTest", group.Name); + Assert.AreEqual(1, group.Documents.First().Pages); + Assert.AreEqual("advanced_signing_flow", group.Documents.First().Settings.First().Key); + Assert.AreEqual(false, group.Documents.First().Settings.First().Value); + } + } +} diff --git a/SignNow.Net.Test/UnitTests/Services/DocumentGroupServiceTest.cs b/SignNow.Net.Test/UnitTests/Services/DocumentGroupServiceTest.cs new file mode 100644 index 00000000..9f3b7ee6 --- /dev/null +++ b/SignNow.Net.Test/UnitTests/Services/DocumentGroupServiceTest.cs @@ -0,0 +1,152 @@ +using System; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SignNow.Net.Model; +using SignNow.Net.Model.Requests; +using SignNow.Net.Model.Responses; +using SignNow.Net.Service; +using SignNow.Net.Test.FakeModels; + +namespace UnitTests.Services +{ + [TestClass] + public class DocumentGroupServiceTest : SignNowTestBase + { + [TestMethod] + public async Task CreateDocumentGroupAsyncTest() + { + var service = new DocumentGroupService(ApiBaseUrl, new Token(), + SignNowClientMock("{\"id\":\"add9e5af17ad0876ed1ec327cc86209d0377181d\"}")); + + var documents = new SignNowDocumentFaker().Generate(5); + var response = await service.CreateDocumentGroupAsync("test group", documents).ConfigureAwait(false); + + Assert.IsInstanceOfType(response, typeof(DocumentGroupCreateResponse)); + Assert.AreEqual("add9e5af17ad0876ed1ec327cc86209d0377181d", response.Id); + } + + [TestMethod] + public async Task ThrowsExceptionForWrongParamsTest() + { + var service = new DocumentGroupService(ApiBaseUrl, new Token(), SignNowClientMock("{}")); + + var options = new LimitOffsetOptions + { + Limit = 0 + }; + var limitException = await Assert.ThrowsExceptionAsync( + async () => await service + .GetDocumentGroupsAsync(options) + .ConfigureAwait(false) + ).ConfigureAwait(false); + + StringAssert.Contains(limitException.Message, "Limit must be greater than 0 but less than or equal to 50."); + Assert.AreEqual("options", limitException.ParamName); + + options.Limit = 2; + options.Offset = -1; + + var offsetException = await Assert.ThrowsExceptionAsync( + async () => await service + .GetDocumentGroupsAsync(options) + .ConfigureAwait(false) + ).ConfigureAwait(false); + + StringAssert.Contains(offsetException.Message, "Offset must be 0 or greater."); + Assert.AreEqual("options", offsetException.ParamName); + } + + [TestMethod] + public async Task ThrowsExceptionForWrongParamsClassTest() + { + var service = new DocumentGroupService(ApiBaseUrl, new Token(), SignNowClientMock("{}")); + + var instanceException = await Assert.ThrowsExceptionAsync( + async () => await service + .GetDocumentGroupsAsync(new PagePaginationOptions()) + .ConfigureAwait(false) + ).ConfigureAwait(false); + + StringAssert.Contains(instanceException.Message, "Query params does not have 'limit' and 'offset' options. Use \"LimitOffsetOptions\" class."); + Assert.AreEqual("options", instanceException.ParamName); + } + + [TestMethod] + public async Task GetDocumentGroupInfoAsyncTest() + { + var jsonResponse = @"{ + ""data"": { + ""id"": ""03c74b3083f34ebf8ef40a3039dfb32c85a08437"", + ""name"": ""CreateDocumentGroupTest"", + ""created"": 1729535107, + ""updated"": 1729535107, + ""invite_id"": null, + ""pending_step_id"": null, + ""state"": ""created"", + ""sign_as_merged"": null, + ""last_invite_id"": null, + ""owner_email"": ""signnow-tests@gmail.com"", + ""folder_id"": ""e1d8d63ba51c4009ab8241f279c908a0fd5a5e48"", + ""share_info"": { + ""is_team_shared"": false, + ""role"": ""owner"", + ""is_personally_shared_to_others"": false + }, + ""documents"": [ + { + ""roles"": [], + ""document_name"": ""ForDocumentGroupFile-1"", + ""page_count"": 1, + ""id"": ""66974a4b421546a69167ba342d1ae94af56ce351"", + ""updated"": 1729535105, + ""folder_id"": ""e1d8d63ba51c4009ab8241f279c908a0fd5a5e48"", + ""owner"": { + ""id"": ""40204b3344984733bb16d61f8550f8b5edfd719a"", + ""email"": ""signnow-tests@gmail.com"" + }, + ""thumbnail"": { + ""small"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce351/thumbnail?size=small"", + ""medium"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce351/thumbnail?size=medium"", + ""large"": ""https://api-eval.signnow.com/document/66974a4b421546a69167ba342d1ae94af56ce351/thumbnail?size=large"" + }, + ""origin_document_id"": null, + ""has_unassigned_field"": false, + ""has_credit_card_number"": false, + ""field_invites"": [], + ""shared_with_team"": null, + ""settings"": [], + ""allow_to_remove"": true + } + ], + ""owner"": { + ""id"": ""40204b3344984733bb16d61f8550f8b5edfd719a"", + ""email"": ""signnow.tutorial+dotnet@gmail.com"", + ""organization"": { + ""id"": ""1ef2dc3eef1c2222a8aea48d238968402abc745f"" + } + }, + ""cc_emails"": [], + ""freeform_invite"": { + ""id"": null, + ""last_id"": null + }, + ""mail_provider"": null + } + }"; + var service = new DocumentGroupService(ApiBaseUrl, new Token(), + SignNowClientMock(jsonResponse)); + + var response = await service.GetDocumentGroupInfoAsync("03c74b3083f34ebf8ef40a3039dfb32c85a08437").ConfigureAwait(false); + + Assert.IsInstanceOfType(response, typeof(DocumentGroupInfoResponse)); + Assert.AreEqual("03c74b3083f34ebf8ef40a3039dfb32c85a08437", response.Data.Id); + Assert.AreEqual("CreateDocumentGroupTest", response.Data.Name); + Assert.AreEqual("created", response.Data.State); + Assert.AreEqual("e1d8d63ba51c4009ab8241f279c908a0fd5a5e48", response.Data.FolderId); + Assert.AreEqual(1, response.Data.Documents.Count); + Assert.AreEqual("66974a4b421546a69167ba342d1ae94af56ce351", response.Data.Documents[0].Id); + Assert.AreEqual("ForDocumentGroupFile-1", response.Data.Documents[0].Name); + Assert.AreEqual("40204b3344984733bb16d61f8550f8b5edfd719a", response.Data.Owner.Id); + } + } +} diff --git a/SignNow.Net.Test/UnitTests/Services/SignNowContextTest.cs b/SignNow.Net.Test/UnitTests/Services/SignNowContextTest.cs index 910dfb0e..ad9a619e 100644 --- a/SignNow.Net.Test/UnitTests/Services/SignNowContextTest.cs +++ b/SignNow.Net.Test/UnitTests/Services/SignNowContextTest.cs @@ -19,6 +19,7 @@ public void ShouldCreateContextUsingToken() Assert.IsInstanceOfType(context.Invites, typeof(UserService)); Assert.IsInstanceOfType(context.Users, typeof(UserService)); Assert.IsInstanceOfType(context.Folders, typeof(FolderService)); + Assert.IsInstanceOfType(context.DocumentGroup, typeof(DocumentGroupService)); } [TestMethod] diff --git a/SignNow.Net/Interfaces/IDocumentGroup.cs b/SignNow.Net/Interfaces/IDocumentGroup.cs new file mode 100644 index 00000000..019c54a9 --- /dev/null +++ b/SignNow.Net/Interfaces/IDocumentGroup.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using SignNow.Net.Model; +using SignNow.Net.Model.Requests.DocumentGroup; +using SignNow.Net.Model.Responses; + +namespace SignNow.Net.Interfaces +{ + /// + /// Interface for any operations with a Document Groups in signNow + /// can be used to create, rename, delete, move a document group etc. + /// + public interface IDocumentGroup + { + /// + /// Creates a document group from a list of document ids + /// + /// Name for the Document Group + /// The list of the documents to create the document group. + /// Propagates notification that operations should be canceled. + /// + Task CreateDocumentGroupAsync(string groupName, IEnumerable documents, CancellationToken cancellationToken = default); + + /// + /// Getting basic information about document groups. + /// + /// ID of the Document Group + /// Propagates notification that operations should be canceled. + /// + Task GetDocumentGroupInfoAsync(string documentGroupId, CancellationToken cancellationToken = default); + + /// + /// Returns back all document groups the user owns. + /// The call is paginated by last_updated, so offset and limit query parameters are required + /// + /// Limit and offset query options + /// Propagates notification that operations should be canceled. + /// + Task GetDocumentGroupsAsync(IQueryToString options, CancellationToken cancellationToken = default); + + /// + /// Renames document group + /// + /// New name for the document group. + /// ID of the Document Group. + /// Propagates notification that operations should be canceled. + /// + Task RenameDocumentGroupAsync(string newName, string documentGroupId, CancellationToken cancellationToken = default); + + /// + /// Allows users to move a document group to another folder. When a document group is moved, + /// all its documents are moved to the same folder except documents in the Deleted folder (Deleted from Trash) + /// and documents that are located in the Shared folders. + /// + /// ID of the Document Group. + /// ID of the folder to move the document group to. Allowed folder types: Documents, Archive, Shared Documents folders. + /// Whether to move shared documents that are in this document group. With this parameter, a document group can only be moved to trash. + /// Propagates notification that operations should be canceled. + /// + Task MoveDocumentGroupAsync(string documentGroupId, string folderId, bool withSharedDocuments = false, CancellationToken cancellationToken = default); + + /// + /// Copy a document group in any status and set a new name to it. + /// + /// ID of the Document Group. + /// The name of the new document group copy. + /// Propagates notification that operations should be canceled. + /// + Task CopyDocumentGroupAsync(string documentGroupId, string newName, CancellationToken cancellationToken = default); + + /// + /// Deletes a document group. Documents within the group are not deleted. Document groups cannot be deleted while they have a group invite pending. + /// + /// ID of the Document Group. + /// Propagates notification that operations should be canceled. + /// + Task DeleteDocumentGroupAsync(string documentGroupId, CancellationToken cancellationToken = default); + + /// + /// Download all documents of the document group. + /// + /// ID of the Document Group. + /// Options for download for Document Group. + /// Propagates notification that operations should be canceled. + /// + Task DownloadDocumentGroupAsync(string documentGroupId, DownloadOptions options, CancellationToken cancellationToken = default); + } +} diff --git a/SignNow.Net/Model/Organization.cs b/SignNow.Net/Model/Organization.cs new file mode 100644 index 00000000..700bdf96 --- /dev/null +++ b/SignNow.Net/Model/Organization.cs @@ -0,0 +1,9 @@ +using SignNow.Net.Model.Responses.GenericResponses; + +namespace SignNow.Net.Model +{ + public class Organization : IdResponse + { + + } +} diff --git a/SignNow.Net/Model/Owner.cs b/SignNow.Net/Model/Owner.cs new file mode 100644 index 00000000..b8af48db --- /dev/null +++ b/SignNow.Net/Model/Owner.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace SignNow.Net.Model +{ + public class Owner + { + [JsonProperty("id")] + public string Id { get; set; } + + /// + /// Email of document owner. + /// + [JsonProperty("email")] + public string Email { get; set; } + + /// + /// Organization info. + /// + [JsonProperty("organization", NullValueHandling = NullValueHandling.Ignore)] + public Organization Organization { get; set; } + } +} diff --git a/SignNow.Net/Model/Requests/DocumentGroup/CopyDocumentGroupRequest.cs b/SignNow.Net/Model/Requests/DocumentGroup/CopyDocumentGroupRequest.cs new file mode 100644 index 00000000..1f2c671b --- /dev/null +++ b/SignNow.Net/Model/Requests/DocumentGroup/CopyDocumentGroupRequest.cs @@ -0,0 +1,22 @@ +using System; +using Newtonsoft.Json; +using SignNow.Net.Internal.Helpers.Converters; + +namespace SignNow.Net.Model.Requests.DocumentGroup +{ + public class CopyDocumentGroupRequest : JsonHttpContent + { + /// + /// The name of the new document group copy. + /// + [JsonProperty("document_group_name")] + public string DocumentGroupName { get; set; } + + /// + /// Timestamp document was created. + /// + [JsonProperty("client_timestamp")] + [JsonConverter(typeof(UnixTimeStampJsonConverter))] + public DateTime ClientTimestamp { get; set; } = DateTime.UtcNow; + } +} diff --git a/SignNow.Net/Model/Requests/DocumentGroup/DocumentHistoryType.cs b/SignNow.Net/Model/Requests/DocumentGroup/DocumentHistoryType.cs new file mode 100644 index 00000000..d6e0f325 --- /dev/null +++ b/SignNow.Net/Model/Requests/DocumentGroup/DocumentHistoryType.cs @@ -0,0 +1,28 @@ +using System.Runtime.Serialization; + +namespace SignNow.Net.Model.Requests.DocumentGroup +{ + /// + /// Whether or not to attach document history to the download. + /// + public enum DocumentHistoryType + { + /// + /// Do not attach document history to the download. + /// + [EnumMember(Value = "no")] + NoHistory, + + /// + /// Attach document history to the download after each document. + /// + [EnumMember(Value = "after_each_document")] + AfterEachDocument, + + /// + /// Attach document history to the download after the merged PDF. + /// + [EnumMember(Value = "after_merged_pdf")] + AfterMergedPdf + } +} diff --git a/SignNow.Net/Model/Requests/DocumentGroup/DownloadOptions.cs b/SignNow.Net/Model/Requests/DocumentGroup/DownloadOptions.cs new file mode 100644 index 00000000..76811b52 --- /dev/null +++ b/SignNow.Net/Model/Requests/DocumentGroup/DownloadOptions.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace SignNow.Net.Model.Requests.DocumentGroup +{ + public class DownloadOptions : JsonHttpContent + { + /// + [JsonProperty("type")] + [JsonConverter(typeof(StringEnumConverter))] + public DownloadType DownloadType { get; set; } = DownloadType.Zip; + + /// + [JsonProperty("with_history")] + [JsonConverter(typeof(StringEnumConverter))] + public DocumentHistoryType WithHistory { get; set; } = DocumentHistoryType.NoHistory; + + /// + /// The order of documents in the merged file. e.g: ordered list with signNow document ids. + /// + [JsonProperty("document_order", NullValueHandling = NullValueHandling.Ignore)] + public List DocumentOrder { get; set; } = new List(); + } +} diff --git a/SignNow.Net/Model/Requests/DocumentGroup/DownloadType.cs b/SignNow.Net/Model/Requests/DocumentGroup/DownloadType.cs new file mode 100644 index 00000000..c8fe626e --- /dev/null +++ b/SignNow.Net/Model/Requests/DocumentGroup/DownloadType.cs @@ -0,0 +1,34 @@ +using System.Runtime.Serialization; + +namespace SignNow.Net.Model.Requests.DocumentGroup +{ + /// + /// Types of download for document group + /// + public enum DownloadType + { + /// + /// zipped binary file (application/zip content) + /// + [EnumMember(Value = "zip")] + Zip, + + /// + /// pdf file that contains all the documents of the group + /// + [EnumMember(Value = "merged")] + MergedPdf, + + /// + /// pdf file with all the documents + attachments, all with the document group stamp, with history. + /// + [EnumMember(Value = "certificate")] + PdfWithCertificate, + + /// + /// returns zip file with document group that has ID on each page at the documents, attachments and history file in case history has been requested. + /// + [EnumMember(Value = "email")] + ZipForEmail + } +} diff --git a/SignNow.Net/Model/Requests/LimitOffsetOptions.cs b/SignNow.Net/Model/Requests/LimitOffsetOptions.cs new file mode 100644 index 00000000..dad908fd --- /dev/null +++ b/SignNow.Net/Model/Requests/LimitOffsetOptions.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using SignNow.Net.Interfaces; + +namespace SignNow.Net.Model.Requests +{ + public class LimitOffsetOptions : IQueryToString + { + /// + /// Limit of the items in response. + /// + public int? Limit { get; set; } + + /// + /// Offset size for pagination. + /// + public int? Offset { get; set; } + + public string ToQueryString() + { + if (Limit == null && Offset == null) + { + return String.Empty; + } + + var options = new List(); + + if (Limit != null) + { + options.Add($"limit={Limit}"); + } + + if (Offset != null) + { + options.Add($"offset={Offset}"); + } + + return String.Join("&", options); + } + } +} diff --git a/SignNow.Net/Model/Requests/UpdateUserOptions.cs b/SignNow.Net/Model/Requests/UpdateUserOptions.cs index 2703561b..710945fc 100644 --- a/SignNow.Net/Model/Requests/UpdateUserOptions.cs +++ b/SignNow.Net/Model/Requests/UpdateUserOptions.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using SignNow.Net.Internal.Helpers.Converters; namespace SignNow.Net.Model.Requests { @@ -33,6 +34,7 @@ public class UpdateUserOptions : JsonHttpContent /// if "false" - all user tokens except current one are expired /// [JsonProperty("logout_all")] + [JsonConverter(typeof(BoolToStringJsonConverter))] public bool LogOutAll { get; set; } = true; } } diff --git a/SignNow.Net/Model/Responses/CreateDocumentFromTemplateResponse.cs b/SignNow.Net/Model/Responses/CreateDocumentFromTemplateResponse.cs index 443a5739..273e9d22 100644 --- a/SignNow.Net/Model/Responses/CreateDocumentFromTemplateResponse.cs +++ b/SignNow.Net/Model/Responses/CreateDocumentFromTemplateResponse.cs @@ -1,16 +1,11 @@ -using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model.Responses { /// /// Represents response from signNow API for Create Document from Template request. /// - public class CreateDocumentFromTemplateResponse + public class CreateDocumentFromTemplateResponse :IdResponse { - /// - /// Identity of new Document. - /// - [JsonProperty("id")] - public string Id { get; set; } } } diff --git a/SignNow.Net/Model/Responses/CreateTemplateFromDocumentResponse.cs b/SignNow.Net/Model/Responses/CreateTemplateFromDocumentResponse.cs index 24a93ce4..2339adbd 100644 --- a/SignNow.Net/Model/Responses/CreateTemplateFromDocumentResponse.cs +++ b/SignNow.Net/Model/Responses/CreateTemplateFromDocumentResponse.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model.Responses { @@ -6,12 +7,5 @@ namespace SignNow.Net.Model.Responses /// Represents response from signNow API for Create Template from Document request. /// [JsonObject] - public class CreateTemplateFromDocumentResponse - { - /// - /// Identity of new Template. - /// - [JsonProperty("id")] - public string Id { get; set; } - } + public class CreateTemplateFromDocumentResponse : IdResponse { } } diff --git a/SignNow.Net/Model/Responses/DocumentGroupCreateResponse.cs b/SignNow.Net/Model/Responses/DocumentGroupCreateResponse.cs new file mode 100644 index 00000000..1b1a5a02 --- /dev/null +++ b/SignNow.Net/Model/Responses/DocumentGroupCreateResponse.cs @@ -0,0 +1,9 @@ +using SignNow.Net.Model.Responses.GenericResponses; + +namespace SignNow.Net.Model.Responses +{ + /// + /// Represents response from signNow API for Create Document Group request. + /// + public class DocumentGroupCreateResponse : IdResponse { } +} diff --git a/SignNow.Net/Model/Responses/DocumentGroupInfoResponse.cs b/SignNow.Net/Model/Responses/DocumentGroupInfoResponse.cs new file mode 100644 index 00000000..077d5fd6 --- /dev/null +++ b/SignNow.Net/Model/Responses/DocumentGroupInfoResponse.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using SignNow.Net.Internal.Helpers.Converters; +using SignNow.Net.Model.Responses.GenericResponses; + +namespace SignNow.Net.Model.Responses +{ + public class DocumentGroupInfoResponse + { + [JsonProperty("data")] + public DocumentGroupData Data { get; set; } + } + + public class DocumentGroupData : IdResponse + { + /// + /// Document Group name. + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Timestamp of document group creation. + /// + [JsonProperty("created")] + [JsonConverter(typeof(UnixTimeStampJsonConverter))] + public DateTime Created { get; set; } + + /// + /// Timestamp of document group update. + /// + [JsonProperty("updated")] + [JsonConverter(typeof(UnixTimeStampJsonConverter))] + public DateTime Updated { get; set; } + + /// + /// Identity of invite for Document Group. + /// + [JsonProperty("invite_id")] + public string InviteId { get; set; } + + /// + /// Identity of pending step for Document Group. + /// + [JsonProperty("pending_step_id")] + public string PendingStepId { get; set; } + + /// + /// Identity of last invite for Document Group. + /// + [JsonProperty("last_invite_id")] + public string LastInviteId { get; set; } + + /// + /// Document Group state. + /// + [JsonProperty("state")] + public string State { get; set; } + + /// + /// Owner email address. + /// + [JsonProperty("owner_email")] + public string OwnerEmail { get; set; } + + /// + /// An ID of folder with document group. + /// + [JsonProperty("folder_id")] + public string FolderId { get; set; } + + [JsonProperty("documents")] + public IReadOnlyList Documents { get; set; } + + /// + /// Document owner info. + /// + [JsonProperty("owner")] + public Owner Owner { get; set; } + + /// + /// CC emails list. + /// + [JsonProperty("cc_emails")] + public IReadOnlyList CcEmails { get; set; } + + /// + /// Freeform invite info. + /// + [JsonProperty("freeform_invite")] + public FreeFormInviteInfo FreeFormInvite { get; set; } + } + + public class GroupDocumentsInfo : IdResponse + { + /// + /// List with document roles. + /// + [JsonProperty("roles")] + public IReadOnlyList Roles { get; set; } + + /// + /// Document name. + /// + [JsonProperty("document_name")] + public string Name { get; set; } + + /// + /// Pages count. + /// + [JsonProperty("page_count")] + public int Pages { get; set; } + + /// + /// Timestamp of document update. + /// + [JsonProperty("updated")] + [JsonConverter(typeof(UnixTimeStampJsonConverter))] + public DateTime Updated { get; set; } + + /// + /// An ID of folder with document. + /// + [JsonProperty("folder_id")] + public string FolderId { get; set; } + + /// + /// Document owner info. + /// + [JsonProperty("owner")] + public Owner Owner { get; set; } + + /// + /// Document thumbnails + /// + [JsonProperty("thumbnail")] + public Thumbnail Thumbnail { get; set; } + + /// + /// Document settings. + /// + [JsonProperty("settings", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(KeyValueOrEmptyArrayConverter))] + public IReadOnlyDictionary Settings { get; set; } + + /// + /// An ID of document origin. + /// + [JsonProperty("origin_document_id")] + public string OriginDocumentId { get; set; } + + /// + /// Is the document has unassigned field. + /// + [JsonProperty("has_unassigned_field")] + public bool HasUnassignedField { get; set; } + + /// + /// Is the document has a credit card number. + /// + [JsonProperty("has_credit_card_number")] + public bool HasCreditCardNumber { get; set; } + + /// + /// Is the document can be removed from document group. + /// + [JsonProperty("allow_to_remove", NullValueHandling = NullValueHandling.Ignore)] + public bool IsAllowedToRemove { get; set; } + } + + public class FreeFormInviteInfo : IdResponse + { + [JsonProperty("last_id", NullValueHandling = NullValueHandling.Ignore)] + public string LastId { get; set; } + } +} diff --git a/SignNow.Net/Model/Responses/DocumentGroupsResponse.cs b/SignNow.Net/Model/Responses/DocumentGroupsResponse.cs new file mode 100644 index 00000000..9165fb3b --- /dev/null +++ b/SignNow.Net/Model/Responses/DocumentGroupsResponse.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using SignNow.Net.Internal.Helpers.Converters; + +namespace SignNow.Net.Model.Responses +{ + public class DocumentGroupsResponse + { + [JsonProperty("document_groups")] + public IReadOnlyList Data { get; set; } + + /// + /// Total documents count in document group. + /// + [JsonProperty("document_group_total_count")] + public int TotalCount { get; set; } + } + + public class DocumentGroups + { + /// + /// An ID of folder with document group. + /// + [JsonProperty("folder_id")] + public string FolderId { get; set; } + + /// + /// Timestamp of document group update. + /// + [JsonProperty("last_updated")] + [JsonConverter(typeof(UnixTimeStampJsonConverter))] + public DateTime Updated { get; set; } + + /// + /// Document Group name. + /// + [JsonProperty("group_name")] + public string Name { get; set; } + + /// + /// An ID of document group. + /// + [JsonProperty("group_id")] + public string GroupId { get; set; } + + /// + /// Identity of invite for Document Group. + /// + [JsonProperty("invite_id")] + public string InviteId { get; set; } + + /// + /// Invite status + /// + [JsonProperty("invite_status")] + public string InviteStatus { get; set; } + + [JsonProperty("documents")] + public IReadOnlyList Documents { get; set; } + + /// + /// Is the document full declined. + /// + [JsonProperty("is_full_declined")] + public bool IsFullDeclined { get; set; } + + /// + /// Is the document embedded. + /// + [JsonProperty("is_embedded")] + public bool IsEmbedded { get; set; } + + /// + /// Freeform invite info. + /// + [JsonProperty("freeform_invite")] + public FreeFormInviteInfo FreeFormInvite { get; set; } + + /// + /// Document Group state. + /// + [JsonProperty("state")] + public string State { get; set; } + + /// + /// Is the document has guest signer. + /// + [JsonProperty("has_guest_signer")] + public bool HasGuestSigner { get; set; } + + /// + /// Is the document has signing group. + /// + [JsonProperty("has_signing_group")] + public bool HasSigningGroup { get; set; } + } +} diff --git a/SignNow.Net/Model/Responses/DownloadDocumentResponse.cs b/SignNow.Net/Model/Responses/DownloadDocumentResponse.cs index 359a8921..d53aa16b 100644 --- a/SignNow.Net/Model/Responses/DownloadDocumentResponse.cs +++ b/SignNow.Net/Model/Responses/DownloadDocumentResponse.cs @@ -17,6 +17,11 @@ public class DownloadDocumentResponse /// public long Length { get; set; } + /// + /// Type of content. + /// + public string MediaType { get; set; } + /// /// File contents as Stream. /// diff --git a/SignNow.Net/Model/Responses/EditDocumentResponse.cs b/SignNow.Net/Model/Responses/EditDocumentResponse.cs index edc3e6e2..451d2d3a 100644 --- a/SignNow.Net/Model/Responses/EditDocumentResponse.cs +++ b/SignNow.Net/Model/Responses/EditDocumentResponse.cs @@ -1,16 +1,11 @@ -using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model.Responses { /// /// Represents response for edit document. /// - public class EditDocumentResponse + public class EditDocumentResponse : IdResponse { - /// - /// Identity of the document. - /// - [JsonProperty("id")] - public string Id { get; set; } } } diff --git a/SignNow.Net/Model/Responses/EmbeddedInviteResponse.cs b/SignNow.Net/Model/Responses/EmbeddedInviteResponse.cs index f3045625..176b1460 100644 --- a/SignNow.Net/Model/Responses/EmbeddedInviteResponse.cs +++ b/SignNow.Net/Model/Responses/EmbeddedInviteResponse.cs @@ -1,20 +1,15 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model { /// /// Represents response from signNow API for create embedded invite request. /// - public class EmbeddedInviteData + public class EmbeddedInviteData : IdResponse { - /// - /// Identity of embedded invite request. - /// - [JsonProperty("id")] - public string Id { get; set; } - /// /// Signer's email address. /// diff --git a/SignNow.Net/Model/Responses/EventUpdateResponse.cs b/SignNow.Net/Model/Responses/EventUpdateResponse.cs index 74cdd262..0986d5aa 100644 --- a/SignNow.Net/Model/Responses/EventUpdateResponse.cs +++ b/SignNow.Net/Model/Responses/EventUpdateResponse.cs @@ -1,13 +1,8 @@ -using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model.Responses { - public class EventUpdateResponse + public class EventUpdateResponse : IdResponse { - /// - /// Event subscription identity - /// - [JsonProperty("id")] - public string Id { get; set; } } } diff --git a/SignNow.Net/Model/Responses/FolderIdentityResponse.cs b/SignNow.Net/Model/Responses/FolderIdentityResponse.cs index ce347657..52270116 100644 --- a/SignNow.Net/Model/Responses/FolderIdentityResponse.cs +++ b/SignNow.Net/Model/Responses/FolderIdentityResponse.cs @@ -1,10 +1,8 @@ +using SignNow.Net.Model.Responses.GenericResponses; + namespace SignNow.Net.Model.Responses { - public class FolderIdentityResponse + public class FolderIdentityResponse : IdResponse { - /// - /// Identity of then newly created/renamed folder. - /// - public string Id { get; set; } } } diff --git a/SignNow.Net/Model/Responses/GenericResponses/IdResponse.cs b/SignNow.Net/Model/Responses/GenericResponses/IdResponse.cs new file mode 100644 index 00000000..7e62dc6a --- /dev/null +++ b/SignNow.Net/Model/Responses/GenericResponses/IdResponse.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace SignNow.Net.Model.Responses.GenericResponses +{ + /// + /// Represents response from signNow API with identity of the signNow object. + /// + public abstract class IdResponse + { + [JsonProperty("id")] + public string Id { get; set; } + } +} diff --git a/SignNow.Net/Model/Responses/InviteResponse.cs b/SignNow.Net/Model/Responses/InviteResponse.cs index 495b9cec..4af36474 100644 --- a/SignNow.Net/Model/Responses/InviteResponse.cs +++ b/SignNow.Net/Model/Responses/InviteResponse.cs @@ -1,18 +1,13 @@ using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model { /// /// Represents response from signNow API for create invite request. /// - public class InviteResponse + public class InviteResponse : IdResponse { - /// - /// Identity of freeform invite request. - /// - [JsonProperty("id")] - public string Id { get; set; } - /// /// Role-based invite status /// diff --git a/SignNow.Net/Model/Responses/UploadDocumentResponse.cs b/SignNow.Net/Model/Responses/UploadDocumentResponse.cs index d5deac1b..a26c6c9e 100644 --- a/SignNow.Net/Model/Responses/UploadDocumentResponse.cs +++ b/SignNow.Net/Model/Responses/UploadDocumentResponse.cs @@ -1,13 +1,11 @@ +using SignNow.Net.Model.Responses.GenericResponses; + namespace SignNow.Net.Model { /// /// Represents response from signNow API for upload document request. /// - public class UploadDocumentResponse + public class UploadDocumentResponse : IdResponse { - /// - /// Document ID of then newly created file. - /// - public string Id { get; set; } } } diff --git a/SignNow.Net/Model/Responses/UserCreateResponse.cs b/SignNow.Net/Model/Responses/UserCreateResponse.cs index 47814b45..913e3b50 100644 --- a/SignNow.Net/Model/Responses/UserCreateResponse.cs +++ b/SignNow.Net/Model/Responses/UserCreateResponse.cs @@ -1,18 +1,13 @@ using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model { /// /// Represents response from signNow API for User create request. /// - public class UserCreateResponse + public class UserCreateResponse : IdResponse { - /// - /// User identity. - /// - [JsonProperty("id")] - public string Id { get; set; } - /// /// User is verified or not. /// diff --git a/SignNow.Net/Model/Responses/UserUpdateResponse.cs b/SignNow.Net/Model/Responses/UserUpdateResponse.cs index 1440898a..e8fae090 100644 --- a/SignNow.Net/Model/Responses/UserUpdateResponse.cs +++ b/SignNow.Net/Model/Responses/UserUpdateResponse.cs @@ -1,18 +1,13 @@ using Newtonsoft.Json; +using SignNow.Net.Model.Responses.GenericResponses; namespace SignNow.Net.Model { /// /// Represents response from signNow API for User update request. /// - public class UserUpdateResponse + public class UserUpdateResponse : IdResponse { - /// - /// User identity. - /// - [JsonProperty("id")] - public string Id { get; set; } - /// /// User firstname. /// diff --git a/SignNow.Net/Service/DocumentGroupService.cs b/SignNow.Net/Service/DocumentGroupService.cs new file mode 100644 index 00000000..49fa8555 --- /dev/null +++ b/SignNow.Net/Service/DocumentGroupService.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using SignNow.Net.Interfaces; +using SignNow.Net.Internal.Extensions; +using SignNow.Net.Internal.Helpers; +using SignNow.Net.Internal.Requests; +using SignNow.Net.Model; +using SignNow.Net.Model.Requests; +using SignNow.Net.Model.Requests.DocumentGroup; +using SignNow.Net.Model.Responses; + +namespace SignNow.Net.Service +{ + public class DocumentGroupService : WebClientBase, IDocumentGroup + { + /// + /// Creates new instance of + /// + /// Base signNow API URL + /// Access token + /// signNow Http client + public DocumentGroupService(Uri apiBaseUrl, Token token, ISignNowClient signNowClient) : base(apiBaseUrl, token, signNowClient) + { + } + + /// + public async Task CreateDocumentGroupAsync(string groupName, IEnumerable documents, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + var requestOptions = new PostHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, "/documentgroup"), + Content = new CreateDocumentGroupRequest(documents) {GroupName = groupName}, + Token = Token + }; + + return await SignNowClient + .RequestAsync(requestOptions, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// If document group identity is not valid. + public async Task GetDocumentGroupInfoAsync(string documentGroupId, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + var requestOption = new GetHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/v2/document-groups/{documentGroupId.ValidateId()}"), + Token = Token + }; + + return await SignNowClient + .RequestAsync(requestOption, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// Limit must be greater than 0 but less than or equal to 50. + /// Offset must be 0 or greater. + public async Task GetDocumentGroupsAsync(IQueryToString options, CancellationToken cancellationToken = default) + { + if (options.GetType() != typeof(LimitOffsetOptions)) + { + throw new ArgumentException("Query params does not have 'limit' and 'offset' options. Use \"LimitOffsetOptions\" class.", nameof(options)); + } + + var opts = (LimitOffsetOptions)options; + if (opts.Limit <= 0 || opts.Limit > 50) + { + throw new ArgumentException("Limit must be greater than 0 but less than or equal to 50.", nameof(options)); + } + + if (opts.Offset < 0) + { + throw new ArgumentException("Offset must be 0 or greater.", nameof(options)); + } + + var query = options?.ToQueryString(); + var filters = string.IsNullOrEmpty(query) + ? string.Empty + : $"?{query}"; + + Token.TokenType = TokenType.Bearer; + var requestOptions = new GetHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/user/documentgroups{filters}"), + Token = Token + }; + + return await SignNowClient + .RequestAsync(requestOptions, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// If document group identity is not valid. + public async Task RenameDocumentGroupAsync(string newName, string documentGroupId, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + + var requestOptions = new PutHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/v2/document-groups/{documentGroupId.ValidateId()}"), + Content = new RenameDocumentGroupRequest { GroupName = newName}, + Token = Token + }; + + await SignNowClient + .RequestAsync(requestOptions, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// If document group identity or folder identity is not valid. + public async Task MoveDocumentGroupAsync(string documentGroupId, string folderId, bool withSharedDocuments = false, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + + var requestOptions = new PostHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/v2/document-groups/{documentGroupId.ValidateId()}/move"), + Content = new MoveDocumentGroupRequest { FolderId = folderId.ValidateId(), WithSharedDocuments = withSharedDocuments }, + Token = Token + }; + + await SignNowClient + .RequestAsync(requestOptions, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// If document group identity is not valid. + public async Task CopyDocumentGroupAsync(string documentGroupId, string newName, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + + var requestOptions = new PostHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/v2/document-groups/{documentGroupId.ValidateId()}/copy"), + Content = new CopyDocumentGroupRequest { DocumentGroupName = newName }, + Token = Token + }; + + await SignNowClient + .RequestAsync(requestOptions, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// If document group identity is not valid. + public async Task DeleteDocumentGroupAsync(string documentGroupId, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + + var requestOptions = new DeleteHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/documentgroup/{documentGroupId.ValidateId()}"), + Token = Token + }; + + await SignNowClient + .RequestAsync(requestOptions, cancellationToken) + .ConfigureAwait(false); + } + + /// + /// If document group identity is not valid. + public async Task DownloadDocumentGroupAsync(string documentGroupId, DownloadOptions options, CancellationToken cancellationToken = default) + { + Token.TokenType = TokenType.Bearer; + + var requestOptions = new PostHttpRequestOptions + { + RequestUrl = new Uri(ApiBaseUrl, $"/documentgroup/{documentGroupId.ValidateId()}/downloadall"), + Content = options, + Token = Token + }; + + return await SignNowClient + .RequestAsync(requestOptions, new HttpContentToDownloadDocumentResponseAdapter(), HttpCompletionOption.ResponseHeadersRead, cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/SignNow.Net/SignNowContext.cs b/SignNow.Net/SignNowContext.cs index ceaf82f8..590592c9 100644 --- a/SignNow.Net/SignNowContext.cs +++ b/SignNow.Net/SignNowContext.cs @@ -30,6 +30,9 @@ public class SignNowContext : WebClientBase, ISignNowContext /// public IEventSubscriptionService Events { get; protected set; } + /// + public IDocumentGroup DocumentGroup { get; protected set; } + /// /// Create all the services using single instance of and other dependencies. /// @@ -59,6 +62,7 @@ public SignNowContext(Uri baseApiUrl, Token token, ISignNowClient signNowClient Documents = new DocumentService(ApiBaseUrl, Token, SignNowClient); Folders = new FolderService(ApiBaseUrl, Token, SignNowClient); Events = new EventSubscriptionService(ApiBaseUrl, Token, SignNowClient); + DocumentGroup = new DocumentGroupService(ApiBaseUrl, Token, SignNowClient); } /// @@ -85,6 +89,7 @@ private void SyncTokens() ((DocumentService)Documents).Token = Token; ((FolderService)Folders).Token = Token; ((EventSubscriptionService)Events).Token = Token; + ((DocumentGroupService)DocumentGroup).Token = Token; } } } diff --git a/SignNow.Net/_Internal/Helpers/Converters/BoolToStringJsonConverter.cs b/SignNow.Net/_Internal/Helpers/Converters/BoolToStringJsonConverter.cs new file mode 100644 index 00000000..5e2ca22f --- /dev/null +++ b/SignNow.Net/_Internal/Helpers/Converters/BoolToStringJsonConverter.cs @@ -0,0 +1,27 @@ +using System; +using Newtonsoft.Json; + +namespace SignNow.Net.Internal.Helpers.Converters +{ + /// + /// Converts to + /// + internal class BoolToStringJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var strValue = value != null && (bool)value == true ? "true" : "false"; + writer.WriteValue(strValue); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return reader.Value?.ToString().ToLower() == "true"; + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(bool); + } + } +} diff --git a/SignNow.Net/_Internal/Helpers/Converters/KeyValueOrEmptyArrayConverter.cs b/SignNow.Net/_Internal/Helpers/Converters/KeyValueOrEmptyArrayConverter.cs new file mode 100644 index 00000000..356720d9 --- /dev/null +++ b/SignNow.Net/_Internal/Helpers/Converters/KeyValueOrEmptyArrayConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace SignNow.Net.Internal.Helpers.Converters +{ + public class KeyValueOrEmptyArrayConverter: JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Dictionary); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.StartObject) + { + return serializer.Deserialize>(reader); + } + + if (reader.TokenType == JsonToken.StartArray) + { + JArray array = JArray.Load(reader); + if (array.Count == 0) + { + return new Dictionary(); + } + } + + throw new JsonSerializationException("Unexpected token type: " + reader.TokenType); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is Dictionary dictionary && dictionary.Count == 0) + { + writer.WriteStartArray(); + writer.WriteEndArray(); + } + else + { + serializer.Serialize(writer, value); + } + } + + } +} diff --git a/SignNow.Net/_Internal/Helpers/HttpContentAdapter.cs b/SignNow.Net/_Internal/Helpers/HttpContentAdapter.cs index 077e5859..424f9981 100644 --- a/SignNow.Net/_Internal/Helpers/HttpContentAdapter.cs +++ b/SignNow.Net/_Internal/Helpers/HttpContentAdapter.cs @@ -7,7 +7,7 @@ namespace SignNow.Net.Internal.Helpers { - class HttpContentToObjectAdapter : IHttpContentAdapter + internal class HttpContentToObjectAdapter : IHttpContentAdapter { readonly IHttpContentAdapter contentToStringAdapter; @@ -26,7 +26,7 @@ public async Task Adapt(HttpContent content) } } - class HttpContentToStringAdapter : IHttpContentAdapter + internal class HttpContentToStringAdapter : IHttpContentAdapter { /// /// Content as a @@ -36,7 +36,7 @@ public async Task Adapt(HttpContent content) } } - class HttpContentToDownloadDocumentResponseAdapter : IHttpContentAdapter + internal class HttpContentToDownloadDocumentResponseAdapter : IHttpContentAdapter { public async Task Adapt(HttpContent content) { @@ -45,7 +45,8 @@ public async Task Adapt(HttpContent content) var document = new DownloadDocumentResponse { Filename = content.Headers.ContentDisposition?.FileName?.Replace("\"", ""), - Length = content.Headers.ContentLength ?? default, + Length = content.Headers.ContentLength ?? 0, + MediaType = content.Headers.ContentType?.MediaType, Document = rawStream }; @@ -53,7 +54,7 @@ public async Task Adapt(HttpContent content) } } - class HttpContentToStreamAdapter : IHttpContentAdapter + internal class HttpContentToStreamAdapter : IHttpContentAdapter { /// /// Content as a diff --git a/SignNow.Net/_Internal/Requests/CreateDocumentGroupRequest.cs b/SignNow.Net/_Internal/Requests/CreateDocumentGroupRequest.cs new file mode 100644 index 00000000..bf3e0a37 --- /dev/null +++ b/SignNow.Net/_Internal/Requests/CreateDocumentGroupRequest.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using SignNow.Net.Model; +using SignNow.Net.Model.Requests; + +namespace SignNow.Net.Internal.Requests +{ + internal class CreateDocumentGroupRequest : JsonHttpContent + { + /// + /// A list of document unique ids from that Document Group will be created. + /// + [JsonProperty("document_ids")] + public List DocumentIds { get; set; } = new List(); + + /// + /// Name of the Document Group. + /// + [JsonProperty("group_name")] + public string GroupName { get; set; } + + public CreateDocumentGroupRequest(IEnumerabledocuments) + { + foreach (var document in documents) + { + DocumentIds.Add(document.Id); + } + } + } +} diff --git a/SignNow.Net/_Internal/Requests/MoveDocumentGroupRequest.cs b/SignNow.Net/_Internal/Requests/MoveDocumentGroupRequest.cs new file mode 100644 index 00000000..906e506d --- /dev/null +++ b/SignNow.Net/_Internal/Requests/MoveDocumentGroupRequest.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using SignNow.Net.Model.Requests; + +namespace SignNow.Net.Internal.Requests +{ + public class MoveDocumentGroupRequest: JsonHttpContent + { + /// + /// ID of the folder to move the document group to. Allowed folder types: Documents, Archive, Shared Documents folders. + /// + [JsonProperty("folder_id")] + internal string FolderId { get; set; } + + /// + /// Whether to move shared documents that are in this document group. With this parameter, a document group can only be moved to trash. + /// + [JsonProperty("with_shared_documents")] + internal bool WithSharedDocuments { get; set; } + } +} diff --git a/SignNow.Net/_Internal/Requests/RenameDocumentGroupRequest.cs b/SignNow.Net/_Internal/Requests/RenameDocumentGroupRequest.cs new file mode 100644 index 00000000..7d2068e6 --- /dev/null +++ b/SignNow.Net/_Internal/Requests/RenameDocumentGroupRequest.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; +using SignNow.Net.Model.Requests; + +namespace SignNow.Net.Internal.Requests +{ + internal class RenameDocumentGroupRequest : JsonHttpContent + { + [JsonProperty("group_name")] + public string GroupName { get; set; } + } +}