hana-cli
Version:
HANA Developer Command Line Interface
618 lines (526 loc) • 23.8 kB
JavaScript
// @ts-nocheck
/**
* @module Profile Integration Tests - Integration tests for PostgreSQL and SQLite profile functionality
* Tests the actual database client behavior with different profiles
*/
import { assert, addContext, exec } from '../base.js'
import dbClientClass from '../../utils/database/index.js'
import PostgresClient from '../../utils/database/postgres.js'
import SqliteClient from '../../utils/database/sqlite.js'
import * as fs from 'fs'
import * as path from 'path'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
describe('Profile Integration Tests', function () {
this.timeout(30000) // Some operations may take longer with actual databases
describe('PostgreSQL Profile Client', function () {
it('should create PostgreSQL client instance', function () {
const prompts = {
profile: 'postgres',
debug: false,
table: '*',
limit: 200,
schema: 'public'
}
const optionsCDS = {
kind: 'postgres',
credentials: {
host: 'localhost',
port: 5432,
user: 'test',
password: 'test',
database: 'test',
schema: 'public'
}
}
const instance = new PostgresClient(prompts, optionsCDS)
assert.ok(instance instanceof PostgresClient)
assert.ok(instance instanceof dbClientClass)
})
it('should have correct schema for PostgreSQL', function () {
const prompts = {
profile: 'postgres',
debug: false,
schema: 'myschema'
}
const optionsCDS = {
kind: 'postgres',
credentials: { schema: 'myschema' }
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, 'myschema')
})
it('should default to public schema when not specified', function () {
const prompts = {
profile: 'postgres',
debug: false
}
const optionsCDS = {
kind: 'postgres'
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, 'public')
})
it('should handle CURRENT_SCHEMA placeholder for PostgreSQL', function () {
const prompts = {
profile: 'postgres',
debug: false,
schema: '**CURRENT_SCHEMA**'
}
const optionsCDS = {
kind: 'postgres',
credentials: { schema: 'myschema' }
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, 'myschema')
})
it('should convert wildcard for table names', function () {
const prompts = {
profile: 'postgres',
debug: false,
table: '*'
}
const optionsCDS = { kind: 'postgres' }
const instance = new PostgresClient(prompts, optionsCDS)
const result = instance.adjustWildcard('*')
assert.strictEqual(result, '%')
})
it('should have listTables method', function () {
const prompts = {
profile: 'postgres',
debug: false,
table: 'test%',
limit: 100
}
const optionsCDS = {
kind: 'postgres',
credentials: { schema: 'public' }
}
const instance = new PostgresClient(prompts, optionsCDS)
assert.ok(typeof instance.listTables === 'function')
})
it('should handle PostgreSQL-specific query structure', async function () {
const prompts = {
profile: 'postgres',
debug: false,
table: 'test',
limit: 10,
schema: 'public'
}
const optionsCDS = {
kind: 'postgres',
credentials: { schema: 'public' }
}
const instance = new PostgresClient(prompts, optionsCDS)
// Mock the database connection
const mockDB = {
run: async (query) => {
// Verify the query structure for PostgreSQL
if (typeof query === 'string' && query.includes('SET search_path')) {
return []
}
if (typeof query === 'object') {
// This is a SELECT query object from CDS
assert.ok(query, 'Query should exist')
return [
{ SCHEMA_NAME: 'public', TABLE_NAME: 'test_table' }
]
}
return []
}
}
instance.setDB(mockDB)
try {
const results = await instance.listTables()
assert.ok(Array.isArray(results))
} catch (error) {
// Expected if CDS SELECT is not available in test context
addContext(this, { title: 'Expected Error', value: error.message })
assert.ok(error.message)
}
})
})
describe('SQLite Profile Client', function () {
it('should create SQLite client instance', function () {
const prompts = {
profile: 'sqlite',
debug: false,
table: '*',
limit: 200
}
const optionsCDS = {
kind: 'sqlite',
credentials: {
database: ':memory:'
}
}
const instance = new SqliteClient(prompts, optionsCDS)
assert.ok(instance instanceof SqliteClient)
assert.ok(instance instanceof dbClientClass)
})
it('should handle in-memory SQLite database', function () {
const prompts = {
profile: 'sqlite',
debug: false
}
const optionsCDS = {
kind: 'sqlite',
credentials: { database: ':memory:' }
}
const instance = new SqliteClient(prompts, optionsCDS)
assert.ok(instance instanceof SqliteClient)
})
it('should handle file-based SQLite database', function () {
const prompts = {
profile: 'sqlite',
debug: false
}
const optionsCDS = {
kind: 'sqlite',
credentials: { database: './test.db' }
}
const instance = new SqliteClient(prompts, optionsCDS)
assert.ok(instance instanceof SqliteClient)
})
it('should convert wildcard for table names', function () {
const prompts = {
profile: 'sqlite',
debug: false,
table: '*'
}
const optionsCDS = { kind: 'sqlite' }
const instance = new SqliteClient(prompts, optionsCDS)
const result = instance.adjustWildcard('*')
assert.strictEqual(result, '%')
})
it('should have listTables method', function () {
const prompts = {
profile: 'sqlite',
debug: false,
table: 'test%',
limit: 100
}
const optionsCDS = {
kind: 'sqlite',
credentials: { database: ':memory:' }
}
const instance = new SqliteClient(prompts, optionsCDS)
assert.ok(typeof instance.listTables === 'function')
})
it('should handle SQLite-specific query structure', async function () {
const prompts = {
profile: 'sqlite',
debug: false,
table: 'test',
limit: 10
}
const optionsCDS = {
kind: 'sqlite',
credentials: { database: ':memory:' }
}
const instance = new SqliteClient(prompts, optionsCDS)
// Mock the database connection
const mockDB = {
run: async (query) => {
// Verify the query uses sqlite_schema
if (typeof query === 'object') {
// This is a SELECT query object from CDS
assert.ok(query, 'Query should exist')
return [
{ TABLE_NAME: 'test_table' }
]
}
return []
}
}
instance.setDB(mockDB)
try {
const results = await instance.listTables()
assert.ok(Array.isArray(results))
} catch (error) {
// Expected if CDS SELECT is not available in test context
addContext(this, { title: 'Expected Error', value: error.message })
assert.ok(error.message)
}
})
})
describe('Profile Factory Method - getNewClient', function () {
it('should reject invalid profile with meaningful error', async function () {
const prompts = {
profile: 'invaliddb',
debug: false
}
try {
// Set NODE_ENV to invalid profile to force an error
const oldEnv = process.env.NODE_ENV
process.env.NODE_ENV = 'invaliddb'
process.env.CDS_ENV = 'invaliddb'
await dbClientClass.getNewClient(prompts)
// Restore env
if (oldEnv) process.env.NODE_ENV = oldEnv
else delete process.env.NODE_ENV
delete process.env.CDS_ENV
assert.fail('Should have thrown an error for invalid profile')
} catch (error) {
// Restore env if error thrown
delete process.env.NODE_ENV
delete process.env.CDS_ENV
assert.ok(error instanceof Error)
// The error could be about missing CDS config, unsupported database, or module not found
assert.ok(error.message.length > 0)
}
})
it('should handle missing profile gracefully (defaults to hybrid)', async function () {
const prompts = {
debug: false
}
try {
const client = await dbClientClass.getNewClient(prompts)
// Should default to hybrid profile
assert.strictEqual(prompts.profile, 'hybrid')
} catch (error) {
// May fail due to missing connection, but profile should be set
assert.strictEqual(prompts.profile, 'hybrid')
}
})
})
describe('Profile-based Command Line Integration', function () {
it('should accept postgres profile in tables command', function (done) {
exec('node bin/tables.js --profile postgres --quiet --limit 3',
(error, stdout, stderr) => {
addContext(this, { title: 'Stdout', value: stdout })
addContext(this, { title: 'Stderr', value: stderr })
// Should not reject the profile flag
const hasProfileError = stderr.includes('Unknown or Unsupported database client type')
const hasInvalidParam = stderr.includes('Invalid') && stderr.includes('profile')
// Profile should be accepted (may fail on connection, but not on profile validation)
assert.ok(!hasInvalidParam, 'Should accept postgres profile parameter')
// If there's an error, it should be connection-related, not profile-related
if (error && stderr) {
const isConnectionError =
stderr.includes('No CAP/CDS Project Configuration') ||
stderr.includes('connection') ||
stderr.includes('ECONNREFUSED') ||
stderr.includes('credentials')
assert.ok(
isConnectionError || !hasProfileError,
'Errors should be connection-related, not profile validation errors'
)
}
done()
})
})
it('should accept sqlite profile in tables command', function (done) {
exec('node bin/tables.js --profile sqlite --quiet --limit 3',
(error, stdout, stderr) => {
addContext(this, { title: 'Stdout', value: stdout })
addContext(this, { title: 'Stderr', value: stderr })
// Should not reject the profile flag
const hasProfileError = stderr.includes('Unknown or Unsupported database client type')
const hasInvalidParam = stderr.includes('Invalid') && stderr.includes('profile')
// Profile should be accepted (may fail on connection, but not on profile validation)
assert.ok(!hasInvalidParam, 'Should accept sqlite profile parameter')
// If there's an error, it should be connection-related, not profile-related
if (error && stderr) {
const isConnectionError =
stderr.includes('No CAP/CDS Project Configuration') ||
stderr.includes('connection') ||
stderr.includes('database') ||
stderr.includes('credentials')
assert.ok(
isConnectionError || !hasProfileError,
'Errors should be connection-related, not profile validation errors'
)
}
done()
})
})
it('should accept -p alias with postgres', function (done) {
exec('node bin/tables.js -p postgres --quiet --limit 3',
(error, stdout, stderr) => {
addContext(this, { title: 'Stdout', value: stdout })
addContext(this, { title: 'Stderr', value: stderr })
const hasInvalidParam = stderr.includes('Invalid') && stderr.includes('profile')
assert.ok(!hasInvalidParam, 'Should accept -p alias for profile')
done()
})
})
it('should accept -p alias with sqlite', function (done) {
exec('node bin/tables.js -p sqlite --quiet --limit 3',
(error, stdout, stderr) => {
addContext(this, { title: 'Stdout', value: stdout })
addContext(this, { title: 'Stderr', value: stderr })
const hasInvalidParam = stderr.includes('Invalid') && stderr.includes('profile')
assert.ok(!hasInvalidParam, 'Should accept -p alias for profile')
done()
})
})
it('should handle pg as alias for postgres profile', function (done) {
exec('node bin/tables.js --profile pg --quiet --limit 3',
(error, stdout, stderr) => {
addContext(this, { title: 'Stdout', value: stdout })
addContext(this, { title: 'Stderr', value: stderr })
// pg is a common alias - should be accepted
const hasInvalidParam = stderr.includes('Invalid') && stderr.includes('profile')
assert.ok(!hasInvalidParam, 'Should accept pg as postgres alias')
done()
})
})
})
describe('Profile-specific Schema Handling', function () {
it('should correctly calculate PostgreSQL schema from credentials', function () {
const prompts = {
profile: 'postgres',
schema: '**CURRENT_SCHEMA**'
}
const optionsCDS = {
kind: 'postgres',
credentials: { schema: 'myapp' }
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, 'myapp')
})
it('should use public as default for PostgreSQL when no schema in credentials', function () {
const prompts = {
profile: 'postgres',
schema: '**CURRENT_SCHEMA**'
}
const optionsCDS = {
kind: 'postgres',
credentials: {}
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, 'public')
})
it('should override with explicit schema for PostgreSQL', function () {
const prompts = {
profile: 'postgres',
schema: 'custom_schema'
}
const optionsCDS = {
kind: 'postgres',
credentials: { schema: 'default_schema' }
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, 'custom_schema')
})
it('should handle wildcard schema for PostgreSQL', function () {
const prompts = {
profile: 'postgres',
schema: '*'
}
const optionsCDS = {
kind: 'postgres'
}
const instance = new PostgresClient(prompts, optionsCDS)
const schema = instance.schemaCalculation(prompts, optionsCDS)
assert.strictEqual(schema, '%')
})
})
describe('Profile Client Methods', function () {
it('should expose getPrompts for PostgreSQL client', function () {
const prompts = {
profile: 'postgres',
debug: true,
table: 'test'
}
const optionsCDS = { kind: 'postgres' }
const instance = new PostgresClient(prompts, optionsCDS)
const retrieved = instance.getPrompts()
assert.deepStrictEqual(retrieved, prompts)
})
it('should expose getKind for PostgreSQL client', function () {
const prompts = { profile: 'postgres' }
const optionsCDS = { kind: 'postgres' }
const instance = new PostgresClient(prompts, optionsCDS)
const kind = instance.getKind()
assert.strictEqual(kind, 'postgres')
})
it('should expose getPrompts for SQLite client', function () {
const prompts = {
profile: 'sqlite',
debug: false,
table: 'sample'
}
const optionsCDS = { kind: 'sqlite' }
const instance = new SqliteClient(prompts, optionsCDS)
const retrieved = instance.getPrompts()
assert.deepStrictEqual(retrieved, prompts)
})
it('should expose getKind for SQLite client', function () {
const prompts = { profile: 'sqlite' }
const optionsCDS = { kind: 'sqlite' }
const instance = new SqliteClient(prompts, optionsCDS)
const kind = instance.getKind()
assert.strictEqual(kind, 'sqlite')
})
it('should allow setDB/getDB for PostgreSQL client', function () {
const prompts = { profile: 'postgres' }
const optionsCDS = { kind: 'postgres' }
const instance = new PostgresClient(prompts, optionsCDS)
const mockDB = { type: 'postgres-mock' }
instance.setDB(mockDB)
const retrieved = instance.getDB()
assert.deepStrictEqual(retrieved, mockDB)
})
it('should allow setDB/getDB for SQLite client', function () {
const prompts = { profile: 'sqlite' }
const optionsCDS = { kind: 'sqlite' }
const instance = new SqliteClient(prompts, optionsCDS)
const mockDB = { type: 'sqlite-mock' }
instance.setDB(mockDB)
const retrieved = instance.getDB()
assert.deepStrictEqual(retrieved, mockDB)
})
})
describe('Profile Error Handling', function () {
it('should provide clear error for unsupported profile via getNewClient', async function () {
const prompts = {
profile: 'mongodb', // Unsupported
debug: false
}
try {
await dbClientClass.getNewClient(prompts)
assert.fail('Should have thrown error for unsupported profile')
} catch (error) {
assert.ok(error instanceof Error)
assert.ok(error.message.length > 0)
}
})
it('should handle missing CDS configuration gracefully', async function () {
const prompts = {
profile: 'postgres',
debug: false
}
// Save original env
const originalEnv = process.env.CDS_ENV
try {
await dbClientClass.getNewClient(prompts)
} catch (error) {
// Expected - may fail due to missing CDS config in test environment
assert.ok(error instanceof Error)
const errorMsg = error.message.toLowerCase()
assert.ok(
errorMsg.includes('cds') ||
errorMsg.includes('configuration') ||
errorMsg.includes('module') ||
errorMsg.includes('cannot find')
)
} finally {
// Restore env
if (originalEnv) {
process.env.CDS_ENV = originalEnv
} else {
delete process.env.CDS_ENV
}
}
})
})
})