A:Access数据库功能

This commit is contained in:
jimmy committed 2025-11-19 08:42:28 +08:00
1 parent f7660c49e8
commit 1d412bba33
37 files changed
+990 -23

No files matched your search

@@ -0,0 +1,74 @@
using System;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sharp.Data.Access;
using Sharp.Data.Access.DependencyInjection;
using Sharp.Data.Access.Options;
using Sharp.Data.Access.Security;
using Sharp.Data.Access.Connections;
namespace Data.Access.Test;
[TestClass]
public sealed class AccessConnectionStringFactoryTests
{
[TestMethod]
public void Create_UsesDecryptedPasswordAndAdditionalParameters()
{
var tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempDirectory);
var options = new AccessDatabaseOptions
{
DatabaseFile = "data/app.accdb",
DataDirectory = tempDirectory,
EncryptedPassword = "encrypted-secret",
ProviderName = AccessProviderNames.AceOleDb12
};
options.AdditionalParameters["Jet OLEDB:Database Locking Mode"] = "1";
var decryptor = new FakeDecryptor("plain-text");
var services = new ServiceCollection();
services.AddSingleton<IAccessPasswordDecryptor>(decryptor);
services.AddAccessDataContext<FakeContext>(ctx =>
{
ctx.Database.ConnectionString = $"Provider={AccessProviderNames.AceOleDb16};Data Source=fake;";
}, registerDataContext: false);
var provider = services.BuildServiceProvider();
var factory = provider.GetRequiredService<IAccessConnectionStringFactory>();
var connectionString = factory.Create(options, provider);
var expectedPath = Path.GetFullPath(Path.Combine(tempDirectory, "data", "app.accdb"));
StringAssert.Contains(connectionString, $"Provider={AccessProviderNames.AceOleDb12}", "Provider should be set.");
StringAssert.Contains(connectionString, expectedPath, "Database path should be resolved to absolute path.");
StringAssert.Contains(connectionString, "Jet OLEDB:Database Password=plain-text", "Decrypted password should be used.");
StringAssert.Contains(connectionString, "Jet OLEDB:Database Locking Mode=1", "Additional parameters should be appended.");
Assert.AreEqual("encrypted-secret", decryptor.LastValue, "Decryptor should receive encrypted value.");
}
private sealed class FakeDecryptor : IAccessPasswordDecryptor
{
private readonly string? _result;
public FakeDecryptor(string? result) => _result = result;
public string? LastValue { get; private set; }
public string? Decrypt(string? encryptedPassword)
{
LastValue = encryptedPassword;
return _result;
}
}
private sealed class FakeContext : Microsoft.EntityFrameworkCore.DbContext
{
public FakeContext(Microsoft.EntityFrameworkCore.DbContextOptions<FakeContext> options) : base(options)
{
}
}
}
@@ -0,0 +1,82 @@
using System;
using System.IO;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sharp.Data.Access;
using Sharp.Data.Access.DependencyInjection;
using Sharp.Data.Access.Initialization;
using Sharp.Data.Access.Options;
namespace Data.Access.Test;
[TestClass]
public sealed class AccessDatabaseInitializerTests
{
[TestMethod]
public void EnsureDatabase_CopiesTemplateWhenMissing()
{
var tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempRoot);
var templateDir = Path.Combine(tempRoot, "template");
Directory.CreateDirectory(templateDir);
var templatePath = Path.Combine(templateDir, "seed.accdb");
File.WriteAllText(templatePath, "seed-data");
var options = new AccessDatabaseOptions
{
DatabaseFile = "db/app.accdb",
TemplateFile = "template/seed.accdb",
DataDirectory = tempRoot,
CopyTemplateWhenMissing = true,
ThrowIfMissing = true
};
var services = BuildServices();
var initializer = services.GetRequiredService<IAccessDatabaseInitializer>();
initializer.EnsureDatabase(options, services);
var targetPath = Path.Combine(tempRoot, "db", "app.accdb");
Assert.IsTrue(File.Exists(targetPath), "Database file should be created from template.");
Assert.AreEqual("seed-data", File.ReadAllText(targetPath));
}
[TestMethod]
public void EnsureDatabase_ThrowsWhenMissingAndNoTemplate()
{
var tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(tempRoot);
var options = new AccessDatabaseOptions
{
DatabaseFile = "missing/app.accdb",
DataDirectory = tempRoot,
CopyTemplateWhenMissing = false,
ThrowIfMissing = true
};
var services = BuildServices();
var initializer = services.GetRequiredService<IAccessDatabaseInitializer>();
Assert.ThrowsException<FileNotFoundException>(() =>
initializer.EnsureDatabase(options, services));
}
private static ServiceProvider BuildServices()
{
var services = new ServiceCollection();
services.AddAccessDataContext<FakeContext>(ctx =>
{
ctx.Database.ConnectionString = $"Provider={AccessProviderNames.AceOleDb16};Data Source=fake;";
}, registerDataContext: false);
return services.BuildServiceProvider();
}
private sealed class FakeContext : Microsoft.EntityFrameworkCore.DbContext
{
public FakeContext(Microsoft.EntityFrameworkCore.DbContextOptions<FakeContext> options) : base(options)
{
}
}
}
@@ -0,0 +1,49 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sharp.Data;
using Sharp.Data.Access;
using Sharp.Data.Access.DependencyInjection;
using Sharp.Data.Access.Options;
namespace Data.Access.Test;
[TestClass]
public sealed class AccessServiceCollectionExtensionsTests
{
[TestMethod]
public void AddAccessDataContext_RegistersFactoryAndOptions()
{
var services = new ServiceCollection();
services.AddAccessDataContext<FakeContext>(options =>
{
options.Database.ConnectionString = $"Provider={AccessProviderNames.AceOleDb16};Data Source=fake;";
options.ConfigureDataContext = (sp, builder) =>
{
builder.UseAudit(false)
.UseSoftDelete(false)
.UseAutoMigration(false)
.EnableDetailedErrors(false);
};
}, registerDataContext: false);
var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<AccessContextOptions<FakeContext>>();
Assert.AreEqual($"Provider={AccessProviderNames.AceOleDb16};Data Source=fake;", options.Database.ConnectionString);
var decryptor = provider.GetRequiredService<Sharp.Data.Access.Security.IAccessPasswordDecryptor>();
Assert.IsInstanceOfType(decryptor, typeof(Sharp.Data.Access.Security.PassthroughAccessPasswordDecryptor));
var factory = provider.GetRequiredService<IAccessDataContextFactory<FakeContext>>();
Assert.IsNotNull(factory);
}
private sealed class FakeContext : DbContext
{
public FakeContext(DbContextOptions<FakeContext> options) : base(options)
{
}
}
}
@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Sharp.Data;
using Sharp.Data.Access.Extensions;
using Sharp.Data.Context;
namespace Data.Access.Test.Usage;
[TestClass]
public sealed class AccessUsageSampleTests
{
/// <summary>
/// 演示如何在无 DI 场景下使用 DataContextBuilder 配置 Access。
/// 这里只验证 API 能够被调用(不真正连接数据库)。
/// </summary>
[TestMethod]
public void ManualBuilder_AllowsAccessConfigurationWithoutExceptions()
{
var builder = new DataContextBuilder<FakeContext>();
builder.UseAccessDatabase(options =>
{
options.ConnectionString = $"Provider=Microsoft.ACE.OLEDB.16.0;Data Source=fake.accdb;";
options.CopyTemplateWhenMissing = false;
options.ThrowIfMissing = false;
});
// 不调用 Build(),避免实际访问文件;只要不抛出异常即为通过。
Assert.IsTrue(true);
}
private sealed class FakeContext : DbContext
{
public FakeContext(DbContextOptions<FakeContext> options) : base(options)
{
}
}
}
Binary file not shown.