hana-cli
Version:
HANA Developer Command Line Interface
618 lines (513 loc) • 24.2 kB
JavaScript
// @ts-nocheck
/**
* @module Base Utilities Tests - Unit tests for the base utility functions
*
* These tests validate the core functionality in utils/base.js that supports
* all hana-cli commands, including:
* - Debug flag handling (isDebug)
* - Builder functions (getBuilder, getPromptSchema)
* - Prompt handler functionality
* - Connection and debug parameter injection
*/
import { assert } from '../base.js'
import * as base from '../../utils/base.js'
describe('Base Utility Functions', function () {
describe('isDebug', function () {
it('should return true when debug flag is set to true', function () {
const prompts = { debug: true }
const result = base.isDebug(prompts)
assert.strictEqual(result, true)
})
it('should return false when debug flag is set to false', function () {
const prompts = { debug: false }
const result = base.isDebug(prompts)
assert.strictEqual(result, false)
})
it('should return false when debug flag is not present', function () {
const prompts = { other: 'value' }
const result = base.isDebug(prompts)
assert.strictEqual(result, false)
})
it('should return false when prompts is null', function () {
const result = base.isDebug(null)
assert.strictEqual(result, false)
})
it('should return false when prompts is undefined', function () {
const result = base.isDebug(undefined)
assert.strictEqual(result, false)
})
it('should handle truthy values for debug flag', function () {
const prompts = { debug: 'true' }
const result = base.isDebug(prompts)
// Should be truthy
assert.ok(result)
})
})
describe('isGui', function () {
it('should return true when isGui flag is set to true', function () {
const prompts = { isGui: true }
const result = base.isGui(prompts)
assert.strictEqual(result, true)
})
it('should return false when isGui flag is set to false', function () {
const prompts = { isGui: false }
const result = base.isGui(prompts)
assert.strictEqual(result, false)
})
it('should return false when isGui flag is not present', function () {
const prompts = { other: 'value' }
const result = base.isGui(prompts)
assert.strictEqual(result, false)
})
})
describe('getBuilder', function () {
it('should include debug options when iDebug is true', function () {
const input = { myOption: { type: 'string' } }
const builder = base.getBuilder(input, true, true)
assert.ok(builder.debug, 'Builder should include debug option')
assert.ok(builder.disableVerbose, 'Builder should include disableVerbose option')
assert.strictEqual(builder.debug.type, 'boolean')
assert.strictEqual(builder.debug.default, false)
})
it('should not include debug options when iDebug is false', function () {
const input = { myOption: { type: 'string' } }
const builder = base.getBuilder(input, true, false)
assert.ok(!builder.debug, 'Builder should not include debug option when iDebug is false')
assert.ok(!builder.disableVerbose, 'Builder should not include disableVerbose option')
})
it('should include connection options when iConn is true', function () {
const input = { myOption: { type: 'string' } }
const builder = base.getBuilder(input, true, true)
assert.ok(builder.admin, 'Builder should include admin option')
assert.ok(builder.conn, 'Builder should include conn option')
assert.strictEqual(builder.admin.type, 'boolean')
assert.strictEqual(builder.admin.default, false)
})
it('should not include connection options when iConn is false', function () {
const input = { myOption: { type: 'string' } }
const builder = base.getBuilder(input, false, true)
assert.ok(!builder.admin, 'Builder should not include admin option when iConn is false')
assert.ok(!builder.conn, 'Builder should not include conn option')
})
it('should preserve input options', function () {
const input = {
myOption: {
type: 'string',
desc: 'My test option'
}
}
const builder = base.getBuilder(input, true, true)
assert.ok(builder.myOption, 'Builder should preserve input options')
assert.strictEqual(builder.myOption.type, 'string')
assert.strictEqual(builder.myOption.desc, 'My test option')
})
it('should have correct group assignments', function () {
const input = { myOption: { type: 'string' } }
const builder = base.getBuilder(input, true, true)
// Debug options should be in debug group
assert.ok(builder.debug.group)
assert.ok(builder.disableVerbose.group)
// Connection options should be in connection group
assert.ok(builder.admin.group)
assert.ok(builder.conn.group)
})
it('should have correct aliases', function () {
const input = { myOption: { type: 'string' } }
const builder = base.getBuilder(input, true, true)
// Check debug aliases
assert.ok(builder.debug.alias.includes('d'), 'debug should have d alias')
// Check admin aliases
assert.ok(builder.admin.alias.includes('a'), 'admin should have a alias')
// Check disableVerbose aliases
assert.ok(builder.disableVerbose.alias.includes('quiet'), 'disableVerbose should have quiet alias')
})
})
describe('getPromptSchema', function () {
it('should include debug properties when iDebug is true', function () {
const inputSchema = { myProp: { type: 'string' } }
const schema = base.getPromptSchema(inputSchema, true, true)
assert.ok(schema.properties.debug, 'Schema should include debug property')
assert.ok(schema.properties.disableVerbose, 'Schema should include disableVerbose property')
assert.strictEqual(schema.properties.debug.type, 'boolean')
assert.strictEqual(schema.properties.debug.required, true)
})
it('should not include debug properties when iDebug is false', function () {
const inputSchema = { myProp: { type: 'string' } }
const schema = base.getPromptSchema(inputSchema, true, false)
assert.ok(!schema.properties.debug, 'Schema should not include debug when iDebug is false')
assert.ok(!schema.properties.disableVerbose, 'Schema should not include disableVerbose')
})
it('should include connection properties when iConn is true', function () {
const inputSchema = { myProp: { type: 'string' } }
const schema = base.getPromptSchema(inputSchema, true, true)
assert.ok(schema.properties.admin, 'Schema should include admin property')
assert.ok(schema.properties.conn, 'Schema should include conn property')
assert.strictEqual(schema.properties.admin.type, 'boolean')
})
it('should not include connection properties when iConn is false', function () {
const inputSchema = { myProp: { type: 'string' } }
const schema = base.getPromptSchema(inputSchema, false, true)
assert.ok(!schema.properties.admin, 'Schema should not include admin when iConn is false')
assert.ok(!schema.properties.conn, 'Schema should not include conn')
})
it('should preserve input schema properties', function () {
const inputSchema = {
myProp: {
type: 'string',
description: 'Test property',
required: true
}
}
const schema = base.getPromptSchema(inputSchema, true, true)
assert.ok(schema.properties.myProp, 'Schema should preserve input properties')
assert.strictEqual(schema.properties.myProp.type, 'string')
assert.strictEqual(schema.properties.myProp.description, 'Test property')
assert.strictEqual(schema.properties.myProp.required, true)
})
it('should set ask to askFalse for debug and connection properties', function () {
const inputSchema = { myProp: { type: 'string' } }
const schema = base.getPromptSchema(inputSchema, true, true)
// These properties should not be prompted for
assert.ok(schema.properties.debug.ask instanceof Function, 'debug.ask should be a function')
assert.ok(schema.properties.disableVerbose.ask instanceof Function, 'disableVerbose.ask should be a function')
assert.ok(schema.properties.admin.ask instanceof Function, 'admin.ask should be a function')
assert.ok(schema.properties.conn.ask instanceof Function, 'conn.ask should be a function')
// askFalse should return false
assert.strictEqual(schema.properties.debug.ask(), false)
assert.strictEqual(schema.properties.admin.ask(), false)
})
})
describe('askFalse', function () {
it('should always return false', function () {
const result = base.askFalse()
assert.strictEqual(result, false)
})
})
describe('debug function', function () {
it('should be callable without throwing', function () {
// Debug function should not throw even if debug is not enabled
assert.doesNotThrow(() => {
base.debug('Test message')
})
})
it('should accept string messages', function () {
assert.doesNotThrow(() => {
base.debug('Simple string message')
})
})
it('should accept object messages', function () {
assert.doesNotThrow(() => {
base.debug({ key: 'value', nested: { data: 123 } })
})
})
})
describe('promptHandler critical path', function () {
// This test verifies the fix for the --debug flag issue
// It ensures that argv values are copied to result even when ask() returns false
it('should copy debug flag from argv to result object', async function () {
this.timeout(5000)
let capturedResult = null
// Mock processing function that captures the result
const mockProcessing = async (result) => {
capturedResult = result
}
// Mock argv with debug flag
const mockArgv = {
debug: true,
schema: 'TEST_SCHEMA',
limit: 10
}
// Simple input schema
const inputSchema = {
schema: {
description: 'Schema name',
type: 'string',
required: true,
ask: () => false // Don't prompt
},
limit: {
description: 'Limit',
type: 'number',
required: true,
ask: () => false // Don't prompt
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
// Verify that debug flag was transferred to result
assert.ok(capturedResult, 'Processing function should have been called')
assert.strictEqual(capturedResult.debug, true, 'Debug flag should be copied from argv to result')
assert.strictEqual(capturedResult.schema, 'TEST_SCHEMA', 'Schema should be copied from argv')
assert.strictEqual(capturedResult.limit, 10, 'Limit should be copied from argv')
} catch (err) {
// Prompt handler might throw if user cancels, but we're testing the copy logic
// If it throws, the test should still pass as long as we got to the assertion
if (capturedResult) {
assert.strictEqual(capturedResult.debug, true, 'Debug flag should be copied even if error occurs')
}
}
})
it('should copy disableVerbose flag from argv', async function () {
this.timeout(5000)
let capturedResult = null
const mockProcessing = async (result) => {
capturedResult = result
}
const mockArgv = {
disableVerbose: true,
schema: 'TEST'
}
const inputSchema = {
schema: {
description: 'Schema',
type: 'string',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
assert.ok(capturedResult, 'Processing function should have been called')
assert.strictEqual(capturedResult.disableVerbose, true, 'disableVerbose should be copied from argv')
} catch (err) {
if (capturedResult) {
assert.strictEqual(capturedResult.disableVerbose, true)
}
}
})
it('should copy admin flag from argv', async function () {
this.timeout(5000)
let capturedResult = null
const mockProcessing = async (result) => {
capturedResult = result
}
const mockArgv = {
admin: true,
schema: 'TEST'
}
const inputSchema = {
schema: {
description: 'Schema',
type: 'string',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
assert.ok(capturedResult, 'Processing function should have been called')
assert.strictEqual(capturedResult.admin, true, 'admin flag should be copied from argv')
} catch (err) {
if (capturedResult) {
assert.strictEqual(capturedResult.admin, true)
}
}
})
it('should copy conn value from argv', async function () {
this.timeout(5000)
let capturedResult = null
const mockProcessing = async (result) => {
capturedResult = result
}
const mockArgv = {
conn: 'default.env',
schema: 'TEST'
}
const inputSchema = {
schema: {
description: 'Schema',
type: 'string',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
assert.ok(capturedResult, 'Processing function should have been called')
assert.strictEqual(capturedResult.conn, 'default.env', 'conn value should be copied from argv')
} catch (err) {
if (capturedResult) {
assert.strictEqual(capturedResult.conn, 'default.env')
}
}
})
})
describe('unknown options warning', function () {
let consoleWarnOutput = []
let originalConsoleWarn
beforeEach(function () {
// Capture console.warn output
consoleWarnOutput = []
originalConsoleWarn = console.warn
console.warn = (msg) => {
consoleWarnOutput.push(msg)
}
})
afterEach(function () {
// Restore original console.warn
console.warn = originalConsoleWarn
})
it('should warn about unknown options', async function () {
this.timeout(5000)
const mockProcessing = async (result) => {
// Do nothing
}
const mockArgv = {
schema: 'TEST_SCHEMA',
unknownOption: true,
anotherBadOption: 'value'
}
const inputSchema = {
schema: {
description: 'Schema name',
type: 'string',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
// Check that warnings were issued
assert.ok(consoleWarnOutput.length > 0, 'Should have warning messages')
const warningText = consoleWarnOutput.join(' ')
assert.ok(warningText.includes('unknownOption') || warningText.includes('unknown-option'),
'Should warn about unknownOption')
assert.ok(warningText.includes('anotherBadOption') || warningText.includes('another-bad-option'),
'Should warn about anotherBadOption')
} catch (err) {
// Prompt handler might throw, but warnings should still have been issued
assert.ok(consoleWarnOutput.length > 0, 'Should have warnings even if prompt cancelled')
}
})
it('should not warn about valid options', async function () {
this.timeout(5000)
const mockProcessing = async (result) => {
// Do nothing
}
const mockArgv = {
schema: 'TEST_SCHEMA',
limit: 100,
debug: true,
admin: false
}
const inputSchema = {
schema: {
description: 'Schema name',
type: 'string',
required: true,
ask: () => false
},
limit: {
description: 'Limit',
type: 'number',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
// Check that no warnings were issued for valid options
const warningText = consoleWarnOutput.join(' ')
assert.ok(!warningText.includes('schema'), 'Should not warn about schema')
assert.ok(!warningText.includes('limit'), 'Should not warn about limit')
assert.ok(!warningText.includes('debug'), 'Should not warn about debug')
assert.ok(!warningText.includes('admin'), 'Should not warn about admin')
} catch (err) {
// Even if prompt throws, check warnings
const warningText = consoleWarnOutput.join(' ')
assert.ok(!warningText.includes('schema'), 'Should not warn about valid options')
}
})
it('should detect aliases as valid options', async function () {
this.timeout(5000)
const mockProcessing = async (result) => {
// Do nothing
}
// When using -l 100, yargs creates both l:100 and limit:100
const mockArgv = {
schema: 'TEST_SCHEMA',
s: 'TEST_SCHEMA', // alias for schema
limit: 100,
l: 100 // alias for limit
}
const inputSchema = {
schema: {
description: 'Schema name',
type: 'string',
required: true,
ask: () => false
},
limit: {
description: 'Limit',
type: 'number',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
// Check that aliases are not flagged as unknown
const warningText = consoleWarnOutput.join(' ')
assert.ok(!warningText.includes("'s'") && !warningText.includes("'l'"),
'Should not warn about aliases that share values with known options')
} catch (err) {
// Check even if error
const warningText = consoleWarnOutput.join(' ')
assert.ok(!warningText.includes("'s'") && !warningText.includes("'l'"),
'Should not warn about valid aliases')
}
})
it('should not duplicate warnings for kebab-case and camelCase', async function () {
this.timeout(5000)
const mockProcessing = async (result) => {
// Do nothing
}
// Yargs creates both when you use --unknown-option
const mockArgv = {
schema: 'TEST_SCHEMA',
'unknown-option': true,
unknownOption: true
}
const inputSchema = {
schema: {
description: 'Schema name',
type: 'string',
required: true,
ask: () => false
}
}
try {
await base.promptHandler(mockArgv, mockProcessing, inputSchema, true, true)
// Should only warn once, not twice
const unknownWarnings = consoleWarnOutput.filter(msg =>
msg.includes('unknown-option') || msg.includes('unknownOption')
)
assert.ok(unknownWarnings.length <= 1,
'Should not duplicate warnings for kebab-case and camelCase variants')
} catch (err) {
// Check even if error
const unknownWarnings = consoleWarnOutput.filter(msg =>
msg.includes('unknown-option') || msg.includes('unknownOption')
)
assert.ok(unknownWarnings.length <= 1, 'Should not duplicate warnings')
}
})
})
describe('color utilities', function () {
it('should have colors object available', function () {
assert.ok(base.colors, 'colors should be available')
assert.ok(typeof base.colors === 'object' || typeof base.colors === 'function')
})
})
describe('bundle and localization', function () {
it('should have bundle object available', function () {
assert.ok(base.bundle, 'bundle should be available')
})
it('should be able to get text from bundle', function () {
assert.doesNotThrow(() => {
const text = base.bundle.getText('usage')
assert.ok(typeof text === 'string')
})
})
})
})