From a62580c026e6ef088e5dd02f435e85fb9bd018b3 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Tue, 21 Dec 2021 17:00:30 +0100 Subject: [PATCH] Added copy & move for #57, also used some of the new C# 10 language features. --- azure-pipelines.yml | 12 +- .../ConfluenceOAuthClient.cs | 87 ++- .../ConfluenceOAuthSettings.cs | 51 +- .../Dapplo.Confluence.OAuth.csproj | 10 +- .../AttachmentTests.cs | 143 ++-- .../ConfluenceIntegrationTests.cs | 39 +- src/Dapplo.Confluence.Tests/ContentTests.cs | 331 ++++---- .../Dapplo.Confluence.Tests.csproj | 8 +- src/Dapplo.Confluence.Tests/GroupTests.cs | 55 +- src/Dapplo.Confluence.Tests/JsonParseTests.cs | 85 +- src/Dapplo.Confluence.Tests/QueryTests.cs | 109 ++- src/Dapplo.Confluence.Tests/ServerTests.cs | 29 +- .../SpaceQueryTests.cs | 89 ++- src/Dapplo.Confluence.Tests/SpaceTests.cs | 113 ++- src/Dapplo.Confluence.Tests/UserQueryTests.cs | 129 ++-- src/Dapplo.Confluence.Tests/UserTests.cs | 239 +++--- src/Dapplo.Confluence/AttachmentExtensions.cs | 379 +++++---- src/Dapplo.Confluence/ConfluenceClient.cs | 391 +++++----- .../ConfluenceClientConfig.cs | 113 ++- src/Dapplo.Confluence/ConfluenceException.cs | 42 +- src/Dapplo.Confluence/ContentExtensions.cs | 724 +++++++++--------- .../Dapplo.Confluence.csproj | 6 +- src/Dapplo.Confluence/Entities/BaseEntity.cs | 111 ++- src/Dapplo.Confluence/Entities/Body.cs | 33 +- src/Dapplo.Confluence/Entities/BodyContent.cs | 31 +- src/Dapplo.Confluence/Entities/Children.cs | 44 +- src/Dapplo.Confluence/Entities/Content.cs | 104 ++- src/Dapplo.Confluence/Entities/CopyContent.cs | 62 ++ .../Entities/CopyDestinations.cs | 25 + .../Entities/CopyPageRequestDestination.cs | 28 + src/Dapplo.Confluence/Entities/Description.cs | 23 +- src/Dapplo.Confluence/Entities/Error.cs | 31 +- src/Dapplo.Confluence/Entities/Group.cs | 33 +- src/Dapplo.Confluence/Entities/History.cs | 85 +- .../Entities/IUserIdentifier.cs | 27 +- src/Dapplo.Confluence/Entities/Label.cs | 43 +- src/Dapplo.Confluence/Entities/LastUpdated.cs | 73 +- src/Dapplo.Confluence/Entities/Links.cs | 104 ++- .../Entities/LongRunningTask.cs | 31 +- src/Dapplo.Confluence/Entities/Metadata.cs | 43 +- .../Entities/PagingInformation.cs | 31 +- src/Dapplo.Confluence/Entities/Picture.cs | 53 +- src/Dapplo.Confluence/Entities/Plain.cs | 35 +- src/Dapplo.Confluence/Entities/Positions.cs | 26 + src/Dapplo.Confluence/Entities/Result.cs | 78 +- .../Entities/SearchDetails.cs | 50 +- src/Dapplo.Confluence/Entities/Space.cs | 86 +-- .../Entities/SpaceContents.cs | 41 +- .../Entities/SystemInfoEntity.cs | 23 +- src/Dapplo.Confluence/Entities/User.cs | 112 ++- src/Dapplo.Confluence/Entities/UserWatch.cs | 23 +- src/Dapplo.Confluence/Entities/Version.cs | 72 +- src/Dapplo.Confluence/GlobalUsings.cs | 22 + src/Dapplo.Confluence/GroupExtensions.cs | 129 ++-- src/Dapplo.Confluence/IConfluenceClient.cs | 187 +++-- .../IConfluenceClientPlugins.cs | 13 +- .../Internals/AttachmentContainer.cs | 57 +- .../Internals/HttpResponseExtensions.cs | 278 ++++--- src/Dapplo.Confluence/MiscExtensions.cs | 98 ++- src/Dapplo.Confluence/Query/Clause.cs | 191 +++-- src/Dapplo.Confluence/Query/ContentClause.cs | 106 ++- src/Dapplo.Confluence/Query/ContentTypes.cs | 61 +- src/Dapplo.Confluence/Query/DatetimeClause.cs | 266 ++++--- src/Dapplo.Confluence/Query/Fields.cs | 157 ++-- src/Dapplo.Confluence/Query/IContentClause.cs | 41 +- .../Query/IDatetimeClause.cs | 49 +- .../Query/IDatetimeClauseWithoutValue.cs | 117 ++- src/Dapplo.Confluence/Query/IFinalClause.cs | 45 +- .../Query/ISimpleValueClause.cs | 33 +- src/Dapplo.Confluence/Query/ISpaceClause.cs | 67 +- src/Dapplo.Confluence/Query/ITextClause.cs | 25 +- src/Dapplo.Confluence/Query/ITitleClause.cs | 41 +- src/Dapplo.Confluence/Query/ITypeClause.cs | 71 +- src/Dapplo.Confluence/Query/IUserClause.cs | 97 ++- src/Dapplo.Confluence/Query/Operators.cs | 33 +- .../Query/SimpleValueClause.cs | 86 +-- src/Dapplo.Confluence/Query/SpaceClause.cs | 129 ++-- src/Dapplo.Confluence/Query/TextClause.cs | 68 +- src/Dapplo.Confluence/Query/TitleClause.cs | 91 ++- src/Dapplo.Confluence/Query/TypeClause.cs | 136 ++-- src/Dapplo.Confluence/Query/UserClause.cs | 145 ++-- src/Dapplo.Confluence/Query/Where.cs | 235 +++--- src/Dapplo.Confluence/SpaceExtensions.cs | 368 +++++---- src/Dapplo.Confluence/UserExtensions.cs | 691 +++++++++-------- .../UserIdentifierExtensions.cs | 23 +- src/Directory.Build.props | 8 +- src/global.json | 2 +- 87 files changed, 4461 insertions(+), 4450 deletions(-) create mode 100644 src/Dapplo.Confluence/Entities/CopyContent.cs create mode 100644 src/Dapplo.Confluence/Entities/CopyDestinations.cs create mode 100644 src/Dapplo.Confluence/Entities/CopyPageRequestDestination.cs create mode 100644 src/Dapplo.Confluence/Entities/Positions.cs create mode 100644 src/Dapplo.Confluence/GlobalUsings.cs diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1df35bf..43f707c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,16 +30,16 @@ stages: - task: NuGetToolInstaller@1 - task: UseDotNet@2 - displayName: 'Use .NET Core sdk 3.1.10' + displayName: 'Use .NET Core sdk 3.1.22' inputs: packageType: sdk - version: 3.1.404 + version: 3.1.416 - task: UseDotNet@2 - displayName: 'Use .NET Core sdk 5.0.10' + displayName: 'Use .NET Core sdk 6.0.1' inputs: packageType: sdk - version: 5.0.401 + version: 6.0.101 - task: DotNetCoreCLI@2 displayName: Test @@ -131,9 +131,9 @@ stages: artifact: 'drop' - task: NuGetToolInstaller@0 - displayName: 'Use NuGet 5.11.0' + displayName: 'Use NuGet 6.0.0' inputs: - versionSpec: 5.11.0 + versionSpec: 6.0.0 checkLatest: true - task: NuGetCommand@2 diff --git a/src/Dapplo.Confluence.OAuth/ConfluenceOAuthClient.cs b/src/Dapplo.Confluence.OAuth/ConfluenceOAuthClient.cs index af16a6f..0c476ac 100644 --- a/src/Dapplo.Confluence.OAuth/ConfluenceOAuthClient.cs +++ b/src/Dapplo.Confluence.OAuth/ConfluenceOAuthClient.cs @@ -8,53 +8,52 @@ using Dapplo.HttpExtensions.Extensions; using Dapplo.HttpExtensions; -namespace Dapplo.Confluence.OAuth +namespace Dapplo.Confluence.OAuth; + +/// +/// A Confluence client with OAuth support build by using Dapplo.HttpExtensions +/// +public class ConfluenceOAuthClient : ConfluenceClient { - /// - /// A Confluence client with OAuth support build by using Dapplo.HttpExtensions - /// - public class ConfluenceOAuthClient : ConfluenceClient + private ConfluenceOAuthClient(Uri baseUri, ConfluenceOAuthSettings confluenceOAuthSettings, IHttpSettings httpSettings = null) : base(baseUri, httpSettings) { - private ConfluenceOAuthClient(Uri baseUri, ConfluenceOAuthSettings confluenceOAuthSettings, IHttpSettings httpSettings = null) : base(baseUri, httpSettings) - { - var confluenceOAuthUri = ConfluenceUri.AppendSegments("plugins", "servlet", "oauth"); - - var oAuthSettings = new OAuth1Settings - { - TokenUrl = confluenceOAuthUri.AppendSegments("request-token"), - TokenMethod = HttpMethod.Post, - AccessTokenUrl = confluenceOAuthUri.AppendSegments("access-token"), - AccessTokenMethod = HttpMethod.Post, - CheckVerifier = false, - SignatureType = OAuth1SignatureTypes.RsaSha1, - // According to here - // the OAuth arguments need to be passed in the query - SignatureTransport = OAuth1SignatureTransports.QueryParameters, - Token = confluenceOAuthSettings.Token, - ClientId = confluenceOAuthSettings.ConsumerKey, - CloudServiceName = confluenceOAuthSettings.CloudServiceName, - RsaSha1Provider = confluenceOAuthSettings.RsaSha1Provider, - AuthorizeMode = confluenceOAuthSettings.AuthorizeMode, - AuthorizationUri = confluenceOAuthUri.AppendSegments("authorize") - .ExtendQuery(new Dictionary - { - {OAuth1Parameters.Token.EnumValueOf(), "{RequestToken}"}, - {OAuth1Parameters.Callback.EnumValueOf(), "{RedirectUrl}"} - }) - }; + var confluenceOAuthUri = ConfluenceUri.AppendSegments("plugins", "servlet", "oauth"); - // Configure the OAuth1Settings - Behaviour = ConfigureBehaviour(OAuth1HttpBehaviourFactory.Create(oAuthSettings), httpSettings); - } - /// - /// Create the IConfluenceClient, using OAuth 1 for the communication, here the HttpClient is configured - /// - /// Base URL, e.g. https://yourconfluenceserver - /// ConfluenceOAuthSettings - /// IHttpSettings or null for default - public static IConfluenceClient Create(Uri baseUri, ConfluenceOAuthSettings confluenceOAuthSettings, IHttpSettings httpSettings = null) + var oAuthSettings = new OAuth1Settings { - return new ConfluenceOAuthClient(baseUri, confluenceOAuthSettings, httpSettings); - } + TokenUrl = confluenceOAuthUri.AppendSegments("request-token"), + TokenMethod = HttpMethod.Post, + AccessTokenUrl = confluenceOAuthUri.AppendSegments("access-token"), + AccessTokenMethod = HttpMethod.Post, + CheckVerifier = false, + SignatureType = OAuth1SignatureTypes.RsaSha1, + // According to here + // the OAuth arguments need to be passed in the query + SignatureTransport = OAuth1SignatureTransports.QueryParameters, + Token = confluenceOAuthSettings.Token, + ClientId = confluenceOAuthSettings.ConsumerKey, + CloudServiceName = confluenceOAuthSettings.CloudServiceName, + RsaSha1Provider = confluenceOAuthSettings.RsaSha1Provider, + AuthorizeMode = confluenceOAuthSettings.AuthorizeMode, + AuthorizationUri = confluenceOAuthUri.AppendSegments("authorize") + .ExtendQuery(new Dictionary + { + {OAuth1Parameters.Token.EnumValueOf(), "{RequestToken}"}, + {OAuth1Parameters.Callback.EnumValueOf(), "{RedirectUrl}"} + }) + }; + + // Configure the OAuth1Settings + Behaviour = ConfigureBehaviour(OAuth1HttpBehaviourFactory.Create(oAuthSettings), httpSettings); + } + /// + /// Create the IConfluenceClient, using OAuth 1 for the communication, here the HttpClient is configured + /// + /// Base URL, e.g. https://yourconfluenceserver + /// ConfluenceOAuthSettings + /// IHttpSettings or null for default + public static IConfluenceClient Create(Uri baseUri, ConfluenceOAuthSettings confluenceOAuthSettings, IHttpSettings httpSettings = null) + { + return new ConfluenceOAuthClient(baseUri, confluenceOAuthSettings, httpSettings); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.OAuth/ConfluenceOAuthSettings.cs b/src/Dapplo.Confluence.OAuth/ConfluenceOAuthSettings.cs index e0c4da1..69b45a0 100644 --- a/src/Dapplo.Confluence.OAuth/ConfluenceOAuthSettings.cs +++ b/src/Dapplo.Confluence.OAuth/ConfluenceOAuthSettings.cs @@ -4,37 +4,36 @@ using System.Security.Cryptography; using Dapplo.HttpExtensions.OAuth; -namespace Dapplo.Confluence.OAuth +namespace Dapplo.Confluence.OAuth; + +/// +/// OAuth 1 settings for Confluence Oauth connections +/// +public class ConfluenceOAuthSettings { /// - /// OAuth 1 settings for Confluence Oauth connections + /// Consumer Key which is set in the Confluence Application link /// - public class ConfluenceOAuthSettings - { - /// - /// Consumer Key which is set in the Confluence Application link - /// - public string ConsumerKey { get; set; } + public string ConsumerKey { get; set; } - /// - /// Confluence uses OAuth1 with RSA-SHA1, for this a RSACryptoServiceProvider is used. - /// This needs to be created from a private key, the represented public key is set in the linked-applications - /// - public RSACryptoServiceProvider RsaSha1Provider { get; set; } + /// + /// Confluence uses OAuth1 with RSA-SHA1, for this a RSACryptoServiceProvider is used. + /// This needs to be created from a private key, the represented public key is set in the linked-applications + /// + public RSACryptoServiceProvider RsaSha1Provider { get; set; } - /// - /// The AuthorizeMode to use - /// - public AuthorizeModes AuthorizeMode { get; set; } = AuthorizeModes.LocalhostServer; + /// + /// The AuthorizeMode to use + /// + public AuthorizeModes AuthorizeMode { get; set; } = AuthorizeModes.LocalhostServer; - /// - /// Name of the cloud service, which is displayed in the embedded browser / browser when using AuthorizeModes.EmbeddedBrowser - /// - public string CloudServiceName { get; set; } = "Confluence"; + /// + /// Name of the cloud service, which is displayed in the embedded browser / browser when using AuthorizeModes.EmbeddedBrowser + /// + public string CloudServiceName { get; set; } = "Confluence"; - /// - /// The token object for storing the OAuth 1 secret etc, implement your own IOAuth1Token to be able to store these - /// - public IOAuth1Token Token { get; set; } = new OAuth1Token(); - } + /// + /// The token object for storing the OAuth 1 secret etc, implement your own IOAuth1Token to be able to store these + /// + public IOAuth1Token Token { get; set; } = new OAuth1Token(); } \ No newline at end of file diff --git a/src/Dapplo.Confluence.OAuth/Dapplo.Confluence.OAuth.csproj b/src/Dapplo.Confluence.OAuth/Dapplo.Confluence.OAuth.csproj index e1e0cee..edfb462 100644 --- a/src/Dapplo.Confluence.OAuth/Dapplo.Confluence.OAuth.csproj +++ b/src/Dapplo.Confluence.OAuth/Dapplo.Confluence.OAuth.csproj @@ -1,7 +1,7 @@  A library for accessing Atlassians Confluence from .NET via OAuth - net471;net461;netcoreapp3.1;net5.0-windows + net471;net461;netcoreapp3.1;net5.0-windows;net6.0-windows atlassian;confluence;dapplo @@ -9,11 +9,11 @@ - - + + - - + + diff --git a/src/Dapplo.Confluence.Tests/AttachmentTests.cs b/src/Dapplo.Confluence.Tests/AttachmentTests.cs index ca71091..1ff8824 100644 --- a/src/Dapplo.Confluence.Tests/AttachmentTests.cs +++ b/src/Dapplo.Confluence.Tests/AttachmentTests.cs @@ -10,93 +10,92 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests for the attachment domain +/// +[CollectionDefinition("Dapplo.Confluence")] +public class AttachmentTests : ConfluenceIntegrationTests { + public AttachmentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + } + + // TODO: Enable again whenever I got the rights working + //[Fact] + public async Task TestGetAttachments() + { + var attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(950274); + Assert.NotNull(attachments); + Assert.True(attachments.Results.Count > 0); + await using var attachmentMemoryStream = await ConfluenceTestClient.Attachment.GetContentAsync(attachments.FirstOrDefault()); + Assert.True(attachmentMemoryStream.Length > 0); + } + /// - /// Tests for the attachment domain + /// Doesn't work yet, as deleting an attachment (with multiple versions) is not supported + /// See CONF-36015 /// - [CollectionDefinition("Dapplo.Confluence")] - public class AttachmentTests : ConfluenceIntegrationTests + /// + //[Fact] + public async Task TestAttach() { - public AttachmentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) - { - } + const long testPageId = 950274; + var attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); + Assert.NotNull(attachments); - // TODO: Enable again whenever I got the rights working - //[Fact] - public async Task TestGetAttachments() + // Delete all attachments + foreach (var attachment in attachments.Results) { - var attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(950274); - Assert.NotNull(attachments); - Assert.True(attachments.Results.Count > 0); - await using var attachmentMemoryStream = await ConfluenceTestClient.Attachment.GetContentAsync(attachments.FirstOrDefault()); - Assert.True(attachmentMemoryStream.Length > 0); + // Attachments are content!! + await ConfluenceTestClient.Attachment.DeleteAsync(attachment); } - /// - /// Doesn't work yet, as deleting an attachment (with multiple versions) is not supported - /// See CONF-36015 - /// - /// - //[Fact] - public async Task TestAttach() - { - const long testPageId = 950274; - var attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); - Assert.NotNull(attachments); - - // Delete all attachments - foreach (var attachment in attachments.Results) - { - // Attachments are content!! - await ConfluenceTestClient.Attachment.DeleteAsync(attachment); - } + const string attachmentContent = "Testing 1 2 3"; + attachments = await ConfluenceTestClient.Attachment.AttachAsync(testPageId, attachmentContent, "test.txt", "This is a test"); + Assert.NotNull(attachments); - const string attachmentContent = "Testing 1 2 3"; - attachments = await ConfluenceTestClient.Attachment.AttachAsync(testPageId, attachmentContent, "test.txt", "This is a test"); - Assert.NotNull(attachments); + attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); + Assert.NotNull(attachments); + Assert.True(attachments.Results.Count > 0); - attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); - Assert.NotNull(attachments); - Assert.True(attachments.Results.Count > 0); - - // Test if the content is correct - foreach (var attachment in attachments.Results) - { - var content = await ConfluenceTestClient.Attachment.GetContentAsync(attachment); - Assert.Equal(attachmentContent, content); - } - // Delete all attachments - foreach (var attachment in attachments.Results) - { - // Btw. Attachments are content!! - await ConfluenceTestClient.Attachment.DeleteAsync(attachment.Id); - } - attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); - Assert.NotNull(attachments); - Assert.True(attachments.Results.Count == 0); + // Test if the content is correct + foreach (var attachment in attachments.Results) + { + var content = await ConfluenceTestClient.Attachment.GetContentAsync(attachment); + Assert.Equal(attachmentContent, content); } - - /// - /// Doesn't work yet, as deleting an attachment (with multiple versions) is not supported - /// See CONF-36015 - /// - /// - //[Fact] - public async Task TestAttachBitmap() + // Delete all attachments + foreach (var attachment in attachments.Results) { - const long testPageId = 550731777; + // Btw. Attachments are content!! + await ConfluenceTestClient.Attachment.DeleteAsync(attachment.Id); + } + attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); + Assert.NotNull(attachments); + Assert.True(attachments.Results.Count == 0); + } - // TODO: make the test work like this, delete all attachments, AttachAsync, UpdateAsync + /// + /// Doesn't work yet, as deleting an attachment (with multiple versions) is not supported + /// See CONF-36015 + /// + /// + //[Fact] + public async Task TestAttachBitmap() + { + const long testPageId = 550731777; - using Stream stream = File.OpenRead(@"TestFiles\icon.png"); + // TODO: make the test work like this, delete all attachments, AttachAsync, UpdateAsync - var attachments = await ConfluenceTestClient.Attachment.AttachAsync(testPageId, stream, "streamed.png", "Just attached a bitmap"); - Assert.NotNull(attachments); + using Stream stream = File.OpenRead(@"TestFiles\icon.png"); - attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); - Assert.NotNull(attachments); - Assert.True(attachments.Results.Count > 0); - } + var attachments = await ConfluenceTestClient.Attachment.AttachAsync(testPageId, stream, "streamed.png", "Just attached a bitmap"); + Assert.NotNull(attachments); + + attachments = await ConfluenceTestClient.Attachment.GetAttachmentsAsync(testPageId); + Assert.NotNull(attachments); + Assert.True(attachments.Results.Count > 0); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/ConfluenceIntegrationTests.cs b/src/Dapplo.Confluence.Tests/ConfluenceIntegrationTests.cs index c583692..5700b05 100644 --- a/src/Dapplo.Confluence.Tests/ConfluenceIntegrationTests.cs +++ b/src/Dapplo.Confluence.Tests/ConfluenceIntegrationTests.cs @@ -7,31 +7,30 @@ using Dapplo.Log.XUnit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Base class for integration tests +/// +public abstract class ConfluenceIntegrationTests { - /// - /// Base class for integration tests - /// - public abstract class ConfluenceIntegrationTests - { - // Test against a "well known" Confluence - private static readonly Uri TestConfluenceUri = new Uri("https://greenshot.atlassian.net/wiki"); + // Test against a "well known" Confluence + private static readonly Uri TestConfluenceUri = new("https://greenshot.atlassian.net/wiki"); - protected readonly IConfluenceClient ConfluenceTestClient; + protected readonly IConfluenceClient ConfluenceTestClient; - protected ConfluenceIntegrationTests(ITestOutputHelper testOutputHelper) - { - LogSettings.ExceptionToStacktrace = exception => exception.ToStringDemystified(); + protected ConfluenceIntegrationTests(ITestOutputHelper testOutputHelper) + { + LogSettings.ExceptionToStacktrace = exception => exception.ToStringDemystified(); - LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); - ConfluenceTestClient = ConfluenceClient.Create(TestConfluenceUri); + LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); + ConfluenceTestClient = ConfluenceClient.Create(TestConfluenceUri); - var username = Environment.GetEnvironmentVariable("confluence_test_username"); - var password = Environment.GetEnvironmentVariable("confluence_test_password"); - if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) - { - ConfluenceTestClient.SetBasicAuthentication(username, password); - } + var username = Environment.GetEnvironmentVariable("confluence_test_username"); + var password = Environment.GetEnvironmentVariable("confluence_test_password"); + if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) + { + ConfluenceTestClient.SetBasicAuthentication(username, password); } } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/ContentTests.cs b/src/Dapplo.Confluence.Tests/ContentTests.cs index b30dbd5..622b8eb 100644 --- a/src/Dapplo.Confluence.Tests/ContentTests.cs +++ b/src/Dapplo.Confluence.Tests/ContentTests.cs @@ -15,198 +15,205 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests +/// +[CollectionDefinition("Dapplo.Confluence")] +public class ContentTests : ConfluenceIntegrationTests { - /// - /// Tests - /// - [CollectionDefinition("Dapplo.Confluence")] - public class ContentTests : ConfluenceIntegrationTests - { - private static readonly LogSource Log = new LogSource(); - public ContentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) +#pragma warning disable IDE0090 // Use 'new(...)' + private static readonly LogSource Log = new LogSource(); +#pragma warning restore IDE0090 // Use 'new(...)' + public ContentTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + // Add BitmapHttpContentConverter if it was not yet added + if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapHttpContentConverter))) { - // Add BitmapHttpContentConverter if it was not yet added - if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapHttpContentConverter))) - { - HttpExtensionsGlobals.HttpContentConverters.Add(BitmapHttpContentConverter.Instance.Value); - } - // Add BitmapSourceHttpContentConverter if it was not yet added - if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapSourceHttpContentConverter))) - { - HttpExtensionsGlobals.HttpContentConverters.Add(BitmapSourceHttpContentConverter.Instance.Value); - } + HttpExtensionsGlobals.HttpContentConverters.Add(BitmapHttpContentConverter.Instance.Value); } - - [Fact] - public async Task Test_IsDefault() + // Add BitmapSourceHttpContentConverter if it was not yet added + if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapSourceHttpContentConverter))) { - var query = Where.And(Where.Space.Is("TEST"), Where.Type.IsPage, Where.Title.Contains("Doesn't exist")); - var searchResults = await ConfluenceTestClient.Content.SearchAsync(query); + HttpExtensionsGlobals.HttpContentConverters.Add(BitmapSourceHttpContentConverter.Instance.Value); + } + } - var searchResult = searchResults.FirstOrDefault(); + [Fact] + public async Task Test_IsDefault() + { + var query = Where.And(Where.Space.Is("TEST"), Where.Type.IsPage, Where.Title.Contains("Doesn't exist")); + var searchResults = await ConfluenceTestClient.Content.SearchAsync(query); - Assert.True(searchResult == default); - } + var searchResult = searchResults.FirstOrDefault(); - [Fact] - public async Task Test_ContentVersion() - { - var query = Where.And(Where.Space.Is("TEST"), Where.Type.IsPage, Where.Title.Contains("Test Home")); - var searchResults = await ConfluenceTestClient.Content.SearchAsync(query); - - var searchResult = searchResults.First(); - Log.Info().WriteLine("Version = {0}", searchResult.Version.Number); - query = Where.Title.Contains("Test Home"); - searchResults = await ConfluenceTestClient.Content.SearchAsync(query); - searchResult = searchResults.First(); - Log.Info().WriteLine("Version = {0}", searchResult.Version.Number); - - var content = await ConfluenceTestClient.Content.GetAsync(searchResult, ConfluenceClientConfig.ExpandGetContentWithStorage); - Log.Info().WriteLine("Version = {0}", content.Version.Number); - } + Assert.True(searchResult == default); + } - /// - /// Test GetAsync - /// - //[Fact] - public async Task TestGetContent() - { - var content = await ConfluenceTestClient.Content.GetAsync(950274); - Assert.NotNull(content); - Assert.NotNull(content.Version); - Assert.NotNull(content.Ancestors); - Assert.True(content.Ancestors.Count > 0); - } + [Fact] + public async Task Test_ContentVersion() + { + var query = Where.And(Where.Space.Is("TEST"), Where.Type.IsPage, Where.Title.Contains("Test Home")); + var searchResults = await ConfluenceTestClient.Content.SearchAsync(query); + + var searchResult = searchResults.First(); + Log.Info().WriteLine("Version = {0}", searchResult.Version.Number); + query = Where.Title.Contains("Test Home"); + searchResults = await ConfluenceTestClient.Content.SearchAsync(query); + searchResult = searchResults.First(); + Log.Info().WriteLine("Version = {0}", searchResult.Version.Number); + + var content = await ConfluenceTestClient.Content.GetAsync(searchResult, ConfluenceClientConfig.ExpandGetContentWithStorage); + Log.Info().WriteLine("Version = {0}", content.Version.Number); + } - /// - /// Test UpdateAsync - /// - [Fact] - public async Task TestContentUpdate() - { - var content = await ConfluenceTestClient.Content.GetAsync(550731777, ConfluenceClientConfig.ExpandGetContentForUpdate); - Assert.NotNull(content); - Assert.NotNull(content.Version); - content.Body.Storage.Value += $"\r\nTesting 1 - 2 -3 {DateTimeOffset.Now}"; - content.Version = new Entities.Version { IsMinorEdit = false, Number = content.Version.Number + 1 }; - await ConfluenceTestClient.Content.UpdateAsync(content); - } + /// + /// Test GetAsync + /// + //[Fact] +#pragma warning disable xUnit1013 // Public method should be marked as test + public async Task TestGetContent() +#pragma warning restore xUnit1013 // Public method should be marked as test + { + var content = await ConfluenceTestClient.Content.GetAsync(950274); + Assert.NotNull(content); + Assert.NotNull(content.Version); + Assert.NotNull(content.Ancestors); + Assert.True(content.Ancestors.Count > 0); + } - /// - /// Test .GetChildren - /// - [Fact] - public async Task TestGetChildren() - { - var results = await ConfluenceTestClient.Content.GetChildrenAsync(550731777, new PagingInformation {Limit = 1}); - Assert.NotNull(results); - Assert.True(results.HasNext); - Assert.True(results.Size > 0); - } + /// + /// Test UpdateAsync + /// + [Fact] + public async Task TestContentUpdate() + { + var content = await ConfluenceTestClient.Content.GetAsync(550731777, ConfluenceClientConfig.ExpandGetContentForUpdate); + Assert.NotNull(content); + Assert.NotNull(content.Version); + content.Body.Storage.Value += $"\r\nTesting 1 - 2 -3 {DateTimeOffset.Now}"; + content.Version = new Entities.Version { IsMinorEdit = false, Number = content.Version.Number + 1 }; + await ConfluenceTestClient.Content.UpdateAsync(content); + } - /// - /// Test GetHistoryAsync - /// - //[Fact] - public async Task TestGetContentHistory() - { - var history = await ConfluenceTestClient.Content.GetHistoryAsync(950274); - Assert.NotNull(history); - Assert.NotNull(history.CreatedBy); - } + /// + /// Test .GetChildren + /// + [Fact] + public async Task TestGetChildren() + { + var results = await ConfluenceTestClient.Content.GetChildrenAsync(550731777, new PagingInformation {Limit = 1}); + Assert.NotNull(results); + Assert.True(results.HasNext); + Assert.True(results.Size > 0); + } - [Fact] - public async Task TestCreateContent() - { - var query = Where.And(Where.Space.Is("TEST"), Where.Type.IsPage, Where.Title.Contains("Testing 1 2 3")); - var searchResults = await ConfluenceTestClient.Content.SearchAsync(query); - var oldPage = searchResults.Results.FirstOrDefault(); - if (oldPage != null) - { - await ConfluenceTestClient.Content.DeleteAsync(oldPage); - } - await Task.Delay(1000); - var page = await ConfluenceTestClient.Content.CreateAsync(ContentTypes.Page, "Testing 1 2 3", "TEST", "

