Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial code for repository instance API. #84

Merged
merged 11 commits into from
Feb 23, 2023
Merged
120 changes: 120 additions & 0 deletions src/SenseNet.Client.Tests/RepositoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Linq;
using System.Threading;
using SenseNet.Extensions.DependencyInjection;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using NSubstitute;
using SenseNet.Client;
using SenseNet.Testing;

namespace DifferentNamespace
{
public class MyContent : Content
{
public MyContent(ILogger<MyContent> logger) : base(logger) { }
}
}
namespace SenseNet.Client.Tests
{
public class MyContent : Content
{
public string HelloMessage => $"Hello {this.Name}!";
public MyContent(ILogger<MyContent> logger) : base(logger) { }
}
public class MyContent2 : Content { public MyContent2(ILogger<MyContent> logger) : base(logger) { } }
public class MyContent3 : Content { public MyContent3(ILogger<MyContent> logger) : base(logger) { } }
public class MyContent4 : Content { public MyContent4(ILogger<MyContent> logger) : base(logger) { } }

[TestClass]
public class RepositoryTests
{

private const string ExampleUrl = "https://example.com";

[TestMethod]
public async Task Repository_Default()
{
// ALIGN
var repositoryService = GetRepositoryService(services =>
{
services.ConfigureSenseNetRepository(opt => { opt.Url = ExampleUrl; });
});

// ACTION
var repository = await repositoryService.GetRepositoryAsync(CancellationToken.None).ConfigureAwait(false);

// ASSERT
Assert.AreEqual(ExampleUrl, repository.Server.Url);
}
[TestMethod]
public async Task Repository_Named()
{
// ALIGN
var repositoryService = GetRepositoryService(services =>
{
services.ConfigureSenseNetRepository("repo1", opt => { opt.Url = ExampleUrl; });
services.ConfigureSenseNetRepository("repo2", opt => { opt.Url = "https://url2"; });
});

// ACT
var repo = await repositoryService.GetRepositoryAsync(CancellationToken.None).ConfigureAwait(false);
var repo1 = await repositoryService.GetRepositoryAsync("repo1", CancellationToken.None).ConfigureAwait(false);
var repo2 = await repositoryService.GetRepositoryAsync("repo2", CancellationToken.None).ConfigureAwait(false);

Assert.IsNull(repo.Server.Url);
Assert.AreEqual(ExampleUrl, repo1.Server.Url);
Assert.AreEqual("https://url2", repo2.Server.Url);
}

[TestMethod]
public async Task Repository_LoadContent_Localhost()
{
// PREPARATION
var repositoryService = GetRepositoryService();
var repository = await repositoryService.GetRepositoryAsync("local", CancellationToken.None)
.ConfigureAwait(false);

// ACTION
var content = await repository.LoadContentAsync("/Root/Content", CancellationToken.None)
.ConfigureAwait(false);

Assert.AreEqual("Content", content.Name);
}

private static IRepositoryCollection GetRepositoryService(Action<IServiceCollection> addServices = null)
{
var services = new ServiceCollection();

services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Trace);
});

var config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true)
.AddUserSecrets<RepositoryTests>()
.Build();

services
.AddSingleton<IConfiguration>(config)
.AddSenseNetClient()
//.AddSingleton<ITokenProvider, TestTokenProvider>()
//.AddSingleton<ITokenStore, TokenStore>()
.ConfigureSenseNetRepository("local", repositoryOptions =>
{
// set test url and authentication in user secret
config.GetSection("sensenet:repository").Bind(repositoryOptions);
});

addServices?.Invoke(services);

var provider = services.BuildServiceProvider();
return provider.GetRequiredService<IRepositoryCollection>();
}
}
}
21 changes: 18 additions & 3 deletions src/SenseNet.Client.Tests/SenseNet.Client.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>

<IsPackable>false</IsPackable>

<UserSecretsId>fde4652a-54ea-4d87-a4fa-b34974fe7549</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
<PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
<PackageReference Include="coverlet.collector" Version="1.0.1" />
<PackageReference Include="NSubstitute" Version="5.0.0" />
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.16">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SenseNet.Client\SenseNet.Client.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions src/SenseNet.Client.Tests/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"sensenet": {
"repository": {
"url": "https://localhost:44362",
"authentication": {
"ClientId": "",
"ClientSecret": ""
}
}
}
}
6 changes: 6 additions & 0 deletions src/SenseNet.Client.lutconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<LUTConfig Version="1.0">
<Repository>..\</Repository>
<ParallelBuilds>true</ParallelBuilds>
<ParallelTestRuns>true</ParallelTestRuns>
<TestCaseTimeout>180000</TestCaseTimeout>
</LUTConfig>
22 changes: 11 additions & 11 deletions src/SenseNet.Client/Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SenseNet.Client.Security;

