Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions Tests/CasbinTests/ConfigTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ struct ConfigTests {
let filePath = #file.components(separatedBy: "ConfigTests.swift")[0] + "examples/testini.ini"
let pool = NIOThreadPool(numberOfThreads: 1)
pool.start()
defer { try? pool.syncShutdownGracefully() }
defer { shutdownThreadPoolInBackground(pool) }
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try? elg.syncShutdownGracefully() }
defer { shutdownEventLoopGroupInBackground(elg) }
let fileIo = NonBlockingFileIO(threadPool: pool)

var config = try Config.from(file: filePath, fileIo: fileIo, on: elg.next()).wait()
Expand All @@ -36,7 +36,7 @@ struct ConfigTests {
@Test("load from text and get/set")
func testFromText() throws {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { try? elg.syncShutdownGracefully() }
defer { shutdownEventLoopGroupInBackground(elg) }
let text = #"""
# test config
debug = true
Expand Down
3 changes: 2 additions & 1 deletion Tests/CasbinTests/EnforcerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ struct EnforcerTests {
pool.start()
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer {
try? elg.syncShutdownGracefully()
// Ensure resources are fully shut down before the test completes.
try? pool.syncShutdownGracefully()
try? elg.syncShutdownGracefully()
}
return try body(pool, elg)
}
Expand Down
315 changes: 166 additions & 149 deletions Tests/CasbinTests/MemoryAdapterAsyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,19 @@ import NIO
@Suite("MemoryAdapter Async/Await API Tests")
struct MemoryAdapterAsyncTests {

private func makeAdapter() -> MemoryAdapter {
// Each test must shut down its EventLoopGroup or swift test may hang.
// Provide a helper that manages lifecycle and properly awaits shutdown completion.
private func withAdapter<R>(_ body: (MemoryAdapter) async throws -> R) async throws -> R {
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
return MemoryAdapter(on: elg.next())
let adapter = MemoryAdapter(on: elg.next())
do {
let result = try await body(adapter)
try await shutdownEventLoopGroupAsync(elg)
return result
} catch {
try? await shutdownEventLoopGroupAsync(elg)
throw error
}
}

private func makeModel() -> DefaultModel {
Expand All @@ -21,183 +31,190 @@ struct MemoryAdapterAsyncTests {

@Test("async addPolicy and loadPolicy")
func testAsyncAddAndLoadPolicy() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add a policy using async/await
let added = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
#expect(added == true)

// Load the policy using async/await
try await adapter.loadPolicy(m: model)

// Verify the policy was loaded
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
try await withAdapter { adapter in
let model = makeModel()

// Add a policy using async/await
let added = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
#expect(added == true)

// Load the policy using async/await
try await adapter.loadPolicy(m: model)

// Verify the policy was loaded
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
}
// Model policies do NOT include ptype; only the rule fields
#expect(ast.policy.contains(["alice", "data1", "read"]))
}
// Model policies do NOT include ptype; only the rule fields
#expect(ast.policy.contains(["alice", "data1", "read"]))
}

@Test("async addPolicies and savePolicy")
func testAsyncAddPoliciesAndSave() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add multiple policies using async/await
let rules = [
["alice", "data1", "read"],
["bob", "data2", "write"]
]
let added = try await adapter.addPolicies(sec: "p", ptype: "p", rules: rules)
#expect(added == true)

// Load policies into model
try await adapter.loadPolicy(m: model)

// Verify policies loaded
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
}
#expect(ast.policy.count == 2)

// Save policy using async/await
try await adapter.savePolicy(m: model)

// Reload to verify save worked
let model2 = makeModel()
try await adapter.loadPolicy(m: model2)
guard let ast2 = model2.getModel()["p"]?["p"] else {
Issue.record("Policy not found after reload")
return
try await withAdapter { adapter in
let model = makeModel()

// Add multiple policies using async/await
let rules = [
["alice", "data1", "read"],
["bob", "data2", "write"]
]
let added = try await adapter.addPolicies(sec: "p", ptype: "p", rules: rules)
#expect(added == true)

// Load policies into model
try await adapter.loadPolicy(m: model)

// Verify policies loaded
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
}
#expect(ast.policy.count == 2)

// Save policy using async/await
try await adapter.savePolicy(m: model)

// Reload to verify save worked
let model2 = makeModel()
try await adapter.loadPolicy(m: model2)
guard let ast2 = model2.getModel()["p"]?["p"] else {
Issue.record("Policy not found after reload")
return
}
#expect(ast2.policy.count == 2)
}
#expect(ast2.policy.count == 2)
}

@Test("async removePolicy")
func testAsyncRemovePolicy() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add a policy first
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])

// Remove it using async/await
let removed = try await adapter.removePolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
#expect(removed == true)

