1import * as NodeCrypto from "@effect/platform-node/NodeCrypto"
2import { assert, describe, it } from "@effect/vitest"
3import * as Crypto from "effect/Crypto"
4import * as Effect from "effect/Effect"
5import * as TestClock from "effect/testing/TestClock"
6
7const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
8const uuidV7Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
9
10const hex = (bytes: Uint8Array): string => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("")
11
12describe("NodeCrypto", () => {
13 it.effect("generates empty random bytes", () =>
14 Effect.gen(function*() {
15 const crypto = yield* Crypto.Crypto
16 const bytes = yield* crypto.randomBytes(0)
17 assert.deepStrictEqual(bytes, new Uint8Array(0))
18 }).pipe(Effect.provide(NodeCrypto.layer)))
19
20 it.effect("generates random bytes with the requested size", () =>
21 Effect.gen(function*() {
22 const crypto = yield* Crypto.Crypto
23 const bytes = yield* crypto.randomBytes(32)
24 assert.strictEqual(bytes.length, 32)
25 }).pipe(Effect.provide(NodeCrypto.layer)))
26
27 it.effect("fails invalid random byte sizes", () =>
28 Effect.gen(function*() {
29 const crypto = yield* Crypto.Crypto
30 const error = yield* Effect.flip(crypto.randomBytes(-1))
31 assert.strictEqual(error._tag, "PlatformError")
32 }).pipe(Effect.provide(NodeCrypto.layer)))
33
34 it.effect("generates UUIDv4 values", () =>
35 Effect.gen(function*() {
36 const crypto = yield* Crypto.Crypto
37 const uuid1 = yield* crypto.randomUUIDv4
38 const uuid2 = yield* crypto.randomUUIDv4
39 assert.match(uuid1, uuidV4Regex)
40 assert.match(uuid2, uuidV4Regex)
41 assert.notStrictEqual(uuid1, uuid2)
42 }).pipe(Effect.provide(NodeCrypto.layer)))
43
44 it.effect("generates UUIDv7 values", () =>
45 Effect.gen(function*() {
46 yield* TestClock.setTime(0x0123456789ab)
47 const crypto = yield* Crypto.Crypto
48 const uuid = yield* crypto.randomUUIDv7
49 assert.match(uuid, uuidV7Regex)
50 assert.strictEqual(uuid.slice(0, 13), "01234567-89ab")
51 }).pipe(Effect.provide(NodeCrypto.layer)))
52
53 it.effect("computes SHA-256 digests", () =>
54 Effect.gen(function*() {
55 const crypto = yield* Crypto.Crypto
56 const digest = yield* crypto.digest("SHA-256", new TextEncoder().encode("hello"))
57 assert.strictEqual(hex(digest), "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
58 }).pipe(Effect.provide(NodeCrypto.layer)))
59})