Files
wristivo/Packages/WristivoKit/Tests/RemoteCoreTests/AdmissionTests.swift
T

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))
}