Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions src/test/migrations-memoize.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
'use strict'

// Simple test for memoizePromise helper function
describe('memoizePromise helper', () => {
it('should cache promise results correctly', async () => {
// Create a simple memoized function for testing
const memoizePromise = <T extends any[], R>(
fn: (...args: T) => Promise<R>
): ((...args: T) => Promise<R>) => {
const cache = new Map<string, Promise<R>>()

return async (...args: T): Promise<R> => {
const key = JSON.stringify(args)

if (cache.has(key)) {
return cache.get(key)!
}

const promise = fn(...args)
cache.set(key, promise)
return promise
}
}

// Test function that returns different values
let callCount = 0
const testFunction = async (arg: string): Promise<string> => {
callCount++
return `result-${arg}-${callCount}`
}

const memoizedFunction = memoizePromise(testFunction)

// First call
const result1 = await memoizedFunction('test')
expect(result1).toBe('result-test-1')
expect(callCount).toBe(1)

// Second call with same argument should return cached result
const result2 = await memoizedFunction('test')
expect(result2).toBe('result-test-1') // Same as first call
expect(callCount).toBe(1) // Function not called again

// Different argument should call function again
const result3 = await memoizedFunction('different')
expect(result3).toBe('result-different-2')
expect(callCount).toBe(2)
})

it('should handle different argument combinations', async () => {
const memoizePromise = <T extends any[], R>(
fn: (...args: T) => Promise<R>
): ((...args: T) => Promise<R>) => {
const cache = new Map<string, Promise<R>>()

return async (...args: T): Promise<R> => {
const key = JSON.stringify(args)

if (cache.has(key)) {
return cache.get(key)!
}

const promise = fn(...args)
cache.set(key, promise)
return promise
}
}

let callCount = 0
const testFunction = async (arg1: string, arg2: number): Promise<string> => {
callCount++
return `${arg1}-${arg2}-${callCount}`
}

const memoizedFunction = memoizePromise(testFunction)

// Different argument combinations should not use cache
const result1 = await memoizedFunction('a', 1)
const result2 = await memoizedFunction('b', 2)
const result3 = await memoizedFunction('a', 1) // Same as first call

expect(result1).toBe('a-1-1')
expect(result2).toBe('b-2-2')
expect(result3).toBe('a-1-1') // Cached result
expect(callCount).toBe(2) // Only called twice
})

it('should generate keys for objects and primitives', async () => {
const memoizePromise = <T extends any[], R>(
fn: (...args: T) => Promise<R>
): ((...args: T) => Promise<R>) => {
const cache = new Map<string, Promise<R>>()

return async (...args: T): Promise<R> => {
const key = JSON.stringify(args)

if (cache.has(key)) {
return cache.get(key)!
}

const promise = fn(...args)
cache.set(key, promise)
return promise
}
}

let callCount = 0
const testFunction = async (obj: { name: string }, num: number): Promise<string> => {
callCount++
return `${obj.name}-${num}-${callCount}`
}

const memoizedFunction = memoizePromise(testFunction)

const obj1 = { name: 'test' }
const obj2 = { name: 'test' }

// Same object content should use cache
const result1 = await memoizedFunction(obj1, 1)
const result2 = await memoizedFunction(obj2, 1)

expect(result1).toBe('test-1-1')
expect(result2).toBe('test-1-1') // Cached result
expect(callCount).toBe(1) // Only called once
})

it('should handle promise rejections correctly', async () => {
const memoizePromise = <T extends any[], R>(
fn: (...args: T) => Promise<R>
): ((...args: T) => Promise<R>) => {
const cache = new Map<string, Promise<R>>()

return async (...args: T): Promise<R> => {
const key = JSON.stringify(args)

if (cache.has(key)) {
return cache.get(key)!
}

const promise = fn(...args)
cache.set(key, promise)
return promise
}
}

let callCount = 0
const testFunction = async (shouldFail: boolean): Promise<string> => {
callCount++
if (shouldFail) {
throw new Error('Test error')
}
return `success-${callCount}`
}

const memoizedFunction = memoizePromise(testFunction)

// First call should fail
await expect(memoizedFunction(true)).rejects.toThrow('Test error')
expect(callCount).toBe(1)

// Second call with same argument should fail again (cached error)
await expect(memoizedFunction(true)).rejects.toThrow('Test error')
expect(callCount).toBe(1) // Still 1 because error was cached

// Different argument should work
const result = await memoizedFunction(false)
expect(result).toBe('success-2')
expect(callCount).toBe(2) // Only called twice (once for true, once for false)
})
})
112 changes: 112 additions & 0 deletions src/test/migrations-transformers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
'use strict'

