Skip to content
Open
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
63 changes: 63 additions & 0 deletions src/allowlist/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { describe, it, expect, vi } from 'vitest'
import { isQueryAllowed } from './index'
import { DataSource } from '../types'
import { StarbaseDBConfiguration } from '../handler'

describe('isQueryAllowed', () => {
const createMockDataSource = (rows: any[] = []): DataSource => ({
source: 'main',
rpc: {
executeQuery: vi.fn().mockResolvedValue(rows),
executeBatch: vi.fn(),
},
})

it('returns true immediately when allowlist is disabled', async () => {
const ds = createMockDataSource()
const result = await isQueryAllowed({
sql: 'SELECT * FROM users',
isEnabled: false,
dataSource: ds,
config: { role: 'user' } as StarbaseDBConfiguration,
})
expect(result).toBe(true)
})

it('returns true immediately when role is admin', async () => {
const ds = createMockDataSource()
const result = await isQueryAllowed({
sql: 'DROP TABLE users',
isEnabled: true,
dataSource: ds,
config: { role: 'admin' } as StarbaseDBConfiguration,
})
expect(result).toBe(true)
})

it('allows matching SQL query regardless of trailing semicolon', async () => {
const ds = createMockDataSource([
{ sql_statement: 'SELECT id, name FROM users', source: 'main' },
])
const result = await isQueryAllowed({
sql: 'SELECT id, name FROM users;',
isEnabled: true,
dataSource: ds,
config: { role: 'user' } as StarbaseDBConfiguration,
})
expect(result).toBe(true)
})

it('throws Query not allowed for unlisted SQL statement', async () => {
const ds = createMockDataSource([
{ sql_statement: 'SELECT id FROM users', source: 'main' },
])
await expect(
isQueryAllowed({
sql: 'DELETE FROM users',
isEnabled: true,
dataSource: ds,
config: { role: 'user' } as StarbaseDBConfiguration,
})
).rejects.toThrow('Query not allowed')
})
})
84 changes: 84 additions & 0 deletions src/import/csv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { importTableFromCsvRoute } from './csv'
import { executeOperation } from '../export'
import { DataSource } from '../types'
import { StarbaseDBConfiguration } from '../handler'

vi.mock('../export', () => ({
executeOperation: vi.fn(),
}))

describe('importTableFromCsvRoute', () => {
const mockDataSource: DataSource = {
source: 'test_source',
rpc: {
executeQuery: vi.fn(),
executeBatch: vi.fn(),
},
}
const mockConfig: StarbaseDBConfiguration = {
role: 'admin',
} as any

beforeEach(() => {
vi.clearAllMocks()
})

it('returns 400 when request body is empty', async () => {
const req = new Request('http://localhost/import/csv', { method: 'POST' })
const res = await importTableFromCsvRoute('users', req, mockDataSource, mockConfig)
expect(res.status).toBe(400)
const json: any = await res.json()
expect(json.error).toBe('Request body is empty')
})

it('returns 400 on unsupported Content-Type', async () => {
const req = new Request('http://localhost/import/csv', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: 'id,name\n1,Alice',
})
const res = await importTableFromCsvRoute('users', req, mockDataSource, mockConfig)
expect(res.status).toBe(400)
const json: any = await res.json()
expect(json.error).toBe('Unsupported Content-Type')
})

it('imports valid text/csv records successfully', async () => {
vi.mocked(executeOperation).mockResolvedValueOnce(undefined as any)
const req = new Request('http://localhost/import/csv', {
method: 'POST',
headers: { 'Content-Type': 'text/csv' },
body: 'id,name\n1,Alice',
})
const res = await importTableFromCsvRoute('users', req, mockDataSource, mockConfig)
expect(res.status).toBe(200)
const json: any = await res.json()
expect(json.data.message).toContain('Imported 1 out of 1 records successfully')
expect(executeOperation).toHaveBeenCalledWith(
[{ sql: 'INSERT INTO users (id, name) VALUES (?, ?)', params: ['1', 'Alice'] }],
mockDataSource,
mockConfig
)
})

it('imports application/json with columnMapping and tracks failed statements', async () => {
vi.mocked(executeOperation)
.mockResolvedValueOnce(undefined as any)
.mockRejectedValueOnce(new Error('Unique constraint failed'))

const req = new Request('http://localhost/import/csv', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data: 'user_id,full_name\n1,Alice\n2,Bob',
columnMapping: { user_id: 'id', full_name: 'name' },
}),
})
const res = await importTableFromCsvRoute('users', req, mockDataSource, mockConfig)
expect(res.status).toBe(200)
const json: any = await res.json()
expect(json.data.message).toContain('Imported 1 out of 2 records successfully. 1 records failed.')
expect(json.data.failedStatements).toHaveLength(1)
})
})