76 lines
4.2 KiB
Swift
76 lines
4.2 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import RemoteCore
|
|
|
|
private let now = Date(timeIntervalSince1970: 1_000)
|
|
|
|
private func envelope(session: UUID, tv: UUID, sequence: UInt64 = 1,
|
|
id: UUID = UUID(), issued: Date = now,
|
|
expires: Date = now.addingTimeInterval(0.5),
|
|
version: Int = 1) -> CommandEnvelope {
|
|
CommandEnvelope(sessionID: session, televisionID: tv, sequence: sequence,
|
|
issuedAt: issued, expiresAt: expires, command: .right,
|
|
id: id, schemaVersion: version)
|
|
}
|
|
|
|
@Test func admitsExactlyOnceAndRequiresIncreasingSequence() {
|
|
let session = UUID(), tv = UUID()
|
|
var gate = CommandAdmission(sessionID: session, televisionID: tv)
|
|
let first = envelope(session: session, tv: tv)
|
|
#expect(gate.admit(first, now: now) == .accepted)
|
|
#expect(gate.admit(first, now: now) == .rejected(.duplicate))
|
|
#expect(gate.admit(envelope(session: session, tv: tv), now: now) == .rejected(.outOfOrder))
|
|
#expect(gate.admit(envelope(session: session, tv: tv, sequence: 2), now: now) == .accepted)
|
|
}
|
|
|
|
@Test func rejectsWrongRoutingAndUnsupportedSchemaWithoutConsumingSequence() {
|
|
let session = UUID(), tv = UUID()
|
|
var gate = CommandAdmission(sessionID: session, televisionID: tv)
|
|
#expect(gate.admit(envelope(session: UUID(), tv: tv), now: now) == .rejected(.wrongSession))
|
|
#expect(gate.admit(envelope(session: session, tv: UUID()), now: now) == .rejected(.wrongTelevision))
|
|
#expect(gate.admit(envelope(session: session, tv: tv, version: 2), now: now) == .rejected(.unsupportedSchema))
|
|
#expect(gate.admit(envelope(session: session, tv: tv), now: now) == .accepted)
|
|
}
|
|
|
|
@Test func rejectsStaleExpiredFutureAndInvalidLifetime() {
|
|
let session = UUID(), tv = UUID()
|
|
var gate = CommandAdmission(sessionID: session, televisionID: tv)
|
|
#expect(gate.admit(envelope(session: session, tv: tv, issued: now.addingTimeInterval(-2), expires: now.addingTimeInterval(-1.5)), now: now) == .rejected(.stale))
|
|
#expect(gate.admit(envelope(session: session, tv: tv, issued: now.addingTimeInterval(-0.5), expires: now), now: now) == .rejected(.expired))
|
|
#expect(gate.admit(envelope(session: session, tv: tv, issued: now.addingTimeInterval(0.3), expires: now.addingTimeInterval(0.8)), now: now) == .rejected(.futureTimestamp))
|
|
#expect(gate.admit(envelope(session: session, tv: tv, expires: now.addingTimeInterval(2)), now: now) == .rejected(.invalidLifetime))
|
|
#expect(gate.admit(envelope(session: session, tv: tv, expires: now), now: now) == .rejected(.invalidLifetime))
|
|
#expect(gate.admit(envelope(session: session, tv: tv), now: Date(timeIntervalSince1970: .infinity)) == .rejected(.invalidTimestamp))
|
|
}
|
|
|
|
@Test func acceptsSkewBoundaryAndMaximumLifetime() {
|
|
let session = UUID(), tv = UUID()
|
|
var gate = CommandAdmission(sessionID: session, televisionID: tv)
|
|
let issued = now.addingTimeInterval(0.25)
|
|
#expect(gate.admit(envelope(session: session, tv: tv, issued: issued, expires: issued.addingTimeInterval(1)), now: now) == .accepted)
|
|
}
|
|
|
|
@Test func resetUsesFreshSessionAndOldReplayCannotPassAfterIDEviction() {
|
|
let session = UUID(), tv = UUID()
|
|
var gate = CommandAdmission(sessionID: session, televisionID: tv)
|
|
let first = envelope(session: session, tv: tv, sequence: 0)
|
|
#expect(gate.admit(first, now: now) == .accepted)
|
|
for value in 1...CommandAdmission.rememberedIDLimit {
|
|
#expect(gate.admit(envelope(session: session, tv: tv, sequence: UInt64(value)), now: now) == .accepted)
|
|
}
|
|
#expect(gate.admit(first, now: now) == .rejected(.outOfOrder))
|
|
let newSession = gate.startNewSession(televisionID: tv)
|
|
#expect(newSession != session)
|
|
#expect(gate.admit(first, now: now) == .rejected(.wrongSession))
|
|
#expect(gate.admit(envelope(session: newSession, tv: tv, sequence: 0), now: now) == .accepted)
|
|
}
|
|
|
|
@Test func decodingDoesNotBypassAdmission() throws {
|
|
let session = UUID(), tv = UUID()
|
|
let invalid = envelope(session: session, tv: tv, version: 99)
|
|
let decoded = try JSONDecoder().decode(CommandEnvelope.self, from: JSONEncoder().encode(invalid))
|
|
#expect(decoded == invalid)
|
|
var gate = CommandAdmission(sessionID: session, televisionID: tv)
|
|
#expect(gate.admit(decoded, now: now) == .rejected(.unsupportedSchema))
|
|
}
|