feat: add fnOS packaging, storage workflows and release pipeline
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using dy.net.model.dto;
|
||||
using dy.net.model.entity;
|
||||
using dy.net.service;
|
||||
using dy.net.storage;
|
||||
using dy.net.Tests.TestInfrastructure;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using SqlSugar;
|
||||
using MediaStorageType = dy.net.model.dto.StorageType;
|
||||
|
||||
namespace dy.net.Tests;
|
||||
|
||||
public sealed class OpenListDirectoryRepairServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Preflight_OnlyIncludesExactTimestampDirectoriesAndKeepsCanonicalDirectory()
|
||||
{
|
||||
using var temporary = new TemporaryDirectory();
|
||||
using var database = new SqlSugarClient(new ConnectionConfig
|
||||
{
|
||||
ConnectionString = $"DataSource={Path.Combine(temporary.Path, "repair.sqlite")}",
|
||||
DbType = DbType.Sqlite,
|
||||
InitKeyType = InitKeyType.Attribute,
|
||||
IsAutoCloseConnection = true
|
||||
});
|
||||
database.CodeFirst.InitTables<AppConfig, WebDavSettings, OpenListSettings,
|
||||
OpenListDirectoryRepairTask, OpenListDirectoryRepairItem>();
|
||||
await database.Insertable(new AppConfig
|
||||
{
|
||||
Id = "config",
|
||||
StorageType = MediaStorageType.OpenList,
|
||||
FollowedTitleTemplate = string.Empty,
|
||||
FollowedTitleSeparator = string.Empty,
|
||||
FullFollowedTitleTemplate = string.Empty
|
||||
}).ExecuteCommandAsync();
|
||||
|
||||
var provider = DataProtectionProvider.Create(new DirectoryInfo(Path.Combine(temporary.Path, "keys")),
|
||||
builder => builder.SetApplicationName("dysync.net"));
|
||||
var legacy = new WebDavSettingsService(database, provider);
|
||||
var settingsService = new OpenListSettingsService(database, provider, legacy);
|
||||
var settings = await settingsService.BuildCandidateAsync(new OpenListTestRequest
|
||||
{
|
||||
Endpoint = "https://openlist.example.test",
|
||||
BasePath = "/归档",
|
||||
SourcePath = "/源挂载",
|
||||
LocalStagingPath = Path.Combine(temporary.Path, "staging"),
|
||||
UserName = "account",
|
||||
Password = "password"
|
||||
});
|
||||
await settingsService.SaveAsync(settings, true, "tested");
|
||||
|
||||
using var factory = new RepairHttpClientFactory();
|
||||
var service = new OpenListDirectoryRepairService(database, new DouyinCommonService(database),
|
||||
settingsService, new OpenListClient(factory));
|
||||
|
||||
var preflight = await service.PreflightAsync(
|
||||
new OpenListDirectoryRepairPreflightRequest { LogicalPath = "/collect/Kk" },
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(preflight.CanStart);
|
||||
Assert.Equal("/collect/Kk", preflight.RequestedPath);
|
||||
Assert.Equal("/collect/KK", preflight.CanonicalPath);
|
||||
Assert.Equal(1, preflight.CandidateCount);
|
||||
|
||||
var created = await service.CreateAsync(new CreateOpenListDirectoryRepairRequest
|
||||
{
|
||||
LogicalPath = preflight.RequestedPath,
|
||||
ConfigurationFingerprint = preflight.ConfigurationFingerprint
|
||||
}, CancellationToken.None);
|
||||
var items = await database.Queryable<OpenListDirectoryRepairItem>().ToListAsync();
|
||||
|
||||
Assert.Equal(1, created.Task.TotalCount);
|
||||
var item = Assert.Single(items);
|
||||
Assert.Equal("Kk_20260809_123456", item.DirectoryName);
|
||||
Assert.DoesNotContain(items, x => x.DirectoryName == "KK");
|
||||
}
|
||||
|
||||
private sealed class RepairHttpClientFactory : IHttpClientFactory, IDisposable
|
||||
{
|
||||
private readonly Handler _handler = new();
|
||||
|
||||
public HttpClient CreateClient(string name) => new(_handler, disposeHandler: false);
|
||||
|
||||
public void Dispose() => _handler.Dispose();
|
||||
}
|
||||
|
||||
private sealed class Handler : HttpMessageHandler
|
||||
{
|
||||
protected override async Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var api = request.RequestUri!.AbsolutePath;
|
||||
if (api.EndsWith("/api/auth/login", StringComparison.Ordinal))
|
||||
return Json(new { code = 200, message = "success", data = new { token = "test-token" } });
|
||||
|
||||
var body = JsonDocument.Parse(await request.Content!.ReadAsStringAsync(cancellationToken)).RootElement;
|
||||
var path = body.GetProperty("path").GetString();
|
||||
if (api.EndsWith("/api/fs/get", StringComparison.Ordinal))
|
||||
{
|
||||
if (path == "/归档/collect/KK")
|
||||
return Json(new { code = 200, message = "success", data = new { name = "KK", size = 0, is_dir = true, hash_info = new { } } });
|
||||
return Json(new { code = 404, message = "object not found", data = (object)null });
|
||||
}
|
||||
|
||||
Assert.EndsWith("/api/fs/list", api, StringComparison.Ordinal);
|
||||
var entries = path switch
|
||||
{
|
||||
"/" => new[] { Entry("归档", true) },
|
||||
"/归档" => new[] { Entry("collect", true) },
|
||||
"/归档/collect" => new[]
|
||||
{
|
||||
Entry("KK", true),
|
||||
Entry("Kk_20260809_123456", true),
|
||||
Entry("Kk_20260809_12345", true),
|
||||
Entry("Kk_20260809_123456_extra", true),
|
||||
Entry("KK_20260809_123456", true),
|
||||
Entry("Kkkk_20260809_123456", true),
|
||||
Entry("Kk_20260810_123456", false)
|
||||
},
|
||||
_ => Array.Empty<object>()
|
||||
};
|
||||
return Json(new { code = 200, message = "success", data = new { content = entries, write = true } });
|
||||
}
|
||||
|
||||
private static object Entry(string name, bool directory) => new { name, size = 0, is_dir = directory };
|
||||
|
||||
private static HttpResponseMessage Json(object value) => new(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(JsonSerializer.Serialize(value), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user