Skip to content
Open
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
249 changes: 249 additions & 0 deletions src/operation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ import type { DataSource } from './types'
import type { StarbaseDBConfiguration } from './handler'
import type { SqlConnection } from '@outerbase/sdk/dist/connections/sql-base'

const mockSdkConnection = vi.hoisted(() => ({
connect: vi.fn(),
raw: vi.fn(),
}))
const mockPostgresEnd = vi.hoisted(() => vi.fn())
const mockPostgresUnsafe = vi.hoisted(() => vi.fn())
const mockPostgresFactory = vi.hoisted(() =>
vi.fn(() => ({
unsafe: mockPostgresUnsafe,
end: mockPostgresEnd,
}))
)

vi.mock('pg', () => ({ Client: vi.fn() }))
vi.mock('mysql2', () => ({ createConnection: vi.fn(() => ({})) }))
vi.mock('@libsql/client/web', () => ({ createClient: vi.fn(() => ({})) }))
vi.mock('postgres', () => ({ default: mockPostgresFactory }))
vi.mock('@outerbase/sdk', () => ({
CloudflareD1Connection: vi.fn(() => mockSdkConnection),
MySQLConnection: vi.fn(() => mockSdkConnection),
PostgreSQLConnection: vi.fn(() => mockSdkConnection),
StarbaseConnection: vi.fn(() => mockSdkConnection),
TursoConnection: vi.fn(() => mockSdkConnection),
}))

