291 lines
11 KiB
C#
291 lines
11 KiB
C#
using FsCheck;
|
||
using FsCheck.Xunit;
|
||
using System.IO.Compression;
|
||
using WorkCameraExport.Models;
|
||
using Xunit;
|
||
|
||
namespace WorkCameraExport.Tests
|
||
{
|
||
/// <summary>
|
||
/// ZIP 目录结构属性测试
|
||
/// Feature: work-camera-2.0.1, Property 9: ZIP 目录结构
|
||
/// Validates: Requirements 9.2, 9.3
|
||
/// </summary>
|
||
public class ZipDirectoryStructurePropertyTests : IDisposable
|
||
{
|
||
private readonly string _testOutputDir;
|
||
|
||
public ZipDirectoryStructurePropertyTests()
|
||
{
|
||
_testOutputDir = Path.Combine(Path.GetTempPath(), $"ZipStructure_Test_{Guid.NewGuid():N}");
|
||
Directory.CreateDirectory(_testOutputDir);
|
||
}
|
||
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
if (Directory.Exists(_testOutputDir))
|
||
{
|
||
Directory.Delete(_testOutputDir, true);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略清理错误
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 9: ZIP 目录结构
|
||
/// For any 下载的照片 ZIP 文件,解压后的目录结构应包含:
|
||
/// 当日照片/、参与人员/{人员姓名}/、工作内容/{工作内容}/、部门/{部门名称}/ 四个分类目录。
|
||
/// Validates: Requirements 9.2, 9.3
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property ZipStructure_ShouldContainRequiredDirectories()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.From<PositiveInt>(),
|
||
Arb.From<PositiveInt>(),
|
||
(workerCountGen, seedGen) =>
|
||
{
|
||
var workerCount = (workerCountGen.Get % 3) + 1; // 1-3 个工人
|
||
var seed = seedGen.Get;
|
||
var deptName = $"部门{seed % 100}";
|
||
var content = $"内容{seed % 100}";
|
||
var workers = Enumerable.Range(1, workerCount)
|
||
.Select(i => $"工人{seed}_{i}")
|
||
.ToList();
|
||
|
||
var yearMonth = "2025-01";
|
||
var recordDate = new DateTime(2025, 1, 15);
|
||
|
||
// 创建测试目录结构
|
||
var directories = CreateDirectoryStructure(
|
||
_testOutputDir, yearMonth, recordDate, deptName, content, workers);
|
||
|
||
// 验证目录结构
|
||
var basePath = Path.Combine(_testOutputDir, "workfiles", "202501", "20250115");
|
||
|
||
// 1. 验证当日照片目录存在
|
||
var dailyDirExists = Directory.Exists(Path.Combine(basePath, "当日照片"));
|
||
|
||
// 2. 验证参与人员目录存在
|
||
var workerDirsExist = workers.All(w =>
|
||
Directory.Exists(Path.Combine(basePath, "参与人员", w)));
|
||
|
||
// 3. 验证工作内容目录存在
|
||
var contentDirExists = Directory.Exists(Path.Combine(basePath, "工作内容", content));
|
||
|
||
// 4. 验证部门目录存在
|
||
var deptDirExists = Directory.Exists(Path.Combine(basePath, "部门", deptName));
|
||
|
||
// 清理
|
||
CleanupDirectories(_testOutputDir);
|
||
|
||
return (dailyDirExists && workerDirsExist && contentDirExists && deptDirExists)
|
||
.Label($"All required directories should exist: daily={dailyDirExists}, workers={workerDirsExist}, content={contentDirExists}, dept={deptDirExists}");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 9 扩展: 目录结构应按日期组织
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property ZipStructure_ShouldBeOrganizedByDate()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.From<PositiveInt>(),
|
||
Arb.From<PositiveInt>(),
|
||
(monthGen, dayGen) =>
|
||
{
|
||
var month = (monthGen.Get % 12) + 1; // 1-12
|
||
var day = (dayGen.Get % 28) + 1; // 1-28
|
||
var yearMonth = $"2025-{month:D2}";
|
||
var recordDate = new DateTime(2025, month, day);
|
||
var expectedYYYYMM = $"2025{month:D2}";
|
||
var expectedYYYYMMDD = $"2025{month:D2}{day:D2}";
|
||
|
||
var directories = CreateDirectoryStructure(
|
||
_testOutputDir, yearMonth, recordDate, "测试部门", "测试内容", new List<string> { "测试工人" });
|
||
|
||
// 验证目录路径包含正确的日期格式
|
||
var basePath = Path.Combine(_testOutputDir, "workfiles", expectedYYYYMM, expectedYYYYMMDD);
|
||
var pathExists = Directory.Exists(basePath);
|
||
|
||
CleanupDirectories(_testOutputDir);
|
||
|
||
return pathExists.Label($"Directory path should contain {expectedYYYYMM}/{expectedYYYYMMDD}");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 9 扩展: 每个工人应有独立的目录
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property ZipStructure_EachWorker_ShouldHaveOwnDirectory()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.From<PositiveInt>(),
|
||
(workerCountGen) =>
|
||
{
|
||
var workerCount = (workerCountGen.Get % 5) + 1; // 1-5 个工人
|
||
var workers = Enumerable.Range(1, workerCount)
|
||
.Select(i => $"工人{i}")
|
||
.ToList();
|
||
|
||
var yearMonth = "2025-01";
|
||
var recordDate = new DateTime(2025, 1, 15);
|
||
|
||
var directories = CreateDirectoryStructure(
|
||
_testOutputDir, yearMonth, recordDate, "测试部门", "测试内容", workers);
|
||
|
||
var basePath = Path.Combine(_testOutputDir, "workfiles", "202501", "20250115", "参与人员");
|
||
|
||
// 验证每个工人都有独立目录
|
||
var allWorkersHaveDir = workers.All(w =>
|
||
Directory.Exists(Path.Combine(basePath, w)));
|
||
|
||
// 验证目录数量正确
|
||
var dirCount = Directory.Exists(basePath)
|
||
? Directory.GetDirectories(basePath).Length
|
||
: 0;
|
||
|
||
CleanupDirectories(_testOutputDir);
|
||
|
||
return (allWorkersHaveDir && dirCount == workerCount)
|
||
.Label($"Each of {workerCount} workers should have own directory, found {dirCount}");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// Property 9 扩展: 空工人列表应只创建当日照片目录
|
||
/// </summary>
|
||
[Fact]
|
||
public void ZipStructure_EmptyWorkers_ShouldStillCreateDailyDirectory()
|
||
{
|
||
var yearMonth = "2025-01";
|
||
var recordDate = new DateTime(2025, 1, 15);
|
||
|
||
var directories = CreateDirectoryStructure(
|
||
_testOutputDir, yearMonth, recordDate, "测试部门", "测试内容", new List<string>());
|
||
|
||
var basePath = Path.Combine(_testOutputDir, "workfiles", "202501", "20250115");
|
||
|
||
// 当日照片目录应该存在
|
||
Assert.True(Directory.Exists(Path.Combine(basePath, "当日照片")));
|
||
|
||
// 参与人员目录下应该没有子目录
|
||
var workerBasePath = Path.Combine(basePath, "参与人员");
|
||
if (Directory.Exists(workerBasePath))
|
||
{
|
||
Assert.Empty(Directory.GetDirectories(workerBasePath));
|
||
}
|
||
|
||
CleanupDirectories(_testOutputDir);
|
||
}
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 创建目录结构(模拟 ExportService.CreateDirectoryStructure)
|
||
/// </summary>
|
||
private List<string> CreateDirectoryStructure(
|
||
string baseDir,
|
||
string yearMonth,
|
||
DateTime recordDate,
|
||
string deptName,
|
||
string content,
|
||
List<string> workers)
|
||
{
|
||
var directories = new List<string>();
|
||
var yyyyMM = yearMonth.Replace("-", "");
|
||
var yyyyMMdd = recordDate.ToString("yyyyMMdd");
|
||
|
||
var basePath = Path.Combine(baseDir, "workfiles", yyyyMM, yyyyMMdd);
|
||
|
||
// 1. 当日照片目录
|
||
var dailyDir = Path.Combine(basePath, "当日照片");
|
||
EnsureDirectoryExists(dailyDir);
|
||
directories.Add(dailyDir);
|
||
|
||
// 2. 参与人员目录
|
||
foreach (var worker in workers.Where(w => !string.IsNullOrEmpty(w)))
|
||
{
|
||
var workerDir = Path.Combine(basePath, "参与人员", SanitizeFileName(worker));
|
||
EnsureDirectoryExists(workerDir);
|
||
directories.Add(workerDir);
|
||
}
|
||
|
||
// 3. 工作内容目录
|
||
if (!string.IsNullOrEmpty(content))
|
||
{
|
||
var contentDir = Path.Combine(basePath, "工作内容", SanitizeFileName(content));
|
||
EnsureDirectoryExists(contentDir);
|
||
directories.Add(contentDir);
|
||
}
|
||
|
||
// 4. 部门目录
|
||
if (!string.IsNullOrEmpty(deptName))
|
||
{
|
||
var deptDir = Path.Combine(basePath, "部门", SanitizeFileName(deptName));
|
||
EnsureDirectoryExists(deptDir);
|
||
directories.Add(deptDir);
|
||
}
|
||
|
||
return directories;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保目录存在
|
||
/// </summary>
|
||
private void EnsureDirectoryExists(string path)
|
||
{
|
||
if (!Directory.Exists(path))
|
||
{
|
||
Directory.CreateDirectory(path);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理文件名中的非法字符
|
||
/// </summary>
|
||
private string SanitizeFileName(string fileName)
|
||
{
|
||
var invalidChars = Path.GetInvalidFileNameChars();
|
||
var sanitized = fileName;
|
||
foreach (var c in invalidChars)
|
||
{
|
||
sanitized = sanitized.Replace(c, '_');
|
||
}
|
||
if (sanitized.Length > 50)
|
||
{
|
||
sanitized = sanitized.Substring(0, 50);
|
||
}
|
||
return sanitized;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理测试目录
|
||
/// </summary>
|
||
private void CleanupDirectories(string baseDir)
|
||
{
|
||
try
|
||
{
|
||
var workfilesDir = Path.Combine(baseDir, "workfiles");
|
||
if (Directory.Exists(workfilesDir))
|
||
{
|
||
Directory.Delete(workfilesDir, true);
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 忽略清理错误
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
}
|