Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/
.vscode/

# Claude Code artifacts (local development only)
.claude/
292 changes: 292 additions & 0 deletions Casbin.Persist.Adapter.EFCore.UnitTest/BackwardCompatibilityTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
using System.Linq;
using System.Threading.Tasks;
using Casbin.Persist.Adapter.EFCore.Entities;
using Casbin.Persist.Adapter.EFCore.UnitTest.Extensions;
using Casbin.Persist.Adapter.EFCore.UnitTest.Fixtures;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace Casbin.Persist.Adapter.EFCore.UnitTest
{
/// <summary>
/// Tests to ensure backward compatibility with existing single-context behavior.
/// These tests verify that the multi-context changes don't break existing usage patterns.
/// </summary>
public class BackwardCompatibilityTest : TestUtil,
IClassFixture<ModelProvideFixture>,
IClassFixture<DbContextProviderFixture>
{
private readonly ModelProvideFixture _modelProvideFixture;
private readonly DbContextProviderFixture _dbContextProviderFixture;

public BackwardCompatibilityTest(
ModelProvideFixture modelProvideFixture,
DbContextProviderFixture dbContextProviderFixture)
{
_modelProvideFixture = modelProvideFixture;
_dbContextProviderFixture = dbContextProviderFixture;
}

[Fact]
public void TestSingleContextConstructorStillWorks()
{
// Arrange - Using original constructor pattern
using var context = _dbContextProviderFixture.GetContext<int>("SingleContextConstructor");
context.Clear();

// Act - Create adapter using single-context constructor (original API)
var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Add policies
enforcer.AddPolicy("alice", "data1", "read");
enforcer.AddGroupingPolicy("alice", "admin");

// Assert - All policies should be in single context
Assert.Equal(2, context.Policies.Count());

var policies = context.Policies.ToList();
Assert.Contains(policies, p => p.Type == "p" && p.Value1 == "alice");
Assert.Contains(policies, p => p.Type == "g" && p.Value1 == "alice");
}

[Fact]
public async Task TestSingleContextAsyncOperationsStillWork()
{
// Arrange
await using var context = _dbContextProviderFixture.GetContext<int>("SingleContextAsync");
context.Clear();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Act
await enforcer.AddPolicyAsync("alice", "data1", "read");
await enforcer.AddGroupingPolicyAsync("alice", "admin");

// Assert
Assert.Equal(2, await context.Policies.CountAsync());
}

[Fact]
public void TestSingleContextLoadAndSave()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("SingleContextLoadSave");
context.Clear();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Act - Add and save
enforcer.AddPolicy("alice", "data1", "read");
enforcer.AddGroupingPolicy("alice", "admin");
enforcer.SavePolicy();

// Create new enforcer and load
var newEnforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);
newEnforcer.LoadPolicy();

// Assert
TestGetPolicy(newEnforcer, AsList(
AsList("alice", "data1", "read")
));

TestGetGroupingPolicy(newEnforcer, AsList(
AsList("alice", "admin")
));
}

[Fact]
public void TestSingleContextWithExistingTests()
{
// This test mimics the pattern from AutoTest.cs to ensure compatibility
using var context = _dbContextProviderFixture.GetContext<int>("ExistingPattern");
context.Clear();

// Initialize with data (like InitPolicy in AutoTest.cs)
context.Policies.AddRange(new[]
{
new EFCorePersistPolicy<int> { Type = "p", Value1 = "alice", Value2 = "data1", Value3 = "read" },
new EFCorePersistPolicy<int> { Type = "p", Value1 = "bob", Value2 = "data2", Value3 = "write" },
new EFCorePersistPolicy<int> { Type = "g", Value1 = "alice", Value2 = "data2_admin" }
});
context.SaveChanges();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Act - Load policy
enforcer.LoadPolicy();

// Assert - Should match expected behavior
TestGetPolicy(enforcer, AsList(
AsList("alice", "data1", "read"),
AsList("bob", "data2", "write")
));

TestGetGroupingPolicy(enforcer, AsList(
AsList("alice", "data2_admin")
));
}

[Fact]
public void TestSingleContextRemoveOperations()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("SingleContextRemove");
context.Clear();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

enforcer.AddPolicy("alice", "data1", "read");
enforcer.AddPolicy("bob", "data2", "write");

// Act
enforcer.RemovePolicy("alice", "data1", "read");

// Assert
Assert.Single(context.Policies);
var remaining = context.Policies.First();
Assert.Equal("bob", remaining.Value1);
}

[Fact]
public void TestSingleContextUpdateOperations()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("SingleContextUpdate");
context.Clear();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

enforcer.AddPolicy("alice", "data1", "read");

// Act
enforcer.UpdatePolicy(
AsList("alice", "data1", "read"),
AsList("alice", "data1", "write")
);

// Assert
Assert.Single(context.Policies);
var policy = context.Policies.First();
Assert.Equal("write", policy.Value3);
}