This is a test

"); - Assert.NotNull(page); - Assert.True(page.Id > 0); - await Task.Delay(1000); - await ConfluenceTestClient.Content.DeleteAsync(page); - } + /// + /// Test GetHistoryAsync + /// + //[Fact] +#pragma warning disable xUnit1013 // Public method should be marked as test + public async Task TestGetContentHistory() +#pragma warning restore xUnit1013 // Public method should be marked as test + { + var history = await ConfluenceTestClient.Content.GetHistoryAsync(950274); + Assert.NotNull(history); + Assert.NotNull(history.CreatedBy); + } - //[Fact] - public async Task TestDeleteContent() + [Fact] + public async Task TestCreateContent() + { + var query = Where.And(Where.Space.Is("TEST"), Where.Type.IsPage, Where.Title.Contains("Testing 1 2 3")); + var searchResults = await ConfluenceTestClient.Content.SearchAsync(query); + var oldPage = searchResults.Results.FirstOrDefault(); + if (oldPage != null) { - await ConfluenceTestClient.Content.DeleteAsync(30375945); + await ConfluenceTestClient.Content.DeleteAsync(oldPage); } + await Task.Delay(1000); + var page = await ConfluenceTestClient.Content.CreateAsync(ContentTypes.Page, "Testing 1 2 3", "TEST", "