import { DisableConcurrentIndexTransformer } from '@internal/database/migrations/transformers'

describe('DisableConcurrentIndexTransformer', () => {
const transformer = new DisableConcurrentIndexTransformer()

it('should replace INDEX CONCURRENTLY with INDEX', () => {
const migration = {
id: 1,
name: 'test-migration',
hash: 'abc123',
sql: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
contents: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
fileName: 'test.sql',
}

const result = transformer.transform(migration)

expect(result.sql).toBe('CREATE INDEX idx_name ON table (column);')
expect(result.contents).toBe('CREATE INDEX idx_name ON table (column);')
expect(result.id).toBe(1)
expect(result.name).toBe('test-migration')
expect(result.hash).toBe('abc123')
})

it('should remove disable-transaction directive', () => {
const migration = {
id: 2,
name: 'test-migration-2',
hash: 'def456',
sql: '-- postgres-migrations disable-transaction\nCREATE INDEX CONCURRENTLY idx_name ON table (column);',
contents:
'-- postgres-migrations disable-transaction\nCREATE INDEX CONCURRENTLY idx_name ON table (column);',
fileName: 'test2.sql',
}

const result = transformer.transform(migration)

expect(result.sql).toBe('\nCREATE INDEX idx_name ON table (column);')
expect(result.contents).toBe('\nCREATE INDEX idx_name ON table (column);')
})

it('should handle migrations without CONCURRENTLY (no-op)', () => {
const migration = {
id: 3,
name: 'test-migration-3',
hash: 'ghi789',
sql: 'CREATE TABLE test_table (id SERIAL PRIMARY KEY);',
contents: 'CREATE TABLE test_table (id SERIAL PRIMARY KEY);',
fileName: 'test3.sql',
}

const result = transformer.transform(migration)

expect(result).toEqual(migration)
})

it('should handle multiple CONCURRENTLY occurrences', () => {
const migration = {
id: 4,
name: 'test-migration-4',
hash: 'jkl012',
sql: 'CREATE INDEX CONCURRENTLY idx1 ON table1 (col1);\nCREATE INDEX CONCURRENTLY idx2 ON table2 (col2);',
contents:
'CREATE INDEX CONCURRENTLY idx1 ON table1 (col1);\nCREATE INDEX CONCURRENTLY idx2 ON table2 (col2);',
fileName: 'test4.sql',
}

const result = transformer.transform(migration)

expect(result.sql).toBe(
'CREATE INDEX idx1 ON table1 (col1);\nCREATE INDEX idx2 ON table2 (col2);'
)
expect(result.contents).toBe(
'CREATE INDEX idx1 ON table1 (col1);\nCREATE INDEX idx2 ON table2 (col2);'
)
})

it('should preserve migration structure', () => {
const migration = {
id: 5,
name: 'complex-migration',
hash: 'mno345',
sql: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
contents: 'CREATE INDEX CONCURRENTLY idx_name ON table (column);',
fileName: 'complex.sql',
}

const result = transformer.transform(migration)

expect(result.id).toBe(migration.id)
expect(result.name).toBe(migration.name)
expect(result.hash).toBe(migration.hash)
expect(result.fileName).toBe(migration.fileName)
})

it('should handle edge cases (empty sql, no matches)', () => {
const migration = {
id: 6,
name: 'empty-migration',
hash: 'pqr678',
sql: '',
contents: '',
fileName: 'empty.sql',
}

const result = transformer.transform(migration)

expect(result).toEqual(migration)
})
})
Loading
Loading