Initial Commit
This commit is contained in:
commit
86fc06b877
37 changed files with 3424 additions and 0 deletions
40
tests/Decky.Core.Tests/CardUpdateRequestTests.cs
Normal file
40
tests/Decky.Core.Tests/CardUpdateRequestTests.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System.Text.Json;
|
||||
using Decky.Core.Api.Models;
|
||||
using Decky.Core.Api.Requests;
|
||||
|
||||
namespace Decky.Core.Tests;
|
||||
|
||||
public sealed class CardUpdateRequestTests
|
||||
{
|
||||
[Fact]
|
||||
public void FromCard_preserves_fields_and_serializes_explicit_null_done()
|
||||
{
|
||||
var card = new CardDto
|
||||
{
|
||||
Id = 7,
|
||||
StackId = 3,
|
||||
Title = "Original",
|
||||
Description = "Details",
|
||||
Type = "plain",
|
||||
Owner = "elijah",
|
||||
Order = 42,
|
||||
Archived = false,
|
||||
Done = DateTimeOffset.Parse("2026-07-19T12:00:00Z"),
|
||||
DueDate = DateTimeOffset.Parse("2026-07-20T12:00:00Z"),
|
||||
StartDate = DateTimeOffset.Parse("2026-07-18T12:00:00Z"),
|
||||
Color = "d99080",
|
||||
};
|
||||
|
||||
var request = CardUpdateRequest.FromCard(card, title: "Changed", done: null, replaceDone: true);
|
||||
var json = JsonSerializer.Serialize(request, new JsonSerializerOptions(JsonSerializerDefaults.Web));
|
||||
using var document = JsonDocument.Parse(json);
|
||||
var root = document.RootElement;
|
||||
|
||||
Assert.Equal("Changed", root.GetProperty("title").GetString());
|
||||
Assert.Equal("elijah", root.GetProperty("owner").GetString());
|
||||
Assert.Equal("D99080", root.GetProperty("color").GetString());
|
||||
Assert.Equal("2026-07-20T12:00:00+00:00", root.GetProperty("duedate").GetString());
|
||||
Assert.Equal(JsonValueKind.Null, root.GetProperty("done").ValueKind);
|
||||
}
|
||||
}
|
||||
|
||||
90
tests/Decky.Core.Tests/DeckApiClientTests.cs
Normal file
90
tests/Decky.Core.Tests/DeckApiClientTests.cs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
using System.Net;
|
||||
using System.Text;
|
||||
using Decky.Core.Api;
|
||||
using Decky.Core.Configuration;
|
||||
|
||||
namespace Decky.Core.Tests;
|
||||
|
||||
public sealed class DeckApiClientTests
|
||||
{
|
||||
[Fact]
|
||||
public void Connection_options_reject_insecure_remote_servers()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentException>(() =>
|
||||
DeckConnectionOptions.Create("http://cloud.example.test", "elijah", "app-password"));
|
||||
|
||||
Assert.Contains("HTTPS", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetBoardsAsync_sends_required_headers_and_deserializes_response()
|
||||
{
|
||||
HttpRequestMessage? capturedRequest = null;
|
||||
var handler = new StubHttpMessageHandler(request =>
|
||||
{
|
||||
capturedRequest = CloneRequest(request);
|
||||
return JsonResponse("[{\"id\":10,\"title\":\"Homework\",\"color\":\"ff0000\"}]");
|
||||
});
|
||||
using var httpClient = new HttpClient(handler);
|
||||
var options = DeckConnectionOptions.Create("https://cloud.example.test", "elijah", "app-password");
|
||||
var client = new DeckApiClient(
|
||||
httpClient,
|
||||
options,
|
||||
new Uri("https://cloud.example.test/index.php/apps/deck/api/v1.0/"));
|
||||
|
||||
var boards = await client.GetBoardsAsync();
|
||||
|
||||
Assert.Single(boards);
|
||||
Assert.Equal("Homework", boards[0].Title);
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.Equal(
|
||||
"https://cloud.example.test/index.php/apps/deck/api/v1.0/boards",
|
||||
capturedRequest.RequestUri!.ToString());
|
||||
Assert.Equal("true", capturedRequest.Headers.GetValues("OCS-APIRequest").Single());
|
||||
Assert.Equal("Basic", capturedRequest.Headers.Authorization!.Scheme);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConnectAsync_falls_back_to_rewritten_path_after_not_found()
|
||||
{
|
||||
var requestedPaths = new List<string>();
|
||||
var handler = new StubHttpMessageHandler(request =>
|
||||
{
|
||||
requestedPaths.Add(request.RequestUri!.AbsolutePath);
|
||||
return request.RequestUri.AbsolutePath.Contains("/index.php/", StringComparison.Ordinal)
|
||||
? new HttpResponseMessage(HttpStatusCode.NotFound)
|
||||
: JsonResponse("[]");
|
||||
});
|
||||
using var httpClient = new HttpClient(handler);
|
||||
var options = DeckConnectionOptions.Create("https://cloud.example.test", "elijah", "app-password");
|
||||
|
||||
var client = await DeckApiClient.ConnectAsync(httpClient, options);
|
||||
|
||||
Assert.Equal("https://cloud.example.test/apps/deck/api/v1.0/", client.ApiBaseUri.ToString());
|
||||
Assert.Equal(2, requestedPaths.Count);
|
||||
}
|
||||
|
||||
private static HttpResponseMessage JsonResponse(string json) => new(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json"),
|
||||
};
|
||||
|
||||
private static HttpRequestMessage CloneRequest(HttpRequestMessage request)
|
||||
{
|
||||
var clone = new HttpRequestMessage(request.Method, request.RequestUri);
|
||||
foreach (var header in request.Headers)
|
||||
{
|
||||
clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
private sealed class StubHttpMessageHandler(
|
||||
Func<HttpRequestMessage, HttpResponseMessage> responseFactory) : HttpMessageHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken) => Task.FromResult(responseFactory(request));
|
||||
}
|
||||
}
|
||||
24
tests/Decky.Core.Tests/DeckColorTests.cs
Normal file
24
tests/Decky.Core.Tests/DeckColorTests.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Decky.Core.Utilities;
|
||||
|
||||
namespace Decky.Core.Tests;
|
||||
|
||||
public sealed class DeckColorTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("#d99080", "D99080")]
|
||||
[InlineData(" 7FBC9F ", "7FBC9F")]
|
||||
public void Normalize_returns_api_hex(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, DeckColor.Normalize(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("FFFFFF", true)]
|
||||
[InlineData("000000", false)]
|
||||
[InlineData("D99080", true)]
|
||||
public void UseDarkText_uses_relative_luminance(string color, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, DeckColor.UseDarkText(color));
|
||||
}
|
||||
}
|
||||
|
||||
19
tests/Decky.Core.Tests/Decky.Core.Tests.csproj
Normal file
19
tests/Decky.Core.Tests/Decky.Core.Tests.csproj
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Decky.Core\Decky.Core.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
75
tests/Decky.Core.Tests/JsonSettingsServiceTests.cs
Normal file
75
tests/Decky.Core.Tests/JsonSettingsServiceTests.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using Decky.Core.Models;
|
||||
using Decky.Core.Services;
|
||||
|
||||
namespace Decky.Core.Tests;
|
||||
|
||||
public sealed class JsonSettingsServiceTests : IDisposable
|
||||
{
|
||||
private readonly string _testDirectory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
$"Decky.Tests.{Guid.NewGuid():N}");
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_returns_defaults_when_file_does_not_exist()
|
||||
{
|
||||
using var service = CreateService();
|
||||
|
||||
var settings = await service.LoadAsync();
|
||||
|
||||
Assert.Equal(30, settings.RefreshIntervalSeconds);
|
||||
Assert.Empty(settings.OpenBoardTabs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveAsync_round_trips_connection_and_board_settings()
|
||||
{
|
||||
using var service = CreateService();
|
||||
var expected = new AppSettings
|
||||
{
|
||||
ServerUrl = "https://cloud.example.test/",
|
||||
Username = "elijah",
|
||||
ApiPath = "index.php/apps/deck/api/v1.0/",
|
||||
OpenBoardTabs = [17],
|
||||
Boards = new Dictionary<int, BoardViewSettings>
|
||||
{
|
||||
[17] = new() { Orientation = BoardOrientation.Vertical },
|
||||
},
|
||||
};
|
||||
|
||||
await service.SaveAsync(expected);
|
||||
var actual = await service.LoadAsync();
|
||||
|
||||
Assert.Equal(expected.ServerUrl, actual.ServerUrl);
|
||||
Assert.Equal(expected.Username, actual.Username);
|
||||
Assert.Equal(expected.ApiPath, actual.ApiPath);
|
||||
Assert.Equal([17], actual.OpenBoardTabs);
|
||||
Assert.Equal(BoardOrientation.Vertical, actual.Boards[17].Orientation);
|
||||
Assert.DoesNotContain("password", await File.ReadAllTextAsync(GetSettingsPath()), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoadAsync_preserves_corrupt_file_and_returns_defaults()
|
||||
{
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
await File.WriteAllTextAsync(GetSettingsPath(), "{ definitely not json }");
|
||||
using var service = CreateService();
|
||||
|
||||
var settings = await service.LoadAsync();
|
||||
|
||||
Assert.Equal(string.Empty, settings.ServerUrl);
|
||||
Assert.False(File.Exists(GetSettingsPath()));
|
||||
Assert.Single(Directory.GetFiles(_testDirectory, "settings.corrupt-*.json"));
|
||||
}
|
||||
|
||||
private JsonSettingsService CreateService() => new(GetSettingsPath());
|
||||
|
||||
private string GetSettingsPath() => Path.Combine(_testDirectory, "settings.json");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_testDirectory))
|
||||
{
|
||||
Directory.Delete(_testDirectory, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
tests/Decky.Core.Tests/Usings.cs
Normal file
1
tests/Decky.Core.Tests/Usings.cs
Normal file
|
|
@ -0,0 +1 @@
|
|||
global using Xunit;
|
||||
Loading…
Add table
Add a link
Reference in a new issue