diff options
Diffstat (limited to 'javascript/test/snowflake.test.ts')
| -rw-r--r-- | javascript/test/snowflake.test.ts | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/javascript/test/snowflake.test.ts b/javascript/test/snowflake.test.ts new file mode 100644 index 0000000..8a38d68 --- /dev/null +++ b/javascript/test/snowflake.test.ts @@ -0,0 +1,196 @@ +import { + Snowflake, + InvalidEncodingError, + InvalidIDLengthError, + ID_BYTE_SIZE, + ALPHABET, +} from "../src/snowflake"; +import { describe, it, expect } from "vitest"; + +describe("generate", () => { + it("New returns non-zero Snowflake", () => { + const s = Snowflake.generate(); + expect(s.isZero()).toBe(false); + }); + + it("New returns unique IDs", () => { + const seen = new Set<string>(); + for (let i = 0; i < 1000; i++) { + const s = Snowflake.generate(); + const str = s.toString(); + expect(seen.has(str)).toBe(false); + seen.add(str); + } + }); +}); + +describe("zero", () => { + it("zero is zero", () => { + const z = Snowflake.zero(); + expect(z.isZero()).toBe(true); + }); + + it("generated is not zero", () => { + const s = Snowflake.generate(); + expect(s.isZero()).toBe(false); + }); +}); + +describe("base62 round-trip", () => { + it("single round-trip", () => { + const original = Snowflake.generate(); + const str = original.toString(); + const parsed = Snowflake.fromString(str); + expect(parsed).toEqual(original); + }); + + it("many round-trips", () => { + for (let i = 0; i < 100; i++) { + const original = Snowflake.generate(); + const str = original.toString(); + const parsed = Snowflake.fromString(str); + expect(parsed).toEqual(original); + } + }); +}); + +describe("base62 errors", () => { + it("invalid characters", () => { + expect(() => Snowflake.fromString("!!invalid!!")).toThrow(InvalidEncodingError); + }); + + it("empty string produces zero", () => { + const s = Snowflake.fromString(""); + expect(s.isZero()).toBe(true); + }); + + it("too long string throws", () => { + const tooLong = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + expect(() => Snowflake.fromString(tooLong)).toThrow(InvalidEncodingError); + }); +}); + +describe("zero stringification", () => { + it("zero stringifies to '0'", () => { + const z = Snowflake.zero(); + expect(z.toString()).toBe("0"); + }); + + it("zero round-trips", () => { + const z = Snowflake.zero(); + const str = z.toString(); + const parsed = Snowflake.fromString(str); + expect(parsed.isZero()).toBe(true); + }); +}); + +describe("hex encoding", () => { + it("hex has correct length and characters", () => { + const s = Snowflake.generate(); + const hex = s.toHex(); + expect(hex.length).toBe(ID_BYTE_SIZE * 2); + expect(hex).toMatch(/^[0-9a-f]+$/); + }); + + it("hex round-trip", () => { + const original = Snowflake.generate(); + const hex = original.toHex(); + const parsed = Snowflake.fromHex(hex); + expect(parsed).toEqual(original); + }); + + it("invalid hex throws", () => { + expect(() => Snowflake.fromHex("xyz")).toThrow(InvalidEncodingError); + }); + + it("wrong length hex throws", () => { + expect(() => Snowflake.fromHex("01")).toThrow(InvalidIDLengthError); + }); +}); + +describe("JSON serialization", () => { + it("toJSON returns base62 string", () => { + const s = Snowflake.generate(); + const json = JSON.stringify(s); + const parsed = JSON.parse(json); + expect(parsed).toBe(s.toString()); + }); + + it("fromJSON round-trip", () => { + const s = Snowflake.generate(); + const json = JSON.stringify(s); + const parsed = Snowflake.fromJSON(json); + expect(parsed).toEqual(s); + }); + + it("fromJSON rejects missing quotes", () => { + expect(() => Snowflake.fromJSON("invalid")).toThrow(InvalidEncodingError); + }); + + it("fromJSON empty string produces zero", () => { + const s = Snowflake.fromJSON('""'); + expect(s.isZero()).toBe(true); + }); + + it("fromJSON invalid inner content throws", () => { + expect(() => Snowflake.fromJSON('"!!!"')).toThrow(InvalidEncodingError); + }); +}); + +describe("bytes", () => { + it("toBytes returns correct length", () => { + const s = Snowflake.generate(); + const bytes = s.toBytes(); + expect(bytes.length).toBe(ID_BYTE_SIZE); + }); + + it("fromBytes round-trip with Uint8Array", () => { + const original = Snowflake.generate(); + const bytes = original.toBytes(); + const parsed = Snowflake.fromBytes(bytes); + expect(parsed).toEqual(original); + }); + + it("fromBytes round-trip with string", () => { + const original = Snowflake.generate(); + const bytes = original.toBytes(); + const str = String.fromCharCode(...bytes); + const parsed = Snowflake.fromBytes(str); + expect(parsed).toEqual(original); + }); + + it("fromBytes nil-like (empty) throws", () => { + expect(() => Snowflake.fromBytes(new Uint8Array(0))).toThrow(InvalidIDLengthError); + }); + + it("fromBytes wrong length throws", () => { + expect(() => Snowflake.fromBytes(new Uint8Array([1, 2, 3]))).toThrow(InvalidIDLengthError); + }); + + it("fromBytes zero bytes produces zero", () => { + const zeroBytes = new Uint8Array(ID_BYTE_SIZE); + const s = Snowflake.fromBytes(zeroBytes); + expect(s.isZero()).toBe(true); + }); +}); + +describe("JSON embedding", () => { + it("embedded in struct round-trips", () => { + const s = Snowflake.generate(); + const wrapper = { name: "test", id: s, ref: s }; + const data = JSON.stringify(wrapper); + const decoded = JSON.parse(data); + expect(decoded.id).toBe(s.toString()); + expect(decoded.ref).toBe(s.toString()); + }); +}); + +describe("cross-encoding consistency", () => { + it("toString and toHex encode the same bytes", () => { + const s = Snowflake.generate(); + const fromBase62 = Snowflake.fromString(s.toString()); + const fromHex = Snowflake.fromHex(s.toHex()); + expect(fromBase62.bytes).toEqual(fromHex.bytes); + }); +}); |