595 green, 0 warnings. Completes TASK 55 (slice 1, log rotation, shipped 2026-10-22).
- **`AlertClipCleanup`**: the default alert clip is materialised into `%TEMP%` for ffmpeg to read, and
nothing ever deleted it — one orphan per profile that ever played an alert. Now: delete this session's
file in `Shutdown`, and sweep `ytLive-alert-*.mp4` at startup for anything a crash left behind. Only
the prefix we own is touched; a directory is never deleted.
- **`DiskSpaceGuard`**: a recording that dies of a full disk is a trust breach, so the numbers are a
pure policy — refuse to start under **1 GB** (toast; the stream still starts, recording does not),
warn under **5 GB** at start ("about eighty minutes" at 8 Mbps ≈ 3.6 GB/h), and one mid-show warning
under **2 GB** — the last useful moment before the encoder fails for a reason it won't explain.
Unreadable drives (a USB stick that just left) return null and never block a running recording.
Tests are policy-level on purpose: filling a real drive is not a test. `DiskHygieneTests` proves the
sweep only removes our own files and that the thresholds behave at both ends.
71 lines
2.5 KiB
C#
71 lines
2.5 KiB
C#
using System;
|
||
using System.IO;
|
||
using Xunit;
|
||
using ytLive.Services;
|
||
|
||
namespace ytLive.Tests;
|
||
|
||
/// <summary>
|
||
/// TASK 55·2–3 — the two disk debts a free app must not leave: the temp alert clip it materialises and
|
||
/// never deletes, and a recording that starts with no room to finish.
|
||
///
|
||
/// <para>Both are tested as policy, because the alternative — filling a real drive — is not a test.</para>
|
||
/// </summary>
|
||
public sealed class DiskHygieneTests : IDisposable
|
||
{
|
||
private readonly string _dir = Path.Combine(Path.GetTempPath(), $"ytLlive-disk-{Guid.NewGuid():N}");
|
||
|
||
public DiskHygieneTests() => Directory.CreateDirectory(_dir);
|
||
|
||
public void Dispose()
|
||
{
|
||
try { Directory.Delete(_dir, recursive: true); } catch { /* best effort */ }
|
||
}
|
||
|
||
[Fact]
|
||
public void Sweep_RemovesOnlyTheClipsWeCreated()
|
||
{
|
||
var ours1 = Path.Combine(_dir, "ytLive-alert-abc.mp4");
|
||
var ours2 = Path.Combine(_dir, "ytLive-alert-def.mp4");
|
||
var theirs = Path.Combine(_dir, "someone-elses-recording.mp4");
|
||
File.WriteAllText(ours1, "x");
|
||
File.WriteAllText(ours2, "x");
|
||
File.WriteAllText(theirs, "x");
|
||
|
||
var removed = AlertClipCleanup.Sweep(_dir);
|
||
|
||
Assert.Equal(2, removed);
|
||
Assert.False(File.Exists(ours1));
|
||
Assert.False(File.Exists(ours2));
|
||
Assert.True(File.Exists(theirs)); // never touch anything that isn't ours
|
||
}
|
||
|
||
[Fact]
|
||
public void Delete_IsSafeOnMissingBlankAndNull()
|
||
{
|
||
Assert.False(AlertClipCleanup.Delete(null));
|
||
Assert.False(AlertClipCleanup.Delete(""));
|
||
Assert.False(AlertClipCleanup.Delete(Path.Combine(_dir, "not-there.mp4")));
|
||
|
||
var file = Path.Combine(_dir, "ytLive-alert-gone.mp4");
|
||
File.WriteAllText(file, "x");
|
||
Assert.True(AlertClipCleanup.Delete(file));
|
||
Assert.False(File.Exists(file));
|
||
}
|
||
|
||
[Fact]
|
||
public void RecordingIsRefusedBelowAGigabyte_WarnedBelowFive_AndQuietAbove()
|
||
{
|
||
Assert.Equal(DiskSpaceGuard.StartDecision.Block, DiskSpaceGuard.ForStart(500L * 1024 * 1024));
|
||
Assert.Equal(DiskSpaceGuard.StartDecision.Warn, DiskSpaceGuard.ForStart(2L * 1024 * 1024 * 1024));
|
||
Assert.Equal(DiskSpaceGuard.StartDecision.Ok, DiskSpaceGuard.ForStart(20L * 1024 * 1024 * 1024));
|
||
}
|
||
|
||
[Fact]
|
||
public void MidRecordingWarnsOnceBelowTwoGigabytes()
|
||
{
|
||
Assert.False(DiskSpaceGuard.ShouldWarnDuring(3L * 1024 * 1024 * 1024));
|
||
Assert.True(DiskSpaceGuard.ShouldWarnDuring(1L * 1024 * 1024 * 1024));
|
||
}
|
||
}
|