import { describe, it, expect, beforeAll } from '@jest/globals';
/**
* LIVE INTEGRATION TESTS FOR UPDATE SET OPERATIONS
*
* These tests make real API calls to ServiceNow using actual credentials.
* They will ONLY run if SERVICENOW_* environment variables are set.
*
* Purpose: Validate real-world update set operations and XML reassignment
* Run with: npm run test:integration
*
* ⚠️ WARNING: These tests create and modify update sets in ServiceNow!
* Only run against sandbox/dev instances with appropriate permissions.
*/
const skipIfNoCredentials = () => {
const hasCredentials =
process.env.SERVICENOW_ACE_INSTANCE &&
process.env.SERVICENOW_ACE_USERNAME &&
process.env.SERVICENOW_ACE_PASSWORD;
if (!hasCredentials) {
console.log('\n⚠️ Skipping live update set integration tests - ServiceNow credentials not found');
console.log(' Set SERVICENOW_ACE_INSTANCE, SERVICENOW_ACE_USERNAME, SERVICENOW_ACE_PASSWORD to run');
}
return hasCredentials;
};
// Only run these tests if we have credentials
const testSuite = skipIfNoCredentials() ? describe : describe.skip;
testSuite('Live ServiceNow Update Set Integration Tests', () => {
let client: any;
let createdUpdateSetId: string | null = null;
beforeAll(async () => {
try {
// Dynamic import to avoid Jest parsing issues
const { ServiceNowUpdateSetClient } = await import('../servicenow/updateSetClient.js');
client = new ServiceNowUpdateSetClient();
} catch (error) {
console.error('Failed to initialize ServiceNow update set client:', error);
throw error;
}
});
describe('Update Set Lifecycle Operations', () => {
it('should create a new update set', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'create',
name: `SkyeNet Test Update Set ${Date.now()}`,
description: 'Created by SkyeNet MCP ACE integration test',
scope: 'x_cls_clear_skye_i',
set_as_working: true,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('update_set');
expect(result.data?.update_set?.name).toContain('SkyeNet Test Update Set');
expect(result.data?.update_set?.state).toBe('in_progress');
expect(result.data?.working_set).toBeDefined();
createdUpdateSetId = result.data?.update_set?.sys_id;
});
it('should show working update set', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'show_working',
});
expect(result.success).toBe(true);
expect(result.data?.working_set).toBeDefined();
expect(result.data?.working_set?.sys_id).toBe(createdUpdateSetId);
});
it('should list update sets', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'list',
filters: { scope: 'x_cls_clear_skye_i' },
limit: 10,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('update_sets');
expect(Array.isArray(result.data?.update_sets)).toBe(true);
expect(result.data?.update_sets.length).toBeGreaterThan(0);
});
it('should get update set info', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set created for info test');
}
const result = await client.executeUpdateSetOperation({
operation: 'info',
update_set_sys_id: createdUpdateSetId,
});
expect(result.success).toBe(true);
expect(result.data?.update_set?.sys_id).toBe(createdUpdateSetId);
expect(result.data?.update_set?.xml_count).toBeDefined();
});
});
describe('Record Operations with XML Reassignment', () => {
it('should insert record with automatic XML reassignment', async () => {
if (!createdUpdateSetId) {
throw new Error('No working update set available for insert test');
}
const result = await client.executeUpdateSetOperation({
operation: 'insert',
table: 'sys_script_include',
data: {
name: `TestScriptInclude_${Date.now()}`,
script: '// Test script include created by SkyeNet MCP ACE',
active: true,
},
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('record');
expect(result.data?.record?.name).toContain('TestScriptInclude_');
expect(result.data?.record?.sys_id).toBeDefined();
// Check if XML reassignment occurred
if (result.data?.xml_reassignment) {
expect(result.data.xml_reassignment.success).toBe(true);
expect(result.data.xml_reassignment.reassigned_count).toBeGreaterThan(0);
}
});
it('should update record with automatic XML reassignment', async () => {
if (!createdUpdateSetId) {
throw new Error('No working update set available for update test');
}
// First create a record to update
const createResult = await client.executeUpdateSetOperation({
operation: 'insert',
table: 'sys_script_include',
data: {
name: `TestScriptForUpdate_${Date.now()}`,
script: '// Original script',
active: true,
},
});
expect(createResult.success).toBe(true);
const recordSysId = createResult.data?.record?.sys_id;
expect(recordSysId).toBeDefined();
// Now update the record
const result = await client.executeUpdateSetOperation({
operation: 'update',
table: 'sys_script_include',
sys_id: recordSysId,
data: {
script: '// Updated script content',
description: 'Updated by SkyeNet MCP ACE',
},
});
expect(result.success).toBe(true);
expect(result.data?.record?.sys_id).toBe(recordSysId);
// Check if XML reassignment occurred
if (result.data?.xml_reassignment) {
expect(result.data.xml_reassignment.success).toBe(true);
}
});
});
describe('XML Management Operations', () => {
it('should get update set contents', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set created for contents test');
}
const result = await client.executeUpdateSetOperation({
operation: 'contents',
update_set_sys_id: createdUpdateSetId,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('update_set_sys_id');
expect(result.data?.update_set_sys_id).toBe(createdUpdateSetId);
expect(result.data?.total_count).toBeGreaterThanOrEqual(0);
expect(result.data?.by_type).toBeDefined();
expect(result.data?.by_table).toBeDefined();
});
it('should get recent XML activity', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'recent',
limit: 20,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('total_count');
expect(result.data?.total_count).toBeGreaterThanOrEqual(0);
expect(result.data?.records).toBeDefined();
expect(Array.isArray(result.data?.records)).toBe(true);
});
it('should diff against default update set', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set created for diff test');
}
const result = await client.executeUpdateSetOperation({
operation: 'diff_default',
update_set_sys_id: createdUpdateSetId,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('working_set');
expect(result.data?.working_set).toBeDefined();
expect(result.data?.default_set).toBeDefined();
expect(result.data?.stray_count).toBeGreaterThanOrEqual(0);
});
});
describe('Update Set State Management', () => {
it('should complete update set', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set created for complete test');
}
const result = await client.executeUpdateSetOperation({
operation: 'complete',
update_set_sys_id: createdUpdateSetId,
});
expect(result.success).toBe(true);
expect(result.data?.update_set?.state).toBe('complete');
});
it('should reopen update set', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set created for reopen test');
}
const result = await client.executeUpdateSetOperation({
operation: 'reopen',
update_set_sys_id: createdUpdateSetId,
});
expect(result.success).toBe(true);
expect(result.data?.update_set?.state).toBe('in_progress');
});
it('should delete update set', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set created for delete test');
}
const result = await client.executeUpdateSetOperation({
operation: 'delete',
update_set_sys_id: createdUpdateSetId,
});
expect(result.success).toBe(true);
expect(result.data?.update_set?.state).toBe('deleted');
});
});
describe('Working Set Management', () => {
it('should clear working set', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'clear_working',
});
expect(result.success).toBe(true);
expect(result.data?.working_set).toBeNull();
});
it('should handle operations without working set gracefully', async () => {
// Clear working set first
await client.executeUpdateSetOperation({ operation: 'clear_working' });
// Try an operation that requires working set
await expect(
client.executeUpdateSetOperation({
operation: 'insert',
table: 'sys_script_include',
data: { name: 'TestScript', script: '// test' },
})
).rejects.toThrow();
});
});
describe('Error Handling', () => {
it('should handle invalid update set sys_id', async () => {
await expect(
client.executeUpdateSetOperation({
operation: 'info',
update_set_sys_id: 'invalid_sys_id_12345',
})
).rejects.toThrow();
});
it('should handle missing required parameters', async () => {
await expect(
client.executeUpdateSetOperation({
operation: 'create',
// Missing name parameter
})
).rejects.toThrow();
});
it('should handle invalid operation', async () => {
await expect(
client.executeUpdateSetOperation({
operation: 'invalid_operation' as any,
})
).rejects.toThrow();
});
});
describe('Performance and Metadata', () => {
it('should include execution metadata', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'list',
limit: 5,
});
expect(result.success).toBe(true);
expect(result.metadata).toHaveProperty('executionTime');
expect(result.metadata).toHaveProperty('timestamp');
expect(result.metadata).toHaveProperty('operation');
expect(result.metadata.executionTime).toBeGreaterThan(0);
});
it('should handle context overflow prevention', async () => {
const result = await client.executeUpdateSetOperation({
operation: 'recent',
limit: 1000, // Large limit to test overflow prevention
});
expect(result.success).toBe(true);
expect(result.metadata).toHaveProperty('responseSize');
expect(result.metadata).toHaveProperty('contextOverflowPrevention');
});
});
describe('Batch Operations', () => {
it('should handle batch insert operations', async () => {
// Create a new update set for batch testing
const createResult = await client.executeUpdateSetOperation({
operation: 'create',
name: `SkyeNet Batch Test ${Date.now()}`,
description: 'Batch testing update set',
set_as_working: true,
});
expect(createResult.success).toBe(true);
const batchUpdateSetId = createResult.data?.update_set?.sys_id;
// Test batch insert
const result = await client.executeUpdateSetOperation({
operation: 'insert',
table: 'sys_script_include',
data: [
{
name: `BatchScript1_${Date.now()}`,
script: '// Batch script 1',
active: true,
},
{
name: `BatchScript2_${Date.now()}`,
script: '// Batch script 2',
active: true,
},
],
batch: true,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('record');
expect(result.data?.record).toBeDefined();
expect(result.data?.record.sys_class_name).toBe('sys_script_include');
});
});
describe('XML Rehoming Operations', () => {
it('should rehome XML records by query', async () => {
if (!createdUpdateSetId) {
throw new Error('No update set available for rehoming test');
}
// Create a new update set for rehoming
const createResult = await client.executeUpdateSetOperation({
operation: 'create',
name: `SkyeNet Rehome Test ${Date.now()}`,
description: 'Rehoming test update set',
set_as_working: true,
});
expect(createResult.success).toBe(true);
const rehomeUpdateSetId = createResult.data?.update_set?.sys_id;
// Try to rehome XML records (may not find any, but should not error)
const result = await client.executeUpdateSetOperation({
operation: 'rehome',
query: 'sys_created_on>=javascript:gs.daysAgo(1)',
update_set_sys_id: rehomeUpdateSetId,
force: false,
});
expect(result.success).toBe(true);
expect(result.data).toHaveProperty('reassigned_count');
expect(result.data?.reassigned_count).toBeGreaterThanOrEqual(0);
});
});
});