mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: jarred <jarred@bun.sh> Co-authored-by: Meghan Denny <meghan@bun.sh>
383 lines
12 KiB
TypeScript
383 lines
12 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import * as uuid from "uuid";
|
|
|
|
describe("randomUUIDv5", () => {
|
|
const dnsNamespace = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
|
|
const urlNamespace = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
|
|
|
|
test("basic functionality", () => {
|
|
const result = Bun.randomUUIDv5("www.example.com", dnsNamespace);
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
|
|
|
|
// Check that it's version 5
|
|
expect(result[14]).toBe("5");
|
|
});
|
|
|
|
test("deterministic output", () => {
|
|
const uuid1 = Bun.randomUUIDv5("www.example.com", dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5("www.example.com", dnsNamespace);
|
|
|
|
// Should always generate the same UUID for the same namespace + name
|
|
expect(uuid1).toBe(uuid2);
|
|
});
|
|
|
|
test("compatibility with uuid library", () => {
|
|
const name = "www.example.com";
|
|
const bunUuid = Bun.randomUUIDv5(name, dnsNamespace);
|
|
const uuidLibUuid = uuid.v5(name, dnsNamespace);
|
|
|
|
expect(bunUuid).toBe(uuidLibUuid);
|
|
});
|
|
|
|
test("predefined namespace strings", () => {
|
|
// Test with predefined namespace strings
|
|
const uuid1 = Bun.randomUUIDv5("www.example.com", "dns");
|
|
const uuid2 = Bun.randomUUIDv5("www.example.com", dnsNamespace);
|
|
|
|
expect(uuid1).toBe(uuid2);
|
|
|
|
const uuid3 = Bun.randomUUIDv5("http://example.com", "url");
|
|
const uuid4 = Bun.randomUUIDv5("http://example.com", urlNamespace);
|
|
|
|
expect(uuid3).toBe(uuid4);
|
|
});
|
|
|
|
test("empty name", () => {
|
|
const result = Bun.randomUUIDv5("", dnsNamespace);
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result[14]).toBe("5");
|
|
});
|
|
|
|
test("long name", () => {
|
|
const longName = "a".repeat(1000);
|
|
const result = Bun.randomUUIDv5(longName, dnsNamespace);
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result[14]).toBe("5");
|
|
});
|
|
|
|
test("unicode name", () => {
|
|
const unicodeName = "测试.example.com";
|
|
const result = Bun.randomUUIDv5(unicodeName, dnsNamespace);
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result[14]).toBe("5");
|
|
|
|
// Should be deterministic
|
|
const uuid2 = Bun.randomUUIDv5(unicodeName, dnsNamespace);
|
|
expect(result).toBe(uuid2);
|
|
});
|
|
|
|
test("name as ArrayBuffer", () => {
|
|
const nameString = "test";
|
|
const nameBuffer = new TextEncoder().encode(nameString);
|
|
|
|
const uuid1 = Bun.randomUUIDv5(nameString, dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5(nameBuffer, dnsNamespace);
|
|
|
|
expect(uuid1).toBe(uuid2);
|
|
});
|
|
|
|
test("name as TypedArray", () => {
|
|
const nameString = "test";
|
|
const nameArray = new Uint8Array(new TextEncoder().encode(nameString));
|
|
|
|
const uuid1 = Bun.randomUUIDv5(nameString, dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5(nameArray, dnsNamespace);
|
|
|
|
expect(uuid1).toBe(uuid2);
|
|
});
|
|
|
|
test("error handling - invalid namespace", () => {
|
|
expect(() => {
|
|
Bun.randomUUIDv5("test", "invalid-uuid");
|
|
}).toThrow();
|
|
});
|
|
|
|
test("error handling - wrong namespace buffer size", () => {
|
|
const wrongSizeBuffer = new Uint8Array(15); // Should be 16 bytes
|
|
expect(() => {
|
|
Bun.randomUUIDv5("test", wrongSizeBuffer);
|
|
}).toThrow();
|
|
});
|
|
|
|
test("error handling - invalid encoding", () => {
|
|
expect(() => {
|
|
// @ts-expect-error - testing invalid encoding
|
|
Bun.randomUUIDv5("test", dnsNamespace, "invalid");
|
|
}).toThrow();
|
|
});
|
|
|
|
test("variant bits are correct", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace);
|
|
const bytes = result.replace(/-/g, "");
|
|
|
|
// Extract the variant byte (17th hex character, index 16)
|
|
const variantByte = parseInt(bytes.substr(16, 2), 16);
|
|
|
|
// Variant bits should be 10xxxxxx (0x80-0xBF)
|
|
expect(variantByte & 0xc0).toBe(0x80);
|
|
});
|
|
|
|
test("version bits are correct", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace);
|
|
const bytes = result.replace(/-/g, "");
|
|
|
|
// Extract the version byte (13th hex character, index 12)
|
|
const versionByte = parseInt(bytes.substr(12, 2), 16);
|
|
|
|
// Version bits should be 0101xxxx (0x50-0x5F)
|
|
expect(versionByte & 0xf0).toBe(0x50);
|
|
});
|
|
|
|
test("case insensitive namespace strings", () => {
|
|
const uuid1 = Bun.randomUUIDv5("test", "DNS");
|
|
const uuid2 = Bun.randomUUIDv5("test", "dns");
|
|
const uuid3 = Bun.randomUUIDv5("test", "Dns");
|
|
|
|
expect(uuid1).toBe(uuid2);
|
|
expect(uuid2).toBe(uuid3);
|
|
});
|
|
|
|
test("all predefined namespaces", () => {
|
|
const name = "test";
|
|
|
|
const dnsUuid = Bun.randomUUIDv5(name, "dns");
|
|
const urlUuid = Bun.randomUUIDv5(name, "url");
|
|
const oidUuid = Bun.randomUUIDv5(name, "oid");
|
|
const x500Uuid = Bun.randomUUIDv5(name, "x500");
|
|
|
|
// All should be different
|
|
expect(dnsUuid).not.toBe(urlUuid);
|
|
expect(urlUuid).not.toBe(oidUuid);
|
|
expect(oidUuid).not.toBe(x500Uuid);
|
|
|
|
// All should be valid UUIDs
|
|
[dnsUuid, urlUuid, oidUuid, x500Uuid].forEach(result => {
|
|
expect(result).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
|
|
expect(result[14]).toBe("5");
|
|
});
|
|
});
|
|
|
|
test("different namespaces produce different UUIDs", () => {
|
|
const uuid1 = Bun.randomUUIDv5("www.example.com", dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5("www.example.com", urlNamespace);
|
|
|
|
expect(uuid1).not.toBe(uuid2);
|
|
expect(uuid.v5("www.example.com", dnsNamespace)).toBe(uuid1);
|
|
expect(uuid.v5("www.example.com", urlNamespace)).toBe(uuid2);
|
|
});
|
|
|
|
test("different names produce different UUIDs", () => {
|
|
const uuid1 = Bun.randomUUIDv5("www.example.com", dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5("api.example.com", dnsNamespace);
|
|
|
|
expect(uuid1).not.toBe(uuid2);
|
|
});
|
|
|
|
test("hex encoding (default)", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace);
|
|
expect(result).toMatch(/^[0-9a-f-]+$/);
|
|
expect(result.length).toBe(36); // Standard UUID string length
|
|
});
|
|
|
|
test("buffer encoding", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace, "buffer");
|
|
expect(result).toBeInstanceOf(Uint8Array);
|
|
expect(result.byteLength).toBe(16);
|
|
});
|
|
|
|
test("base64 encoding", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace, "base64");
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result).toMatch(/^[A-Za-z0-9+/=]+$/);
|
|
});
|
|
|
|
test("base64url encoding", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace, "base64url");
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result).toMatch(/^[A-Za-z0-9_-]+$/);
|
|
});
|
|
|
|
test("namespace as Buffer", () => {
|
|
// Convert UUID string to buffer
|
|
const nsBytes = new Uint8Array(16);
|
|
const nsString = dnsNamespace.replace(/-/g, "");
|
|
for (let i = 0; i < 16; i++) {
|
|
nsBytes[i] = parseInt(nsString.substr(i * 2, 2), 16);
|
|
}
|
|
|
|
const uuid1 = Bun.randomUUIDv5("test", dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5("test", nsBytes);
|
|
|
|
expect(uuid1).toBe(uuid2);
|
|
});
|
|
|
|
test("name as Buffer", () => {
|
|
const nameBuffer = new TextEncoder().encode("test");
|
|
const uuid1 = Bun.randomUUIDv5("test", dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5(nameBuffer, dnsNamespace);
|
|
|
|
expect(uuid1).toBe(uuid2);
|
|
});
|
|
|
|
// Ported v5 tests from uuid library test suite
|
|
test("v5 - hello.example.com with DNS namespace", () => {
|
|
expect(Bun.randomUUIDv5("hello.example.com", dnsNamespace)).toBe("fdda765f-fc57-5604-a269-52a7df8164ec");
|
|
});
|
|
|
|
test("v5 - http://example.com/hello with URL namespace", () => {
|
|
expect(Bun.randomUUIDv5("http://example.com/hello", urlNamespace)).toBe("3bbcee75-cecc-5b56-8031-b6641c1ed1f1");
|
|
});
|
|
|
|
test("v5 - hello with custom namespace", () => {
|
|
expect(Bun.randomUUIDv5("hello", "0f5abcd1-c194-47f3-905b-2df7263a084b")).toBe(
|
|
"90123e1c-7512-523e-bb28-76fab9f2f73d",
|
|
);
|
|
});
|
|
|
|
test("v5 namespace.toUpperCase", () => {
|
|
expect(Bun.randomUUIDv5("hello.example.com", dnsNamespace.toUpperCase())).toBe(
|
|
"fdda765f-fc57-5604-a269-52a7df8164ec",
|
|
);
|
|
expect(Bun.randomUUIDv5("http://example.com/hello", urlNamespace.toUpperCase())).toBe(
|
|
"3bbcee75-cecc-5b56-8031-b6641c1ed1f1",
|
|
);
|
|
expect(Bun.randomUUIDv5("hello", "0f5abcd1-c194-47f3-905b-2df7263a084b".toUpperCase())).toBe(
|
|
"90123e1c-7512-523e-bb28-76fab9f2f73d",
|
|
);
|
|
});
|
|
|
|
test("v5 namespace string validation", () => {
|
|
expect(() => {
|
|
Bun.randomUUIDv5("hello.example.com", "zyxwvuts-rqpo-nmlk-jihg-fedcba000000");
|
|
}).toThrow();
|
|
|
|
expect(() => {
|
|
Bun.randomUUIDv5("hello.example.com", "invalid uuid value");
|
|
}).toThrow();
|
|
|
|
expect(Bun.randomUUIDv5("hello.example.com", "00000000-0000-0000-0000-000000000000")).toBeTypeOf("string");
|
|
});
|
|
|
|
test("v5 namespace buffer validation", () => {
|
|
expect(() => {
|
|
Bun.randomUUIDv5("hello.example.com", new Uint8Array(15));
|
|
}).toThrow();
|
|
|
|
expect(() => {
|
|
Bun.randomUUIDv5("hello.example.com", new Uint8Array(17));
|
|
}).toThrow();
|
|
|
|
expect(Bun.randomUUIDv5("hello.example.com", new Uint8Array(16).fill(0))).toBeTypeOf("string");
|
|
});
|
|
|
|
test("v5 fill buffer", () => {
|
|
const expectedUuid = Buffer.from([
|
|
0xfd, 0xda, 0x76, 0x5f, 0xfc, 0x57, 0x56, 0x04, 0xa2, 0x69, 0x52, 0xa7, 0xdf, 0x81, 0x64, 0xec,
|
|
]);
|
|
|
|
const result = Bun.randomUUIDv5("hello.example.com", dnsNamespace, "buffer");
|
|
expect(result.toString("hex")).toEqual(expectedUuid.toString("hex"));
|
|
});
|
|
|
|
test("v5 undefined/null", () => {
|
|
// @ts-expect-error testing invalid input
|
|
expect(() => Bun.randomUUIDv5()).toThrow();
|
|
// @ts-expect-error testing invalid input
|
|
expect(() => Bun.randomUUIDv5("hello")).toThrow();
|
|
// @ts-expect-error testing invalid input
|
|
expect(() => Bun.randomUUIDv5("hello.example.com", undefined)).toThrow();
|
|
// @ts-expect-error testing invalid input
|
|
expect(() => Bun.randomUUIDv5("hello.example.com", null)).toThrow();
|
|
});
|
|
|
|
test("RFC 4122 test vectors", () => {
|
|
// These should be deterministic
|
|
const uuid1 = Bun.randomUUIDv5("http://www.example.com/", dnsNamespace);
|
|
const uuid2 = Bun.randomUUIDv5("http://www.example.com/", urlNamespace);
|
|
|
|
// Both should be valid version 5 UUIDs
|
|
expect(uuid1).toEqual("b50f73c9-e407-5ea4-8540-70886e8aa2cd");
|
|
expect(uuid2).toEqual("fcde3c85-2270-590f-9e7c-ee003d65e0e2");
|
|
});
|
|
|
|
test("error cases", () => {
|
|
// Missing namespace
|
|
// @ts-expect-error
|
|
expect(() => Bun.randomUUIDv5()).toThrow();
|
|
|
|
// Missing name
|
|
// @ts-expect-error
|
|
expect(() => Bun.randomUUIDv5(dnsNamespace)).toThrow();
|
|
|
|
// Invalid namespace format
|
|
expect(() => Bun.randomUUIDv5("test", "invalid-uuid")).toThrow();
|
|
|
|
// Invalid encoding
|
|
// @ts-expect-error
|
|
expect(() => Bun.randomUUIDv5("test", dnsNamespace, "invalid")).toThrow();
|
|
|
|
// Namespace buffer wrong size
|
|
expect(() => Bun.randomUUIDv5("test", new Uint8Array(10))).toThrow();
|
|
});
|
|
|
|
test("long names", () => {
|
|
const longName = "a".repeat(10000);
|
|
const result = Bun.randomUUIDv5(longName, dnsNamespace);
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result[14]).toBe("5");
|
|
});
|
|
|
|
test("unicode names", () => {
|
|
const unicodeName = "测试🌟";
|
|
const result = Bun.randomUUIDv5(unicodeName, dnsNamespace);
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result[14]).toBe("5");
|
|
|
|
// Should be deterministic
|
|
const uuid2 = Bun.randomUUIDv5(unicodeName, dnsNamespace);
|
|
expect(result).toBe(uuid2);
|
|
|
|
expect(uuid.v5(unicodeName, dnsNamespace)).toBe(result);
|
|
});
|
|
|
|
test("variant bits are set correctly", () => {
|
|
const result = Bun.randomUUIDv5("test", dnsNamespace, "buffer");
|
|
|
|
// Check variant bits (bits 6-7 of clock_seq_hi_and_reserved should be 10)
|
|
const variantByte = result[8];
|
|
const variantBits = (variantByte & 0xc0) >> 6;
|
|
expect(variantBits).toBe(2); // Binary 10
|
|
|
|
expect(uuid.v5("test", dnsNamespace).replace(/-/g, "")).toEqual(result.toString("hex"));
|
|
});
|
|
|
|
test("url namespace", () => {
|
|
const result = Bun.randomUUIDv5("test", "6ba7b811-9dad-11d1-80b4-00c04fd430c8");
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result).toEqual("da5b8893-d6ca-5c1c-9a9c-91f40a2a3649");
|
|
|
|
expect(uuid.v5("test", urlNamespace)).toEqual(result);
|
|
});
|
|
|
|
test("dns namespace", () => {
|
|
const result = Bun.randomUUIDv5("test", "dns");
|
|
expect(result).toBeTypeOf("string");
|
|
expect(result[14]).toBe("5");
|
|
expect(result).toEqual(uuid.v5("test", uuid.v5.DNS));
|
|
});
|
|
|
|
test("consistent across multiple calls", () => {
|
|
const results: string[] = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
results.push(Bun.randomUUIDv5("consistency-test", dnsNamespace));
|
|
}
|
|
|
|
// All results should be identical
|
|
const first = results[0];
|
|
for (const result of results) {
|
|
expect(result).toBe(first);
|
|
}
|
|
});
|
|
});
|