using System.Diagnostics;
namespace VoiceCat.Tests;
public class PublishServerScriptTests
{
[Theory]
[InlineData("clients/windows/publish-client.ps1")]
[InlineData("scripts/publish-server.ps1")]
public async Task PublishUsesRegeneratedIntermediateRuntimeLock(string relativePath)
{
string script = await File.ReadAllTextAsync(Path.Combine(FindRoot(), relativePath.Replace('/', Path.DirectorySeparatorChar)));
Assert.Contains("NuGetLockFilePath=obj/packages.publish.", script);
Assert.Contains("RestoreLockedMode=false", script);
Assert.DoesNotContain("NuGetLockFilePath=packages.publish.", script);
}
[Fact]
public async Task LicenseAuditIgnoresGeneratedBuildLocks()
{
string script = await File.ReadAllTextAsync(Path.Combine(FindRoot(), "scripts", "check-licenses.ps1"));
Assert.Contains("(?:bin|obj)", script);
}
[Fact]
public async Task MacPublishPreservesAppEntitlementsAndReplacesBrokenWorkloadBundle()
{
string root = FindRoot();
string script = await File.ReadAllTextAsync(Path.Combine(root, "clients", "apple", "publish-macos.sh"));
string entitlements = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.Mac", "VoiceCat.Mac.entitlements"));
Assert.Contains("sign_nested_code", script);
Assert.Contains("--entitlements \"$entitlements\"", script);
Assert.Contains("install_verified_primary_bundle", script);
Assert.DoesNotContain("codesign --force --deep", script);
Assert.Contains("com.apple.security.cs.allow-jit", entitlements);
}
[Fact]
public async Task IosDeviceBuildUsesAnIntermediateDeviceLockAndScopesKnownTrimWarnings()
{
string root = FindRoot();
string script = await File.ReadAllTextAsync(Path.Combine(root, "clients", "apple", "build-ios-device.sh"));
string project = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "VoiceCat.iOS.csproj"));
Assert.Contains("NuGetLockFilePath=\"$device_lock\"", script);
Assert.Contains("RestoreLockedMode=false", script);
Assert.Contains("$(WarningsNotAsErrors);IL2104", project);
Assert.DoesNotContain("false", project);
}
[Fact]
public async Task IosBroadcastExtensionPreservesSharedAppGroupAndSupportsDistributionSigning()
{
string root = FindRoot();
string entitlements = await File.ReadAllTextAsync(Path.Combine(
root, "native", "apple", "broadcast", "VoiceCatBroadcast.entitlements"));
string script = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "build-broadcast-extension.sh"));
Assert.Contains("com.apple.security.application-groups", entitlements);
Assert.Contains("group.me.iamtalon.voicecat", entitlements);
Assert.Contains("CODE_SIGN_STYLE=Manual", script);
Assert.Contains("PROVISIONING_PROFILE_SPECIFIER=\"$VOICECAT_BROADCAST_CODESIGN_PROVISION\"", script);
Assert.Contains("CURRENT_PROJECT_VERSION=\"$VOICECAT_BUILD_NUMBER\"", script);
Assert.Contains("MARKETING_VERSION=\"${VOICECAT_DISPLAY_VERSION:-0.0.1}\"", script);
string deviceScript = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "build-ios-device.sh"));
Assert.Contains("-p:ApplicationVersion=\"$VOICECAT_BUILD_NUMBER\"", deviceScript);
string appManifest = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "Info.plist"));
string extensionManifest = await File.ReadAllTextAsync(Path.Combine(
root, "native", "apple", "broadcast", "Info.plist"));
Assert.DoesNotContain("CFBundleVersion", appManifest);
Assert.Contains("$(CURRENT_PROJECT_VERSION)", extensionManifest);
}
[Fact]
public async Task IosScreenSharingDeclaresBackgroundCaptureAndKeepsReplayKitFallback()
{
string root = FindRoot();
string manifest = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "Info.plist"));
string settings = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "SettingsController.cs"));
Assert.Contains("screen-capture", manifest);
Assert.Contains("if (IosScreenCapture.IsAvailable)", settings);
Assert.Contains("new RPSystemBroadcastPickerView", settings);
Assert.DoesNotContain("if (OperatingSystem.IsIOSVersionAtLeast(27)) { model.ToggleScreenAudio();", settings);
}
[Fact]
public async Task IosCaptureDoesNotDependOnCoalescedManagedTimers()
{
string root = FindRoot();
string microphone = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "IosAudioEngine.cs"));
string screen = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.iOS", "BroadcastAudioPump.cs"));
Assert.DoesNotContain("PeriodicTimer", microphone);
Assert.Contains("owner.Audio.FeedPcm(route.StreamId", microphone);
Assert.Contains("new Thread(Run)", screen);
Assert.DoesNotContain("Task.Delay(10", screen);
Assert.Contains("MaximumCaptureCallbackFrames = 16_384", microphone);
Assert.DoesNotContain("Math.Ceiling(4_096 * 48_000", microphone);
}
[Fact]
public async Task IosAudioCommitsInputRouteBeforeChannelsAndVoiceProcessingBeforeConnections()
{
string root = FindRoot();
string router = await File.ReadAllTextAsync(Path.Combine(root, "clients", "apple", "VoiceCat.iOS", "IosAudioRouter.cs"));
string engine = await File.ReadAllTextAsync(Path.Combine(root, "clients", "apple", "VoiceCat.iOS", "IosAudioEngine.cs"));
Assert.True(router.IndexOf("ApplyInputSelection(session)", StringComparison.Ordinal) <
router.IndexOf("SetActive(true", StringComparison.Ordinal));
Assert.DoesNotContain("session.SetPreferredInputNumberOfChannels", router);
Assert.DoesNotContain("MaximumInputNumberOfChannels", router);
Assert.Contains("SelectedDataSourceId = null; SelectedPolarPattern = AVAudioDataSourcePolarPattern.Unknown", router);
Assert.Contains("ClearStereoPolarPattern(session)", router);
Assert.Contains("CaptureChannels == 2\n ? port.DataSources?.FirstOrDefault", router);
Assert.True(engine.IndexOf("SetVoiceProcessingEnabled", StringComparison.Ordinal) <
engine.IndexOf("next.Connect(source", StringComparison.Ordinal));
Assert.Contains("AVAudioEngine.ConfigurationChangeNotification", engine);
Assert.Contains("ReferenceEquals(engine, next) && !next.Running", engine);
}
[Fact]
public async Task DefaultPublishTargetsWindowsAndLinuxWhileRuntimeCanSelectOne()
{
string script = Path.Combine(FindRoot(), "scripts", "publish-server.ps1");
string allTargets = await RunWhatIf(script);
Assert.Contains("win-x64", allTargets);
Assert.Contains("linux-x64", allTargets);
string linuxOnly = await RunWhatIf(script, "-Runtime", "linux-x64");
Assert.Contains("linux-x64", linuxOnly);
Assert.DoesNotContain("win-x64", linuxOnly);
}
private static async Task RunWhatIf(string script, params string[] arguments)
{
string powerShell = OperatingSystem.IsWindows() ? "powershell.exe" : "pwsh";
var start = new ProcessStartInfo(powerShell)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
};
foreach (string argument in new[] { "-NoProfile", "-NonInteractive", "-File", script, "-WhatIf" }.Concat(arguments))
start.ArgumentList.Add(argument);
using Process process = Process.Start(start) ?? throw new InvalidOperationException("Could not start PowerShell.");
Task stdout = process.StandardOutput.ReadToEndAsync();
Task stderr = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
string output = await stdout + await stderr;
Assert.True(process.ExitCode == 0, output);
return output;
}
private static string FindRoot()
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "VoiceCat.slnx")))
directory = directory.Parent;
return directory?.FullName ?? throw new DirectoryNotFoundException("Repository root not found.");
}
}