1import { assert, describe, it } from "@effect/vitest"
2import { Result } from "effect"
3import * as Hex from "effect/encoding/Hex"
4
5const assertRandom = (length: number, expectedLength: number): void => {
6 const value = Hex.random(length)
7 assert.strictEqual(value.length, expectedLength)
8 assert.match(value, /^[0-9a-f]*$/)
9}
10
11describe("Hex", () => {
12 it("encodes strings and bytes as lowercase hexadecimal", () => {
13 assert.strictEqual(Hex.encode("hello"), "68656c6c6f")
14 assert.strictEqual(Hex.encode(new Uint8Array([72, 101, 108, 108, 111])), "48656c6c6f")
15 })
16
17 it("decodes mixed-case bytes and UTF-8 strings", () => {
18 assert.deepStrictEqual(
19 Result.getOrThrow(Hex.decode("48656C6c6F")),
20 new Uint8Array([72, 101, 108, 108, 111])
21 )
22 assert.strictEqual(Result.getOrThrow(Hex.decodeString("f09f918b")), "👋")
23 })
24
25 it("rejects odd lengths and non-hexadecimal characters", () => {
26 const oddLength = Hex.decode("abc")
27 assert.isTrue(Result.isFailure(oddLength))
28 if (Result.isFailure(oddLength)) {
29 assert.strictEqual(oddLength.failure.kind, "Decode")
30 assert.strictEqual(oddLength.failure.module, "Hex")
31 assert.strictEqual(oddLength.failure.input, "abc")
32 }
33
34 assert.isTrue(Result.isFailure(Hex.decode("zz")))
35 })
36
37 describe("random", () => {
38 it("generates lowercase hexadecimal words", () => {
39 assertRandom(0, 0)
40 assertRandom(8, 8)
41 assertRandom(16, 16)
42 assertRandom(32, 32)
43 })
44
45 it("rounds non-negative lengths down to multiples of 8", () => {
46 assertRandom(7, 0)
47 assertRandom(15.9, 8)
48 assertRandom(23, 16)
49 })
50
51 it("uses unsigned 32-bit coercion without validation", () => {
52 assertRandom(Number.NaN, 0)
53 assertRandom(Number.POSITIVE_INFINITY, 0)
54 assertRandom(2 ** 32, 0)
55 assertRandom(2 ** 32 + 8, 8)
56 })
57 })
58})