This is a test

"); + Assert.NotNull(page); + Assert.True(page.Id > 0); + await Task.Delay(1000); + await ConfluenceTestClient.Content.DeleteAsync(page); + } - [Fact] - public async Task TestSearch() - { - ConfluenceClientConfig.ExpandSearch = new[] { "version", "space", "space.icon", "space.description", "space.homepage", "history.lastUpdated" }; + //[Fact] +#pragma warning disable xUnit1013 // Public method should be marked as test + public async Task TestDeleteContent() +#pragma warning restore xUnit1013 // Public method should be marked as test + { + await ConfluenceTestClient.Content.DeleteAsync(30375945); + } - var searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation {Limit = 20}); - Assert.Equal(ContentTypes.Page, searchResult.First().Type); - var uri = ConfluenceTestClient.CreateWebUiUri(searchResult.FirstOrDefault()?.Links); - Assert.NotNull(uri); - } + [Fact] + public async Task TestSearch() + { + ConfluenceClientConfig.ExpandSearch = new[] { "version", "space", "space.icon", "space.description", "space.homepage", "history.lastUpdated" }; - [Fact] - public async Task TestSearchAttachment() - { - ConfluenceClientConfig.ExpandSearch = new[] { "version", "space", "space.icon", "space.description", "space.homepage", "history.lastUpdated" }; + var searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation {Limit = 20}); + Assert.Equal(ContentTypes.Page, searchResult.First().Type); + var uri = ConfluenceTestClient.CreateWebUiUri(searchResult.FirstOrDefault()?.Links); + Assert.NotNull(uri); + } - var query = Where.And(Where.Type.IsAttachment, Where.Text.Contains("404")); + [Fact] + public async Task TestSearchAttachment() + { + ConfluenceClientConfig.ExpandSearch = new[] { "version", "space", "space.icon", "space.description", "space.homepage", "history.lastUpdated" }; - var searchResult = await ConfluenceTestClient.Content.SearchAsync(query, pagingInformation: new PagingInformation { Limit = 1 }); - var attachment = searchResult.First(); - Assert.Equal(ContentTypes.Attachment, attachment.Type); - Assert.NotNull(ConfluenceTestClient.Attachment.CreateDownloadUri(attachment.Links)); - // I know the attachment is a bitmap, this should work - var bitmap = await ConfluenceTestClient.Attachment.GetContentAsync(attachment); - Assert.True(bitmap.Width > 0); - } + var query = Where.And(Where.Type.IsAttachment, Where.Text.Contains("404")); - [Fact] - public async Task TestSearchLabels() - { - var searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation { Limit = 1 }); - var contentId = searchResult.First().Id; + var searchResult = await ConfluenceTestClient.Content.SearchAsync(query, pagingInformation: new PagingInformation { Limit = 1 }); + var attachment = searchResult.First(); + Assert.Equal(ContentTypes.Attachment, attachment.Type); + Assert.NotNull(ConfluenceTestClient.Attachment.CreateDownloadUri(attachment.Links)); + // I know the attachment is a bitmap, this should work + var bitmap = await ConfluenceTestClient.Attachment.GetContentAsync(attachment); + Assert.True(bitmap.Width > 0); + } - var labels = new[] { "test1", "test2" }; - await ConfluenceTestClient.Content.AddLabelsAsync(contentId, labels.Select(s => new Label { Name = s })); + [Fact] + public async Task TestSearchLabels() + { + var searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation { Limit = 1 }); + var contentId = searchResult.First().Id; + + var labels = new[] { "test1", "test2" }; + await ConfluenceTestClient.Content.AddLabelsAsync(contentId, labels.Select(s => new Label { Name = s })); - ConfluenceClientConfig.ExpandSearch = new[] { "version", "space", "space.icon", "space.description", "space.homepage", "history.lastUpdated", "metadata.labels" }; + ConfluenceClientConfig.ExpandSearch = new[] { "version", "space", "space.icon", "space.description", "space.homepage", "history.lastUpdated", "metadata.labels" }; - searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation { Limit = 1 }); - var labelEntities = searchResult.First().Metadata.Labels.Results; + searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation { Limit = 1 }); + var labelEntities = searchResult.First().Metadata.Labels.Results; - Assert.NotEmpty(labelEntities); + Assert.NotEmpty(labelEntities); - // Delete all - foreach (var label in labelEntities) - { - await ConfluenceTestClient.Content.DeleteLabelAsync(contentId, label.Name); - } + // Delete all + foreach (var label in labelEntities) + { + await ConfluenceTestClient.Content.DeleteLabelAsync(contentId, label.Name); } + } + + [Fact] + public async Task TestLabels() + { + var searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation { Limit = 1 }); + var contentId = searchResult.First().Id; + + var labels = new[] { "test1", "test2" }; + await ConfluenceTestClient.Content.AddLabelsAsync(contentId, labels.Select(s => new Label { Name = s })); + var labelsForContent = await ConfluenceTestClient.Content.GetLabelsAsync(contentId); + Assert.Equal(labels.Length, labelsForContent.Count(label => labels.Contains(label.Name))); - [Fact] - public async Task TestLabels() + // Delete all + foreach (var label in labelsForContent) { - var searchResult = await ConfluenceTestClient.Content.SearchAsync(Where.And(Where.Type.IsPage, Where.Text.Contains("Test Home")), pagingInformation: new PagingInformation { Limit = 1 }); - var contentId = searchResult.First().Id; - - var labels = new[] { "test1", "test2" }; - await ConfluenceTestClient.Content.AddLabelsAsync(contentId, labels.Select(s => new Label { Name = s })); - var labelsForContent = await ConfluenceTestClient.Content.GetLabelsAsync(contentId); - Assert.Equal(labels.Length, labelsForContent.Count(label => labels.Contains(label.Name))); - - // Delete all - foreach (var label in labelsForContent) - { - await ConfluenceTestClient.Content.DeleteLabelAsync(contentId, label.Name); - } + await ConfluenceTestClient.Content.DeleteLabelAsync(contentId, label.Name); } } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/Dapplo.Confluence.Tests.csproj b/src/Dapplo.Confluence.Tests/Dapplo.Confluence.Tests.csproj index 1b8ad41..8b67fa6 100644 --- a/src/Dapplo.Confluence.Tests/Dapplo.Confluence.Tests.csproj +++ b/src/Dapplo.Confluence.Tests/Dapplo.Confluence.Tests.csproj @@ -2,7 +2,7 @@ Tests for Dapplo.Confluence - netcoreapp3.1;net5.0-windows + netcoreapp3.1;net5.0-windows;net6.0-windows true true @@ -19,13 +19,13 @@ - - + + All - + all diff --git a/src/Dapplo.Confluence.Tests/GroupTests.cs b/src/Dapplo.Confluence.Tests/GroupTests.cs index a70d668..23651c3 100644 --- a/src/Dapplo.Confluence.Tests/GroupTests.cs +++ b/src/Dapplo.Confluence.Tests/GroupTests.cs @@ -6,40 +6,39 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests for group related functionality +/// +[CollectionDefinition("Dapplo.Confluence")] +public class GroupTests : ConfluenceIntegrationTests { + public GroupTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + } + /// - /// Tests for group related functionality + /// Test if the list of Groups is returned correctly /// - [CollectionDefinition("Dapplo.Confluence")] - public class GroupTests : ConfluenceIntegrationTests + [Fact] + public async Task TestGetGroups() { - public GroupTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) - { - } - - /// - /// Test if the list of Groups is returned correctly - /// - [Fact] - public async Task TestGetGroups() - { - var groups = await ConfluenceTestClient.Group.GetGroupsAsync(); - Assert.NotEmpty(groups); - } + var groups = await ConfluenceTestClient.Group.GetGroupsAsync(); + Assert.NotEmpty(groups); + } - /// - /// Test if one of the groups the current user belongs to, also has the current user as a member - /// - [Fact] - public async Task TestGetGroupMembersAsync() - { - var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); - var groupsForUser = await ConfluenceTestClient.User.GetGroupMembershipsAsync(currentUser); + /// + /// Test if one of the groups the current user belongs to, also has the current user as a member + /// + [Fact] + public async Task TestGetGroupMembersAsync() + { + var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); + var groupsForUser = await ConfluenceTestClient.User.GetGroupMembershipsAsync(currentUser); - var usersInGroup = await ConfluenceTestClient.Group.GetGroupMembersAsync(groupsForUser.First().Name); + var usersInGroup = await ConfluenceTestClient.Group.GetGroupMembersAsync(groupsForUser.First().Name); - Assert.Contains(currentUser.AccountId, usersInGroup.Select(u => u.AccountId)); - } + Assert.Contains(currentUser.AccountId, usersInGroup.Select(u => u.AccountId)); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/JsonParseTests.cs b/src/Dapplo.Confluence.Tests/JsonParseTests.cs index 6d1bebc..5d2638a 100644 --- a/src/Dapplo.Confluence.Tests/JsonParseTests.cs +++ b/src/Dapplo.Confluence.Tests/JsonParseTests.cs @@ -11,58 +11,57 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +public class JsonParseTests { - public class JsonParseTests + private const string FilesDir = "JsonTestFiles"; + private readonly string _testFileLocation; + + public JsonParseTests(ITestOutputHelper testOutputHelper) { - private const string FilesDir = "JsonTestFiles"; - private readonly string _testFileLocation; + LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); - public JsonParseTests(ITestOutputHelper testOutputHelper) + _testFileLocation = FilesDir; + if (Directory.Exists(FilesDir)) { - LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); + return; + } - _testFileLocation = FilesDir; - if (Directory.Exists(FilesDir)) - { - return; - } + var location = Path.GetDirectoryName(GetType().Assembly.Location) ?? throw new NotSupportedException(); + _testFileLocation = Path.Combine(location, FilesDir); + } - var location = Path.GetDirectoryName(GetType().Assembly.Location) ?? throw new NotSupportedException(); - _testFileLocation = Path.Combine(location, FilesDir); - } + [Fact] + public void TestParseContent() + { + var json = File.ReadAllText(Path.Combine(_testFileLocation, "content.json")); + var content = JsonConvert.DeserializeObject(json); + Assert.NotNull(content); + Assert.Equal("http://myhost:8080/confluence/rest/api/content/1234", content.Links.Self.AbsoluteUri); + } - [Fact] - public void TestParseContent() - { - var json = File.ReadAllText(Path.Combine(_testFileLocation, "content.json")); - var content = JsonConvert.DeserializeObject(json); - Assert.NotNull(content); - Assert.Equal("http://myhost:8080/confluence/rest/api/content/1234", content.Links.Self.AbsoluteUri); - } + [Fact] + public void TestSerializeVersion() + { + var jsonNetJsonSerializer = ConfluenceClient.CreateJsonNetJsonSerializer(); - [Fact] - public void TestSerializeVersion() + var version = new Entities.Version { - var jsonNetJsonSerializer = ConfluenceClient.CreateJsonNetJsonSerializer(); + // NOTE: No fractional seconds (milliseconds) in date + When = new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero) + }; + var json = jsonNetJsonSerializer.Serialize(version); + Assert.NotEmpty(json); + Assert.Equal(39, json.Length); - var version = new Entities.Version - { - // NOTE: No fractional seconds (milliseconds) in date - When = new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero) - }; - var json = jsonNetJsonSerializer.Serialize(version); - Assert.NotEmpty(json); - Assert.Equal(39, json.Length); - - version = new Entities.Version - { - // NOTE: Fractional seconds (milliseconds) in date - When = new DateTimeOffset(2020, 1, 1, 0, 0, 0, 10, TimeSpan.Zero) - }; - json = jsonNetJsonSerializer.Serialize(version); - Assert.NotEmpty(json); - Assert.Equal(39, json.Length); - } + version = new Entities.Version + { + // NOTE: Fractional seconds (milliseconds) in date + When = new DateTimeOffset(2020, 1, 1, 0, 0, 0, 10, TimeSpan.Zero) + }; + json = jsonNetJsonSerializer.Serialize(version); + Assert.NotEmpty(json); + Assert.Equal(39, json.Length); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/QueryTests.cs b/src/Dapplo.Confluence.Tests/QueryTests.cs index 0800ecd..91a1734 100644 --- a/src/Dapplo.Confluence.Tests/QueryTests.cs +++ b/src/Dapplo.Confluence.Tests/QueryTests.cs @@ -8,71 +8,70 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests +/// +public class QueryTests { - /// - /// Tests - /// - public class QueryTests + public QueryTests(ITestOutputHelper testOutputHelper) { - public QueryTests(ITestOutputHelper testOutputHelper) - { - LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); - } + LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); + } - [Fact] - public void TestClause_Type_IsPage() - { - var clause = Where.Type.IsPage; - Assert.Equal("type = page", clause.ToString()); - } + [Fact] + public void TestClause_Type_IsPage() + { + var clause = Where.Type.IsPage; + Assert.Equal("type = page", clause.ToString()); + } - [Fact] - public void TestClause_Type_In() - { - var clause = Where.Type.In(ContentTypes.Page, ContentTypes.BlogPost); - Assert.Equal("type in (page, blogpost)", clause.ToString()); - } + [Fact] + public void TestClause_Type_In() + { + var clause = Where.Type.In(ContentTypes.Page, ContentTypes.BlogPost); + Assert.Equal("type in (page, blogpost)", clause.ToString()); + } - [Fact] - public void TestClause_Type_Not_In() - { - var clause = Where.Type.Not.In(ContentTypes.Page, ContentTypes.BlogPost); - Assert.Equal("type not in (page, blogpost)", clause.ToString()); - } + [Fact] + public void TestClause_Type_Not_In() + { + var clause = Where.Type.Not.In(ContentTypes.Page, ContentTypes.BlogPost); + Assert.Equal("type not in (page, blogpost)", clause.ToString()); + } - [Fact] - public void TestClause_Created_StartOfYear() - { - var clause = Where.Created.Before.StartOfYear(); - Assert.Equal("created < startOfYear()", clause.ToString()); - } + [Fact] + public void TestClause_Created_StartOfYear() + { + var clause = Where.Created.Before.StartOfYear(); + Assert.Equal("created < startOfYear()", clause.ToString()); + } - [Fact] - public void TestClause_Created_StartOfDay_WithNegativeIncrement() - { - // Find content created in the last 7 days - // created > startOfDay("-7d") + [Fact] + public void TestClause_Created_StartOfDay_WithNegativeIncrement() + { + // Find content created in the last 7 days + // created > startOfDay("-7d") - var clause = Where.Created.After.StartOfDay(TimeSpan.FromDays(-7)); - Assert.Equal("created > startOfDay(\"-7d\")", clause.ToString()); - } + var clause = Where.Created.After.StartOfDay(TimeSpan.FromDays(-7)); + Assert.Equal("created > startOfDay(\"-7d\")", clause.ToString()); + } - [Fact] - public void TestClause_Created_On() - { - // Find content created today - // created = yyyy-mm-dd + [Fact] + public void TestClause_Created_On() + { + // Find content created today + // created = yyyy-mm-dd - var clause = Where.Created.On.DateTime(DateTime.Today); - Assert.Equal($"created = \"{DateTime.Today:yyyy-MM-dd}\"", clause.ToString()); - } + var clause = Where.Created.On.DateTime(DateTime.Today); + Assert.Equal($"created = \"{DateTime.Today:yyyy-MM-dd}\"", clause.ToString()); + } - [Fact] - public void TestClause_Text_Contains() - { - var clause = Where.Text.Contains("hello"); - Assert.Equal("text ~ \"hello\"", clause.ToString()); - } + [Fact] + public void TestClause_Text_Contains() + { + var clause = Where.Text.Contains("hello"); + Assert.Equal("text ~ \"hello\"", clause.ToString()); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/ServerTests.cs b/src/Dapplo.Confluence.Tests/ServerTests.cs index 1e4874e..1a8b6a9 100644 --- a/src/Dapplo.Confluence.Tests/ServerTests.cs +++ b/src/Dapplo.Confluence.Tests/ServerTests.cs @@ -5,23 +5,22 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests +/// +[CollectionDefinition("Dapplo.Confluence")] +public class ServerTests : ConfluenceIntegrationTests { - /// - /// Tests - /// - [CollectionDefinition("Dapplo.Confluence")] - public class ServerTests : ConfluenceIntegrationTests + public ServerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { - public ServerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) - { - } + } - [Fact] - public async Task Test_IsCloud() - { - var isCloudServer = await ConfluenceTestClient.IsCloudServer(); - Assert.True(isCloudServer); - } + [Fact] + public async Task Test_IsCloud() + { + var isCloudServer = await ConfluenceTestClient.IsCloudServer(); + Assert.True(isCloudServer); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/SpaceQueryTests.cs b/src/Dapplo.Confluence.Tests/SpaceQueryTests.cs index 6c1cb81..4ad0df1 100644 --- a/src/Dapplo.Confluence.Tests/SpaceQueryTests.cs +++ b/src/Dapplo.Confluence.Tests/SpaceQueryTests.cs @@ -7,59 +7,58 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests +/// +public class SpaceQueryTests { - /// - /// Tests - /// - public class SpaceQueryTests + public SpaceQueryTests(ITestOutputHelper testOutputHelper) { - public SpaceQueryTests(ITestOutputHelper testOutputHelper) - { - LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); - } + LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); + } - [Fact] - public void TestClause_Space_Is() - { - var clause = Where.Space.Is("DEV"); - Assert.Equal("space = \"DEV\"", clause.ToString()); - } + [Fact] + public void TestClause_Space_Is() + { + var clause = Where.Space.Is("DEV"); + Assert.Equal("space = \"DEV\"", clause.ToString()); + } - [Fact] - public void TestClause_Space_Not_Is() - { - var clause = Where.Space.Not.Is("DEV"); - Assert.Equal("space != \"DEV\"", clause.ToString()); - } + [Fact] + public void TestClause_Space_Not_Is() + { + var clause = Where.Space.Not.Is("DEV"); + Assert.Equal("space != \"DEV\"", clause.ToString()); + } - [Fact] - public void TestClause_Space_In() - { - var clause = Where.Space.In("DEV", "PRODUCTION"); - Assert.Equal("space in (\"DEV\", \"PRODUCTION\")", clause.ToString()); - } + [Fact] + public void TestClause_Space_In() + { + var clause = Where.Space.In("DEV", "PRODUCTION"); + Assert.Equal("space in (\"DEV\", \"PRODUCTION\")", clause.ToString()); + } - [Fact] - public void TestClause_Space_Not_In() - { - var clause = Where.Space.Not.In("DEV", "PRODUCTION"); - Assert.Equal("space not in (\"DEV\", \"PRODUCTION\")", clause.ToString()); - } + [Fact] + public void TestClause_Space_Not_In() + { + var clause = Where.Space.Not.In("DEV", "PRODUCTION"); + Assert.Equal("space not in (\"DEV\", \"PRODUCTION\")", clause.ToString()); + } - [Fact] - public void TestClause_Space_InFavouriteSpacesAnd() - { - var clause = Where.Space.InFavouriteSpacesAnd("DEV", "PRODUCTION"); - Assert.Equal("space in (favouriteSpaces(), \"DEV\", \"PRODUCTION\")", clause.ToString()); - } + [Fact] + public void TestClause_Space_InFavouriteSpacesAnd() + { + var clause = Where.Space.InFavouriteSpacesAnd("DEV", "PRODUCTION"); + Assert.Equal("space in (favouriteSpaces(), \"DEV\", \"PRODUCTION\")", clause.ToString()); + } - [Fact] - public void TestClause_Space_Not_InFavouriteSpacesAnd() - { - var clause = Where.Space.Not.InFavouriteSpacesAnd("DEV", "PRODUCTION"); - Assert.Equal("space not in (favouriteSpaces(), \"DEV\", \"PRODUCTION\")", clause.ToString()); - } + [Fact] + public void TestClause_Space_Not_InFavouriteSpacesAnd() + { + var clause = Where.Space.Not.InFavouriteSpacesAnd("DEV", "PRODUCTION"); + Assert.Equal("space not in (favouriteSpaces(), \"DEV\", \"PRODUCTION\")", clause.ToString()); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/SpaceTests.cs b/src/Dapplo.Confluence.Tests/SpaceTests.cs index 5bca583..b441ae5 100644 --- a/src/Dapplo.Confluence.Tests/SpaceTests.cs +++ b/src/Dapplo.Confluence.Tests/SpaceTests.cs @@ -6,73 +6,72 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests +/// +[CollectionDefinition("Dapplo.Confluence")] +public class SpaceTests : ConfluenceIntegrationTests { + public SpaceTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + { + } + /// - /// Tests + /// Test GetAsync /// - [CollectionDefinition("Dapplo.Confluence")] - public class SpaceTests : ConfluenceIntegrationTests + [Fact] + public async Task TestGetSpace() { - public SpaceTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) - { - } + var space = await ConfluenceTestClient.Space.GetAsync("TEST"); + Assert.NotNull(space); + Assert.NotNull(space.Description); + } - /// - /// Test GetAsync - /// - [Fact] - public async Task TestGetSpace() - { - var space = await ConfluenceTestClient.Space.GetAsync("TEST"); - Assert.NotNull(space); - Assert.NotNull(space.Description); - } + /// + /// Test Space.GetAllAsync + /// + [Fact] + public async Task TestGetSpaces() + { + var spaces = await ConfluenceTestClient.Space.GetAllAsync(); + Assert.NotNull(spaces); + Assert.True(spaces.Count > 0); + } - /// - /// Test Space.GetAllAsync - /// - [Fact] - public async Task TestGetSpaces() - { - var spaces = await ConfluenceTestClient.Space.GetAllAsync(); - Assert.NotNull(spaces); - Assert.True(spaces.Count > 0); - } + /// + /// Test GetContentsAsync + /// + [Fact] + public async Task TestGetContentsAsync() + { + var spaceContents = await ConfluenceTestClient.Space.GetContentsAsync("TEST"); + Assert.NotNull(spaceContents); + Assert.NotNull(spaceContents.Pages); + Assert.True(spaceContents.Pages.Any()); + } - /// - /// Test GetContentsAsync - /// - [Fact] - public async Task TestGetContentsAsync() + /// + /// Test Space.CreateAsync + /// + [Fact] + public async Task TestCreateAsync() + { + const string key = "TESTTMP"; + var createdSpace = await ConfluenceTestClient.Space.CreatePrivateAsync(key, "Dummy for test", "Created and deleted during test"); + Assert.NotNull(createdSpace); + Assert.Equal(key, createdSpace.Key); + + try { - var spaceContents = await ConfluenceTestClient.Space.GetContentsAsync("TEST"); - Assert.NotNull(spaceContents); - Assert.NotNull(spaceContents.Pages); - Assert.True(spaceContents.Pages.Any()); + var space = await ConfluenceTestClient.Space.GetAsync(key); + Assert.NotNull(space); + Assert.Equal(key, space.Key); } - - /// - /// Test Space.CreateAsync - /// - [Fact] - public async Task TestCreateAsync() + finally { - const string key = "TESTTMP"; - var createdSpace = await ConfluenceTestClient.Space.CreatePrivateAsync(key, "Dummy for test", "Created and deleted during test"); - Assert.NotNull(createdSpace); - Assert.Equal(key, createdSpace.Key); - - try - { - var space = await ConfluenceTestClient.Space.GetAsync(key); - Assert.NotNull(space); - Assert.Equal(key, space.Key); - } - finally - { - await ConfluenceTestClient.Space.DeleteAsync(key); - } + await ConfluenceTestClient.Space.DeleteAsync(key); } } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/UserQueryTests.cs b/src/Dapplo.Confluence.Tests/UserQueryTests.cs index 55d0a69..f664887 100644 --- a/src/Dapplo.Confluence.Tests/UserQueryTests.cs +++ b/src/Dapplo.Confluence.Tests/UserQueryTests.cs @@ -7,82 +7,81 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests +/// +public class UserQueryTests { - /// - /// Tests - /// - public class UserQueryTests + public UserQueryTests(ITestOutputHelper testOutputHelper) { - public UserQueryTests(ITestOutputHelper testOutputHelper) - { - LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); - } + LogSettings.RegisterDefaultLogger(LogLevels.Verbose, testOutputHelper); + } - [Fact] - public void TestClause_AndClause() - { - var clause1 = Where.Creator.IsCurrentUser; - var clause2 = Where.Mention.Not.IsCurrentUser; - Assert.Equal("(creator = currentUser() and mention != currentUser())", Where.And(clause1, clause2).ToString()); - } + [Fact] + public void TestClause_AndClause() + { + var clause1 = Where.Creator.IsCurrentUser; + var clause2 = Where.Mention.Not.IsCurrentUser; + Assert.Equal("(creator = currentUser() and mention != currentUser())", Where.And(clause1, clause2).ToString()); + } - [Fact] - public void TestClause_OrClause() - { - var clause1 = Where.Creator.IsCurrentUser; - var clause2 = Where.Mention.Not.IsCurrentUser; - Assert.Equal("(creator = currentUser() or mention != currentUser())", Where.Or(clause1, clause2).ToString()); - } + [Fact] + public void TestClause_OrClause() + { + var clause1 = Where.Creator.IsCurrentUser; + var clause2 = Where.Mention.Not.IsCurrentUser; + Assert.Equal("(creator = currentUser() or mention != currentUser())", Where.Or(clause1, clause2).ToString()); + } - [Fact] - public void TestClause_IsCurrentUser() - { - var clause = Where.Creator.IsCurrentUser; - Assert.Equal("creator = currentUser()", clause.ToString()); - } + [Fact] + public void TestClause_IsCurrentUser() + { + var clause = Where.Creator.IsCurrentUser; + Assert.Equal("creator = currentUser()", clause.ToString()); + } - [Fact] - public void TestClause_NotIsCurrentUser() - { - var clause = Where.Creator.Not.IsCurrentUser; - Assert.Equal("creator != currentUser()", clause.ToString()); - } + [Fact] + public void TestClause_NotIsCurrentUser() + { + var clause = Where.Creator.Not.IsCurrentUser; + Assert.Equal("creator != currentUser()", clause.ToString()); + } - [Fact] - public void TestClause_IsUser() - { - var clause = Where.Creator.Is("jsmith"); - Assert.Equal("creator = \"jsmith\"", clause.ToString()); - } + [Fact] + public void TestClause_IsUser() + { + var clause = Where.Creator.Is("jsmith"); + Assert.Equal("creator = \"jsmith\"", clause.ToString()); + } - [Fact] - public void TestClause_InCurrentUserAnd() - { - var clause = Where.Creator.InCurrentUserAnd("jsmith"); - Assert.Equal("creator in (currentUser(), \"jsmith\")", clause.ToString()); - } + [Fact] + public void TestClause_InCurrentUserAnd() + { + var clause = Where.Creator.InCurrentUserAnd("jsmith"); + Assert.Equal("creator in (currentUser(), \"jsmith\")", clause.ToString()); + } - [Fact] - public void TestClause_IsUserIn() - { - var clause = Where.Creator.In("jsmith"); - Assert.Equal("creator in (\"jsmith\")", clause.ToString()); - } + [Fact] + public void TestClause_IsUserIn() + { + var clause = Where.Creator.In("jsmith"); + Assert.Equal("creator in (\"jsmith\")", clause.ToString()); + } - [Fact] - public void TestClause_NotIsUser() - { - var clause = Where.Creator.Not.Is("jsmith"); - Assert.Equal("creator != \"jsmith\"", clause.ToString()); - } + [Fact] + public void TestClause_NotIsUser() + { + var clause = Where.Creator.Not.Is("jsmith"); + Assert.Equal("creator != \"jsmith\"", clause.ToString()); + } - [Fact] - public void TestClause_OrderBy() - { - var clause = Where.Creator.Is("jsmith").OrderBy(Fields.Space).OrderByAscending(Fields.Title); - Assert.Equal("creator = \"jsmith\" order by space, title asc", clause.ToString()); - } + [Fact] + public void TestClause_OrderBy() + { + var clause = Where.Creator.Is("jsmith").OrderBy(Fields.Space).OrderByAscending(Fields.Title); + Assert.Equal("creator = \"jsmith\" order by space, title asc", clause.ToString()); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence.Tests/UserTests.cs b/src/Dapplo.Confluence.Tests/UserTests.cs index fca7f8d..12fa3e3 100644 --- a/src/Dapplo.Confluence.Tests/UserTests.cs +++ b/src/Dapplo.Confluence.Tests/UserTests.cs @@ -11,151 +11,150 @@ using Xunit; using Xunit.Abstractions; -namespace Dapplo.Confluence.Tests +namespace Dapplo.Confluence.Tests; + +/// +/// Tests for the user domain +/// +[CollectionDefinition("Dapplo.Confluence")] +public class UserTests : ConfluenceIntegrationTests { - /// - /// Tests for the user domain - /// - [CollectionDefinition("Dapplo.Confluence")] - public class UserTests : ConfluenceIntegrationTests + public UserTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { - public UserTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) + // Add BitmapHttpContentConverter if it was not yet added + if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapHttpContentConverter))) { - // Add BitmapHttpContentConverter if it was not yet added - if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapHttpContentConverter))) - { - HttpExtensionsGlobals.HttpContentConverters.Add(BitmapHttpContentConverter.Instance.Value); - } - // Add BitmapSourceHttpContentConverter if it was not yet added - if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapSourceHttpContentConverter))) - { - HttpExtensionsGlobals.HttpContentConverters.Add(BitmapSourceHttpContentConverter.Instance.Value); - } + HttpExtensionsGlobals.HttpContentConverters.Add(BitmapHttpContentConverter.Instance.Value); } - - /// - /// Test if the current user is correctly retrieved - /// - [Fact] - public async Task TestCurrentUser() + // Add BitmapSourceHttpContentConverter if it was not yet added + if (HttpExtensionsGlobals.HttpContentConverters.All(x => x.GetType() != typeof(BitmapSourceHttpContentConverter))) { - var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); - Assert.NotNull(currentUser); - Assert.True(currentUser.HasIdentifier()); - Assert.NotNull(currentUser.ProfilePicture); - Assert.DoesNotContain("Anonymous", currentUser.DisplayName); - - var currentUserIndirectly = await ConfluenceTestClient.User.GetUserAsync(currentUser); - Assert.Equal(currentUser.DisplayName, currentUserIndirectly.DisplayName); - } - - /// - /// Test if the picture can be downloaded - /// - [Fact] - public async Task TestCurrentUserPicture() - { - var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); - Assert.NotNull(currentUser); - Assert.NotNull(currentUser.ProfilePicture); - Assert.DoesNotContain("Anonymous", currentUser.DisplayName); - - var bitmapSource = await ConfluenceTestClient.Misc.GetPictureAsync(currentUser.ProfilePicture); - Assert.NotNull(bitmapSource); - Assert.True(bitmapSource.Width > 0); + HttpExtensionsGlobals.HttpContentConverters.Add(BitmapSourceHttpContentConverter.Instance.Value); } + } - /// - /// Test if the GetGroupMembershipsAsync returns at least a group - /// - [Fact] - public async Task TestGetGroupMembershipsAsync() - { - var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); - var groupsForUser = await ConfluenceTestClient.User.GetGroupMembershipsAsync(currentUser); - Assert.NotEmpty(groupsForUser); - } + /// + /// Test if the current user is correctly retrieved + /// + [Fact] + public async Task TestCurrentUser() + { + var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); + Assert.NotNull(currentUser); + Assert.True(currentUser.HasIdentifier()); + Assert.NotNull(currentUser.ProfilePicture); + Assert.DoesNotContain("Anonymous", currentUser.DisplayName); + + var currentUserIndirectly = await ConfluenceTestClient.User.GetUserAsync(currentUser); + Assert.Equal(currentUser.DisplayName, currentUserIndirectly.DisplayName); + } - /// - /// Test the label watcher functionality - /// - [Fact] - public async Task TestLabelWatcher() - { - const string testLabel = "Dappl0"; - const long contentId = 550731777; + /// + /// Test if the picture can be downloaded + /// + [Fact] + public async Task TestCurrentUserPicture() + { + var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); + Assert.NotNull(currentUser); + Assert.NotNull(currentUser.ProfilePicture); + Assert.DoesNotContain("Anonymous", currentUser.DisplayName); + + var bitmapSource = await ConfluenceTestClient.Misc.GetPictureAsync(currentUser.ProfilePicture); + Assert.NotNull(bitmapSource); + Assert.True(bitmapSource.Width > 0); + } - var label = new Label - { - Name = testLabel - }; + /// + /// Test if the GetGroupMembershipsAsync returns at least a group + /// + [Fact] + public async Task TestGetGroupMembershipsAsync() + { + var currentUser = await ConfluenceTestClient.User.GetCurrentUserAsync(); + var groupsForUser = await ConfluenceTestClient.User.GetGroupMembershipsAsync(currentUser); + Assert.NotEmpty(groupsForUser); + } - // Make sure there is a label - await ConfluenceTestClient.Content.AddLabelsAsync(contentId, Enumerable.Repeat(label, 1)); + /// + /// Test the label watcher functionality + /// + [Fact] + public async Task TestLabelWatcher() + { + const string testLabel = "Dappl0"; + const long contentId = 550731777; - try - { - if (await ConfluenceTestClient.User.IsLabelWatcher(testLabel)) - { - await ConfluenceTestClient.User.RemoveLabelWatcher(testLabel); - } - Assert.False(await ConfluenceTestClient.User.IsLabelWatcher(testLabel)); + var label = new Label + { + Name = testLabel + }; - // Add the current user as a label watcher - await ConfluenceTestClient.User.AddLabelWatcher(testLabel); - Assert.True(await ConfluenceTestClient.User.IsLabelWatcher(testLabel)); + // Make sure there is a label + await ConfluenceTestClient.Content.AddLabelsAsync(contentId, Enumerable.Repeat(label, 1)); - await ConfluenceTestClient.User.RemoveLabelWatcher(testLabel); - Assert.False(await ConfluenceTestClient.User.IsLabelWatcher(testLabel)); - } - finally + try + { + if (await ConfluenceTestClient.User.IsLabelWatcher(testLabel)) { - // Make sure the label is removed again - await ConfluenceTestClient.Content.DeleteLabelAsync(contentId, testLabel); + await ConfluenceTestClient.User.RemoveLabelWatcher(testLabel); } - } + Assert.False(await ConfluenceTestClient.User.IsLabelWatcher(testLabel)); - /// - /// Test the space watcher functionality - /// - [Fact] - public async Task TestSpaceWatcher() - { - string testSpace = "TEST"; + // Add the current user as a label watcher + await ConfluenceTestClient.User.AddLabelWatcher(testLabel); + Assert.True(await ConfluenceTestClient.User.IsLabelWatcher(testLabel)); - if (await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)) - { - await ConfluenceTestClient.User.RemoveSpaceWatcher(testSpace); - } - Assert.False(await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)); + await ConfluenceTestClient.User.RemoveLabelWatcher(testLabel); + Assert.False(await ConfluenceTestClient.User.IsLabelWatcher(testLabel)); + } + finally + { + // Make sure the label is removed again + await ConfluenceTestClient.Content.DeleteLabelAsync(contentId, testLabel); + } + } - // Add the current user as a space watcher - await ConfluenceTestClient.User.AddSpaceWatcher(testSpace); - Assert.True(await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)); + /// + /// Test the space watcher functionality + /// + [Fact] + public async Task TestSpaceWatcher() + { + string testSpace = "TEST"; + if (await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)) + { await ConfluenceTestClient.User.RemoveSpaceWatcher(testSpace); - Assert.False(await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)); } + Assert.False(await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)); - /// - /// Test the content watcher functionality - /// - [Fact] - public async Task TestContentWatcher() - { - long contentId = 550731777; - if (await ConfluenceTestClient.User.IsContentWatcher(contentId)) - { - await ConfluenceTestClient.User.RemoveContentWatcher(contentId); - } - Assert.False(await ConfluenceTestClient.User.IsContentWatcher(contentId)); + // Add the current user as a space watcher + await ConfluenceTestClient.User.AddSpaceWatcher(testSpace); + Assert.True(await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)); - // Add the current user as a content watcher - await ConfluenceTestClient.User.AddContentWatcher(contentId); - Assert.True(await ConfluenceTestClient.User.IsContentWatcher(contentId)); + await ConfluenceTestClient.User.RemoveSpaceWatcher(testSpace); + Assert.False(await ConfluenceTestClient.User.IsSpaceWatcher(testSpace)); + } + /// + /// Test the content watcher functionality + /// + [Fact] + public async Task TestContentWatcher() + { + long contentId = 550731777; + if (await ConfluenceTestClient.User.IsContentWatcher(contentId)) + { await ConfluenceTestClient.User.RemoveContentWatcher(contentId); - Assert.False(await ConfluenceTestClient.User.IsContentWatcher(contentId)); } + Assert.False(await ConfluenceTestClient.User.IsContentWatcher(contentId)); + + // Add the current user as a content watcher + await ConfluenceTestClient.User.AddContentWatcher(contentId); + Assert.True(await ConfluenceTestClient.User.IsContentWatcher(contentId)); + + await ConfluenceTestClient.User.RemoveContentWatcher(contentId); + Assert.False(await ConfluenceTestClient.User.IsContentWatcher(contentId)); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence/AttachmentExtensions.cs b/src/Dapplo.Confluence/AttachmentExtensions.cs index 65914ae..82a26c0 100644 --- a/src/Dapplo.Confluence/AttachmentExtensions.cs +++ b/src/Dapplo.Confluence/AttachmentExtensions.cs @@ -2,236 +2,227 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Dapplo.Confluence.Entities; -using Dapplo.Confluence.Internals; -using Dapplo.Confluence.Query; -using Dapplo.HttpExtensions; using Enumerable = System.Linq.Enumerable; -namespace Dapplo.Confluence +namespace Dapplo.Confluence; + +/// +/// Marker interface for the attachment domain +/// +public interface IAttachmentDomain : IConfluenceDomain +{ +} + +/// +/// All attachment related extension methods +/// +public static class AttachmentDomain { /// - /// Marker interface for the attachment domain + /// Obsolete: this AttachAsync is a wrapper for the new signature, which only excepts a long for the ID. /// - public interface IAttachmentDomain : IConfluenceDomain + /// Type of the content + /// IAttachmentDomain + /// string + /// TContent + /// string + /// string + /// string + /// CancellationToken + /// Result with Content + [Obsolete("The contentId should be of type long")] + public static Task> AttachAsync(this IAttachmentDomain confluenceClient, string contentId, TContent content, string filename, + string comment = null, string contentType = null, CancellationToken cancellationToken = default) + where TContent : class { + return confluenceClient.AttachAsync(long.Parse(contentId), content, filename, comment, contentType, cancellationToken); } /// - /// All attachment related extension methods + /// Add an attachment to the specified content /// - public static class AttachmentDomain + /// The content to upload + /// IAttachmentDomain to bind the extension method to + /// content to add the attachment to + /// content of type TContent for the attachment + /// Filename of the attachment + /// Comment in the attachments information + /// Content-Type for the content, or null + /// CancellationToken + /// Result with Attachment + public static async Task> AttachAsync(this IAttachmentDomain confluenceClient, long contentId, TContent content, string filename, string comment = null, string contentType = null, CancellationToken cancellationToken = default) + where TContent : class { - /// - /// Obsolete: this AttachAsync is a wrapper for the new signature, which only excepts a long for the ID. - /// - /// Type of the content - /// IAttachmentDomain - /// string - /// TContent - /// string - /// string - /// string - /// CancellationToken - /// Result with Content - [Obsolete("The contentId should be of type long")] - public static Task> AttachAsync(this IAttachmentDomain confluenceClient, string contentId, TContent content, string filename, - string comment = null, string contentType = null, CancellationToken cancellationToken = default) - where TContent : class - { - return confluenceClient.AttachAsync(long.Parse(contentId), content, filename, comment, contentType, cancellationToken); - } - - /// - /// Add an attachment to the specified content - /// - /// The content to upload - /// IAttachmentDomain to bind the extension method to - /// content to add the attachment to - /// content of type TContent for the attachment - /// Filename of the attachment - /// Comment in the attachments information - /// Content-Type for the content, or null - /// CancellationToken - /// Result with Attachment - public static async Task> AttachAsync(this IAttachmentDomain confluenceClient, long contentId, TContent content, string filename, string comment = null, string contentType = null, CancellationToken cancellationToken = default) - where TContent : class + var attachment = new AttachmentContainer { - var attachment = new AttachmentContainer - { - Comment = comment, - FileName = filename, - Content = content, - ContentType = contentType - }; - confluenceClient.Behaviour.MakeCurrent(); - - var postAttachmentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "attachment"); - var response = await postAttachmentUri.PostAsync, Error>>(attachment, cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + Comment = comment, + FileName = filename, + Content = content, + ContentType = contentType + }; + confluenceClient.Behaviour.MakeCurrent(); + + var postAttachmentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "attachment"); + var response = await postAttachmentUri.PostAsync, Error>>(attachment, cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - /// - /// Delete attachment - /// Can't work yet, see CONF-36015 - /// - /// IAttachmentDomain to bind the extension method to - /// Attachment which needs to be deleted - /// cancellationToken - public static async Task DeleteAsync(this IAttachmentDomain confluenceClient, Content attachment, - CancellationToken cancellationToken = default) + /// + /// Delete attachment + /// Can't work yet, see CONF-36015 + /// + /// IAttachmentDomain to bind the extension method to + /// Attachment which needs to be deleted + /// cancellationToken + public static async Task DeleteAsync(this IAttachmentDomain confluenceClient, Content attachment, + CancellationToken cancellationToken = default) + { + if (attachment.Type != ContentTypes.Attachment) { - if (attachment.Type != ContentTypes.Attachment) - { - throw new ArgumentException("Not an attachment", nameof(attachment)); - } - confluenceClient.Behaviour.MakeCurrent(); - - var contentUri = confluenceClient.ConfluenceUri - .AppendSegments("json", "removeattachmentversion.action") - .ExtendQuery("pageId", attachment.Container.Id) - .ExtendQuery("fileName", attachment.Title); - - await contentUri.GetAsAsync(cancellationToken).ConfigureAwait(false); + throw new ArgumentException("Not an attachment", nameof(attachment)); } + confluenceClient.Behaviour.MakeCurrent(); - /// - /// Delete content (attachments are also content) - /// - /// IAttachmentDomain to bind the extension method to - /// ID for the content which needs to be deleted - /// If the content is trashable, you will need to call DeleteAsyc twice, second time with isTrashed = true - /// CancellationToken - public static async Task DeleteAsync(this IAttachmentDomain confluenceClient, long attachtmentId, bool isTrashed = false, CancellationToken cancellationToken = default) - { - var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", $"att{attachtmentId}"); + var contentUri = confluenceClient.ConfluenceUri + .AppendSegments("json", "removeattachmentversion.action") + .ExtendQuery("pageId", attachment.Container.Id) + .ExtendQuery("fileName", attachment.Title); - if (isTrashed) - { - contentUri = contentUri.ExtendQuery("status", "trashed"); - } - confluenceClient.Behaviour.MakeCurrent(); + await contentUri.GetAsAsync(cancellationToken).ConfigureAwait(false); + } - var response = await contentUri.DeleteAsync(cancellationToken).ConfigureAwait(false); - response.HandleStatusCode(isTrashed ? HttpStatusCode.OK : HttpStatusCode.NoContent); - } + /// + /// Delete content (attachments are also content) + /// + /// IAttachmentDomain to bind the extension method to + /// ID for the content which needs to be deleted + /// If the content is trashable, you will need to call DeleteAsyc twice, second time with isTrashed = true + /// CancellationToken + public static async Task DeleteAsync(this IAttachmentDomain confluenceClient, long attachtmentId, bool isTrashed = false, CancellationToken cancellationToken = default) + { + var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", $"att{attachtmentId}"); - /// - /// Obsolete: this GetAttachmentsAsync is a wrapper for the new signature, which only excepts a long for the ID. - /// - /// IAttachmentDomain - /// string - /// CancellationToken - /// Result with Content - [Obsolete("The contentId should be of type long")] - public static Task> GetAttachmentsAsync(this IAttachmentDomain confluenceClient, string contentId, CancellationToken cancellationToken = default) + if (isTrashed) { - return confluenceClient.GetAttachmentsAsync(long.Parse(contentId), cancellationToken); + contentUri = contentUri.ExtendQuery("status", "trashed"); } + confluenceClient.Behaviour.MakeCurrent(); - /// - /// Retrieve the attachments for the specified content - /// - /// IAttachmentDomain to bind the extension method to - /// string with the content id - /// CancellationToken - /// Result with Attachment(s) - public static async Task> GetAttachmentsAsync(this IAttachmentDomain confluenceClient, long contentId, - CancellationToken cancellationToken = default) - { - confluenceClient.Behaviour.MakeCurrent(); + var response = await contentUri.DeleteAsync(cancellationToken).ConfigureAwait(false); + response.HandleStatusCode(isTrashed ? HttpStatusCode.OK : HttpStatusCode.NoContent); + } - var attachmentsUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "attachment"); + /// + /// Obsolete: this GetAttachmentsAsync is a wrapper for the new signature, which only excepts a long for the ID. + /// + /// IAttachmentDomain + /// string + /// CancellationToken + /// Result with Content + [Obsolete("The contentId should be of type long")] + public static Task> GetAttachmentsAsync(this IAttachmentDomain confluenceClient, string contentId, CancellationToken cancellationToken = default) + { + return confluenceClient.GetAttachmentsAsync(long.Parse(contentId), cancellationToken); + } - var expand = string.Join(",", ConfluenceClientConfig.ExpandGetAttachments ?? Enumerable.Empty()); - if (!string.IsNullOrEmpty(expand)) - { - attachmentsUri = attachmentsUri.ExtendQuery("expand", expand); - } + /// + /// Retrieve the attachments for the specified content + /// + /// IAttachmentDomain to bind the extension method to + /// string with the content id + /// CancellationToken + /// Result with Attachment(s) + public static async Task> GetAttachmentsAsync(this IAttachmentDomain confluenceClient, long contentId, + CancellationToken cancellationToken = default) + { + confluenceClient.Behaviour.MakeCurrent(); - var response = await attachmentsUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + var attachmentsUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "attachment"); - /// - /// Retrieve the attachment for the supplied Attachment entity - /// - /// the type to return the result into. e.g. Bitmap,BitmapSource or MemoryStream - /// IAttachmentDomain to bind the extension method to - /// Attachment - /// CancellationToken - /// Bitmap,BitmapSource or MemoryStream (etc) depending on TResponse - public static async Task GetContentAsync(this IAttachmentDomain confluenceClient, Content attachment, - CancellationToken cancellationToken = default) - where TResponse : class + var expand = string.Join(",", ConfluenceClientConfig.ExpandGetAttachments ?? Enumerable.Empty()); + if (!string.IsNullOrEmpty(expand)) { - if (attachment.Type != ContentTypes.Attachment) - { - throw new ArgumentException("Not an attachment", nameof(attachment)); - } - confluenceClient.Behaviour.MakeCurrent(); - - var attachmentUri = confluenceClient.CreateDownloadUri(attachment.Links); - var response = await attachmentUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); + attachmentsUri = attachmentsUri.ExtendQuery("expand", expand); } - /// - /// Update the attachment information - /// - /// IAttachmentDomain to bind the extension method to - /// Attachment - /// CancellationToken - /// Attachment - public static async Task UpdateAsync(this IAttachmentDomain confluenceClient, Content attachment, CancellationToken cancellationToken = default) + var response = await attachmentsUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } + + /// + /// Retrieve the attachment for the supplied Attachment entity + /// + /// the type to return the result into. e.g. Bitmap,BitmapSource or MemoryStream + /// IAttachmentDomain to bind the extension method to + /// Attachment + /// CancellationToken + /// Bitmap,BitmapSource or MemoryStream (etc) depending on TResponse + public static async Task GetContentAsync(this IAttachmentDomain confluenceClient, Content attachment, + CancellationToken cancellationToken = default) + where TResponse : class + { + if (attachment.Type != ContentTypes.Attachment) { - confluenceClient.Behaviour.MakeCurrent(); + throw new ArgumentException("Not an attachment", nameof(attachment)); + } + confluenceClient.Behaviour.MakeCurrent(); - var attachmentsUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", attachment.Container.Id, "child", "attachment", attachment.Id); + var attachmentUri = confluenceClient.CreateDownloadUri(attachment.Links); + var response = await attachmentUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - var expand = string.Join(",", ConfluenceClientConfig.ExpandGetAttachments ?? Enumerable.Empty()); - if (!string.IsNullOrEmpty(expand)) - { - attachmentsUri = attachmentsUri.ExtendQuery("expand", expand); - } + /// + /// Update the attachment information + /// + /// IAttachmentDomain to bind the extension method to + /// Attachment + /// CancellationToken + /// Attachment + public static async Task UpdateAsync(this IAttachmentDomain confluenceClient, Content attachment, CancellationToken cancellationToken = default) + { + confluenceClient.Behaviour.MakeCurrent(); - var response = await attachmentsUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + var attachmentsUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", attachment.Container.Id, "child", "attachment", attachment.Id); - /// - /// Update data (Content) of existing attachment - /// - /// The content to upload - /// IAttachmentDomain to bind the extension method to - /// content to add the attachment to - /// Id of attachment to update - /// content of type TContent for the attachment - /// Filename of the attachment - /// Comment in the attachments information - /// Content-Type for the content, or null - /// CancellationToken - /// - public static async Task> UpdateDataAsync(this IAttachmentDomain confluenceClient, long contentId, long attachmentId, TContent content, string filename, string comment = null, string contentType = null, CancellationToken cancellationToken = default) - where TContent : class + var expand = string.Join(",", ConfluenceClientConfig.ExpandGetAttachments ?? Enumerable.Empty()); + if (!string.IsNullOrEmpty(expand)) { - var attachment = new AttachmentContainer - { - Comment = comment, - FileName = filename, - Content = content, - ContentType = contentType, - }; - confluenceClient.Behaviour.MakeCurrent(); - - var postAttachmentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "attachment", attachmentId, "data"); - var response = await postAttachmentUri.PostAsync, Error>>(attachment, cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); + attachmentsUri = attachmentsUri.ExtendQuery("expand", expand); } + var response = await attachmentsUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); } + + /// + /// Update data (Content) of existing attachment + /// + /// The content to upload + /// IAttachmentDomain to bind the extension method to + /// content to add the attachment to + /// Id of attachment to update + /// content of type TContent for the attachment + /// Filename of the attachment + /// Comment in the attachments information + /// Content-Type for the content, or null + /// CancellationToken + /// + public static async Task> UpdateDataAsync(this IAttachmentDomain confluenceClient, long contentId, long attachmentId, TContent content, string filename, string comment = null, string contentType = null, CancellationToken cancellationToken = default) + where TContent : class + { + var attachment = new AttachmentContainer + { + Comment = comment, + FileName = filename, + Content = content, + ContentType = contentType, + }; + confluenceClient.Behaviour.MakeCurrent(); + + var postAttachmentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "attachment", attachmentId, "data"); + var response = await postAttachmentUri.PostAsync, Error>>(attachment, cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } + } \ No newline at end of file diff --git a/src/Dapplo.Confluence/ConfluenceClient.cs b/src/Dapplo.Confluence/ConfluenceClient.cs index 99128d8..cbd546c 100644 --- a/src/Dapplo.Confluence/ConfluenceClient.cs +++ b/src/Dapplo.Confluence/ConfluenceClient.cs @@ -1,192 +1,180 @@ // Copyright (c) Dapplo and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +namespace Dapplo.Confluence; -using System; -using System.Threading; -using System.Threading.Tasks; -#if NET471 || NET461 -using System.Net.Cache; -#endif - -using Dapplo.Confluence.Entities; -using Dapplo.HttpExtensions; -using Dapplo.HttpExtensions.JsonNet; - -namespace Dapplo.Confluence +/// +/// A Confluence client build by using Dapplo.HttpExtensions +/// +public class ConfluenceClient : IConfluenceClientPlugins, IAttachmentDomain, IUserDomain, ISpaceDomain, IContentDomain, IMiscDomain, IGroupDomain { + private Task _isCloudServer; + /// - /// A Confluence client build by using Dapplo.HttpExtensions + /// Password for the basic authentication /// - public class ConfluenceClient : IConfluenceClientPlugins, IAttachmentDomain, IUserDomain, ISpaceDomain, IContentDomain, IMiscDomain, IGroupDomain - { - private Task _isCloudServer; - - /// - /// Password for the basic authentication - /// - private string _password; + private string _password; - /// - /// User for the basic authentication - /// - private string _user; + /// + /// User for the basic authentication + /// + private string _user; - /// - /// - /// - protected ConfluenceClient() - { + /// + /// + /// + protected ConfluenceClient() + { - } + } - /// - /// Create the ConfluenceApi object, here the HttpClient is configured - /// - /// Base URL, e.g. https://yourConfluenceserver - /// IHttpSettings or null for default - protected ConfluenceClient(Uri confluenceUri, IHttpSettings httpSettings = null) - { - ConfluenceUri = confluenceUri ?? throw new ArgumentNullException(nameof(confluenceUri)); - ConfluenceApiUri = confluenceUri.AppendSegments("rest", "api"); + /// + /// Create the ConfluenceApi object, here the HttpClient is configured + /// + /// Base URL, e.g. https://yourConfluenceserver + /// IHttpSettings or null for default + protected ConfluenceClient(Uri confluenceUri, IHttpSettings httpSettings = null) + { + ConfluenceUri = confluenceUri ?? throw new ArgumentNullException(nameof(confluenceUri)); + ConfluenceApiUri = confluenceUri.AppendSegments("rest", "api"); - Behaviour = ConfigureBehaviour(new HttpBehaviour(), httpSettings); - } + Behaviour = ConfigureBehaviour(new HttpBehaviour(), httpSettings); + } - /// - /// The IHttpBehaviour for this Confluence instance - /// - public IHttpBehaviour HttpBehaviour => Behaviour; + /// + /// The IHttpBehaviour for this Confluence instance + /// + public IHttpBehaviour HttpBehaviour => Behaviour; - /// - /// Store the specific HttpBehaviour, which contains a IHttpSettings and also some additional logic for making a - /// HttpClient which works with Confluence - /// - public IHttpBehaviour Behaviour { get; protected set; } + /// + /// Store the specific HttpBehaviour, which contains a IHttpSettings and also some additional logic for making a + /// HttpClient which works with Confluence + /// + public IHttpBehaviour Behaviour { get; protected set; } - /// - /// Plugins dock to this property by implementing an extension method to IConfluenceClientPlugins - /// - public IConfluenceClientPlugins Plugins => this; + /// + /// Plugins dock to this property by implementing an extension method to IConfluenceClientPlugins + /// + public IConfluenceClientPlugins Plugins => this; - /// - /// Set Basic Authentication for the current client - /// - /// username - /// password - public void SetBasicAuthentication(string user, string password) - { - _user = user; - _password = password; - } + /// + /// Set Basic Authentication for the current client + /// + /// username + /// password + public void SetBasicAuthentication(string user, string password) + { + _user = user; + _password = password; + } - /// - /// The base URI for your Confluence server api calls - /// - public Uri ConfluenceApiUri { get; } + /// + /// The base URI for your Confluence server api calls + /// + public Uri ConfluenceApiUri { get; } - /// - /// The base URI for your Confluence server downloads - /// - public Uri ConfluenceUri { get; } + /// + /// The base URI for your Confluence server downloads + /// + public Uri ConfluenceUri { get; } - /// - public IAttachmentDomain Attachment => this; + /// + public IAttachmentDomain Attachment => this; - /// - public IContentDomain Content => this; + /// + public IContentDomain Content => this; - /// - public IGroupDomain Group => this; + /// + public IGroupDomain Group => this; - /// - public IMiscDomain Misc => this; + /// + public IMiscDomain Misc => this; - /// - public ISpaceDomain Space => this; + /// + public ISpaceDomain Space => this; - /// - public IUserDomain User => this; + /// + public IUserDomain User => this; - /// - public Uri CreateWebUiUri(Links links) + /// + public Uri CreateWebUiUri(Links links) + { + if (links == null) { - if (links == null) - { - throw new ArgumentNullException(nameof(links)); - } - if (links.Base == null) - { - links.Base = ConfluenceUri; - } - return Concat(links.Base, links.WebUi); + throw new ArgumentNullException(nameof(links)); } - - /// - public Uri CreateTinyUiUri(Links links) + if (links.Base == null) { - if (links == null) - { - throw new ArgumentNullException(nameof(links)); - } - if (links.Base == null) - { - links.Base = ConfluenceUri; - } - return Concat(links.Base, links.TinyUi); + links.Base = ConfluenceUri; } + return Concat(links.Base, links.WebUi); + } - /// - public Uri CreateDownloadUri(Links links) + /// + public Uri CreateTinyUiUri(Links links) + { + if (links == null) { - if (links == null) - { - throw new ArgumentNullException(nameof(links)); - } - if (links.Base == null) - { - links.Base = ConfluenceUri; - } - return Concat(links.Base, links.Download); + throw new ArgumentNullException(nameof(links)); + } + if (links.Base == null) + { + links.Base = ConfluenceUri; } + return Concat(links.Base, links.TinyUi); + } - /// - /// Helper method to combine an Uri with a path including optional query - /// - /// Uri for the base - /// Path and optional query - /// Uri - private Uri Concat(Uri baseUri, string pathWithQuery) + /// + public Uri CreateDownloadUri(Links links) + { + if (links == null) { - if (baseUri == null) - { - throw new ArgumentNullException(nameof(baseUri)); - } - if (string.IsNullOrEmpty(pathWithQuery)) - { - return null; - } + throw new ArgumentNullException(nameof(links)); + } + if (links.Base == null) + { + links.Base = ConfluenceUri; + } + return Concat(links.Base, links.Download); + } - var queryStart = pathWithQuery.IndexOf('?'); - var path = queryStart >= 0 ? pathWithQuery.Substring(0, queryStart) : pathWithQuery; - var query = queryStart >= 0 ? pathWithQuery.Substring(queryStart + 1) : null; - // Use the given path, without changing encoding, as it's already correctly encoded by atlassian! - var uriBuilder = new UriBuilder(baseUri.AppendSegments(s => s, path)) - { - Query = query ?? string.Empty - }; - return uriBuilder.Uri; + /// + /// Helper method to combine an Uri with a path including optional query + /// + /// Uri for the base + /// Path and optional query + /// Uri + private static Uri Concat(Uri baseUri, string pathWithQuery) + { + if (baseUri == null) + { + throw new ArgumentNullException(nameof(baseUri)); + } + if (string.IsNullOrEmpty(pathWithQuery)) + { + return null; } - /// - /// Helper method to configure the IChangeableHttpBehaviour - /// - /// IChangeableHttpBehaviour - /// IHttpSettings - /// the behaviour, but configured as IHttpBehaviour - protected IHttpBehaviour ConfigureBehaviour(IChangeableHttpBehaviour behaviour, IHttpSettings httpSettings = null) + var queryStart = pathWithQuery.IndexOf('?'); + var path = queryStart >= 0 ? pathWithQuery.Substring(0, queryStart) : pathWithQuery; + var query = queryStart >= 0 ? pathWithQuery.Substring(queryStart + 1) : null; + // Use the given path, without changing encoding, as it's already correctly encoded by Atlassian! + var uriBuilder = new UriBuilder(baseUri.AppendSegments(s => s, path)) { - behaviour.HttpSettings = httpSettings ?? HttpExtensionsGlobals.HttpSettings; + Query = query ?? string.Empty + }; + return uriBuilder.Uri; + } + + /// + /// Helper method to configure the IChangeableHttpBehaviour + /// + /// IChangeableHttpBehaviour + /// IHttpSettings + /// the behaviour, but configured as IHttpBehaviour + protected IHttpBehaviour ConfigureBehaviour(IChangeableHttpBehaviour behaviour, IHttpSettings httpSettings = null) + { + behaviour.HttpSettings = httpSettings ?? HttpExtensionsGlobals.HttpSettings; #if NET471 || NET461 // Disable caching, if no HTTP settings were provided. // This is needed as was detected here: https://github.com/dapplo/Dapplo.Confluence/issues/11 @@ -195,62 +183,61 @@ protected IHttpBehaviour ConfigureBehaviour(IChangeableHttpBehaviour behaviour, behaviour.HttpSettings.RequestCacheLevel = RequestCacheLevel.NoCacheNoStore; } #endif - // Using our own Json Serializer, implemented with JsonNetJsonSerializer - behaviour.JsonSerializer = CreateJsonNetJsonSerializer(); + // Using our own Json Serializer, implemented with JsonNetJsonSerializer + behaviour.JsonSerializer = CreateJsonNetJsonSerializer(); - behaviour.OnHttpRequestMessageCreated = httpMessage => + behaviour.OnHttpRequestMessageCreated = httpMessage => + { + httpMessage?.Headers.TryAddWithoutValidation("X-Atlassian-Token", "no-check"); + if (!string.IsNullOrEmpty(_user) && _password != null) { - httpMessage?.Headers.TryAddWithoutValidation("X-Atlassian-Token", "no-check"); - if (!string.IsNullOrEmpty(_user) && _password != null) - { - httpMessage?.SetBasicAuthorization(_user, _password); - } - return httpMessage; - }; - return behaviour; - } + httpMessage?.SetBasicAuthorization(_user, _password); + } + return httpMessage; + }; + return behaviour; + } - /// - /// Checks if the client is connected to a cloud server - /// - /// bool - public Task IsCloudServer(CancellationToken cancellationToken = default) - { - return _isCloudServer ??= Task.Run(async () => { - var systemInfo = await this.Misc.GetSystemInfoAsync(cancellationToken); - return !string.IsNullOrEmpty(systemInfo?.CloudId); - }, cancellationToken); - } + /// + /// Checks if the client is connected to a cloud server + /// + /// bool + public Task IsCloudServer(CancellationToken cancellationToken = default) + { + return _isCloudServer ??= Task.Run(async () => { + var systemInfo = await this.Misc.GetSystemInfoAsync(cancellationToken); + return !string.IsNullOrEmpty(systemInfo?.CloudId); + }, cancellationToken); + } - /// - /// Factory for CreateJsonNetJsonSerializer - /// - /// JsonNetJsonSerializer - public static JsonNetJsonSerializer CreateJsonNetJsonSerializer() - { - var result = new JsonNetJsonSerializer(); - // This should fix https://github.com/dapplo/Dapplo.Confluence/issues/41 - result.Settings.DateFormatString = result.Settings.DateFormatString.Replace("FFFFFF", "ff"); - return result; - } + /// + /// Factory for CreateJsonNetJsonSerializer + /// + /// JsonNetJsonSerializer + public static JsonNetJsonSerializer CreateJsonNetJsonSerializer() + { + var result = new JsonNetJsonSerializer(); + // This should fix https://github.com/dapplo/Dapplo.Confluence/issues/41 + result.Settings.DateFormatString = result.Settings.DateFormatString.Replace("FFFFFF", "ff"); + return result; + } - /// - /// Factory method to create a ConfluenceClient - /// - /// Uri to your confluence server - /// IHttpSettings used if you need specific settings - /// IConfluenceClient - public static IConfluenceClient Create(Uri confluenceUri, IHttpSettings httpSettings = null) - { - return new ConfluenceClient(confluenceUri, httpSettings); - } + /// + /// Factory method to create a ConfluenceClient + /// + /// Uri to your confluence server + /// IHttpSettings used if you need specific settings + /// IConfluenceClient + public static IConfluenceClient Create(Uri confluenceUri, IHttpSettings httpSettings = null) + { + return new ConfluenceClient(confluenceUri, httpSettings); + } - /// - /// This makes sure that the HttpBehavior is promoted for the following Http call. - /// - public void PromoteContext() - { - Behaviour.MakeCurrent(); - } + /// + /// This makes sure that the HttpBehavior is promoted for the following Http call. + /// + public void PromoteContext() + { + Behaviour.MakeCurrent(); } } \ No newline at end of file diff --git a/src/Dapplo.Confluence/ConfluenceClientConfig.cs b/src/Dapplo.Confluence/ConfluenceClientConfig.cs index 4d2b776..5f5ee82 100644 --- a/src/Dapplo.Confluence/ConfluenceClientConfig.cs +++ b/src/Dapplo.Confluence/ConfluenceClientConfig.cs @@ -1,63 +1,62 @@ // Copyright (c) Dapplo and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -namespace Dapplo.Confluence +namespace Dapplo.Confluence; + +/// +/// Use this class to configure some of the behaviour. +/// This stores the "expand" settings for the different REST calls, and defines what additional information is +/// requested. +/// +public static class ConfluenceClientConfig { /// - /// Use this class to configure some of the behaviour. - /// This stores the "expand" settings for the different REST calls, and defines what additional information is - /// requested. - /// - public static class ConfluenceClientConfig - { - /// - /// These expand values can be used when getting the content with storage, instead of view - /// - public static string[] ExpandGetContentWithStorage { get; set; } = { "body", "body.storage", "version" }; - - /// - /// The values that are expanded in the GetAttachments result - /// - public static string[] ExpandGetAttachments { get; set; } = { "version", "container" }; - - /// - /// The values that are expanded in the GetChildren results - /// - public static string[] ExpandGetChildren { get; set; } = { "page" }; - - /// - /// The values that are expanded in the GetContent result - /// - public static string[] ExpandGetContent { get; set; } = { "body", "body.view", "version" }; - - /// - /// The values that are expanded in the GetContent result, but for an update use case - /// - public static string[] ExpandGetContentForUpdate { get; set; } = { "body.storage", "version" }; - - /// - /// The values that are expanded in the GetContentByTitle results - /// - public static string[] ExpandGetContentByTitle { get; set; } - - /// - /// The values that are expanded in the Space.GetContentsAsync results - /// - public static string[] ExpandSpaceGetContents { get; set; } - - /// - /// The values that are expanded in the GetSpace result - /// - public static string[] ExpandGetSpace { get; set; } = { "icon", "description.plain", "homepage" }; - - /// - /// The values that are expanded in the GetSpaces results - /// - public static string[] ExpandGetSpaces { get; set; } = { "icon", "description.plain", "homepage" }; - - /// - /// The values that are expanded in the Search results - /// - public static string[] ExpandSearch { get; set; } = { "version", "space", "space.icon", "space.description", "space.homepage" }; - } + /// These expand values can be used when getting the content with storage, instead of view + /// + public static string[] ExpandGetContentWithStorage { get; set; } = { "body", "body.storage", "version" }; + + /// + /// The values that are expanded in the GetAttachments result + /// + public static string[] ExpandGetAttachments { get; set; } = { "version", "container" }; + + /// + /// The values that are expanded in the GetChildren results + /// + public static string[] ExpandGetChildren { get; set; } = { "page" }; + + /// + /// The values that are expanded in the GetContent result + /// + public static string[] ExpandGetContent { get; set; } = { "body", "body.view", "version" }; + + /// + /// The values that are expanded in the GetContent result, but for an update use case + /// + public static string[] ExpandGetContentForUpdate { get; set; } = { "body.storage", "version" }; + + /// + /// The values that are expanded in the GetContentByTitle results + /// + public static string[] ExpandGetContentByTitle { get; set; } + + /// + /// The values that are expanded in the Space.GetContentsAsync results + /// + public static string[] ExpandSpaceGetContents { get; set; } + + /// + /// The values that are expanded in the GetSpace result + /// + public static string[] ExpandGetSpace { get; set; } = { "icon", "description.plain", "homepage" }; + + /// + /// The values that are expanded in the GetSpaces results + /// + public static string[] ExpandGetSpaces { get; set; } = { "icon", "description.plain", "homepage" }; + + /// + /// The values that are expanded in the Search results + /// + public static string[] ExpandSearch { get; set; } = { "version", "space", "space.icon", "space.description", "space.homepage" }; } \ No newline at end of file diff --git a/src/Dapplo.Confluence/ConfluenceException.cs b/src/Dapplo.Confluence/ConfluenceException.cs index ec42066..990bb69 100644 --- a/src/Dapplo.Confluence/ConfluenceException.cs +++ b/src/Dapplo.Confluence/ConfluenceException.cs @@ -2,34 +2,30 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Net; using System.Net.Http; -using Dapplo.Confluence.Entities; -namespace Dapplo.Confluence +namespace Dapplo.Confluence; + +/// +/// This wraps the HttpRequestException with Confluence specific informationen +/// +public class ConfluenceException : HttpRequestException { /// - /// This wraps the HttpRequestException with Confluence specific informationen + /// Constructor with a HttpStatus code and an error response /// - public class ConfluenceException : HttpRequestException + /// HttpStatusCode + /// string with the error response message + public ConfluenceException(HttpStatusCode httpStatusCode, string response) : base($"{httpStatusCode}({(int)httpStatusCode}) : {response}") { - /// - /// Constructor with a HttpStatus code and an error response - /// - /// HttpStatusCode - /// string with the error response message - public ConfluenceException(HttpStatusCode httpStatusCode, string response) : base($"{httpStatusCode}({(int)httpStatusCode}) : {response}") - { - } - - /// - /// Constructor with a HttpStatus code and an Error object - /// - /// HttpStatusCode - /// Error - public ConfluenceException(HttpStatusCode httpStatusCode, Error error = null) : base(error?.Message ?? $"{httpStatusCode}({(int)httpStatusCode})") - { - } } -} + /// + /// Constructor with a HttpStatus code and an Error object + /// + /// HttpStatusCode + /// Error + public ConfluenceException(HttpStatusCode httpStatusCode, Error error = null) : base(error?.Message ?? $"{httpStatusCode}({(int)httpStatusCode})") + { + } +} \ No newline at end of file diff --git a/src/Dapplo.Confluence/ContentExtensions.cs b/src/Dapplo.Confluence/ContentExtensions.cs index 322c9be..b732d78 100644 --- a/src/Dapplo.Confluence/ContentExtensions.cs +++ b/src/Dapplo.Confluence/ContentExtensions.cs @@ -1,415 +1,451 @@ // Copyright (c) Dapplo and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +namespace Dapplo.Confluence; -using System; -using System.Net; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Dapplo.Confluence.Entities; -using Dapplo.Confluence.Internals; -using Dapplo.Confluence.Query; -using Dapplo.HttpExtensions; - -namespace Dapplo.Confluence +/// +/// The is the marker interface to the content functionality of the Confluence API +/// +public interface IContentDomain : IConfluenceDomain +{ +} + +/// +/// The is the implementation to the content functionality of the Confluence API +/// +public static class ContentExtensions { /// - /// The is the marker interface to the content functionality of the Confluence API + /// Create content /// - public interface IContentDomain : IConfluenceDomain + /// IContentDomain to bind the extension method to + /// Type of content, usually page + /// Title for the content + /// Key of the space to add the content to + /// the complete body (HTML) + /// Optional ID for the ancestor (parent) + /// CancellationToken + /// Content + public static Task CreateAsync(this IContentDomain confluenceClient, ContentTypes contentType, string title, string spaceKey, string body, long? ancestorId = null, CancellationToken cancellationToken = default) { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + if (string.IsNullOrEmpty(spaceKey)) throw new ArgumentNullException(nameof(spaceKey)); + if (string.IsNullOrEmpty(body)) throw new ArgumentNullException(nameof(body)); + var contentBody = new Body + { + Storage = new BodyContent + { + Value = body, + Representation = "storage" + } + }; + return confluenceClient.CreateAsync(contentType, title, spaceKey, contentBody, ancestorId, cancellationToken); } /// - /// The is the implementation to the content functionality of the Confluence API + /// Create content /// - public static class ContentExtensions + /// IContentDomain to bind the extension method to + /// Type of content, usually page + /// Title for the content + /// Key of the space to add the content to + /// Body + /// Optional ID for the ancestor (parent) + /// CancellationToken + /// Content + public static Task CreateAsync(this IContentDomain confluenceClient, ContentTypes contentType, string title, string spaceKey, Body body, long? ancestorId = null, CancellationToken cancellationToken = default) { - /// - /// Create content - /// - /// IContentDomain to bind the extension method to - /// Type of content, usually page - /// Title for the content - /// Key of the space to add the content to - /// the complete body (HTML) - /// Optional ID for the ancestor (parent) - /// CancellationToken - /// Content - public static Task CreateAsync(this IContentDomain confluenceClient, ContentTypes contentType, string title, string spaceKey, string body, long? ancestorId = null, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - if (string.IsNullOrEmpty(spaceKey)) throw new ArgumentNullException(nameof(spaceKey)); - if (string.IsNullOrEmpty(body)) throw new ArgumentNullException(nameof(body)); - var contentBody = new Body - { - Storage = new BodyContent - { - Value = body, - Representation = "storage" - } - }; - return confluenceClient.CreateAsync(contentType, title, spaceKey, contentBody, ancestorId, cancellationToken); - } + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + if (string.IsNullOrEmpty(spaceKey)) throw new ArgumentNullException(nameof(spaceKey)); + if (body == null) throw new ArgumentNullException(nameof(body)); - /// - /// Create content - /// - /// IContentDomain to bind the extension method to - /// Type of content, usually page - /// Title for the content - /// Key of the space to add the content to - /// Body - /// Optional ID for the ancestor (parent) - /// CancellationToken - /// Content - public static Task CreateAsync(this IContentDomain confluenceClient, ContentTypes contentType, string title, string spaceKey, Body body, long? ancestorId = null, CancellationToken cancellationToken = default) + var content = new Content { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - if (string.IsNullOrEmpty(spaceKey)) throw new ArgumentNullException(nameof(spaceKey)); - if (body == null) throw new ArgumentNullException(nameof(body)); - - var content = new Content + Type = contentType, + Title = title, + Space = new Space { - Type = contentType, - Title = title, - Space = new Space - { - Key = spaceKey - }, - Body = body, - Ancestors = !ancestorId.HasValue ? null : new List + Key = spaceKey + }, + Body = body, + Ancestors = !ancestorId.HasValue ? null : new List + { + new Content { - new Content - { - Id = ancestorId.Value - } + Id = ancestorId.Value } - }; - return confluenceClient.CreateAsync(content, cancellationToken); - } - - /// - /// Create content - /// - /// IContentDomain to bind the extension method to - /// Content (e.g. Page) to create - /// CancellationToken - /// Content - public static async Task CreateAsync(this IContentDomain confluenceClient, Content content, CancellationToken cancellationToken = default) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content"); - - confluenceClient.Behaviour.MakeCurrent(); - var response = await contentUri.PostAsync>(content, cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + } + }; + return confluenceClient.CreateAsync(content, cancellationToken); + } - /// - /// Delete content (attachments are also content) - /// - /// IContentDomain to bind the extension method to - /// ID for the content which needs to be deleted - /// If the content is trash-able, you will need to call DeleteAsync twice, second time with isTrashed = true - /// CancellationToken - public static async Task DeleteAsync(this IContentDomain confluenceClient, long contentId, bool isTrashed = false, CancellationToken cancellationToken = default) - { - if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); - var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId); + /// + /// Create content + /// + /// IContentDomain to bind the extension method to + /// Content (e.g. Page) to create + /// CancellationToken + /// Content + public static async Task CreateAsync(this IContentDomain confluenceClient, Content content, CancellationToken cancellationToken = default) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content"); - if (isTrashed) - { - contentUri = contentUri.ExtendQuery("status", "trashed"); - } - confluenceClient.Behaviour.MakeCurrent(); + confluenceClient.Behaviour.MakeCurrent(); + var response = await contentUri.PostAsync>(content, cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - var response = await contentUri.DeleteAsync(cancellationToken).ConfigureAwait(false); - response.HandleStatusCode(isTrashed ? HttpStatusCode.OK : HttpStatusCode.NoContent); - } + /// + /// Delete content (attachments are also content) + /// + /// IContentDomain to bind the extension method to + /// ID for the content which needs to be deleted + /// If the content is trash-able, you will need to call DeleteAsync twice, second time with isTrashed = true + /// CancellationToken + public static async Task DeleteAsync(this IContentDomain confluenceClient, long contentId, bool isTrashed = false, CancellationToken cancellationToken = default) + { + if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); + var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId); - /// - /// Get Content information see here - /// - /// IContentDomain to bind the extension method to - /// content id (as content implements an implicit cast, you can also pass the content instance) - /// Specify the expand values, if null the default from the configuration is used - /// CancellationToken - /// Content - public static async Task GetAsync(this IContentDomain confluenceClient, long contentId, IEnumerable expandGetContent = null, CancellationToken cancellationToken = default) + if (isTrashed) { - if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); - var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId); - - var expand = string.Join(",", expandGetContent ?? ConfluenceClientConfig.ExpandGetContent ?? Enumerable.Empty()); - if (!string.IsNullOrEmpty(expand)) - { - contentUri = contentUri.ExtendQuery("expand", expand); - } + contentUri = contentUri.ExtendQuery("status", "trashed"); + } + confluenceClient.Behaviour.MakeCurrent(); - confluenceClient.Behaviour.MakeCurrent(); + var response = await contentUri.DeleteAsync(cancellationToken).ConfigureAwait(false); + response.HandleStatusCode(isTrashed ? HttpStatusCode.OK : HttpStatusCode.NoContent); + } - var response = await contentUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + /// + /// Get Content information see here + /// + /// IContentDomain to bind the extension method to + /// content id (as content implements an implicit cast, you can also pass the content instance) + /// Specify the expand values, if null the default from the configuration is used + /// CancellationToken + /// Content + public static async Task GetAsync(this IContentDomain confluenceClient, long contentId, IEnumerable expandGetContent = null, CancellationToken cancellationToken = default) + { + if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); + var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId); - /// - /// Get content by title - /// See: https://docs.atlassian.com/confluence/REST/latest/#d2e4539 - /// - /// IContentDomain to bind the extension method to - /// Space key - /// Title of the content - /// PagingInformation used for paging - /// CancellationToken - /// Results with content items - public static async Task> GetByTitleAsync(this IContentDomain confluenceClient, string spaceKey, string title, PagingInformation pagingInformation = null, CancellationToken cancellationToken = default) + var expand = string.Join(",", expandGetContent ?? ConfluenceClientConfig.ExpandGetContent ?? Enumerable.Empty()); + if (!string.IsNullOrEmpty(expand)) { - if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); - if (string.IsNullOrEmpty(spaceKey)) throw new ArgumentNullException(nameof(spaceKey)); + contentUri = contentUri.ExtendQuery("expand", expand); + } - confluenceClient.Behaviour.MakeCurrent(); - pagingInformation ??= new PagingInformation - { - Limit = 200, - Start = 0 - }; - var searchUri = confluenceClient.ConfluenceApiUri.AppendSegments("content").ExtendQuery(new Dictionary - { - { - "start", pagingInformation.Start - }, - { - "limit", pagingInformation.Limit - }, - { - "type", "page" - }, - { - "spaceKey", spaceKey - }, - { - "title", title - } - }); + confluenceClient.Behaviour.MakeCurrent(); - var expand = string.Join(",", ConfluenceClientConfig.ExpandGetContentByTitle ?? Enumerable.Empty()); - if (!string.IsNullOrEmpty(expand)) - { - searchUri = searchUri.ExtendQuery("expand", expand); - } + var response = await contentUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - var response = await searchUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + /// + /// Get content by title + /// See: https://docs.atlassian.com/confluence/REST/latest/#d2e4539 + /// + /// IContentDomain to bind the extension method to + /// Space key + /// Title of the content + /// PagingInformation used for paging + /// CancellationToken + /// Results with content items + public static async Task> GetByTitleAsync(this IContentDomain confluenceClient, string spaceKey, string title, PagingInformation pagingInformation = null, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(title)) throw new ArgumentNullException(nameof(title)); + if (string.IsNullOrEmpty(spaceKey)) throw new ArgumentNullException(nameof(spaceKey)); - /// - /// Get Content information see here - /// - /// IContentDomain to bind the extension method to - /// content id - /// CancellationToken - /// PagingInformation - /// int representing the version of the content to retrieve children for. - /// List with Content - public static async Task> GetChildrenAsync(this IContentDomain confluenceClient, long contentId, PagingInformation pagingInformation = null, int? parentVersion = null, CancellationToken cancellationToken = default) + confluenceClient.Behaviour.MakeCurrent(); + pagingInformation ??= new PagingInformation + { + Limit = 200, + Start = 0 + }; + var searchUri = confluenceClient.ConfluenceApiUri.AppendSegments("content").ExtendQuery(new Dictionary { - if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); - var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "page"); - - if (pagingInformation?.Start != null) { - contentUri = contentUri.ExtendQuery("start", pagingInformation.Start); - } - - if (pagingInformation?.Limit != null) + "start", pagingInformation.Start + }, { - contentUri = contentUri.ExtendQuery("limit", pagingInformation.Limit); - } - - if (parentVersion.HasValue) + "limit", pagingInformation.Limit + }, { - contentUri = contentUri.ExtendQuery("parentVersion", parentVersion); - } - - var expand = string.Join(",", ConfluenceClientConfig.ExpandGetChildren ?? Enumerable.Empty()); - if (!string.IsNullOrEmpty(expand)) + "type", "page" + }, + { + "spaceKey", spaceKey + }, { - contentUri = contentUri.ExtendQuery("expand", expand); + "title", title } - confluenceClient.Behaviour.MakeCurrent(); + }); - var response = await contentUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); + var expand = string.Join(",", ConfluenceClientConfig.ExpandGetContentByTitle ?? Enumerable.Empty()); + if (!string.IsNullOrEmpty(expand)) + { + searchUri = searchUri.ExtendQuery("expand", expand); } - /// - /// Get Content History information see here - /// - /// IContentDomain to bind the extension method to - /// content id - /// CancellationToken - /// Content - public static async Task GetHistoryAsync(this IContentDomain confluenceClient, long contentId, CancellationToken cancellationToken = default) - { - if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); - var historyUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "history"); + var response = await searchUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - confluenceClient.Behaviour.MakeCurrent(); + /// + /// Get Content information see here + /// + /// IContentDomain to bind the extension method to + /// content id + /// CancellationToken + /// PagingInformation + /// int representing the version of the content to retrieve children for. + /// List with Content + public static async Task> GetChildrenAsync(this IContentDomain confluenceClient, long contentId, PagingInformation pagingInformation = null, int? parentVersion = null, CancellationToken cancellationToken = default) + { + if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); + var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "child", "page"); - var response = await historyUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); + if (pagingInformation?.Start != null) + { + contentUri = contentUri.ExtendQuery("start", pagingInformation.Start); } - /// - /// Possible since 5.7 - /// Search for issues, with a CQL (e.g. from a filter) see - /// here - /// - /// IContentDomain to bind the extension method to - /// Confluence Query Language, like SQL, for the search - /// - /// the execution context for CQL functions, provides current space key and content id. If this is - /// not provided some CQL functions will not be available. - /// - /// PagingInformation - /// The expand value for the search, when null the value from the ConfluenceClientConfig.ExpandSearch is taken - /// CancellationToken - /// Result with content items - public static Task> SearchAsync(this IContentDomain confluenceClient, IFinalClause cqlClause, string cqlContext = null, PagingInformation pagingInformation = null, IEnumerable expandSearch = null, - CancellationToken cancellationToken = default) + if (pagingInformation?.Limit != null) { - var searchDetails = new SearchDetails(cqlClause) - { - Start = pagingInformation?.Start, - Limit = pagingInformation?.Limit - }; + contentUri = contentUri.ExtendQuery("limit", pagingInformation.Limit); + } - if (cqlContext != null) - { - searchDetails.CqlContext = cqlContext; - } - if (expandSearch != null) - { - searchDetails.ExpandSearch = expandSearch; - } - return confluenceClient.SearchAsync(searchDetails, cancellationToken); + if (parentVersion.HasValue) + { + contentUri = contentUri.ExtendQuery("parentVersion", parentVersion); } - /// - /// Possible since 5.7 - /// Search for issues, with a CQL (e.g. from a filter) see - /// here - /// - /// IContentDomain to bind the extension method to - /// All the details needed for a search - /// CancellationToken - /// Result with content items - public static async Task> SearchAsync(this IContentDomain confluenceClient, SearchDetails searchDetails, CancellationToken cancellationToken = default) + var expand = string.Join(",", ConfluenceClientConfig.ExpandGetChildren ?? Enumerable.Empty()); + if (!string.IsNullOrEmpty(expand)) { - if (searchDetails == null) throw new ArgumentNullException(nameof(searchDetails)); + contentUri = contentUri.ExtendQuery("expand", expand); + } + confluenceClient.Behaviour.MakeCurrent(); - confluenceClient.Behaviour.MakeCurrent(); + var response = await contentUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - var searchUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", "search").ExtendQuery("cql", searchDetails.Cql); + /// + /// Get Content History information see here + /// + /// IContentDomain to bind the extension method to + /// content id + /// CancellationToken + /// Content + public static async Task GetHistoryAsync(this IContentDomain confluenceClient, long contentId, CancellationToken cancellationToken = default) + { + if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); + var historyUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "history"); - if (searchDetails.Limit.HasValue) - { - searchUri = searchUri.ExtendQuery("limit", searchDetails.Limit); - } - if (searchDetails.Start.HasValue) - { - searchUri = searchUri.ExtendQuery("start", searchDetails.Start); - } + confluenceClient.Behaviour.MakeCurrent(); - var expand = string.Join(",", searchDetails.ExpandSearch ?? ConfluenceClientConfig.ExpandSearch ?? Enumerable.Empty()); - if (!string.IsNullOrEmpty(expand)) - { - searchUri = searchUri.ExtendQuery("expand", expand); - } + var response = await historyUri.GetAsAsync>(cancellationToken).ConfigureAwait(false); + return response.HandleErrors(); + } - if (searchDetails.CqlContext != null) - { - searchUri = searchUri.ExtendQuery("cqlcontext", searchDetails.CqlContext); - } + /// + /// Possible since 5.7 + /// Search for issues, with a CQL (e.g. from a filter) see + /// here + /// + /// IContentDomain to bind the extension method to + /// Confluence Query Language, like SQL, for the search + /// + /// the execution context for CQL functions, provides current space key and content id. If this is + /// not provided some CQL functions will not be available. + /// + /// PagingInformation + /// The expand value for the search, when null the value from the ConfluenceClientConfig.ExpandSearch is taken + /// CancellationToken + /// Result with content items + public static Task> SearchAsync(this IContentDomain confluenceClient, IFinalClause cqlClause, string cqlContext = null, PagingInformation pagingInformation = null, IEnumerable expandSearch = null, + CancellationToken cancellationToken = default) + { + var searchDetails = new SearchDetails(cqlClause) + { + Start = pagingInformation?.Start, + Limit = pagingInformation?.Limit + }; - var response = await searchUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); + if (cqlContext != null) + { + searchDetails.CqlContext = cqlContext; } - - /// - /// Update content - /// - /// IContentDomain to bind the extension method to - /// Content to update - /// CancellationToken - /// Content - public static async Task UpdateAsync(this IContentDomain confluenceClient, Content content, CancellationToken cancellationToken = default) + if (expandSearch != null) { - if (content == null) throw new ArgumentNullException(nameof(content)); - var contentUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", content.Id); - - confluenceClient.Behaviour.MakeCurrent(); - var response = await contentUri.PutAsync>(content, cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); + searchDetails.ExpandSearch = expandSearch; } + return confluenceClient.SearchAsync(searchDetails, cancellationToken); + } - /// - /// Get Labels for content see here - /// - /// IContentDomain to bind the extension method to - /// content id - /// CancellationToken - /// Result with labels - public static async Task> GetLabelsAsync(this IContentDomain confluenceClient, long contentId, CancellationToken cancellationToken = default) - { - if (contentId == 0) throw new ArgumentNullException(nameof(contentId)); - var labelUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", contentId, "label"); - confluenceClient.Behaviour.MakeCurrent(); + /// + /// Possible since 5.7 + /// Search for issues, with a CQL (e.g. from a filter) see + /// here + /// + /// IContentDomain to bind the extension method to + /// All the details needed for a search + /// CancellationToken + /// Result with content items + public static async Task> SearchAsync(this IContentDomain confluenceClient, SearchDetails searchDetails, CancellationToken cancellationToken = default) + { + if (searchDetails == null) throw new ArgumentNullException(nameof(searchDetails)); - var response = await labelUri.GetAsAsync, Error>>(cancellationToken).ConfigureAwait(false); - return response.HandleErrors(); - } + confluenceClient.Behaviour.MakeCurrent(); + + var searchUri = confluenceClient.ConfluenceApiUri.AppendSegments("content", "search").ExtendQuery("cql", searchDetails.Cql); - /// - /// Add Labels to content see here - /// - /// IContentDomain to bind the extension method to - /// content id - /// IEnumerable labels - /// CancellationToken - /// Task - public static async Task AddLabelsAsync(this IContentDomain confluenceClient, long contentId, IEnumerable