Files
Telegram-Panel/tests/TelegramPanel.Web.Tests/EntrypointScriptTests.cs

385 lines
14 KiB
C#

using System.Diagnostics;
using Xunit;
namespace TelegramPanel.Web.Tests;
public sealed class EntrypointScriptTests
{
[Fact]
public void Entrypoint_RollsBackAttemptedUnconfirmedVersion()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
var previous = Path.Combine(data, "app-previous");
Directory.CreateDirectory(imageApp);
CreateRunnableVersion(current, "bad");
CreateRunnableVersion(previous, "good");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-attempted"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(current), File.ReadAllText(resultPath).Trim());
Assert.Equal("good", File.ReadAllText(Path.Combine(current, "version.txt")));
Assert.False(Directory.Exists(previous));
var failed = Assert.Single(Directory.GetDirectories(data, "app-failed-*"));
Assert.Equal("bad", File.ReadAllText(Path.Combine(failed, "version.txt")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_MarksPendingVersionAttemptedBeforeLaunching()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
Directory.CreateDirectory(imageApp);
CreateRunnableVersion(current, "new");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-pending"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(current), File.ReadAllText(resultPath).Trim());
Assert.False(File.Exists(Path.Combine(current, ".telegram-panel-update-pending")));
Assert.True(File.Exists(Path.Combine(current, ".telegram-panel-update-attempted")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_FallsBackToImageVersionWhenNoBackupExists()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
CreateRunnableVersion(imageApp, "image");
CreateRunnableVersion(current, "bad");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-attempted"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(imageApp), File.ReadAllText(resultPath).Trim());
Assert.False(Directory.Exists(current));
var failed = Assert.Single(Directory.GetDirectories(data, "app-failed-*"));
Assert.Equal("bad", File.ReadAllText(Path.Combine(failed, "version.txt")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_MigratesLegacyMarkerForPackageWithNewEntrypoint()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
Directory.CreateDirectory(imageApp);
CreateRunnableVersion(current, "new");
File.WriteAllText(Path.Combine(current, ".telegram-panel-self-update"), "{}");
var packagedEntrypoint = Path.Combine(current, "self-update", "entrypoint.sh");
Directory.CreateDirectory(Path.GetDirectoryName(packagedEntrypoint)!);
File.WriteAllText(packagedEntrypoint, "#!/usr/bin/env sh\n");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(current), File.ReadAllText(resultPath).Trim());
Assert.True(File.Exists(Path.Combine(current, ".telegram-panel-update-attempted")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_PrefersNewerImageOverConfirmedOlderSelfUpdate()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
Directory.CreateDirectory(imageApp);
CreateRunnableVersion(imageApp, "1.31.38");
File.WriteAllText(
Path.Combine(imageApp, "version.txt"),
"1.31.38",
new System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
CreateRunnableVersion(current, "1.31.37");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-confirmed"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(imageApp), File.ReadAllText(resultPath).Trim());
Assert.False(Directory.Exists(current));
var obsolete = Assert.Single(Directory.GetDirectories(data, "app-obsolete-*"));
Assert.Equal("1.31.37", File.ReadAllText(Path.Combine(obsolete, "version.txt")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_PrefersNewerImageOverLegacyOlderSelfUpdate()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
CreateRunnableVersion(imageApp, "1.31.38");
CreateRunnableVersion(current, "1.31.37");
File.WriteAllText(Path.Combine(current, ".telegram-panel-self-update"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(imageApp), File.ReadAllText(resultPath).Trim());
Assert.False(Directory.Exists(current));
var obsolete = Assert.Single(Directory.GetDirectories(data, "app-obsolete-*"));
Assert.Equal("1.31.37", File.ReadAllText(Path.Combine(obsolete, "version.txt")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_PrefersVersionedImageWhenLegacySelfUpdateHasNoVersionFile()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
CreateRunnableVersion(imageApp, "1.31.38");
CreateRunnableVersion(current, "1.31.37");
File.Delete(Path.Combine(current, "version.txt"));
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-confirmed"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(imageApp), File.ReadAllText(resultPath).Trim());
Assert.False(Directory.Exists(current));
var obsolete = Assert.Single(Directory.GetDirectories(data, "app-obsolete-*"));
Assert.False(File.Exists(Path.Combine(obsolete, "version.txt")));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_PrefersNewerConfirmedSelfUpdateOverOlderImage()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
Directory.CreateDirectory(imageApp);
CreateRunnableVersion(imageApp, "1.31.37");
CreateRunnableVersion(current, "1.31.38");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-confirmed"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath);
Assert.Equal(Path.GetFullPath(current), File.ReadAllText(resultPath).Trim());
Assert.True(Directory.Exists(current));
Assert.Empty(Directory.GetDirectories(data, "app-obsolete-*"));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_ImageModeAlwaysUsesImageDirectory()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
CreateRunnableVersion(imageApp, "1.31.37");
CreateRunnableVersion(current, "1.31.38");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-confirmed"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath, "image");
Assert.Equal(Path.GetFullPath(imageApp), File.ReadAllText(resultPath).Trim());
Assert.True(Directory.Exists(current));
}
finally
{
TryDeleteDirectory(root);
}
}
[Fact]
public void Entrypoint_BinaryModeAlwaysUsesConfirmedBinaryDirectory()
{
if (!OperatingSystem.IsLinux())
return;
var root = CreateTempDirectory();
try
{
var data = Path.Combine(root, "data");
var imageApp = Path.Combine(root, "app");
var current = Path.Combine(data, "app-current");
CreateRunnableVersion(imageApp, "1.31.38");
CreateRunnableVersion(current, "1.31.37");
File.WriteAllText(Path.Combine(current, ".telegram-panel-update-confirmed"), "{}");
var resultPath = Path.Combine(root, "started-from.txt");
RunEntrypoint(data, imageApp, resultPath, "binary");
Assert.Equal(Path.GetFullPath(current), File.ReadAllText(resultPath).Trim());
}
finally
{
TryDeleteDirectory(root);
}
}
private static void RunEntrypoint(string data, string imageApp, string resultPath, string? updateMode = null)
{
var scriptPath = FindRepositoryFile(Path.Combine("docker", "entrypoint.sh"));
var startInfo = new ProcessStartInfo("/bin/sh")
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true
};
startInfo.ArgumentList.Add(scriptPath);
startInfo.Environment["TELEGRAM_PANEL_DATA_DIR"] = data;
startInfo.Environment["TELEGRAM_PANEL_DEFAULT_APP_DIR"] = imageApp;
startInfo.Environment["TELEGRAM_PANEL_DOTNET_COMMAND"] = "/bin/sh";
startInfo.Environment["TELEGRAM_PANEL_TEST_RESULT"] = resultPath;
if (!string.IsNullOrWhiteSpace(updateMode))
startInfo.Environment["TELEGRAM_PANEL_UPDATE_MODE"] = updateMode;
using var process = Process.Start(startInfo)
?? throw new InvalidOperationException("无法启动入口脚本测试进程");
if (!process.WaitForExit(10_000))
{
process.Kill(entireProcessTree: true);
throw new TimeoutException("入口脚本测试超过 10 秒");
}
var standardError = process.StandardError.ReadToEnd();
Assert.True(process.ExitCode == 0, $"入口脚本退出码:{process.ExitCode}\n{standardError}");
}
private static void CreateRunnableVersion(string directory, string version)
{
Directory.CreateDirectory(directory);
File.WriteAllText(Path.Combine(directory, "version.txt"), version);
File.WriteAllText(
Path.Combine(directory, "TelegramPanel.Web.dll"),
"printf '%s' \"$PWD\" > \"$TELEGRAM_PANEL_TEST_RESULT\"\n");
}
private static string FindRepositoryFile(string relativePath)
{
for (var directory = new DirectoryInfo(AppContext.BaseDirectory);
directory != null;
directory = directory.Parent)
{
var candidate = Path.Combine(directory.FullName, relativePath);
if (File.Exists(candidate))
return candidate;
}
throw new FileNotFoundException($"未找到仓库文件:{relativePath}");
}
private static string CreateTempDirectory()
{
var path = Path.Combine(Path.GetTempPath(), "telegram-panel-entrypoint-tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(path);
return path;
}
private static void TryDeleteDirectory(string path)
{
try
{
if (Directory.Exists(path))
Directory.Delete(path, recursive: true);
}
catch
{
// 测试清理失败不应掩盖断言结果。
}
}
}