EventSyncError.subscriptionRequired (402-Mapping in SyncHTTPClient), onSyncRequired-Hook + isSyncRequired. Push/Pull: Events bleiben in der Outbox (kein Verlust, kein Attempt-Increment), event-getriggerte Push/Pull unterdrückt, Poll prüft weiter (force) → Auto-Recovery. Wire-Parität zu @mana/event-sync 0.9.0. swift build + tests grün. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
83 lines
3.2 KiB
Swift
83 lines
3.2 KiB
Swift
import XCTest
|
|
@testable import ManaEventSync
|
|
|
|
final class CoreTests: XCTestCase {
|
|
func testULIDFormat() {
|
|
let id = ULID.generate()
|
|
XCTAssertEqual(id.count, 26)
|
|
let alphabet = Set("0123456789ABCDEFGHJKMNPQRSTVWXYZ")
|
|
XCTAssertTrue(id.allSatisfy { alphabet.contains($0) })
|
|
}
|
|
|
|
func testULIDsAreUnique() {
|
|
let ids = (0 ..< 100).map { _ in ULID.generate() }
|
|
XCTAssertEqual(Set(ids).count, ids.count)
|
|
}
|
|
|
|
func testJWTSubjectExtraction() {
|
|
// payload = base64url({"sub":"user_42"})
|
|
let token = "h.eyJzdWIiOiJ1c2VyXzQyIn0.s"
|
|
XCTAssertEqual(JWTSubject.subject(of: token), "user_42")
|
|
}
|
|
|
|
func testJWTSubjectRejectsMalformed() {
|
|
XCTAssertNil(JWTSubject.subject(of: "not-a-jwt"))
|
|
XCTAssertNil(JWTSubject.subject(of: "a.b"))
|
|
}
|
|
|
|
func testJSONValueRoundTrip() throws {
|
|
struct Sample: Codable, Equatable {
|
|
let name: String
|
|
let count: Int
|
|
let tags: [String]
|
|
let nested: [String: Double]
|
|
}
|
|
let sample = Sample(name: "pasta", count: 3, tags: ["a", "b"], nested: ["kcal": 600])
|
|
let value = try JSONValue.from(sample)
|
|
let back: Sample = try value.decode()
|
|
XCTAssertEqual(sample, back)
|
|
XCTAssertEqual(value.get("name"), .string("pasta"))
|
|
}
|
|
|
|
func testEventEnvelopeCodableRoundTrip() throws {
|
|
let envelope = EventEnvelope(
|
|
eventId: ULID.generate(),
|
|
aggregateId: "highlight:01HX",
|
|
appId: "pageta",
|
|
eventType: "HighlightCreated",
|
|
eventVersion: 1,
|
|
occurredAt: "2026-05-25T10:00:00.000Z",
|
|
actor: .user(principalId: "user_42"),
|
|
attributedToUserId: "user_42",
|
|
origin: "user",
|
|
idempotencyKey: "idem-1",
|
|
payload: .object(["text": .string("hallo")])
|
|
)
|
|
let data = try JSONEncoder().encode(envelope)
|
|
let decoded = try JSONDecoder().decode(EventEnvelope.self, from: data)
|
|
XCTAssertEqual(envelope, decoded)
|
|
}
|
|
|
|
func testEnvelopeAttributedRewrite() {
|
|
let envelope = EventEnvelope(
|
|
eventId: "e1", aggregateId: "a:1", appId: "pageta", eventType: "X",
|
|
eventVersion: 1, occurredAt: "t", actor: .user(principalId: "anon:guest-1"),
|
|
attributedToUserId: "anon:guest-1", origin: "user", idempotencyKey: "k", payload: .null
|
|
)
|
|
let claimed = envelope.attributed(to: "user_42")
|
|
XCTAssertEqual(claimed.attributedToUserId, "user_42")
|
|
XCTAssertEqual(claimed.eventId, "e1")
|
|
}
|
|
|
|
func testSubscriptionRequiredErrorIsDistinct() {
|
|
// 402-Mapping: eigener, von schemaOutdated/transport unterscheidbarer
|
|
// Fall mit nutzbarer Beschreibung (Banner-Text der App).
|
|
XCTAssertEqual(EventSyncError.subscriptionRequired, EventSyncError.subscriptionRequired)
|
|
XCTAssertNotEqual(EventSyncError.subscriptionRequired, EventSyncError.schemaOutdated)
|
|
XCTAssertNotEqual(
|
|
EventSyncError.subscriptionRequired,
|
|
EventSyncError.transport(status: 402, body: nil)
|
|
)
|
|
XCTAssertNotNil(EventSyncError.subscriptionRequired.errorDescription)
|
|
}
|
|
}
|