import XCTest @testable import LChatCore // 🤖 Pins the native fader math to home's web fader (home/test/fader.test.mjs // asserts the same values on the JS side) and the claim-side crypto helpers. final class SpaceCreateTests: XCTestCase { // ---- fader math (twin of fader.test.mjs — keep the cases in lockstep) ---- func testFaderValueMapsTheSlotRunTo1Through10AndClampsBeyondIt() { XCTAssertEqual(FaderMath.value(fraction: 0), 1) XCTAssertEqual(FaderMath.value(fraction: 0.86), 10, "the end of the run (runEnd) is 10") XCTAssertEqual(FaderMath.value(fraction: 1), 10, "past the run still clamps to 10 — eleven is not reachable by position") XCTAssertEqual(FaderMath.value(fraction: -0.5), 1, "before the slot clamps to 1") let mid = FaderMath.value(fraction: 0.43) XCTAssertTrue(mid > 5 && mid < 6.6, "mid-run lands mid-scale") } func testFaderLevelIsSilentBelow5HotBy10AndPressureAddsStrain() { XCTAssertEqual(FaderMath.level(phase: .slide, value: 1, pressure: 0), 0) XCTAssertEqual(FaderMath.level(phase: .slide, value: 5, pressure: 0), 0) XCTAssertEqual(FaderMath.level(phase: .slide, value: 10, pressure: 0), 5.0 / 6, accuracy: 1e-9) XCTAssertGreaterThan(FaderMath.level(phase: .slide, value: 10, pressure: 0.8), FaderMath.level(phase: .slide, value: 10, pressure: 0), "the hold adds strain") } func testFaderLevelHoldsPast1AtTheSnapAndThroughTheWholeBuild() { XCTAssertEqual(FaderMath.level(phase: .snap, value: 10, pressure: 0), 1.2) XCTAssertEqual(FaderMath.level(phase: .build, value: 10, pressure: 0), 1.2, "the volume stays AT eleven while building") } func testFaderLevelCutsToZeroAtDone() { XCTAssertEqual(FaderMath.level(phase: .done, value: 10, pressure: 0), 0, "silence before the confetti") } // ---- claim crypto --------------------------------------------------------- func testDerivedClaimSecretHashesToTheClaimHash() throws { // The raw claim secret (sent on CLAIM) must be the exact preimage of the // claim hash (stored on MINT) — the two sides of proof-of-possession. let derived = try InviteCrypto.derive(InviteCrypto.randomSecret()) let hash = SHA256Hex(Data(derived.claimSecret.utf8)) XCTAssertEqual(hash, derived.claimHash) XCTAssertFalse(derived.claimSecret.isEmpty) } func testGeneratedUserKeypairRoundTripsThroughECIES() throws { // A fresh native identity must interoperate with the existing wrap paths: // wrap a key to the new pubkey, unwrap it with the new private key. let kp = ECIES.generateUserKeypair() let raw = ECIES.randomAESKey() let wrapped = try ECIES.wrap(rawKey: raw, recipientPubB64: kp.pub) XCTAssertEqual(try ECIES.unwrap(wrappedB64: wrapped, privPkcs8B64: kp.priv), raw) } func testInviteSecretParsingStripsPrefixAndSuggestedName() { XCTAssertEqual(SpaceClaim.inviteSecret(fromFragment: "e1.abc123"), "abc123") XCTAssertEqual(SpaceClaim.inviteSecret(fromFragment: "e1.abc123~Paul"), "abc123") XCTAssertEqual(SpaceClaim.inviteSecret(fromFragment: "abc123"), "abc123", "older links carry no e1. hint") XCTAssertNil(SpaceClaim.inviteSecret(fromFragment: "")) XCTAssertNil(SpaceClaim.inviteSecret(fromFragment: "e1.")) } // Sealed instances mint 3-slot links: e1.~~ // (docs/no-plaintext.md). T parsing and the suggested name must both // survive the new tail; the key is exposed for the sealed-branding path. func testBrandingKeyFragmentSlots() { let frag = "e1.abc123~Paul~k3m7p2qx4f" XCTAssertEqual(SpaceClaim.inviteSecret(fromFragment: frag), "abc123") XCTAssertEqual(SpaceClaim.suggestedName(fromFragment: frag), "Paul") XCTAssertEqual(SpaceClaim.brandingKey(fromFragment: frag), "k3m7p2qx4f") // Empty name slot (a sealed instance's admin link). XCTAssertEqual(SpaceClaim.inviteSecret(fromFragment: "e1.abc123~~k3m7p2qx4f"), "abc123") XCTAssertNil(SpaceClaim.suggestedName(fromFragment: "e1.abc123~~k3m7p2qx4f")) XCTAssertEqual(SpaceClaim.brandingKey(fromFragment: "e1.abc123~~k3m7p2qx4f"), "k3m7p2qx4f") // Two-slot legacy links: no key, name intact — even when a legacy raw // "~" in the name makes a non-key-shaped tail. XCTAssertNil(SpaceClaim.brandingKey(fromFragment: "e1.abc123~Paul")) XCTAssertEqual(SpaceClaim.suggestedName(fromFragment: "e1.abc123~Zo%C3%AB%20~%20%E2%9C%A8"), "Zoë ~ ✨") } } private func SHA256Hex(_ data: Data) -> String { CryptoKit.SHA256.hash(data: data).map { String(format: "%02x", $0) }.joined() } import CryptoKit