diff --git a/Normal/Services/TimedUnblockService.swift b/Normal/Services/TimedUnblockService.swift index 9a01598..aee7035 100644 --- a/Normal/Services/TimedUnblockService.swift +++ b/Normal/Services/TimedUnblockService.swift @@ -64,7 +64,7 @@ final class TimedUnblockService { cancelMonitoring(activityName: activityName) let start = clock() - let endDate = start.addingTimeInterval(duration.timeInterval) + let endDate = Self.endDate(of: duration, from: start) try scheduleActivity(name: activityName, start: start, endDate: endDate) try persist( @@ -95,7 +95,7 @@ final class TimedUnblockService { cancelMonitoring(activityName: activityName) let start = clock() - let endDate = start.addingTimeInterval(duration.timeInterval) + let endDate = Self.endDate(of: duration, from: start) try scheduleActivity(name: activityName, start: start, endDate: endDate) try persist( @@ -280,6 +280,11 @@ final class TimedUnblockService { } } + private static func endDate(of duration: TimedUnblockDuration, from start: Date) -> Date { + let end = start.addingTimeInterval(duration.timeInterval).timeIntervalSinceReferenceDate + return Date(timeIntervalSinceReferenceDate: end.rounded(.down)) + } + private func scheduleActivity(name: String, start: Date, endDate: Date) throws { let schedule = DeviceActivityScheduleFactory.window(from: start, to: endDate) try activityCenter.startMonitoring( diff --git a/NormalTests/Services/BlockingScenarioTests.swift b/NormalTests/Services/BlockingScenarioTests.swift index 0f4b150..d101766 100644 --- a/NormalTests/Services/BlockingScenarioTests.swift +++ b/NormalTests/Services/BlockingScenarioTests.swift @@ -275,6 +275,27 @@ struct BlockingScenarioTests { #expect(h.blocked == h.apps(1, 2)) } + @Test func timedUnblockAllStartedMidSecondReblocksAtItsRegisteredEnd() throws { + let h = BlockingHarness() + h.blockAll() + h.passTime(to: h.at(10).addingTimeInterval(0.6)) + try h.unblockAllTimed(.thirtyMinutes) + + h.wake(at: try h.registeredEnd(of: SharedConstants.mainTimedUnblockActivityName)) + #expect(h.blocked == h.apps(1, 2, 3)) + } + + @Test func groupTimedUnblockStartedMidSecondReblocksAtItsRegisteredEnd() throws { + let h = BlockingHarness() + h.blockAll() + let social = h.group([1, 2]) + h.passTime(to: h.at(10).addingTimeInterval(0.6)) + try h.unblockGroupTimed(social, .oneHour) + + h.wake(at: try h.registeredEnd(of: SharedConstants.groupTimedUnblockActivityName(for: social.id))) + #expect(h.blocked == h.apps(1, 2, 3)) + } + @Test func blockingAGroupDuringTimedUnblockAllAppliesNowAndAfter() throws { let h = BlockingHarness() h.blockAll() diff --git a/NormalTests/Services/TimedUnblockServiceTests.swift b/NormalTests/Services/TimedUnblockServiceTests.swift index 98f168c..2c47805 100644 --- a/NormalTests/Services/TimedUnblockServiceTests.swift +++ b/NormalTests/Services/TimedUnblockServiceTests.swift @@ -123,7 +123,8 @@ struct TimedUnblockServiceTests { let dto = try #require(store.timedUnblocks.first) let start = try #require(dto.startDate) #expect(start >= before) - #expect(dto.endDate.timeIntervalSince(start) == 5700) + let length = dto.endDate.timeIntervalSince(start) + #expect(length > 5699 && length <= 5700) #expect(service.activeUnblockStartDates()[TimedUnblockService.mainID] == start) } diff --git a/NormalTests/TestSupport/BlockingHarness.swift b/NormalTests/TestSupport/BlockingHarness.swift index 6e81097..8880fae 100644 --- a/NormalTests/TestSupport/BlockingHarness.swift +++ b/NormalTests/TestSupport/BlockingHarness.swift @@ -1,7 +1,9 @@ +import DeviceActivity import FamilyControls import Foundation import ManagedSettings @testable import Normal +import Testing @MainActor final class BlockingHarness { @@ -81,6 +83,11 @@ final class BlockingHarness { Set(ids.map(TokenFixtures.app)) } + func registeredEnd(of activityName: String) throws -> Date { + let schedule = try #require(activity.registeredSchedule(for: DeviceActivityName(activityName))) + return try #require(Calendar.current.date(from: schedule.intervalEnd)) + } + func passTime(to date: Date) { clock.now = date }