util.test.ts125 lines · main
1import { describe, expect, it } from 'vitest'
2
3import { fixSqlBackslashEscapes, selectWeightedKey } from './util'
4
5describe('fixSqlBackslashEscapes', () => {
6 it('converts backslash-apostrophe to double-apostrophe', () => {
7 expect(fixSqlBackslashEscapes("INSERT INTO t (c) VALUES ('We\\'ll be in touch')")).toBe(
8 "INSERT INTO t (c) VALUES ('We''ll be in touch')"
9 )
10 })
11
12 it('handles multiple backslash-apostrophes in one string', () => {
13 expect(fixSqlBackslashEscapes("VALUES ('Don\\'t stop, it\\'s fine')")).toBe(
14 "VALUES ('Don''t stop, it''s fine')"
15 )
16 })
17
18 it('handles multiple string literals in one query', () => {
19 expect(fixSqlBackslashEscapes("INSERT INTO t (a, b) VALUES ('We\\'ll', 'It\\'s ready')")).toBe(
20 "INSERT INTO t (a, b) VALUES ('We''ll', 'It''s ready')"
21 )
22 })
23
24 it('leaves already-correct double apostrophes unchanged', () => {
25 const sql = "INSERT INTO t (c) VALUES ('We''ll be in touch')"
26 expect(fixSqlBackslashEscapes(sql)).toBe(sql)
27 })
28
29 it('leaves SQL with no apostrophes unchanged', () => {
30 const sql = 'SELECT 1 + 1'
31 expect(fixSqlBackslashEscapes(sql)).toBe(sql)
32 })
33
34 it('leaves backslash-apostrophe inside dollar-quoted strings unchanged', () => {
35 const sql = "SELECT $$We\\'ll do it$$ AS greeting"
36 expect(fixSqlBackslashEscapes(sql)).toBe(sql)
37 })
38
39 it('fixes regular strings but leaves dollar-quoted strings unchanged', () => {
40 expect(
41 fixSqlBackslashEscapes("INSERT INTO t (a, b) VALUES ('We\\'ll', $$Don\\'t escape$$)")
42 ).toBe("INSERT INTO t (a, b) VALUES ('We''ll', $$Don\\'t escape$$)")
43 })
44})
45
46describe('selectWeightedKey', () => {
47 it('should return a valid key from the weights object', async () => {
48 const weights = { a: 10, b: 20, c: 30 }
49 const result = await selectWeightedKey('test-input', weights)
50
51 expect(Object.keys(weights)).toContain(result)
52 })
53
54 it('should return consistent results for the same input', async () => {
55 const weights = { region1: 40, region2: 10, region3: 20 }
56 const input = 'consistent-key'
57
58 const result1 = await selectWeightedKey(input, weights)
59 const result2 = await selectWeightedKey(input, weights)
60 const result3 = await selectWeightedKey(input, weights)
61
62 expect(result1).toBe(result2)
63 expect(result2).toBe(result3)
64 })
65
66 it('should distribute keys according to weights', async () => {
67 const weights = { a: 80, b: 10, c: 10 }
68 const numSamples = 10000
69 const samples = Array.from({ length: numSamples }, (_, i) => `sample-${i}`)
70
71 const results = await Promise.all(samples.map((sample) => selectWeightedKey(sample, weights)))
72
73 const counts = results.reduce<Record<string, number>>((acc, key) => {
74 acc[key] = (acc[key] ?? 0) + 1
75 return acc
76 }, {})
77
78 expect(counts.a / numSamples).toBeCloseTo(0.8, 1)
79 expect(counts.b / numSamples).toBeCloseTo(0.1, 1)
80 expect(counts.c / numSamples).toBeCloseTo(0.1, 1)
81 })
82
83 it('should handle equal weights', async () => {
84 const weights = { x: 25, y: 25, z: 25, w: 25 }
85 const numSamples = 8000
86 const samples = Array.from({ length: numSamples }, (_, i) => `equal-${i}`)
87
88 const results = await Promise.all(samples.map((sample) => selectWeightedKey(sample, weights)))
89
90 const counts = results.reduce<Record<string, number>>((acc, key) => {
91 acc[key] = (acc[key] ?? 0) + 1
92 return acc
93 }, {})
94
95 // Each key should get roughly 25% of the samples
96 Object.values(counts).forEach((count) => {
97 expect(count / numSamples).toBeCloseTo(0.25, 1)
98 })
99 })
100
101 it('should handle single key', async () => {
102 const weights = { only: 100 }
103 const result = await selectWeightedKey('any-input', weights)
104
105 expect(result).toBe('only')
106 })
107
108 it('should handle empty string input', async () => {
109 const weights = { a: 10, b: 20 }
110 const result = await selectWeightedKey('', weights)
111
112 expect(Object.keys(weights)).toContain(result)
113 })
114
115 it('should handle unicode characters in input', async () => {
116 const weights = { option1: 50, option2: 50 }
117 const unicodeInput = '🔑-unicode-key-测试'
118
119 const result1 = await selectWeightedKey(unicodeInput, weights)
120 const result2 = await selectWeightedKey(unicodeInput, weights)
121
122 expect(result1).toBe(result2)
123 expect(Object.keys(weights)).toContain(result1)
124 })
125})