adds first implementation details and starts with login / logout

This commit is contained in:
Philipp Häfelfinger 2022-10-06 23:54:44 +02:00
parent 0751dbe9d3
commit 3bf56c7ee7
10 changed files with 189 additions and 21 deletions

View File

@ -9,11 +9,16 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0"/>
<PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/>
<PackageReference Include="NUnit.Analyzers" Version="3.3.0"/>
<PackageReference Include="coverlet.collector" Version="3.1.2"/>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Piwigo.Client\Piwigo.Client.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,45 @@
using Flurl.Http.Testing;
namespace Piwigo.Client.Tests;
public class PiwigoClientTests
{
private PiwigoClient _piwigoClient = null!;
private HttpTest _httpTest = null!;
[SetUp]
public void SetUp()
{
_piwigoClient = new PiwigoClient();
_httpTest = new HttpTest();
}
[TearDown]
public void TearDown()
{
_httpTest.Dispose();
}
[Test]
public async Task Login_should_set_cookies_and_session()
{
await LoginAsync();
}
[Test]
public async Task Logout_should_set_IsLoggedIn_to_false()
{
await LoginAsync();
_httpTest.RespondWith("OK");
await _piwigoClient.Logout();
_piwigoClient.IsLoggedIn.Should().BeFalse();
}
private async Task LoginAsync()
{
_httpTest.RespondWith("OK", 200, cookies: new { pwg_id = "pwg_id" });
await _piwigoClient.LoginAsync(new Uri("http://localhost:8080/foo/bar/ws.php?format=json"), "admin", "admin");
_piwigoClient.IsLoggedIn.Should().BeTrue();
}
}

View File

@ -1,15 +0,0 @@
namespace Piwigo.Client.Tests;
public class Tests
{
[SetUp]
public void Setup()
{
}
[Test]
public void Test1()
{
Assert.Pass();
}
}

View File

@ -1 +1,2 @@
global using NUnit.Framework;
global using NUnit.Framework;
global using FluentAssertions;

View File

@ -0,0 +1,16 @@
using Flurl.Http.Content;
namespace Piwigo.Client;
internal static class CapturedMultipartContentExtensions
{
public static CapturedMultipartContent AddMethod(this CapturedMultipartContent part, string piwigoMethod)
{
if (string.IsNullOrWhiteSpace(piwigoMethod))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(piwigoMethod));
}
return part.AddString("method", piwigoMethod);
}
}

View File

@ -2,5 +2,8 @@
public interface IPiwigoClient
{
bool IsLoggedIn { get; }
int ChunkSize { get; set; }
Task LoginAsync(Uri uri, string username, string password);
Task Logout();
}

View File

@ -0,0 +1,69 @@
using System.Net;
using System.Runtime.CompilerServices;
using Flurl;
using Flurl.Http;
namespace Piwigo.Client;
public class PiwigoClient : IPiwigoClient
{
private CookieJar _cookies = new();
private string _piwigoBaseUri = null!;
public bool IsLoggedIn { get; private set; }
public int ChunkSize { get; set; } = 512;
private IFlurlRequest Request => _piwigoBaseUri.WithCookies(_cookies);
public async Task LoginAsync(Uri uri, string username, string password)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(username));
}
if (string.IsNullOrWhiteSpace(password))
{
throw new ArgumentException("Value cannot be null or whitespace.", nameof(password));
}
if (IsLoggedIn)
{
throw new PiwigoException("The client is already logged in. Create a new instance or log out first!");
}
_piwigoBaseUri = uri.AppendPathSegment("ws.php").SetQueryParam("format", "json");
var response = await _piwigoBaseUri.WithCookies(out var cookieJar).PostMultipartAsync(c =>
c.PiwigoLogin(username, password));
if (response.StatusCode != (int)HttpStatusCode.OK)
{
throw new PiwigoException($"Could not log in to {_piwigoBaseUri} using username {username}");
}
_cookies = cookieJar;
IsLoggedIn = true;
}
public async Task Logout()
{
EnsureLoggedIn();
await Request.PostMultipartAsync(c => c.PiwigoLogout());
IsLoggedIn = false;
_cookies.Clear();
}
private void EnsureLoggedIn([CallerMemberName] string? callerName = null)
{
if (!IsLoggedIn)
{
throw new InvalidOperationException($"Could not execute {callerName} as the client is not logged in.");
}
}
}

View File

@ -0,0 +1,23 @@
using System.Runtime.Serialization;
namespace Piwigo.Client;
[Serializable]
public class PiwigoException : Exception
{
public PiwigoException()
{
}
protected PiwigoException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
public PiwigoException(string? message) : base(message)
{
}
public PiwigoException(string? message, Exception? innerException) : base(message, innerException)
{
}
}

View File

@ -0,0 +1,17 @@
using Flurl.Http.Content;
namespace Piwigo.Client;
internal static class PiwigoMethods
{
public static CapturedMultipartContent PiwigoLogin(this CapturedMultipartContent part, string username,
string password)
{
return part.AddMethod("pwg.session.login").AddString("username", username).AddString("password", password);
}
public static CapturedMultipartContent PiwigoLogout(this CapturedMultipartContent part)
{
return part.AddMethod("pwg.session.logout");
}
}

View File

@ -1,2 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">Required</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">Required</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Piwigo/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>