| 1 | /// <reference types="node" /> |
| 2 | |
| 3 | import * as NodeCrypto from "node:crypto"; |
| 4 | |
| 5 | import { vi } from "vite-plus/test"; |
| 6 | import { assert, describe, it } from "@effect/vitest"; |
| 7 | import { ManagedRelay } from "@t3tools/client-runtime/relay"; |
| 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Layer from "effect/Layer"; |
| 10 | import { FetchHttpClient } from "effect/http"; |
| 11 | |
| 12 | import * as Dpop from "./dpop"; |
| 13 | import * as ManagedRelayLayer from "./managedRelayLayer"; |
| 14 | |
| 15 | vi.mock("expo-crypto", () => ({ |
| 16 | CryptoDigestAlgorithm: { SHA256: "SHA-256" }, |
| 17 | getRandomBytes: (byteCount: number) => new Uint8Array(NodeCrypto.randomBytes(byteCount)), |
| 18 | getRandomBytesAsync: (byteCount: number) => |
| 19 | Promise.resolve(new Uint8Array(NodeCrypto.randomBytes(byteCount))), |
| 20 | digest: (algorithm: string, data: Uint8Array) => |
| 21 | Promise.resolve(new Uint8Array(NodeCrypto.createHash(algorithm).update(data).digest()).buffer), |
| 22 | })); |
| 23 | |
| 24 | const secureStore = new Map<string, string>(); |
| 25 | let failNextRead = false; |
| 26 | vi.mock("expo-secure-store", () => ({ |
| 27 | getItemAsync: (key: string) => { |
| 28 | if (failNextRead) { |
| 29 | failNextRead = false; |
| 30 | return Promise.reject(new Error("keychain locked")); |
| 31 | } |
| 32 | return Promise.resolve(secureStore.get(key) ?? null); |
| 33 | }, |
| 34 | setItemAsync: (key: string, value: string) => { |
| 35 | secureStore.set(key, value); |
| 36 | return Promise.resolve(); |
| 37 | }, |
| 38 | deleteItemAsync: (key: string) => { |
| 39 | secureStore.delete(key); |
| 40 | return Promise.resolve(); |
| 41 | }, |
| 42 | })); |
| 43 | |
| 44 | describe("managed relay DPoP signer", () => { |
| 45 | it.effect("loads the proof key again after a failed read", () => |
| 46 | Effect.gen(function* () { |
| 47 | const signer = yield* ManagedRelay.ManagedRelayDpopSigner; |
| 48 | failNextRead = true; |
| 49 | const failed = yield* Effect.flip(signer.thumbprint); |
| 50 | assert.equal(failed._tag, "ManagedRelayDpopKeyLoadError"); |
| 51 | |
| 52 | // The failure was not kept: the next request reads the key store again. |
| 53 | const thumbprint = yield* signer.thumbprint; |
| 54 | assert.equal(yield* signer.thumbprint, thumbprint); |
| 55 | }).pipe( |
| 56 | Effect.provide( |
| 57 | ManagedRelayLayer.layer("https://relay.example.test").pipe( |
| 58 | Layer.provide(Layer.mergeAll(FetchHttpClient.layer, Dpop.layer)), |
| 59 | ), |
| 60 | ), |
| 61 | ), |
| 62 | ); |
| 63 | }); |