// const mockSqlConnection = vi.hoisted(() => ({
// connect: vi.fn().mockResolvedValue(undefined),
// raw: vi
Expand Down Expand Up @@ -133,6 +158,12 @@ beforeEach(() => {

vi.mocked(beforeQueryCache).mockResolvedValue(null)
vi.mocked(afterQueryCache).mockResolvedValue(null)
mockSdkConnection.connect.mockResolvedValue(undefined)
mockSdkConnection.raw.mockResolvedValue({
data: [{ id: 1, name: 'SDK-Test-Result' }],
})
mockPostgresUnsafe.mockResolvedValue([{ id: 3, name: 'Hyperdrive' }])
mockPostgresEnd.mockResolvedValue(undefined)
// vi.mock('./operation', () => ({
// createSDKPostgresConnection: vi
// .fn()
Expand Down Expand Up @@ -277,6 +308,106 @@ describe('executeQuery', () => {
expect(mockDataSource.rpc.executeQuery).not.toHaveBeenCalled()
})

it('should skip cache lookup for raw queries and convert raw rows back after hooks', async () => {
mockDataSource.rpc.executeQuery = vi.fn().mockResolvedValue({
columns: ['id', 'name'],
rows: [
[1, 'Alice'],
[2, 'Bob'],
],
meta: { rows_read: 2, rows_written: 0 },
})
mockDataSource.registry = {
beforeQuery: vi.fn(async ({ sql, params }) => ({
sql: `${sql} WHERE active = ?`,
params: [...(params ?? []), true],
})),
afterQuery: vi.fn(async ({ result }) =>
result.map((row: any) => ({
...row,
seenByPlugin: true,
}))
),
} as any

const result = await executeQuery({
sql: 'SELECT * FROM users',
params: [],
isRaw: true,
dataSource: mockDataSource,
config: mockConfig,
})

expect(beforeQueryCache).not.toHaveBeenCalled()
expect(afterQueryCache).not.toHaveBeenCalled()
expect(mockDataSource.rpc.executeQuery).toHaveBeenCalledWith({
sql: 'SELECT * FROM users WHERE active = ?',
params: [true],
isRaw: true,
})
expect(result).toEqual({
columns: ['id', 'name', 'seenByPlugin'],
rows: [
[1, 'Alice', true],
[2, 'Bob', true],
],
meta: { rows_read: 2, rows_written: 0 },
})
})

it('should return empty results when an internal query returns nothing', async () => {
mockDataSource.rpc.executeQuery = vi.fn().mockResolvedValue(undefined)

const result = await executeQuery({
sql: 'SELECT * FROM missing',
params: undefined,
isRaw: false,
dataSource: mockDataSource,
config: mockConfig,
})

expect(result).toEqual([])
expect(afterQueryCache).not.toHaveBeenCalled()
})

it('should execute hyperdrive queries and close the postgres client through waitUntil', async () => {
const waitUntil = vi.fn()
const result = await executeQuery({
sql: 'SELECT * FROM users WHERE id = ?',
params: [3],
isRaw: false,
dataSource: {
source: 'hyperdrive',
external: { connectionString: 'postgres://example' },
executionContext: { waitUntil },
} as any,
config: { ...mockConfig, features: {} },
})

expect(mockPostgresFactory).toHaveBeenCalledWith('postgres://example', {
max: 5,
fetch_types: false,
})
expect(mockPostgresUnsafe).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = ?',
[3]
)
expect(waitUntil).toHaveBeenCalled()
expect(result).toEqual([{ id: 3, name: 'Hyperdrive' }])
})

it('should throw when hyperdrive has no connection string', async () => {
await expect(
executeQuery({
sql: 'SELECT 1',
params: undefined,
isRaw: false,
dataSource: { source: 'hyperdrive', external: {} } as any,
config: mockConfig,
})
).rejects.toThrow('Hyperdrive connection string not found')
})

it('should return an empty array if the data source is missing', async () => {
const result = await executeQuery({
sql: 'SELECT * FROM users',
Expand Down Expand Up @@ -445,6 +576,124 @@ describe('executeExternalQuery', () => {

expect(result).toEqual([])
})

it('should call the SDK path when no Outerbase API key is configured', async () => {
const result = await executeExternalQuery({
sql: 'SELECT * FROM users WHERE id = ?',
params: [1],
dataSource: {
source: 'postgresql',
external: {
dialect: 'postgresql',
provider: 'postgresql',
host: 'mock-host',
port: 5432,
user: 'mock-user',
password: 'mock-password',
database: 'mock-db',
},
} as any,
config: { ...mockConfig, outerbaseApiKey: undefined },
})

expect(mockSdkConnection.connect).toHaveBeenCalled()
expect(mockSdkConnection.raw).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = ?',
[1]
)
expect(result).toEqual([{ id: 1, name: 'SDK-Test-Result' }])
})
})

describe('executeSDKQuery', () => {
it.each([
[
'postgresql',
{
dialect: 'postgresql',
provider: 'postgresql',
host: 'mock-host',
port: 5432,
user: 'mock-user',
password: 'mock-password',
database: 'mock-db',
},
],
[
'mysql',
{
dialect: 'mysql',
provider: 'mysql',
host: 'mock-host',
port: 3306,
user: 'mock-user',
password: 'mock-password',
database: 'mock-db',
},
],
[
'cloudflare-d1',
{
provider: 'cloudflare-d1',
apiKey: 'mock-api-key',
accountId: 'account-id',
databaseId: 'database-id',
},
],
[
'starbase',
{
provider: 'starbase',
apiKey: 'mock-api-key',
token: 'https://starbase.example',
},
],
[
'turso',
{
provider: 'turso',
uri: 'libsql://example',
token: 'mock-token',
},
],
])('should execute SDK queries for %s sources', async (_name, external) => {
const result = await executeSDKQuery({
sql: 'SELECT 1',
params: [],
dataSource: { source: 'external', external } as any,
config: mockConfig,
})

expect(mockSdkConnection.connect).toHaveBeenCalled()
expect(mockSdkConnection.raw).toHaveBeenCalledWith('SELECT 1', [])
expect(result).toEqual([{ id: 1, name: 'SDK-Test-Result' }])
})

it('should return an empty array when no external connection exists', async () => {
const result = await executeSDKQuery({
sql: 'SELECT 1',
params: [],
dataSource: { source: 'external' } as any,
config: mockConfig,
})

expect(result).toEqual([])
expect(mockSdkConnection.connect).not.toHaveBeenCalled()
})

it('should reject unsupported external database types', async () => {
await expect(
executeSDKQuery({
sql: 'SELECT 1',
params: [],
dataSource: {
source: 'external',
external: { provider: 'unsupported' },
} as any,
config: mockConfig,
})
).rejects.toThrow('Unsupported external database type')
})
})

// describe('executeSDKQuery', () => {
Expand Down