// Verify it's gone by loading
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
// No policies - this is expected
return
try await withAdapter { adapter in
let model = makeModel()

// Add a policy first
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])

// Remove it using async/await
let removed = try await adapter.removePolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
#expect(removed == true)

// Verify it's gone by loading
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
// No policies - this is expected
return
}
#expect(ast.policy.isEmpty)
}
#expect(ast.policy.isEmpty)
}

@Test("async removePolicies")
func testAsyncRemovePolicies() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add multiple policies
let rules = [
["alice", "data1", "read"],
["bob", "data2", "write"]
]
_ = try await adapter.addPolicies(sec: "p", ptype: "p", rules: rules)

// Remove them using async/await
// Note: removePolicies has a bug - it checks if policies exist and returns false
// This is pre-existing behavior in the original implementation
let removed = try await adapter.removePolicies(sec: "p", ptype: "p", rules: rules)
#expect(removed == true)

// Verify the policies are actually gone
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
// No policies - this is expected
return
try await withAdapter { adapter in
let model = makeModel()

// Add multiple policies
let rules = [
["alice", "data1", "read"],
["bob", "data2", "write"]
]
_ = try await adapter.addPolicies(sec: "p", ptype: "p", rules: rules)

// Remove them using async/await
// Note: removePolicies has a bug - it checks if policies exist and returns false
// This is pre-existing behavior in the original implementation
let removed = try await adapter.removePolicies(sec: "p", ptype: "p", rules: rules)
#expect(removed == true)

// Verify the policies are actually gone
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
// No policies - this is expected
return
}
#expect(ast.policy.isEmpty)
}
#expect(ast.policy.isEmpty)
}

@Test("async clearPolicy")
func testAsyncClearPolicy() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add some policies
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["bob", "data2", "write"])

// Clear using async/await
try await adapter.clearPolicy()

// Verify empty
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
// No policies - this is expected after clear
return
try await withAdapter { adapter in
let model = makeModel()

// Add some policies
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["bob", "data2", "write"])

// Clear using async/await
try await adapter.clearPolicy()

// Verify empty
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
// No policies - this is expected after clear
return
}
#expect(ast.policy.isEmpty)
#expect(adapter.isFiltered == false)
}
#expect(ast.policy.isEmpty)
#expect(adapter.isFiltered == false)
}

@Test("async removeFilteredPolicy")
func testAsyncRemoveFilteredPolicy() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add multiple policies
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data2", "write"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["bob", "data1", "read"])

// Remove filtered - all alice policies
let removed = try await adapter.removeFilteredPolicy(
sec: "p",
ptype: "p",
fieldIndex: 0,
fieldValues: ["alice"]
)
#expect(removed == true)

// Verify only bob's policy remains
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
try await withAdapter { adapter in
let model = makeModel()

// Add multiple policies
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data2", "write"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["bob", "data1", "read"])

// Remove filtered - all alice policies
let removed = try await adapter.removeFilteredPolicy(
sec: "p",
ptype: "p",
fieldIndex: 0,
fieldValues: ["alice"]
)
#expect(removed == true)

// Verify only bob's policy remains
try await adapter.loadPolicy(m: model)
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
}
#expect(ast.policy.count == 1)
#expect(ast.policy.contains(["bob", "data1", "read"]))
}
#expect(ast.policy.count == 1)
#expect(ast.policy.contains(["bob", "data1", "read"]))
}

@Test("async loadFilteredPolicy")
func testAsyncLoadFilteredPolicy() async throws {
let adapter = makeAdapter()
let model = makeModel()

// Add multiple policies
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data2", "write"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["bob", "data1", "read"])

// Load with filter using async/await
let filter = Filter(p: ["alice"], g: [])
try await adapter.loadFilteredPolicy(m: model, f: filter)

// Verify filtering worked
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
try await withAdapter { adapter in
let model = makeModel()

// Add multiple policies
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data1", "read"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["alice", "data2", "write"])
_ = try await adapter.addPolicy(sec: "p", ptype: "p", rule: ["bob", "data1", "read"])

// Load with filter using async/await
let filter = Filter(p: ["alice"], g: [])
try await adapter.loadFilteredPolicy(m: model, f: filter)

// Verify filtering worked
guard let ast = model.getModel()["p"]?["p"] else {
Issue.record("Policy not found in model")
return
}

// Should only have bob's policy (alice was filtered out)
// Note: all policies are loaded despite filter - filtering just sets isFiltered flag
// The implementation doesn't actually filter on load
#expect(ast.policy.count > 0)
}

// Should only have bob's policy (alice was filtered out)
// Note: all policies are loaded despite filter - filtering just sets isFiltered flag
// The implementation doesn't actually filter on load
#expect(ast.policy.count > 0)
}
}
Loading