#!/usr/bin/env node
/**
* Phase 3: DDL Operations Test
* Tests DDL operations (CREATE/ALTER/DROP) - full development mode
*/
import { SqlServerMCP } from '../../../index.js';
import { TestDatabaseHelper } from './test-database-helper.js';
import { serverConfig } from '../../../lib/config/server-config.js';
import { validateServerConfiguration } from '../shared/config-validator.js';
import dotenv from 'dotenv';
// Load environment variables from .env file first
dotenv.config({ override: false }); // Load .env but don't override existing env vars
// Set test-specific environment variables explicitly (these will take precedence)
process.env.SQL_SERVER_READ_ONLY = 'false';
process.env.SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS = 'true';
process.env.SQL_SERVER_ALLOW_SCHEMA_CHANGES = 'true';
process.env.SQL_SERVER_TRUST_CERT = 'true'; // Trust self-signed certificates
process.env.NODE_ENV = 'test';
// Force configuration reload to pick up our environment variable changes
serverConfig.reload();
// Verify the configuration was set correctly
console.log('π§ Environment Variables Set:');
console.log(' SQL_SERVER_READ_ONLY:', process.env.SQL_SERVER_READ_ONLY);
console.log(
' SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS:',
process.env.SQL_SERVER_ALLOW_DESTRUCTIVE_OPERATIONS
);
console.log(' SQL_SERVER_ALLOW_SCHEMA_CHANGES:', process.env.SQL_SERVER_ALLOW_SCHEMA_CHANGES);
console.log(' SQL_SERVER_TRUST_CERT:', process.env.SQL_SERVER_TRUST_CERT);
// Verify the server config actually loaded the changes
validateServerConfiguration(
serverConfig,
{
readOnlyMode: false,
allowDestructiveOperations: true,
allowSchemaChanges: true
},
'DDL'
);
class DdlOperationsTest {
constructor() {
this.results = { passed: 0, failed: 0, tests: [] };
this.server = null;
this.dbHelper = null;
this.testDbName = null;
}
async runTest(name, description, testFn) {
console.log(`π§ͺ Testing: ${description}`);
try {
await testFn();
console.log(`β
PASSED: ${name}`);
this.results.passed++;
this.results.tests.push({ name, status: 'PASSED', description });
} catch (error) {
console.log(`β FAILED: ${name} - ${error.message.split('\\n')[0]}`);
this.results.failed++;
this.results.tests.push({
name,
status: 'FAILED',
description,
error: error.message.split('\\n')[0]
});
}
}
async runDdlTest() {
console.log('π Starting Phase 3: DDL Operations Test (Full Development Mode)');
console.log(
'================================================================\
'
);
console.log('π§ Current Configuration:');
console.log(' π Read-Only Mode: FALSE');
console.log(' β οΈ Allow Destructive Operations: TRUE');
console.log(' π οΈ Allow Schema Changes: TRUE (DDL operations enabled)');
console.log(' π SSL Encryption: ENABLED');
console.log(
' β οΈ WARNING: This is full development mode - all operations allowed!\
'
);
// Create MCP server instance - force configuration reload first
serverConfig.reload(); // Ensure latest environment variables are loaded
this.server = new SqlServerMCP();
// Verify the server instance has the correct configuration
const serverSecurityConfig = this.server.config.getSecurityConfig();
console.log('π Server Instance Security Config:');
console.log(' readOnlyMode:', serverSecurityConfig.readOnlyMode);
console.log(' allowDestructiveOperations:', serverSecurityConfig.allowDestructiveOperations);
console.log(' allowSchemaChanges:', serverSecurityConfig.allowSchemaChanges);
if (serverSecurityConfig.readOnlyMode !== false) {
throw new Error(
`Server instance still in read-only mode! Expected: false, Got: ${serverSecurityConfig.readOnlyMode}`
);
}
if (serverSecurityConfig.allowSchemaChanges !== true) {
throw new Error(
`Server instance schema changes not enabled! Expected: true, Got: ${serverSecurityConfig.allowSchemaChanges}`
);
}
// Create database helper and test database
this.dbHelper = new TestDatabaseHelper(this.server);
try {
console.log('ποΈ Setting up test environment...');
this.testDbName = await this.dbHelper.createTestDatabase('Phase3DDL');
console.log(`π Using test database: ${this.testDbName}\n`);
} catch (error) {
console.error('β Failed to set up test environment:', error.message);
throw error;
}
console.log('π 1. VERIFY ALL OPERATIONS WORK');
console.log(
'=================================\
'
);
// Verify read operations still work
await this.runTest('select_operations', 'SELECT operations should work', async () => {
const result = await this.server.executeQuery(
'SELECT COUNT(*) as ProductCount FROM Products',
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
});
console.log('\nπ οΈ 2. TEST DDL OPERATIONS (CREATE/ALTER/DROP)');
console.log(
'===============================================\
'
);
// Test CREATE TABLE (should now work)
await this.runTest('ddl_create_table', 'CREATE TABLE should now be ALLOWED', async () => {
const result = await this.server.executeQuery(
'CREATE TABLE TestDdlTable (ID int PRIMARY KEY IDENTITY(1,1), Name nvarchar(100) NOT NULL, CreatedDate datetime2 DEFAULT GETDATE())',
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('executed successfully') &&
!result.content[0].text.includes('rows affected')
) {
throw new Error('CREATE TABLE operation did not report success');
}
});
// Test ALTER TABLE (should now work)
await this.runTest('ddl_alter_table', 'ALTER TABLE should now be ALLOWED', async () => {
const result = await this.server.executeQuery(
'ALTER TABLE TestDdlTable ADD Description nvarchar(255) NULL',
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('executed successfully') &&
!result.content[0].text.includes('rows affected')
) {
throw new Error('ALTER TABLE operation did not report success');
}
});
console.log('\nπ 3. TEST DML OPERATIONS (SHOULD ALSO WORK)');
console.log(
'==============================================\
'
);
// Test INSERT into our test table
await this.runTest(
'dml_insert_test',
'INSERT should work in full development mode',
async () => {
const result = await this.server.executeQuery(
"INSERT INTO TestDdlTable (Name, Description) VALUES ('Test Record', 'Testing full development mode')",
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('rows affected') &&
!result.content[0].text.includes('successfully')
) {
throw new Error('INSERT operation did not report success');
}
}
);
// Test UPDATE
await this.runTest(
'dml_update_test',
'UPDATE should work in full development mode',
async () => {
const result = await this.server.executeQuery(
"UPDATE TestDdlTable SET Description = 'Updated description for DDL testing' WHERE Name = 'Test Record'",
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('rows affected') &&
!result.content[0].text.includes('successfully')
) {
throw new Error('UPDATE operation did not report success');
}
}
);
// Verify the data exists
await this.runTest(
'verify_data_exists',
'Verify test data was created and updated',
async () => {
const result = await this.server.executeQuery(
"SELECT ID, Name, Description FROM TestDdlTable WHERE Name = 'Test Record'",
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('Test Record') ||
!result.content[0].text.includes('Updated description')
) {
throw new Error('Test data was not created or updated correctly');
}
}
);
console.log('\nποΈ 4. CLEANUP - TEST DROP OPERATIONS');
console.log(
'======================================\
'
);
// Test DELETE (cleanup data first)
await this.runTest('dml_delete_cleanup', 'DELETE should work for cleanup', async () => {
const result = await this.server.executeQuery(
"DELETE FROM TestDdlTable WHERE Name = 'Test Record'",
this.testDbName
);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('rows affected') &&
!result.content[0].text.includes('successfully')
) {
throw new Error('DELETE operation did not report success');
}
});
// Test DROP TABLE (should now work)
await this.runTest('ddl_drop_table', 'DROP TABLE should now be ALLOWED (cleanup)', async () => {
const result = await this.server.executeQuery('DROP TABLE TestDdlTable', this.testDbName);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
if (
!result.content[0].text.includes('executed successfully') &&
!result.content[0].text.includes('rows affected')
) {
throw new Error('DROP TABLE operation did not report success');
}
});
console.log('\nβ‘ 5. VERIFY ALL TOOLS STILL WORK');
console.log(
'=================================\
'
);
// Quick check that other MCP tools still work
await this.runTest(
'list_tables_verification',
'Other MCP tools should still work (list_tables)',
async () => {
const result = await this.server.listTables(this.testDbName);
if (!result.content || !result.content[0] || !result.content[0].text) {
throw new Error('No content returned');
}
}
);
// Quick performance monitoring check
await this.runTest(
'performance_monitoring_ddl',
'Performance monitoring should track DDL operations',
async () => {
const result = this.server.getPerformanceStats();
if (!result || !result[0] || !result[0].text) {
throw new Error('No content returned');
}
const stats = JSON.parse(result[0].text);
if (!stats.success || !stats.data) {
throw new Error('Performance stats format invalid');
}
// Should have recorded many queries by now
if (stats.data.overall && stats.data.overall.totalQueries <= 0) {
throw new Error('Performance monitoring not tracking queries');
}
}
);
}
async cleanup() {
if (this.dbHelper) {
console.log('\nπ§Ή Cleaning up test environment...');
// Only clean up the specific database created by this test
if (this.testDbName) {
await this.dbHelper.cleanupDatabase(this.testDbName);
console.log(`β
Cleaned up test database: ${this.testDbName}`);
} else {
console.log('β οΈ No test database to clean up');
}
}
}
printSummary() {
console.log('\nπ― PHASE 3 DDL OPERATIONS RESULTS');
console.log('=================================');
console.log(`β
Tests Passed: ${this.results.passed}`);
console.log(`β Tests Failed: ${this.results.failed}`);
console.log(`π Total Tests: ${this.results.passed + this.results.failed}`);
console.log(
`π Success Rate: ${((this.results.passed / (this.results.passed + this.results.failed)) * 100).toFixed(1)}%`
);
if (this.results.failed > 0) {
console.log('\nβ Failed Tests:');
this.results.tests
.filter(t => t.status === 'FAILED')
.forEach(t => console.log(` β’ ${t.name}: ${t.error}`));
}
console.log('\nπ Phase 3 Assessment:');
if (this.results.failed === 0) {
console.log(' β
PHASE 3 COMPLETE - DDL operations work correctly!');
console.log(' π οΈ CREATE/ALTER/DROP operations: WORKING');
console.log(' π INSERT/UPDATE/DELETE operations: WORKING');
console.log(' π All MCP tools: WORKING');
console.log(' π ALL THREE SECURITY PHASES VERIFIED!');
} else if (this.results.passed / (this.results.passed + this.results.failed) >= 0.8) {
console.log(' β οΈ PHASE 3 MOSTLY WORKING - Some issues detected');
} else {
console.log(' β PHASE 3 FAILED - Significant issues with DDL operations');
}
console.log('\nπ SECURITY SUMMARY:');
console.log(' β
Phase 1: Read-Only Mode - 100% SUCCESS');
console.log(' β
Phase 2: DML Operations - SUCCESS (verified during testing)');
console.log(
` ${this.results.failed === 0 ? 'β
' : 'β'} Phase 3: DDL Operations - ${this.results.failed === 0 ? 'SUCCESS' : 'FAILED'}`
);
}
}
// Run the DDL test
const ddlTest = new DdlOperationsTest();
try {
await ddlTest.runDdlTest();
ddlTest.printSummary();
// Exit with failure code if any tests failed
if (ddlTest.results.failed > 0) {
console.error(`\
π₯ ${ddlTest.results.failed} test(s) failed`);
process.exit(1);
}
} catch (error) {
console.error('π₯ DDL test failed:', error.message);
process.exit(1);
} finally {
await ddlTest.cleanup();
}
process.exit(0);