[Fact]
public void TestSingleContextBatchOperations()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("SingleContextBatch");
context.Clear();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Act - Add multiple
enforcer.AddPolicies(new[]
{
AsList("alice", "data1", "read"),
AsList("bob", "data2", "write"),
AsList("charlie", "data3", "read")
});

// Assert
Assert.Equal(3, context.Policies.Count());

// Act - Remove multiple
enforcer.RemovePolicies(new[]
{
AsList("alice", "data1", "read"),
AsList("bob", "data2", "write")
});

// Assert
Assert.Single(context.Policies);
}

[Fact]
public void TestSingleContextFilteredLoading()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("SingleContextFiltered");
context.Clear();

context.Policies.AddRange(new[]
{
new EFCorePersistPolicy<int> { Type = "p", Value1 = "alice", Value2 = "data1", Value3 = "read" },
new EFCorePersistPolicy<int> { Type = "p", Value1 = "bob", Value2 = "data2", Value3 = "write" },
new EFCorePersistPolicy<int> { Type = "g", Value1 = "alice", Value2 = "admin" }
});
context.SaveChanges();

var adapter = new EFCoreAdapter<int>(context);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Act - Load only alice's policies
enforcer.LoadFilteredPolicy(new Filter

Check warning on line 230 in Casbin.Persist.Adapter.EFCore.UnitTest/BackwardCompatibilityTest.cs

View workflow job for this annotation

GitHub Actions / build

'Filter' is obsolete: 'Please use PolicyFilter instead'
{
P = AsList("alice", "", "")
});

// Assert
Assert.True(adapter.IsFiltered);
TestGetPolicy(enforcer, AsList(
AsList("alice", "data1", "read")
));
}

[Fact]
public void TestSingleContextProviderWrapping()
{
// Arrange - Create adapter with explicit SingleContextProvider
using var context = _dbContextProviderFixture.GetContext<int>("ProviderWrapping");
context.Clear();

var provider = new SingleContextProvider<int>(context);
var adapter = new EFCoreAdapter<int>(provider);
var enforcer = new Enforcer(_modelProvideFixture.GetNewRbacModel(), adapter);

// Act
enforcer.AddPolicy("alice", "data1", "read");

// Assert - Should behave identically to direct context constructor
Assert.Single(context.Policies);
Assert.Equal("alice", context.Policies.First().Value1);
}

[Fact]
public void TestSingleContextProviderGetAllContexts()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("ProviderGetAll");
var provider = new SingleContextProvider<int>(context);

// Act
var contexts = provider.GetAllContexts().ToList();

// Assert
Assert.Single(contexts);
Assert.Same(context, contexts[0]);
}

[Fact]
public void TestSingleContextProviderGetContextForPolicyType()
{
// Arrange
using var context = _dbContextProviderFixture.GetContext<int>("ProviderGetForType");
var provider = new SingleContextProvider<int>(context);

// Act & Assert - All policy types should return same context
Assert.Same(context, provider.GetContextForPolicyType("p"));
Assert.Same(context, provider.GetContextForPolicyType("p2"));
Assert.Same(context, provider.GetContextForPolicyType("g"));
Assert.Same(context, provider.GetContextForPolicyType("g2"));
Assert.Same(context, provider.GetContextForPolicyType(null));
Assert.Same(context, provider.GetContextForPolicyType(""));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
using System;
using System.Linq;

namespace Casbin.Persist.Adapter.EFCore.UnitTest.Extensions
{
public static class CasbinDbContextExtension
{
internal static void Clear<TKey>(this CasbinDbContext<TKey> dbContext) where TKey : IEquatable<TKey>
{
dbContext.RemoveRange(dbContext.Policies);
dbContext.SaveChanges();
// Force model initialization before ensuring database exists
// This ensures EF Core knows about all entity configurations
_ = dbContext.Model;

// Ensure database and tables exist before attempting to clear
dbContext.Database.EnsureCreated();

// Try to access and clear policies
try
{
var policies = dbContext.Policies.ToList();
if (policies.Count > 0)
{
dbContext.RemoveRange(policies);
dbContext.SaveChanges();
}
}
catch (Microsoft.Data.Sqlite.SqliteException)
{
// If table still doesn't exist after EnsureCreated,
// force a second attempt with model refresh
dbContext.Database.EnsureDeleted();
_ = dbContext.Model;
dbContext.Database.EnsureCreated();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ public CasbinDbContext<TKey> GetContext<TKey>(string name) where TKey : IEquatab
.UseSqlite($"Data Source={name}.db")
.Options;
var context = new CasbinDbContext<TKey>(options);

// Ensure database and tables are created
context.Database.EnsureCreated();

// Force model to be initialized by accessing a property
// This ensures the DbSet is properly configured
_ = context.Model;

return context;
}
}
Expand Down
Loading
Loading