feat: AudioMetadataReader
This commit is contained in:
Regular → Executable
@@ -1,133 +0,0 @@
|
||||
using FluentAssertions;
|
||||
|
||||
namespace OCES.Resonance.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// AudioLibraryService 集成测试
|
||||
/// </summary>
|
||||
public class AudioLibraryServiceTests : IDisposable
|
||||
{
|
||||
readonly AudioLibraryService _service;
|
||||
readonly string _testDirectory;
|
||||
|
||||
public AudioLibraryServiceTests()
|
||||
{
|
||||
_service = new AudioLibraryService();
|
||||
_testDirectory = Path.Combine(Path.GetTempPath(), $"LibraryServiceTest_{Guid.NewGuid()}");
|
||||
Directory.CreateDirectory(_testDirectory);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_testDirectory))
|
||||
{
|
||||
Directory.Delete(_testDirectory, true);
|
||||
}
|
||||
// 清理测试数据库
|
||||
var dbPath = "default.db";
|
||||
if (File.Exists(dbPath))
|
||||
{
|
||||
File.Delete(dbPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScanAndImportToLibrary_空目录_返回零总数()
|
||||
{
|
||||
// Act
|
||||
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||
|
||||
// Assert
|
||||
result.TotalFiles.Should().Be(0);
|
||||
result.SuccessCount.Should().Be(0);
|
||||
result.ErrorCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScanAndImportToLibrary_包含音频文件_成功导入()
|
||||
{
|
||||
// Arrange
|
||||
var wavFile = Path.Combine(_testDirectory, "test.wav");
|
||||
File.WriteAllText(wavFile, "fake wav content");
|
||||
|
||||
// Act
|
||||
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||
|
||||
// Assert
|
||||
result.TotalFiles.Should().Be(1);
|
||||
result.SuccessCount.Should().Be(1);
|
||||
result.ErrorCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScanAndImportToLibrary_多个文件_正确统计()
|
||||
{
|
||||
// Arrange
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "audio1.wav"), "fake");
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "audio2.mp3"), "fake");
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "audio3.flac"), "fake");
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "document.txt"), "not audio");
|
||||
|
||||
// Act
|
||||
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||
|
||||
// Assert
|
||||
result.TotalFiles.Should().Be(3);
|
||||
result.SuccessCount.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScanAndImportToLibrary_递归扫描子目录()
|
||||
{
|
||||
// Arrange
|
||||
var subDir = Path.Combine(_testDirectory, "subdir");
|
||||
Directory.CreateDirectory(subDir);
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "root.wav"), "fake");
|
||||
File.WriteAllText(Path.Combine(subDir, "sub.wav"), "fake");
|
||||
|
||||
// Act
|
||||
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||
|
||||
// Assert
|
||||
result.TotalFiles.Should().Be(2);
|
||||
result.SuccessCount.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanResult_GetSummary_返回正确格式()
|
||||
{
|
||||
// Arrange
|
||||
var result = new ScanResult
|
||||
{
|
||||
TotalFiles = 10,
|
||||
SuccessCount = 8,
|
||||
SkipCount = 1,
|
||||
ErrorCount = 1,
|
||||
};
|
||||
|
||||
// Act
|
||||
var summary = result.GetSummary();
|
||||
|
||||
// Assert
|
||||
summary.Should().Contain("10");
|
||||
summary.Should().Contain("8");
|
||||
summary.Should().Contain("1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ScanAndImportToLibrary_包含不支持格式_只导入音频()
|
||||
{
|
||||
// Arrange
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "audio.wav"), "fake");
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "image.jpg"), "fake");
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "doc.pdf"), "fake");
|
||||
File.WriteAllText(Path.Combine(_testDirectory, "text.txt"), "fake");
|
||||
|
||||
// Act
|
||||
var result = await _service.ScanAndImportToLibrary(_testDirectory);
|
||||
|
||||
// Assert
|
||||
result.TotalFiles.Should().Be(1);
|
||||
result.SuccessCount.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using System.Globalization;
|
||||
using OCES.Resonance.Core;
|
||||
|
||||
namespace Core.Tests;
|
||||
|
||||
public class AudioMetadataReaderTest
|
||||
{
|
||||
private static readonly string _dataSourceRoot = Path.GetFullPath(
|
||||
Path.Combine(AppContext.BaseDirectory, "../../../../../../data/source"));
|
||||
|
||||
private static string[] GetWavFiles(string vendor)
|
||||
{
|
||||
string dir = Path.Combine(_dataSourceRoot, vendor);
|
||||
return Directory.Exists(dir)
|
||||
? Directory.GetFiles(dir, "*.wav", SearchOption.AllDirectories)
|
||||
: [];
|
||||
}
|
||||
|
||||
private static string? GetAtlTxtPath(string wavPath)
|
||||
{
|
||||
string atlPath = Path.ChangeExtension(wavPath, ".atl.txt");
|
||||
return File.Exists(atlPath) ? atlPath : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 .atl.txt 文件,返回基础属性、元数据字段和额外字段的字典
|
||||
/// </summary>
|
||||
private static (Dictionary<string, string> basic, Dictionary<string, string> meta, Dictionary<string, string> extra)
|
||||
ParseAtlTxt(string atlPath)
|
||||
{
|
||||
var basic = new Dictionary<string, string>();
|
||||
var meta = new Dictionary<string, string>();
|
||||
var extra = new Dictionary<string, string>();
|
||||
|
||||
string[] lines = File.ReadAllLines(atlPath);
|
||||
string section = "";
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line.StartsWith("=== ATL Track 基础属性 ==="))
|
||||
{
|
||||
section = "basic";
|
||||
continue;
|
||||
}
|
||||
if (line.StartsWith("=== ATL 元数据字段 ==="))
|
||||
{
|
||||
section = "meta";
|
||||
continue;
|
||||
}
|
||||
if (line.StartsWith("=== ATL AdditionalFields"))
|
||||
{
|
||||
section = "extra";
|
||||
continue;
|
||||
}
|
||||
if (line.StartsWith("=== ATL AdditionalImages") || line.StartsWith("=== ATL Chapters"))
|
||||
{
|
||||
section = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (section == "extra" && line.StartsWith("["))
|
||||
{
|
||||
int eqIdx = line.IndexOf("] = ");
|
||||
if (eqIdx > 0)
|
||||
{
|
||||
string key = line[1..eqIdx];
|
||||
string value = line[(eqIdx + 4)..];
|
||||
extra[key] = value;
|
||||
}
|
||||
}
|
||||
else if ((section == "basic" || section == "meta") && line.Contains(": "))
|
||||
{
|
||||
int colonIdx = line.IndexOf(": ");
|
||||
if (colonIdx > 0)
|
||||
{
|
||||
string key = line[..colonIdx].Trim();
|
||||
string value = line[(colonIdx + 2)..].Trim();
|
||||
if (section == "basic")
|
||||
basic[key] = value;
|
||||
else
|
||||
meta[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (basic, meta, extra);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_ShouldThrow_FileNotFoundException()
|
||||
{
|
||||
AudioMetadataReader reader = new();
|
||||
FileInfo nonExistentFile = new(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
|
||||
|
||||
Assert.Throws<FileNotFoundException>(() => reader.ReadMetadata(nonExistentFile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boom 目录的 WAV 文件应包含完整的基础技术参数和富元数据
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadMetadata_ShouldPopulateAllFields_ForBoomFiles()
|
||||
{
|
||||
string[] wavFiles = GetWavFiles("Boom");
|
||||
if (wavFiles.Length == 0) return;
|
||||
|
||||
AudioMetadataReader reader = new();
|
||||
|
||||
foreach (string wavPath in wavFiles)
|
||||
{
|
||||
FileInfo file = new(wavPath);
|
||||
AudioFileMeta meta = reader.ReadMetadata(file);
|
||||
string? atlPath = GetAtlTxtPath(wavPath);
|
||||
|
||||
// 必备条目:基础技术参数
|
||||
Assert.NotEqual(0.0, meta.Duration);
|
||||
Assert.True(meta.BitDepth > 0);
|
||||
Assert.True(meta.Channels > 0);
|
||||
Assert.True(meta.SampleRate > 0);
|
||||
Assert.Equal("WAV", meta.Type);
|
||||
Assert.Equal(wavPath, meta.Path);
|
||||
Assert.Equal(file.Name, meta.Filename);
|
||||
Assert.Equal(file.Directory?.Name, meta.Folder);
|
||||
Assert.Equal(file.DirectoryName, meta.Directory);
|
||||
Assert.NotEmpty(meta.UniqueId);
|
||||
Assert.NotEmpty(meta.Md5);
|
||||
Assert.Equal(36, meta.UniqueId.Length); // UUID 格式
|
||||
Assert.Equal(32, meta.Md5.Length); // MD5 十六进制格式
|
||||
Assert.NotNull(meta.ChannelLayout);
|
||||
|
||||
// 文件系统时间
|
||||
Assert.Equal(file.LastWriteTime, meta.LastWriteTime);
|
||||
Assert.Equal(file.CreationTime, meta.CreationTime);
|
||||
|
||||
if (atlPath == null)
|
||||
continue;
|
||||
|
||||
var (basic, metaFields, extra) = ParseAtlTxt(atlPath);
|
||||
|
||||
// 基础技术参数与 ATL 一致
|
||||
Assert.Equal(int.Parse(basic["DurationMs"]) / 1000.0, meta.Duration);
|
||||
Assert.Equal(int.Parse(basic["BitDepth"]), meta.BitDepth);
|
||||
Assert.Equal(int.Parse(basic["Channels"]), meta.Channels);
|
||||
Assert.Equal(int.Parse(basic["SampleRate"]), (int)meta.SampleRate);
|
||||
Assert.Equal(basic["ChannelsArrangement"], meta.ChannelLayout);
|
||||
|
||||
// 标准元数据字段
|
||||
Assert.Equal(metaFields["Title"], meta.TrackTitle);
|
||||
Assert.Equal(metaFields["Artist"], meta.Artist);
|
||||
Assert.Equal(metaFields["Album"], meta.CdTitle);
|
||||
Assert.Equal(metaFields["Composer"], meta.Composer);
|
||||
Assert.Equal(metaFields["Comment"], meta.Description);
|
||||
Assert.Equal(metaFields["Publisher"], meta.Publisher);
|
||||
Assert.Equal(metaFields["Copyright"], meta.Copyright);
|
||||
Assert.Equal(metaFields["Group"], meta.Group);
|
||||
|
||||
// Genre: ATL 规范字段可能与 iXML 不一致,仅做非空校验
|
||||
if (!string.IsNullOrEmpty(metaFields["Genre"]))
|
||||
Assert.Equal(metaFields["Genre"], meta.Genre);
|
||||
|
||||
// TrackNumber
|
||||
if (int.TryParse(metaFields["TrackNumber"], out int trackNum) && trackNum > 0)
|
||||
Assert.Equal(trackNum, meta.TrackNumber);
|
||||
|
||||
// DiscNumber
|
||||
if (int.TryParse(metaFields["DiscNumber"], out int discNum) && discNum > 0)
|
||||
Assert.Equal(discNum, meta.DiscNumber);
|
||||
|
||||
// Year
|
||||
if (int.TryParse(metaFields["Year"], out int year) && year > 0)
|
||||
Assert.Equal(year, meta.TrackYear);
|
||||
|
||||
// BPM
|
||||
if (int.TryParse(metaFields["BPM"], out int bpm) && bpm > 0)
|
||||
Assert.Equal((double)bpm, meta.Bpm);
|
||||
|
||||
// iXML / USER 自定义字段(仅 Boom 有)
|
||||
if (extra.TryGetValue("ixml.ASWG.category", out string? cat) || extra.TryGetValue("ixml.USER.CATEGORY", out cat))
|
||||
Assert.Equal(cat, meta.Category);
|
||||
if (extra.TryGetValue("ixml.ASWG.catId", out string? catId) || extra.TryGetValue("ixml.USER.CATID", out catId))
|
||||
Assert.Equal(catId, meta.CatId);
|
||||
if (extra.TryGetValue("ixml.ASWG.library", out string? lib) || extra.TryGetValue("ixml.USER.LIBRARY", out lib))
|
||||
Assert.Equal(lib, meta.Library);
|
||||
if (extra.TryGetValue("ixml.ASWG.notes", out string? notes) || extra.TryGetValue("ixml.USER.NOTES", out notes))
|
||||
Assert.Equal(notes, meta.Notes);
|
||||
if (extra.TryGetValue("ixml.USER.FXNAME", out string? fxName))
|
||||
Assert.Equal(fxName, meta.FxName);
|
||||
if (extra.TryGetValue("ixml.USER.KEYWORDS", out string? keywords))
|
||||
Assert.Equal(keywords, meta.Keywords);
|
||||
if (extra.TryGetValue("ixml.USER.MANUFACTURER", out string? manufacturer))
|
||||
Assert.Equal(manufacturer, meta.Manufacturer);
|
||||
if (extra.TryGetValue("USER.MICPERSPECTIVE", out string? micPersp))
|
||||
Assert.Equal(micPersp, meta.MicPerspective);
|
||||
if (extra.TryGetValue("USER.SUBCATEGORY", out string? subcat) || extra.TryGetValue("ASWG.subCategory", out subcat))
|
||||
Assert.Equal(subcat, meta.Subcategory);
|
||||
if (extra.TryGetValue("USER.RATING", out string? ratingRaw))
|
||||
{
|
||||
if (double.TryParse(ratingRaw, NumberStyles.Float, CultureInfo.InvariantCulture, out double ratingVal))
|
||||
Assert.Equal((uint)Math.Round(ratingVal), meta.Rating);
|
||||
}
|
||||
|
||||
// BWF bext 字段
|
||||
if (extra.TryGetValue("bext.coding_history", out string? codingHistory))
|
||||
Assert.Equal(codingHistory, meta.BwfCodingHistory);
|
||||
if (extra.TryGetValue("bext.originator", out string? originator))
|
||||
Assert.Equal(originator, meta.BwfOriginator);
|
||||
if (extra.TryGetValue("bext.originatorRef", out string? originatorRef))
|
||||
Assert.Equal(originatorRef, meta.BwfOriginatorRef);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boom 目录的 WAV 文件应包含嵌入的封面图片
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadMetadata_ShouldContainArtwork_ForBoomFiles()
|
||||
{
|
||||
string[] wavFiles = GetWavFiles("Boom");
|
||||
if (wavFiles.Length == 0) return;
|
||||
|
||||
AudioMetadataReader reader = new();
|
||||
|
||||
foreach (string wavPath in wavFiles)
|
||||
{
|
||||
FileInfo file = new(wavPath);
|
||||
AudioFileMeta meta = reader.ReadMetadata(file);
|
||||
|
||||
Assert.NotNull(meta.Artwork);
|
||||
Assert.NotEmpty(meta.Artwork);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sound Idea 目录的 WAV 文件元数据最少:仅基础技术参数,
|
||||
/// 无 iXML、BWF 或嵌入图片
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadMetadata_ShouldHaveNoOptionalMetadata_ForSoundIdeaFiles()
|
||||
{
|
||||
string[] wavFiles = GetWavFiles("Sound Idea");
|
||||
if (wavFiles.Length == 0) return;
|
||||
|
||||
AudioMetadataReader reader = new();
|
||||
|
||||
foreach (string wavPath in wavFiles)
|
||||
{
|
||||
FileInfo file = new(wavPath);
|
||||
AudioFileMeta meta = reader.ReadMetadata(file);
|
||||
|
||||
// 必备条目正确
|
||||
Assert.NotEqual(0.0, meta.Duration);
|
||||
Assert.True(meta.BitDepth > 0);
|
||||
Assert.True(meta.Channels > 0);
|
||||
Assert.True(meta.SampleRate > 0);
|
||||
Assert.Equal("WAV", meta.Type);
|
||||
Assert.Equal(wavPath, meta.Path);
|
||||
Assert.Equal(file.Name, meta.Filename);
|
||||
Assert.NotEmpty(meta.Md5);
|
||||
|
||||
// 可选字段为空或默认值(Sound Idea 无附加字段)
|
||||
Assert.Null(meta.Category);
|
||||
Assert.Null(meta.Subcategory);
|
||||
Assert.Null(meta.CatId);
|
||||
Assert.Null(meta.Library);
|
||||
Assert.Null(meta.FxName);
|
||||
Assert.Null(meta.Keywords);
|
||||
Assert.Null(meta.BwfCodingHistory);
|
||||
Assert.Null(meta.BwfOriginator);
|
||||
Assert.Null(meta.BwfOriginatorRef);
|
||||
Assert.Null(meta.Artwork);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
using FluentAssertions;
|
||||
|
||||
namespace OCES.Resonance.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// AudioMetadataReader 单元测试
|
||||
/// </summary>
|
||||
public class AudioMetadataReaderTests
|
||||
{
|
||||
readonly AudioMetadataReader _reader;
|
||||
readonly string _testDirectory;
|
||||
|
||||
public AudioMetadataReaderTests()
|
||||
{
|
||||
this._reader = new AudioMetadataReader();
|
||||
this._testDirectory = Path.Combine(Path.GetTempPath(), $"MetadataReaderTest_{Guid.NewGuid()}");
|
||||
Directory.CreateDirectory(this._testDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_文件不存在_抛出FileNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
string nonExistentFile = Path.Combine(this._testDirectory, "non_existent.wav");
|
||||
|
||||
// Act & Assert
|
||||
Func<AudioFileMeta> act = () => this._reader.ReadMetadata(nonExistentFile);
|
||||
act.Should().Throw<FileNotFoundException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_空文件_仍然返回元数据()
|
||||
{
|
||||
// Arrange
|
||||
string emptyFile = Path.Combine(this._testDirectory, "empty.wav");
|
||||
File.WriteAllText(emptyFile, "");
|
||||
|
||||
// Act
|
||||
AudioFileMeta result = this._reader.ReadMetadata(emptyFile);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Filename.Should().Be("empty.wav");
|
||||
result.Path.Should().Be(Path.GetFullPath(emptyFile));
|
||||
result.Md5.Should().NotBeNullOrEmpty();
|
||||
result.UniqueId.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_生成唯一ID_每次调用都不同()
|
||||
{
|
||||
// Arrange
|
||||
string testFile = Path.Combine(this._testDirectory, "test.wav");
|
||||
File.WriteAllText(testFile, "fake audio content");
|
||||
|
||||
// Act
|
||||
AudioFileMeta result1 = this._reader.ReadMetadata(testFile);
|
||||
AudioFileMeta result2 = this._reader.ReadMetadata(testFile);
|
||||
|
||||
// Assert
|
||||
result1.UniqueId.Should().NotBe(result2.UniqueId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_相同文件_MD5相同()
|
||||
{
|
||||
// Arrange
|
||||
var testFile = Path.Combine(this._testDirectory, "test.wav");
|
||||
File.WriteAllText(testFile, "fake audio content");
|
||||
|
||||
// Act
|
||||
var result1 = this._reader.ReadMetadata(testFile);
|
||||
var result2 = this._reader.ReadMetadata(testFile);
|
||||
|
||||
// Assert
|
||||
result1.Md5.Should().Be(result2.Md5);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".wav", "WAV")]
|
||||
[InlineData(".mp3", "MP3")]
|
||||
[InlineData(".flac", "FLAC")]
|
||||
[InlineData(".aiff", "AIFF")]
|
||||
public void ReadMetadata_不同格式_正确设置Type字段(string extension, string expectedType)
|
||||
{
|
||||
// Arrange
|
||||
string testFile = Path.Combine(this._testDirectory, $"test{extension}");
|
||||
File.WriteAllText(testFile, "fake audio content");
|
||||
|
||||
// Act
|
||||
AudioFileMeta result = this._reader.ReadMetadata(testFile);
|
||||
|
||||
// Assert
|
||||
result.Type.Should().Be(expectedType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_提取文件夹信息()
|
||||
{
|
||||
// Arrange
|
||||
string subDir = Path.Combine(this._testDirectory, "SubFolder");
|
||||
Directory.CreateDirectory(subDir);
|
||||
string testFile = Path.Combine(subDir, "test.wav");
|
||||
File.WriteAllText(testFile, "fake audio content");
|
||||
|
||||
// Act
|
||||
AudioFileMeta result = this._reader.ReadMetadata(testFile);
|
||||
|
||||
// Assert
|
||||
result.Folder.Should().Be("SubFolder");
|
||||
result.Directory.Should().Be(subDir);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadMetadata_设置DateAdded为当前时间()
|
||||
{
|
||||
// Arrange
|
||||
string testFile = Path.Combine(this._testDirectory, "test.wav");
|
||||
File.WriteAllText(testFile, "fake audio content");
|
||||
DateTime beforeTime = DateTime.Now;
|
||||
|
||||
// Act
|
||||
AudioFileMeta result = this._reader.ReadMetadata(testFile);
|
||||
DateTime afterTime = DateTime.Now;
|
||||
|
||||
// Assert
|
||||
result.DateAdded.Should().BeOnOrAfter(beforeTime);
|
||||
result.DateAdded.Should().BeOnOrBefore(afterTime);
|
||||
}
|
||||
}
|
||||
Regular → Executable
+1
@@ -10,6 +10,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.9.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2025.2.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
using System.Data.SQLite;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace OCES.Resonance.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Database 单元测试
|
||||
/// </summary>
|
||||
public class DatabaseTests : IDisposable
|
||||
{
|
||||
readonly string _testDbName;
|
||||
readonly string _testDbPath;
|
||||
|
||||
public DatabaseTests()
|
||||
{
|
||||
_testDbName = $"test_{Guid.NewGuid():N}";
|
||||
_testDbPath = $"{_testDbName}.db";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_testDbPath))
|
||||
{
|
||||
File.Delete(_testDbPath);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConnection_返回有效连接()
|
||||
{
|
||||
// Act
|
||||
using var connection = Database.GetConnection(_testDbName);
|
||||
|
||||
// Assert
|
||||
connection.Should().NotBeNull();
|
||||
connection.Should().BeOfType<SQLiteConnection>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InitializeDatabase_创建表结构()
|
||||
{
|
||||
// Act
|
||||
Database.InitializeDatabase();
|
||||
|
||||
// Assert - 验证表已创建(通过尝试查询)
|
||||
using var connection = Database.GetConnection();
|
||||
connection.Open();
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT COUNT(*) FROM audio_files;";
|
||||
var count = command.ExecuteScalar();
|
||||
count.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEntry_添加有效记录_返回True()
|
||||
{
|
||||
// Arrange
|
||||
Database.InitializeDatabase();
|
||||
var meta = CreateTestMeta();
|
||||
|
||||
// Act
|
||||
var result = Database.AddEntry(meta);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEntry_添加记录后_可以查询到()
|
||||
{
|
||||
// Arrange
|
||||
Database.InitializeDatabase();
|
||||
var meta = CreateTestMeta();
|
||||
|
||||
// Act
|
||||
Database.AddEntry(meta);
|
||||
|
||||
// Assert
|
||||
using var connection = Database.GetConnection();
|
||||
connection.Open();
|
||||
var command = connection.CreateCommand();
|
||||
command.CommandText = "SELECT filename FROM audio_files WHERE unique_id = @id;";
|
||||
var param = command.CreateParameter();
|
||||
param.ParameterName = "@id";
|
||||
param.Value = meta.UniqueId;
|
||||
command.Parameters.Add(param);
|
||||
var filename = command.ExecuteScalar() as string;
|
||||
filename.Should().Be(meta.Filename);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEntries_批量添加_返回正确数量()
|
||||
{
|
||||
// Arrange
|
||||
Database.InitializeDatabase();
|
||||
var entries = new List<AudioFileMeta>
|
||||
{
|
||||
CreateTestMeta("file1.wav"),
|
||||
CreateTestMeta("file2.wav"),
|
||||
CreateTestMeta("file3.wav"),
|
||||
};
|
||||
|
||||
// Act
|
||||
var count = Database.AddEntries(entries);
|
||||
|
||||
// Assert
|
||||
count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EntryExists_记录存在_返回True()
|
||||
{
|
||||
// Arrange
|
||||
Database.InitializeDatabase();
|
||||
var meta = CreateTestMeta();
|
||||
Database.AddEntry(meta);
|
||||
|
||||
// Act
|
||||
var exists = Database.EntryExists(meta.Md5, meta.Path);
|
||||
|
||||
// Assert
|
||||
exists.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EntryExists_记录不存在_返回False()
|
||||
{
|
||||
// Arrange
|
||||
Database.InitializeDatabase();
|
||||
|
||||
// Act
|
||||
var exists = Database.EntryExists("non_existent_md5", "/non/existent/path.wav");
|
||||
|
||||
// Assert
|
||||
exists.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddEntry_重复UniqueId_第二次添加失败()
|
||||
{
|
||||
// Arrange
|
||||
Database.InitializeDatabase();
|
||||
var meta1 = CreateTestMeta();
|
||||
var meta2 = CreateTestMeta();
|
||||
meta2.UniqueId = meta1.UniqueId; // 使用相同的 UniqueId
|
||||
Database.AddEntry(meta1);
|
||||
|
||||
// Act
|
||||
var result = Database.AddEntry(meta2);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
static AudioFileMeta CreateTestMeta(string filename = "test.wav")
|
||||
{
|
||||
return new AudioFileMeta
|
||||
{
|
||||
Id = 0, // 自增 ID,由数据库生成
|
||||
UniqueId = Guid.NewGuid().ToString("N"),
|
||||
Md5 = Guid.NewGuid().ToString("N"),
|
||||
Path = $"/test/path/{filename}",
|
||||
Filename = filename,
|
||||
Folder = "test",
|
||||
Directory = "/test/path",
|
||||
Duration = 10.5,
|
||||
TotalSamples = 441000,
|
||||
BitDepth = 24,
|
||||
Channels = 2,
|
||||
SampleRate = 44100,
|
||||
Type = "WAV",
|
||||
DateAdded = DateTime.Now,
|
||||
OriginalModificationDate = DateTime.Now.AddDays(-1),
|
||||
OriginationTime = DateTime.Now.AddDays(-1),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user