Skip to content

Commit 73af8f2

Browse files
author
georgiy.rusanov
committed
chore: added a few tests for the custom migration logic
1 parent a6831ce commit 73af8f2

File tree

2 files changed

+276
-0
lines changed

2 files changed

+276
-0
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
'use strict'
2+
3+
// Simple test for memoizePromise helper function
4+
describe('memoizePromise helper', () => {
5+
it('should cache promise results correctly', async () => {
6+
// Create a simple memoized function for testing
7+
const memoizePromise = <T extends any[], R>(
8+
fn: (...args: T) => Promise<R>
9+
): ((...args: T) => Promise<R>) => {
10+
const cache = new Map<string, Promise<R>>()
11+
12+
return async (...args: T): Promise<R> => {
13+
const key = JSON.stringify(args)
14+
15+
if (cache.has(key)) {
16+
return cache.get(key)!
17+
}
18+
19+
const promise = fn(...args)
20+
cache.set(key, promise)
21+
return promise
22+
}
23+
}
24+
25+
// Test function that returns different values
26+
let callCount = 0
27+
const testFunction = async (arg: string): Promise<string> => {
28+
callCount++
29+
return `result-${arg}-${callCount}`
30+
}
31+
32+
const memoizedFunction = memoizePromise(testFunction)
33+
34+
// First call
35+
const result1 = await memoizedFunction('test')
36+
expect(result1).toBe('result-test-1')
37+
expect(callCount).toBe(1)
38+
39+
// Second call with same argument should return cached result
40+
const result2 = await memoizedFunction('test')
41+
expect(result2).toBe('result-test-1') // Same as first call
42+
expect(callCount).toBe(1) // Function not called again
43+
44+
// Different argument should call function again
45+
const result3 = await memoizedFunction('different')
46+
expect(result3).toBe('result-different-2')
47+
expect(callCount).toBe(2)
48+
})
49+
50+
it('should handle different argument combinations', async () => {
51+
const memoizePromise = <T extends any[], R>(
52+
fn: (...args: T) => Promise<R>
53+
): ((...args: T) => Promise<R>) => {
54+
const cache = new Map<string, Promise<R>>()
55+
56+
return async (...args: T): Promise<R> => {
57+
const key = JSON.stringify(args)
58+
59+
if (cache.has(key)) {
60+
return cache.get(key)!
61+
}
62+
63+
const promise = fn(...args)
64+
cache.set(key, promise)
65+
return promise
66+
}
67+
}
68+
69+
let callCount = 0
70+
const testFunction = async (arg1: string, arg2: number): Promise<string> => {
71+
callCount++
72+
return `${arg1}-${arg2}-${callCount}`
73+
}
74+
75+
const memoizedFunction = memoizePromise(testFunction)
76+
77+
// Different argument combinations should not use cache
78+
const result1 = await memoizedFunction('a', 1)
79+
const result2 = await memoizedFunction('b', 2)
80+
const result3 = await memoizedFunction('a', 1) // Same as first call
81+
82+
expect(result1).toBe('a-1-1')
83+
expect(result2).toBe('b-2-2')
84+
expect(result3).toBe('a-1-1') // Cached result
85+
expect(callCount).toBe(2) // Only called twice
86+
})
87+
88+
it('should generate keys for objects and primitives', async () => {
89+
const memoizePromise = <T extends any[], R>(
90+
fn: (...args: T) => Promise<R>
91+
): ((...args: T) => Promise<R>) => {
92+
const cache = new Map<string, Promise<R>>()
93+
94+
return async (...args: T): Promise<R> => {
95+
const key = JSON.stringify(args)
96+
97+
if (cache.has(key)) {
98+
return cache.get(key)!
99+
}
100+
101+
const promise = fn(...args)
102+
cache.set(key, promise)
103+
return promise
104+
}
105+
}
106+
107+
let callCount = 0
108+
const testFunction = async (obj: { name: string }, num: number): Promise<string> => {
109+
callCount++
110+
return `${obj.name}-${num}-${callCount}`
111+
}
112+
113+
const memoizedFunction = memoizePromise(testFunction)
114+
115+
const obj1 = { name: 'test' }
116+
const obj2 = { name: 'test' }
117+
118+
// Same object content should use cache
119+
const result1 = await memoizedFunction(obj1, 1)
120+
const result2 = await memoizedFunction(obj2, 1)
121+
122+
expect(result1).toBe('test-1-1')
123+
expect(result2).toBe('test-1-1') // Cached result
124+
expect(callCount).toBe(1) // Only called once
125+
})
126+
127+
it('should handle promise rejections correctly', async () => {
128+
const memoizePromise = <T extends any[], R>(
129+
fn: (...args: T) => Promise<R>
130+
): ((...args: T) => Promise<R>) => {
131+
const cache = new Map<string, Promise<R>>()
132+
133+
return async (...args: T): Promise<R> => {
134+
const key = JSON.stringify(args)
135+
136+
if (cache.has(key)) {
137+
return cache.get(key)!
138+
}
139+
140+
const promise = fn(...args)
141+
cache.set(key, promise)
142+
return promise
143+
}
144+
}
145+
146+
let callCount = 0
147+
const testFunction = async (shouldFail: boolean): Promise<string> => {
148+
callCount++
149+
if (shouldFail) {
150+
throw new Error('Test error')
151+
}
152+
return `success-${callCount}`
153+
}
154+
155+
const memoizedFunction = memoizePromise(testFunction)
156+
157+
// First call should fail
158+
await expect(memoizedFunction(true)).rejects.toThrow('Test error')
159+
expect(callCount).toBe(1)
160+
161+
// Second call with same argument should fail again (cached error)
162+
await expect(memoizedFunction(true)).rejects.toThrow('Test error')
163+
expect(callCount).toBe(1) // Still 1 because error was cached
164+
165+
// Different argument should work
166+
const result = await memoizedFunction(false)
167+
expect(result).toBe('success-2')
168+
expect(callCount).toBe(2) // Only called twice (once for true, once for false)
169+
})
170+
})
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use strict'
2+
3+
import { DisableConcurrentIndexTransformer } from '@internal/database/migrations/transformers'
4+
5+
describe('DisableConcurrentIndexTransformer', () => {
6+
const transformer = new DisableConcurrentIndexTransformer()
7+
8+
it('should replace INDEX CONCURRENTLY with INDEX', () => {
9+
const migration = {
10+
id: 1,
11+
name: 'test-migration',
12+
hash: 'abc123',
13+
sql: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
14+
contents: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
15+
fileName: 'test.sql',
16+
}
17+
18+
const result = transformer.transform(migration)
19+
20+
expect(result.sql).toBe('CREATE INDEX idx_name ON table (column);')
21+
expect(result.contents).toBe('CREATE INDEX idx_name ON table (column);')
22+
expect(result.id).toBe(1)
23+
expect(result.name).toBe('test-migration')
24+
expect(result.hash).toBe('abc123')
25+
})
26+
27+
it('should remove disable-transaction directive', () => {
28+
const migration = {
29+
id: 2,
30+
name: 'test-migration-2',
31+
hash: 'def456',
32+
sql: '-- postgres-migrations disable-transaction\nCREATE INDEX CONCURRENTLY idx_name ON table (column);',
33+
contents: '-- postgres-migrations disable-transaction\nCREATE INDEX CONCURRENTLY idx_name ON table (column);',
34+
fileName: 'test2.sql',
35+
}
36+
37+
const result = transformer.transform(migration)
38+
39+
expect(result.sql).toBe('\nCREATE INDEX idx_name ON table (column);')
40+
expect(result.contents).toBe('\nCREATE INDEX idx_name ON table (column);')
41+
})
42+
43+
it('should handle migrations without CONCURRENTLY (no-op)', () => {
44+
const migration = {
45+
id: 3,
46+
name: 'test-migration-3',
47+
hash: 'ghi789',
48+
sql: 'CREATE TABLE test_table (id SERIAL PRIMARY KEY);',
49+
contents: 'CREATE TABLE test_table (id SERIAL PRIMARY KEY);',
50+
fileName: 'test3.sql',
51+
}
52+
53+
const result = transformer.transform(migration)
54+
55+
expect(result).toEqual(migration)
56+
})
57+
58+
it('should handle multiple CONCURRENTLY occurrences', () => {
59+
const migration = {
60+
id: 4,
61+
name: 'test-migration-4',
62+
hash: 'jkl012',
63+
sql: 'CREATE INDEX CONCURRENTLY idx1 ON table1 (col1);\nCREATE INDEX CONCURRENTLY idx2 ON table2 (col2);',
64+
contents: 'CREATE INDEX CONCURRENTLY idx1 ON table1 (col1);\nCREATE INDEX CONCURRENTLY idx2 ON table2 (col2);',
65+
fileName: 'test4.sql',
66+
}
67+
68+
const result = transformer.transform(migration)
69+
70+
expect(result.sql).toBe('CREATE INDEX idx1 ON table1 (col1);\nCREATE INDEX idx2 ON table2 (col2);')
71+
expect(result.contents).toBe('CREATE INDEX idx1 ON table1 (col1);\nCREATE INDEX idx2 ON table2 (col2);')
72+
})
73+
74+
it('should preserve migration structure', () => {
75+
const migration = {
76+
id: 5,
77+
name: 'complex-migration',
78+
hash: 'mno345',
79+
sql: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
80+
contents: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
81+
fileName: 'complex.sql',
82+
}
83+
84+
const result = transformer.transform(migration)
85+
86+
expect(result.id).toBe(migration.id)
87+
expect(result.name).toBe(migration.name)
88+
expect(result.hash).toBe(migration.hash)
89+
expect(result.fileName).toBe(migration.fileName)
90+
})
91+
92+
it('should handle edge cases (empty sql, no matches)', () => {
93+
const migration = {
94+
id: 6,
95+
name: 'empty-migration',
96+
hash: 'pqr678',
97+
sql: '',
98+
contents: '',
99+
fileName: 'empty.sql',
100+
}
101+
102+
const result = transformer.transform(migration)
103+
104+
expect(result).toEqual(migration)
105+
})
106+
})

0 commit comments

Comments
 (0)