Full rename ahead of App Store submission: project, targets, bundle identifiers (dev.bchen.Severed -> dev.bchen.OpenAppLock), entitlements file, app entry point, user-facing onboarding strings, test helpers, and docs. Verified: all 62 unit tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
127 lines
4.4 KiB
Swift
127 lines
4.4 KiB
Swift
//
|
|
// RuleEnforcerTests.swift
|
|
// OpenAppLockTests
|
|
//
|
|
|
|
import Foundation
|
|
import Testing
|
|
|
|
@testable import OpenAppLock
|
|
|
|
@MainActor
|
|
@Suite("Rule enforcement → shields")
|
|
struct RuleEnforcerTests {
|
|
let mondayDuringWork = date(2025, 1, 6, 10, 0)
|
|
let mondayEvening = date(2025, 1, 6, 19, 0)
|
|
|
|
@Test("Active schedule rules are shielded; inactive ones are not")
|
|
func shieldsActiveRules() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let active = BlockingRule(name: "Work Time")
|
|
let weekendOnly = BlockingRule(name: "Weekend Zen", days: Weekday.weekends)
|
|
|
|
enforcer.refresh(rules: [active, weekendOnly], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.shieldedRuleIDs == [active.id])
|
|
#expect(enforcer.blockingRuleIDs == [active.id])
|
|
}
|
|
|
|
@Test("Disabled rules are never shielded")
|
|
func skipsDisabledRules() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Work Time", isEnabled: false)
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.shieldedRuleIDs.isEmpty)
|
|
}
|
|
|
|
@Test("Paused rules are not shielded")
|
|
func skipsPausedRules() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Work Time")
|
|
RulePolicy.unblock(rule, at: mondayDuringWork, calendar: utc)
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.shieldedRuleIDs.isEmpty)
|
|
}
|
|
|
|
@Test("Time-limit rules are not schedule-shielded")
|
|
func skipsTimeLimitRules() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Time Keeper", kind: .timeLimit)
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.shieldedRuleIDs.isEmpty)
|
|
}
|
|
|
|
@Test("Shields are cleared when a window ends")
|
|
func clearsShieldAfterWindow() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Work Time")
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
#expect(shields.shieldedRuleIDs == [rule.id])
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayEvening, calendar: utc)
|
|
#expect(shields.shieldedRuleIDs.isEmpty)
|
|
#expect(enforcer.blockingRuleIDs.isEmpty)
|
|
}
|
|
|
|
@Test("Shields are cleared when a rule is deleted")
|
|
func clearsShieldAfterDeletion() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Work Time")
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
enforcer.refresh(rules: [], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.shieldedRuleIDs.isEmpty)
|
|
}
|
|
|
|
@Test("Expired pauses are cleaned up during refresh")
|
|
func clearsExpiredPause() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Work Time")
|
|
rule.pausedUntil = date(2025, 1, 6, 9, 30)
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(rule.pausedUntil == nil)
|
|
#expect(shields.shieldedRuleIDs == [rule.id])
|
|
}
|
|
|
|
@Test("The selection mode is forwarded to the shield layer")
|
|
func forwardsSelectionMode() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let rule = BlockingRule(name: "Focus", selectionMode: .allowOnly)
|
|
|
|
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.appliedModes[rule.id] == .allowOnly)
|
|
}
|
|
|
|
@Test("The adult-content flag is forwarded to the shield layer")
|
|
func forwardsAdultContentFlag() {
|
|
let shields = MockShieldController()
|
|
let enforcer = RuleEnforcer(shields: shields)
|
|
let filtered = BlockingRule(name: "Clean Mode", blockAdultContent: true)
|
|
let unfiltered = BlockingRule(name: "Plain")
|
|
|
|
enforcer.refresh(rules: [filtered, unfiltered], at: mondayDuringWork, calendar: utc)
|
|
|
|
#expect(shields.appliedAdultContentFlags[filtered.id] == true)
|
|
#expect(shields.appliedAdultContentFlags[unfiltered.id] == false)
|
|
}
|
|
}
|