WorkCamera/client/WorkCameraExport.Tests/ZipDirectoryStructurePropertyTests.cs
2026-01-05 23:58:56 +08:00

291 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}