diff --git a/src/Pipedrive.net.Tests.Integration/Clients/FilesClientTests.cs b/src/Pipedrive.net.Tests.Integration/Clients/FilesClientTests.cs new file mode 100644 index 00000000..ff8db6a9 --- /dev/null +++ b/src/Pipedrive.net.Tests.Integration/Clients/FilesClientTests.cs @@ -0,0 +1,135 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Pipedrive.Tests.Integration.Clients +{ + public class FilesClientTests + { + public class TheGetAllMethod + { + [IntegrationTest] + public async Task ReturnsCorrectCountWithoutStart() + { + var pipedrive = Helper.GetAuthenticatedClient(); + + var filters = new FileFilters + { + PageSize = 3, + PageCount = 1 + }; + + var files = await pipedrive.File.GetAll(filters); + Assert.Equal(3, files.Count); + } + + [IntegrationTest] + public async Task ReturnsCorrectCountWithStart() + { + var pipedrive = Helper.GetAuthenticatedClient(); + + var filters = new FileFilters + { + PageSize = 2, + PageCount = 1, + StartPage = 1 + }; + + var files = await pipedrive.File.GetAll(filters); + Assert.Equal(2, files.Count); + } + + [IntegrationTest] + public async Task ReturnsDistinctInfosBasedOnStartPage() + { + var pipedrive = Helper.GetAuthenticatedClient(); + + var startFilters = new FileFilters + { + PageSize = 1, + PageCount = 1 + }; + + var firstPage = await pipedrive.File.GetAll(startFilters); + + var skipStartFilters = new FileFilters + { + PageSize = 1, + PageCount = 1, + StartPage = 1 + }; + + var secondPage = await pipedrive.File.GetAll(skipStartFilters); + + Assert.NotEqual(firstPage[0].Id, secondPage[0].Id); + } + } + + /*public class TheCreateMethod + { + [IntegrationTest] + public async Task CanCreate() + { + var pipedrive = Helper.GetAuthenticatedClient(); + var fixture = pipedrive.File; + + var imageUrl = @"./Content/image.jpg"; + FileStream reader = new FileStream(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), imageUrl), FileMode.Open); + + var newFile = new NewFile(reader); + + var file = await fixture.Create(newFile); + Assert.NotNull(file); + + var retrieved = await fixture.Get(file.Id); + Assert.NotNull(retrieved); + } + } + + public class TheEditMethod + { + [IntegrationTest] + public async Task CanEdit() + { + var pipedrive = Helper.GetAuthenticatedClient(); + var fixture = pipedrive.File; + + byte[] data = System.Convert.FromBase64String("R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); + var newFile = new NewFile(new MemoryStream(data)); + var file = await fixture.Create(newFile); + + var editFile = file.ToUpdate(); + editFile.Name = "updated-name"; + editFile.Description = "updated-description"; + + var updatedFile = await fixture.Edit(file.Id, editFile); + + Assert.Equal("updated-name", updatedFile.Name); + Assert.Equal("updated-description", updatedFile.Description); + } + } + + public class TheDeleteMethod + { + [IntegrationTest] + public async Task CanDelete() + { + var pipedrive = Helper.GetAuthenticatedClient(); + var fixture = pipedrive.File; + + byte[] data = System.Convert.FromBase64String("R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="); + var newFile = new NewFile(new MemoryStream(data)); + var file = await fixture.Create(newFile); + + var createdFile = await fixture.Get(file.Id); + + Assert.NotNull(createdFile); + + await fixture.Delete(createdFile.Id); + + var deletedFile = await fixture.Get(createdFile.Id); + + Assert.False(deletedFile.ActiveFlag); + } + }*/ + } +} diff --git a/src/Pipedrive.net.Tests/Clients/FilesClientTests.cs b/src/Pipedrive.net.Tests/Clients/FilesClientTests.cs new file mode 100644 index 00000000..4a607519 --- /dev/null +++ b/src/Pipedrive.net.Tests/Clients/FilesClientTests.cs @@ -0,0 +1,139 @@ +using NSubstitute; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Xunit; + +namespace Pipedrive.Tests.Clients +{ + public class FilesClientTests + { + public class TheCtor + { + [Fact] + public void EnsuresNonNullArguments() + { + Assert.Throws(() => new FilesClient(null)); + } + } + + public class TheGetAllMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new FilesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.GetAll(null)); + } + + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new FilesClient(connection); + + var filters = new FileFilters + { + PageSize = 1, + PageCount = 1, + StartPage = 0, + }; + + await client.GetAll(filters); + + Received.InOrder(async () => + { + await connection.GetAll( + Arg.Is(u => u.ToString() == "files"), + Arg.Is>(d => d.Count == 0), + Arg.Is(o => o.PageSize == 1 + && o.PageCount == 1 + && o.StartPage == 0) + ); + }); + } + } + + public class TheGetMethod + { + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new FilesClient(connection); + + await client.Get(123); + + Received.InOrder(async () => + { + await connection.Get(Arg.Is(u => u.ToString() == "files/123")); + }); + } + } + + /*public class TheCreateMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new FilesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Create(null)); + } + + [Fact] + public async Task PostsToTheCorrectUrl() + { + var connection = Substitute.For(); + var client = new FilesClient(connection); + + var newFile = new NewFile(new MemoryStream()); + await client.Create(newFile); + + await connection.Received().Post(Arg.Is(u => u.ToString() == "files"), + Arg.Is(nc => nc.File == new MemoryStream())); + } + }*/ + + public class TheEditMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var client = new FilesClient(Substitute.For()); + + await Assert.ThrowsAsync(() => client.Edit(1, null)); + } + + [Fact] + public void PutsCorrectUrl() + { + var connection = Substitute.For(); + var client = new FilesClient(connection); + + var editFile = new FileUpdate { Name = "name", Description = "description" }; + client.Edit(123, editFile); + + connection.Received().Put(Arg.Is(u => u.ToString() == "files/123"), + Arg.Is(nc => nc.Name == "name" + && nc.Description == "description")); + } + } + + public class TheDeleteMethod + { + [Fact] + public void DeletesCorrectUrl() + { + var connection = Substitute.For(); + var client = new FilesClient(connection); + + client.Delete(123); + + connection.Received().Delete(Arg.Is(u => u.ToString() == "files/123")); + } + } + } +} diff --git a/src/Pipedrive.net/Clients/FilesClient.cs b/src/Pipedrive.net/Clients/FilesClient.cs new file mode 100644 index 00000000..3d74505b --- /dev/null +++ b/src/Pipedrive.net/Clients/FilesClient.cs @@ -0,0 +1,91 @@ +using Pipedrive.Helpers; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Pipedrive +{ + /// + /// A client for Pipedrive's File API. + /// + /// + /// See the File API documentation for more information. + public class FilesClient : ApiClient, IFilesClient + { + /// + /// Initializes a new File API client. + /// + /// An API connection + public FilesClient(IApiConnection apiConnection) : base(apiConnection) + { + } + + public Task> GetAll(FileFilters filters) + { + Ensure.ArgumentNotNull(filters, nameof(filters)); + + var parameters = filters.Parameters; + var options = new ApiOptions + { + StartPage = filters.StartPage, + PageCount = filters.PageCount, + PageSize = filters.PageSize + }; + + return ApiConnection.GetAll(ApiUrls.Files(), parameters, options); + } + + public Task Get(long id) + { + return ApiConnection.Get(ApiUrls.File(id)); + } + + /*public async Task Create(NewFile data) + { + Ensure.ArgumentNotNull(data, nameof(data)); + + var content = new MultipartFormDataContent(); + content.Add(new StreamContent(data.File), "\"file\""); + + if (data.DealId.HasValue) + { + content.Add(new StringContent(data.DealId.ToString()), "deal_id"); + } + if (data.PersonId.HasValue) + { + content.Add(new StringContent(data.PersonId.ToString()), "person_id"); + } + if (data.OrgId.HasValue) + { + content.Add(new StringContent(data.OrgId.ToString()), "org_id"); + } + if (data.ProductId.HasValue) + { + content.Add(new StringContent(data.ProductId.ToString()), "product_id"); + } + if (data.ActivityId.HasValue) + { + content.Add(new StringContent(data.ActivityId.ToString()), "activity_id"); + } + if (data.NoteId.HasValue) + { + content.Add(new StringContent(data.NoteId.ToString()), "note_id"); + } + var contentString = content.ReadAsStringAsync(); + + return await ApiConnection.Post(ApiUrls.Files(), content, "application/json", "multipart/form-data"); + }*/ + + public Task Edit(long id, FileUpdate data) + { + Ensure.ArgumentNotNull(data, nameof(data)); + + return ApiConnection.Put(ApiUrls.File(id), data); + } + + public Task Delete(long id) + { + return ApiConnection.Delete(ApiUrls.File(id)); + } + } +} diff --git a/src/Pipedrive.net/Clients/IFilesClient.cs b/src/Pipedrive.net/Clients/IFilesClient.cs new file mode 100644 index 00000000..da7b4d21 --- /dev/null +++ b/src/Pipedrive.net/Clients/IFilesClient.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Pipedrive +{ + /// + /// A client for Pipedrive's File API. + /// + /// + /// See the File API documentation for more information. + public interface IFilesClient + { + Task> GetAll(FileFilters filters); + + Task Get(long id); + + /*Task Create(NewFile data);*/ + + Task Edit(long id, FileUpdate data); + + Task Delete(long id); + } +} diff --git a/src/Pipedrive.net/Helpers/ApiUrls.cs b/src/Pipedrive.net/Helpers/ApiUrls.cs index 9e2f2ada..4ea3d1fe 100644 --- a/src/Pipedrive.net/Helpers/ApiUrls.cs +++ b/src/Pipedrive.net/Helpers/ApiUrls.cs @@ -19,6 +19,8 @@ public static class ApiUrls static readonly Uri _dealFieldsUrl = new Uri("dealFields", UriKind.Relative); + static readonly Uri _filesUrl = new Uri("files", UriKind.Relative); + static readonly Uri _notesUrl = new Uri("notes", UriKind.Relative); static readonly Uri _organizationsUrl = new Uri("organizations", UriKind.Relative); @@ -148,6 +150,24 @@ public static Uri DealField(long id) return new Uri($"dealFields/{id}", UriKind.Relative); } + /// + /// Returns the that returns all of the files in response to a GET request. + /// + /// + public static Uri Files() + { + return _filesUrl; + } + + /// + /// Returns the for the specified file. + /// + /// The id of the file + public static Uri File(long id) + { + return new Uri($"files/{id}", UriKind.Relative); + } + /// /// Returns the that returns all of the notes in response to a GET request. /// diff --git a/src/Pipedrive.net/IPipedriveClient.cs b/src/Pipedrive.net/IPipedriveClient.cs index f50f6e33..23d90e71 100644 --- a/src/Pipedrive.net/IPipedriveClient.cs +++ b/src/Pipedrive.net/IPipedriveClient.cs @@ -20,6 +20,8 @@ public interface IPipedriveClient IDealFieldsClient DealField { get; } + IFilesClient File { get; } + INotesClient Note { get; } IOrganizationsClient Organization { get; } diff --git a/src/Pipedrive.net/Models/Request/FileFilters.cs b/src/Pipedrive.net/Models/Request/FileFilters.cs new file mode 100644 index 00000000..4ab1243f --- /dev/null +++ b/src/Pipedrive.net/Models/Request/FileFilters.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; + +namespace Pipedrive +{ + public class FileFilters + { + public static FileFilters None + { + get { return new FileFilters(); } + } + + public int? StartPage { get; set; } + + public int? PageCount { get; set; } + + public int? PageSize { get; set; } + + public string IncludeDeletedFiles { get; set; } + + public string Sort { get; set; } + + /// + /// Get the query parameters that will be appending onto the search + /// + public IDictionary Parameters + { + get + { + var d = new Dictionary(); + if (!string.IsNullOrWhiteSpace(IncludeDeletedFiles)) + { + d.Add("include_deleted_files", IncludeDeletedFiles); + } + if (!string.IsNullOrWhiteSpace(Sort)) + { + d.Add("sort", Sort); + } + return d; + } + } + } +} diff --git a/src/Pipedrive.net/Models/Request/FileUpdate.cs b/src/Pipedrive.net/Models/Request/FileUpdate.cs new file mode 100644 index 00000000..f0842fca --- /dev/null +++ b/src/Pipedrive.net/Models/Request/FileUpdate.cs @@ -0,0 +1,9 @@ +namespace Pipedrive +{ + public class FileUpdate + { + public string Name { get; set; } + + public string Description { get; set; } + } +} diff --git a/src/Pipedrive.net/Models/Request/NewFile.cs b/src/Pipedrive.net/Models/Request/NewFile.cs new file mode 100644 index 00000000..f6efcd88 --- /dev/null +++ b/src/Pipedrive.net/Models/Request/NewFile.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; +using System.IO; + +namespace Pipedrive +{ + public class NewFile + { + public Stream File { get; set; } + + public long? DealId { get; set; } + + public long? PersonId { get; set; } + + public long? OrgId { get; set; } + + public long? ProductId { get; set; } + + public long? ActivityId { get; set; } + + public long? NoteId { get; set; } + + public NewFile(Stream file) + { + this.File = file; + } + } +} diff --git a/src/Pipedrive.net/Models/Response/File.cs b/src/Pipedrive.net/Models/Response/File.cs index 25d585dc..ac5b81bf 100644 --- a/src/Pipedrive.net/Models/Response/File.cs +++ b/src/Pipedrive.net/Models/Response/File.cs @@ -93,5 +93,14 @@ public class File : IDealUpdateEntity [JsonProperty("description")] public string Description { get; set; } + + public FileUpdate ToUpdate() + { + return new FileUpdate + { + Name = Name, + Description = Description + }; + } } } diff --git a/src/Pipedrive.net/PipedriveClient.cs b/src/Pipedrive.net/PipedriveClient.cs index cf1ba899..46034451 100644 --- a/src/Pipedrive.net/PipedriveClient.cs +++ b/src/Pipedrive.net/PipedriveClient.cs @@ -39,6 +39,7 @@ public PipedriveClient(IConnection connection) Currency = new CurrenciesClient(apiConnection); Deal = new DealsClient(apiConnection); DealField = new DealFieldsClient(apiConnection); + File = new FilesClient(apiConnection); Note = new NotesClient(apiConnection); Organization = new OrganizationsClient(apiConnection); OrganizationField = new OrganizationFieldsClient(apiConnection); @@ -130,6 +131,14 @@ public Uri BaseAddress /// public IDealFieldsClient DealField { get; private set; } + /// + /// Access Pipedrive's File API. + /// + /// + /// Refer to the API documentation for more information: https://developers.pipedrive.com/docs/api/v1/#!/Files + /// + public IFilesClient File { get; private set; } + /// /// Access Pipedrive's Note API. ///