The dev multi-instance use case (one instance screen-capturing the other for
marketing/training videos) exposed a real bug. BuildStartPath in
MainViewModel.Recording.cs builds the start name with no dedup:
var name = RecordingFile.BuildStartName(DateTime.Now) + ".mp4";
var path = Path.Combine(folder, name);
Only FinalizeRecordingAsync calls UniquePath. So two instances that start
recording in the same minute computed the identical path
(ty-20260927-1454-0000.mp4) and both ffmpeg processes wrote one file -- a
corrupt take, not an error message.
Creator's fix (2026-09-27): add a millisecond count to the filename timestamp.
New format is ty-<yyyyMMdd>-<HHmm>-<fff>-<hh2mm2>.mp4. Both the start name and
the final name derive from _recordStartTime, so the ffff segment survives the
rename-on-stop and the final name cannot collide with another same-minute take
either. No UniquePath call needed, which keeps the start path a pure function of
the timestamp and the existing collision suffix behaviour intact.
Tests: 2 new (the same-minute guard and the rename-survival guard), 5 existing
assertions updated for the format. RecordingSaveDialogTests' stem fixtures
updated to match. 369/369 pass, 0 warnings.
Note: the first test run reported 367/367 because only ytLive.csproj had been
built -- the stale test DLL never picked up the new tests. The real count is 369.
75 lines
2.7 KiB
C#
75 lines
2.7 KiB
C#
using Xunit;
|
|
using ytLive.Services;
|
|
|
|
namespace ytLive.Tests;
|
|
|
|
/// <summary>
|
|
/// Local recording filename rules (TASK 18):
|
|
/// <c>ty-<yyyyMMdd>-<HHmm start>-<fff>-<hh2mm2 length>.mp4</c>.
|
|
/// Recordings start with a <c>0000</c> length placeholder and are renamed on stop.
|
|
/// </summary>
|
|
public class RecordingFileTests
|
|
{
|
|
/// <summary>Two instances started in the same minute must not compute the same
|
|
/// path, or both ffmpeg processes write one file and the take is lost. The
|
|
/// millisecond segment is what separates them (dev multi-instance, recording
|
|
/// one instance with the other).</summary>
|
|
[Fact]
|
|
public void TwoStartsInTheSameMinute_ProduceDifferentNames()
|
|
{
|
|
var first = new DateTime(2026, 9, 27, 14, 54, 0, 123);
|
|
var second = new DateTime(2026, 9, 27, 14, 54, 0, 456);
|
|
|
|
Assert.NotEqual(RecordingFile.BuildStartName(first), RecordingFile.BuildStartName(second));
|
|
}
|
|
|
|
/// <summary>The ms segment must also survive the rename-on-stop, or the final
|
|
/// name could collide with a different take started in the same minute.</summary>
|
|
[Fact]
|
|
public void MillisecondsSurviveTheRenameOnStop()
|
|
{
|
|
var start = new DateTime(2026, 9, 27, 14, 54, 0, 123);
|
|
var name = RecordingFile.BuildFinalName(start, TimeSpan.FromMinutes(5));
|
|
|
|
Assert.Equal("ty-20260927-1454-123-0005", name);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildStartName_UsesDateHourAndMinute_WithZeroLength()
|
|
{
|
|
var start = new DateTime(2026, 8, 29, 14, 5, 0);
|
|
Assert.Equal("ty-20260829-1405-000-0000", RecordingFile.BuildStartName(start));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildStartName_PadsSingleDigitHourAndMinute()
|
|
{
|
|
var start = new DateTime(2026, 1, 2, 9, 7, 0);
|
|
Assert.Equal("ty-20260102-0907-000-0000", RecordingFile.BuildStartName(start));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildFinalName_ShortRecording_IsHhmmOfLength()
|
|
{
|
|
var start = new DateTime(2026, 8, 29, 14, 5, 0);
|
|
var length = TimeSpan.FromMinutes(2);
|
|
Assert.Equal("ty-20260829-1405-000-0002", RecordingFile.BuildFinalName(start, length));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildFinalName_HourAndMinutes_ConcatenatesHh2Mm2()
|
|
{
|
|
var start = new DateTime(2026, 8, 29, 14, 5, 0);
|
|
var length = new TimeSpan(1, 15, 0); // 1h 15m
|
|
Assert.Equal("ty-20260829-1405-000-0115", RecordingFile.BuildFinalName(start, length));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildFinalName_OverFlowHours_ShowsTotalHours()
|
|
{
|
|
var start = new DateTime(2026, 8, 29, 14, 5, 0);
|
|
var length = new TimeSpan(12, 30, 0); // 12h 30m
|
|
Assert.Equal("ty-20260829-1405-000-1230", RecordingFile.BuildFinalName(start, length));
|
|
}
|
|
}
|