Files
buzz/mobile/ios/BuzzPushKit/Tests/BuzzPushKitTests/BuzzInteractionCleanupRetryTests.swift
Tom Brow 779af8886c fix(mobile): fail open when age checks are unavailable (#7714)
Age-check errors previously blocked Buzz startup. Normal app startup now
proceeds immediately; only a valid under-18 response from the current
launch request can restrict access.

Age check enforcement remains disabled at compile time by default.
Builds can opt in with `--dart-define=BUZZ_AGE_GATING_ENABLED=true`.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-09-17 16:44:08 -07:00

134 lines
5.4 KiB
Swift

import Darwin
import Foundation
import Testing
@testable import BuzzPushKit
struct BuzzInteractionCleanupRetryTests {
private func directory() throws -> URL {
let url = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
return url
}
private func retry(_ url: URL?, delete: @escaping BuzzInteractionDeletionDeadline.DeleteAllInteractions)
-> BuzzInteractionCleanupRetry {
BuzzInteractionCleanupRetry(containerURL: url, deletion: BuzzInteractionDeletionDeadline(
timeout: 5, deleteAllInteractions: delete, scheduleTimeout: { _, _ in }))
}
@Test func `Failed cleanup survives recreation without restricting access`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
let failure = NSError(domain: "InjectedFailure", code: 1)
var failures = 0
retry(url, delete: { $0(failure) }).request { error in
#expect(error != nil)
failures += 1
}
#expect(failures == 1)
#expect(!BuzzAgeRestrictionSession.isRestricted(containerURL: url))
var deletes = 0
retry(url, delete: { completion in deletes += 1; completion(nil) }).retryPending {
#expect($0 == nil)
}
#expect(deletes == 1)
retry(url, delete: { _ in Issue.record("Successful cleanup must clear its retry") })
.retryPending { #expect($0 == nil) }
}
@Test func `An older success cannot erase newer pending cleanup`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
var first: ((Error?) -> Void)?
var second: ((Error?) -> Void)?
retry(url, delete: { first = $0 }).request { #expect($0 == nil) }
retry(url, delete: { second = $0 }).request { #expect($0 != nil) }
first?(nil)
second?(NSError(domain: "LaterDeletionFailed", code: 1))
var deletes = 0
retry(url, delete: { completion in deletes += 1; completion(nil) })
.retryPending { #expect($0 == nil) }
#expect(deletes == 1)
}
@Test func `An older completion cannot undo a newer acknowledgement`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
var first: ((Error?) -> Void)?
retry(url, delete: { first = $0 }).request { #expect($0 == nil) }
retry(url, delete: { $0(nil) }).request { #expect($0 == nil) }
first?(nil)
retry(url, delete: { _ in Issue.record("Newer cleanup was already acknowledged") })
.retryPending { #expect($0 == nil) }
}
@Test func `A damaged acknowledgement leaves cleanup retryable`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
retry(url, delete: { $0(nil) }).request { #expect($0 == nil) }
try Data("damaged".utf8).write(
to: url.appendingPathComponent("interaction-cleanup-completed"), options: .atomic)
var deletes = 0
retry(url, delete: { completion in deletes += 1; completion(nil) })
.retryPending { #expect($0 == nil) }
#expect(deletes == 1)
}
@Test func `A busy acknowledgement cannot lose newer cleanup intent`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
let lockPath = url.appendingPathComponent("interaction-cleanup-retry.lock").path
let descriptor = open(lockPath, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)
#expect(descriptor >= 0)
defer { close(descriptor) }
#expect(flock(descriptor, LOCK_EX | LOCK_NB) == 0)
var deletes = 0
retry(url, delete: { completion in deletes += 1; completion(nil) }).request {
#expect($0 != nil)
}
#expect(deletes == 1)
#expect(!BuzzAgeRestrictionSession.isRestricted(containerURL: url))
flock(descriptor, LOCK_UN)
retry(url, delete: { completion in deletes += 1; completion(nil) }).retryPending {
#expect($0 == nil)
}
#expect(deletes == 2)
retry(url, delete: { _ in Issue.record("Acknowledged retry must not repeat") })
.retryPending { #expect($0 == nil) }
}
@Test func `Missing deletion callback remains pending after its deadline`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
var timeout: (() -> Void)?
var failures = 0
let deletion = BuzzInteractionDeletionDeadline(timeout: 5,
deleteAllInteractions: { _ in }, scheduleTimeout: { _, action in timeout = action })
BuzzInteractionCleanupRetry(containerURL: url, deletion: deletion).request {
#expect($0 != nil)
failures += 1
}
timeout?()
#expect(failures == 1)
var deletes = 0
retry(url, delete: { completion in deletes += 1; completion(nil) })
.retryPending { #expect($0 == nil) }
#expect(deletes == 1)
#expect(!BuzzAgeRestrictionSession.isRestricted(containerURL: url))
}
@Test func `Normal startup creates no deletion and absent storage cannot restrict`() throws {
let url = try directory()
defer { try? FileManager.default.removeItem(at: url) }
retry(url, delete: { _ in Issue.record("No cleanup was requested") })
.retryPending { #expect($0 == nil) }
retry(nil, delete: { _ in Issue.record("No shared store is configured") })
.retryPending { #expect($0 == nil) }
let missing = url.appendingPathComponent("missing-directory")
retry(missing, delete: { _ in Issue.record("Journal failure must propagate") })
.request { #expect($0 != nil) }
#expect(!BuzzAgeRestrictionSession.isRestricted(containerURL: missing))
}
}