namespace SenseNet.Client
Expand All @@ -18,7 +19,8 @@ namespace SenseNet.Client
/// </summary>
public class Content : DynamicObject
{
private IDictionary<string, object> _fields;
private readonly ILogger<Content> _logger;
private readonly IDictionary<string, object> _fields = new Dictionary<string, object>();

//============================================================================= Content properties

Expand Down Expand Up @@ -65,20 +67,24 @@ public string ParentPath
/// <summary>
/// The target server that this content belongs to.
/// </summary>
public ServerContext Server { get; }
public ServerContext Server { get; internal set; }
public IRepository Repository { get; internal set; }

private dynamic _responseContent;

//============================================================================= Constructors

public Content(ILogger<Content> logger)
{
_logger = logger;
}
/// <summary>
/// Internal constructor for client content.
/// </summary>
/// <param name="server">Target server. If null, the first one will be used from the configuration.</param>
protected Content(ServerContext server)
{
Server = server ?? ClientContext.Current.Server;
_fields = new Dictionary<string, object>();
}
/// <summary>
/// Internal constructor for client content.
Expand All @@ -90,10 +96,10 @@ protected Content(ServerContext server, dynamic responseContent) : this(server)
InitializeFromResponse(responseContent);
}

private void InitializeFromResponse(dynamic responseContent)
internal void InitializeFromResponse(dynamic responseContent)
{
_responseContent = responseContent;
_fields = new Dictionary<string, object>();
_fields.Clear();

// fill local properties from the response object
if (_responseContent is JObject jo)
Expand Down Expand Up @@ -975,9 +981,6 @@ public override bool TryGetMember(GetMemberBinder binder, out object result)
/// <returns>This operation is always succesful.</returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (_fields == null)
_fields = new Dictionary<string, object>();

_fields[binder.Name] = value;

return true;
Expand Down Expand Up @@ -1044,9 +1047,6 @@ public object this[string fieldName]
}
set
{
if (_fields == null)
_fields = new Dictionary<string, object>();

_fields[fieldName] = value;
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/SenseNet.Client/DefaultRestCaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;
using System;

namespace SenseNet.Client;

public class DefaultRestCaller : IRestCaller
{
public Task<string> GetResponseStringAsync(Uri uri, ServerContext server) => RESTCaller.GetResponseStringAsync(uri, server);
}
9 changes: 9 additions & 0 deletions src/SenseNet.Client/IRestCaller.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;

namespace SenseNet.Client;

public interface IRestCaller
{
Task<string> GetResponseStringAsync(Uri uri, ServerContext server);
}
42 changes: 42 additions & 0 deletions src/SenseNet.Client/Repository/IRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Threading.Tasks;
using System.Threading;

// ReSharper disable once CheckNamespace
namespace SenseNet.Client
{
/// <summary>
/// Defines the main client API for managing content items in a sensenet repository.
/// </summary>
public interface IRepository
{
public ServerContext Server { get; set; }

/// <summary>
/// Creates a new content instance in memory.
/// </summary>
/// <returns>A new content instance.</returns>
public Content CreateContent();

/// <summary>
/// Loads an existing content.
/// </summary>
/// <param name="id">Content id</param>
/// <param name="cancel">The token to monitor for cancellation requests.</param>
/// <returns>A task that wraps the content or null.</returns>
public Task<Content> LoadContentAsync(int id, CancellationToken cancel);
/// <summary>
/// Loads an existing content.
/// </summary>
/// <param name="path">Content path</param>
/// <param name="cancel">The token to monitor for cancellation requests.</param>
/// <returns>A task that wraps the content or null.</returns>
public Task<Content> LoadContentAsync(string path, CancellationToken cancel);
/// <summary>
/// Loads an existing content.
/// </summary>
/// <param name="requestData">Detailed request information.</param>
/// <param name="cancel">The token to monitor for cancellation requests.</param>
/// <returns>A task that wraps the content or null.</returns>
public Task<Content> LoadContentAsync(ODataRequest requestData, CancellationToken cancel);
}
}
38 changes: 38 additions & 0 deletions src/SenseNet.Client/Repository/IRepositoryCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Threading;
using System.Threading.Tasks;

// ReSharper disable once CheckNamespace
namespace SenseNet.Client
{
/// <summary>
/// Defines a repository collection.
/// </summary>
/// <remarks>
/// Use this service as a starting point in your application to acquire instances of registered repositories.
/// </remarks>
public interface IRepositoryCollection
{
/// <summary>
/// Returns the unnamed repository.
/// </summary>
/// <remarks>This method will return an authenticated repository instance
/// that can be pinned in the application. This method can be called
/// multiple times as it caches the repository and will return the
/// same object.</remarks>
/// <param name="cancel">The token to monitor for cancellation requests.</param>
/// <returns>A task that wraps a configured repository instance.</returns>
public Task<IRepository> GetRepositoryAsync(CancellationToken cancel);

/// <summary>
/// Returns a named repository.
/// </summary>
/// <remarks>This method will return an authenticated repository instance
/// that can be pinned in the application. This method can be called
/// multiple times as it caches the repository and will return the
/// same object.</remarks>
/// <param name="name">Name of the repository.</param>
/// <param name="cancel">The token to monitor for cancellation requests.</param>
/// <returns>A task that wraps a configured repository instance.</returns>
public Task<IRepository> GetRepositoryAsync(string name, CancellationToken cancel);
}
}
Loading