Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
472 lines
16 KiB
JavaScript
472 lines
16 KiB
JavaScript
const { expect } = require('chai');
|
|
const sinon = require('sinon');
|
|
const TestSetup = require('../setup');
|
|
const TaskExecutionEngine = require('../../src/service/TaskExecutionEngine');
|
|
|
|
describe('TaskExecutionEngine', function() {
|
|
let taskEngine;
|
|
let testDb;
|
|
|
|
before(async function() {
|
|
this.timeout(15000);
|
|
|
|
await TestSetup.setupDatabase();
|
|
await TestSetup.setupRedis();
|
|
await TestSetup.createTestData();
|
|
|
|
testDb = TestSetup.getTestDb();
|
|
taskEngine = new TaskExecutionEngine();
|
|
await taskEngine.initialize();
|
|
});
|
|
|
|
after(async function() {
|
|
if (taskEngine) {
|
|
await taskEngine.shutdown();
|
|
}
|
|
await TestSetup.cleanup();
|
|
});
|
|
|
|
describe('Initialization', function() {
|
|
it('should initialize all components', function() {
|
|
expect(taskEngine.isInitialized).to.be.true;
|
|
expect(taskEngine.accountScheduler).to.not.be.null;
|
|
expect(taskEngine.riskService).to.not.be.null;
|
|
expect(taskEngine.behaviorSimulator).to.not.be.null;
|
|
expect(taskEngine.contentVariator).to.not.be.null;
|
|
});
|
|
|
|
it('should start and stop correctly', async function() {
|
|
await taskEngine.start();
|
|
expect(taskEngine.isRunning).to.be.true;
|
|
|
|
await taskEngine.stop();
|
|
expect(taskEngine.isRunning).to.be.false;
|
|
|
|
// Restart for other tests
|
|
await taskEngine.start();
|
|
});
|
|
});
|
|
|
|
describe('Task Execution Workflow', function() {
|
|
let mockTask;
|
|
|
|
beforeEach(function() {
|
|
mockTask = {
|
|
id: 1,
|
|
name: 'Test Task',
|
|
targetInfo: JSON.stringify({
|
|
targets: [
|
|
{ id: 'group1', name: 'Test Group 1', type: 'group' },
|
|
{ id: 'group2', name: 'Test Group 2', type: 'group' }
|
|
]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Hello, this is a test message!',
|
|
type: 'text'
|
|
}),
|
|
sendingStrategy: JSON.stringify({
|
|
type: 'sequential',
|
|
interval: 2000,
|
|
batchSize: 1
|
|
}),
|
|
status: 'pending'
|
|
};
|
|
});
|
|
|
|
it('should execute task successfully', async function() {
|
|
this.timeout(10000);
|
|
|
|
// Mock the actual Telegram sending
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').resolves({
|
|
success: true,
|
|
messageId: 'msg_123',
|
|
timestamp: new Date()
|
|
});
|
|
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
|
|
expect(result).to.have.property('success', true);
|
|
expect(result).to.have.property('taskId', mockTask.id);
|
|
expect(result).to.have.property('totalTargets', 2);
|
|
expect(result).to.have.property('successCount');
|
|
expect(result).to.have.property('failureCount');
|
|
|
|
sendStub.restore();
|
|
});
|
|
|
|
it('should handle task execution with risk controls', async function() {
|
|
this.timeout(10000);
|
|
|
|
// Mock risk evaluation that returns medium risk
|
|
const riskStub = sinon.stub(taskEngine.riskService, 'evaluateOverallRisk').resolves('medium');
|
|
const actionStub = sinon.stub(taskEngine.riskService, 'executeRiskAction').resolves({
|
|
action: 'delayed',
|
|
delay: 3000,
|
|
success: true
|
|
});
|
|
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').resolves({
|
|
success: true,
|
|
messageId: 'msg_123'
|
|
});
|
|
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
|
|
expect(result.success).to.be.true;
|
|
expect(riskStub.called).to.be.true;
|
|
|
|
riskStub.restore();
|
|
actionStub.restore();
|
|
sendStub.restore();
|
|
});
|
|
|
|
it('should handle account switching on high risk', async function() {
|
|
this.timeout(10000);
|
|
|
|
// Mock high risk scenario requiring account switch
|
|
const riskStub = sinon.stub(taskEngine.riskService, 'evaluateOverallRisk').resolves('high');
|
|
const actionStub = sinon.stub(taskEngine.riskService, 'executeRiskAction').resolves({
|
|
action: 'switched',
|
|
originalAccount: 1,
|
|
newAccount: { accountId: 2, healthScore: 90 },
|
|
success: true
|
|
});
|
|
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').resolves({
|
|
success: true,
|
|
messageId: 'msg_124'
|
|
});
|
|
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
|
|
expect(result.success).to.be.true;
|
|
expect(actionStub.called).to.be.true;
|
|
|
|
riskStub.restore();
|
|
actionStub.restore();
|
|
sendStub.restore();
|
|
});
|
|
});
|
|
|
|
describe('Message Processing', function() {
|
|
it('should apply content variation', async function() {
|
|
const originalMessage = 'Hello world! This is a test message.';
|
|
const context = {
|
|
account: { accountId: 1 },
|
|
target: { id: 'group1' },
|
|
variationLevel: 'medium'
|
|
};
|
|
|
|
// Mock content variation
|
|
const variationStub = sinon.stub(taskEngine.contentVariator, 'generateVariation').resolves({
|
|
content: 'Hi world! This is a test message.',
|
|
variationsApplied: ['greeting_variation']
|
|
});
|
|
|
|
const result = await taskEngine.processMessageContent(originalMessage, context);
|
|
|
|
expect(result).to.have.property('content');
|
|
expect(result).to.have.property('variationsApplied');
|
|
expect(variationStub.called).to.be.true;
|
|
|
|
variationStub.restore();
|
|
});
|
|
|
|
it('should apply behavior simulation', async function() {
|
|
const context = {
|
|
account: { accountId: 1, tier: 'normal' },
|
|
message: { content: 'Test message', length: 12 }
|
|
};
|
|
|
|
// Mock behavior simulation
|
|
const behaviorStub = sinon.stub(taskEngine.behaviorSimulator, 'simulateHumanBehavior').resolves({
|
|
typingTime: 1200,
|
|
readingTime: 800,
|
|
delay: 500,
|
|
patterns: ['natural_typing', 'reading_pause']
|
|
});
|
|
|
|
const result = await taskEngine.simulateBehavior(context);
|
|
|
|
expect(result).to.have.property('typingTime');
|
|
expect(result).to.have.property('readingTime');
|
|
expect(result).to.have.property('delay');
|
|
expect(behaviorStub.called).to.be.true;
|
|
|
|
behaviorStub.restore();
|
|
});
|
|
});
|
|
|
|
describe('Account Management Integration', function() {
|
|
it('should select optimal account for task', async function() {
|
|
const taskRequirements = {
|
|
tier: 'normal',
|
|
messageCount: 5,
|
|
urgency: 'medium'
|
|
};
|
|
|
|
// Mock account selection
|
|
const scheduleStub = sinon.stub(taskEngine.accountScheduler, 'selectOptimalAccount').resolves({
|
|
accountId: 1,
|
|
healthScore: 85,
|
|
status: 'active',
|
|
tier: 'normal'
|
|
});
|
|
|
|
const account = await taskEngine.selectAccount(taskRequirements);
|
|
|
|
expect(account).to.not.be.null;
|
|
expect(account).to.have.property('accountId');
|
|
expect(scheduleStub.called).to.be.true;
|
|
|
|
scheduleStub.restore();
|
|
});
|
|
|
|
it('should update account usage after successful send', async function() {
|
|
const accountId = 1;
|
|
const usageData = {
|
|
sentCount: 1,
|
|
success: true,
|
|
executionTime: 1500,
|
|
riskLevel: 'low'
|
|
};
|
|
|
|
// Mock usage update
|
|
const updateStub = sinon.stub(taskEngine.accountScheduler, 'updateAccountUsage').resolves(true);
|
|
|
|
await taskEngine.updateAccountUsage(accountId, usageData);
|
|
|
|
expect(updateStub.calledWith(accountId, usageData)).to.be.true;
|
|
|
|
updateStub.restore();
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', function() {
|
|
it('should handle send failures gracefully', async function() {
|
|
const mockTask = {
|
|
id: 1,
|
|
targetInfo: JSON.stringify({
|
|
targets: [{ id: 'group1', type: 'group' }]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Test message'
|
|
}),
|
|
sendingStrategy: JSON.stringify({
|
|
type: 'sequential',
|
|
interval: 1000
|
|
})
|
|
};
|
|
|
|
// Mock send failure
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').rejects(
|
|
new Error('Rate limit exceeded')
|
|
);
|
|
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
|
|
expect(result).to.have.property('success', false);
|
|
expect(result).to.have.property('failureCount');
|
|
expect(result.failureCount).to.be.greaterThan(0);
|
|
|
|
sendStub.restore();
|
|
});
|
|
|
|
it('should handle account unavailability', async function() {
|
|
// Mock no available accounts
|
|
const scheduleStub = sinon.stub(taskEngine.accountScheduler, 'selectOptimalAccount').resolves(null);
|
|
|
|
const mockTask = {
|
|
id: 1,
|
|
targetInfo: JSON.stringify({
|
|
targets: [{ id: 'group1', type: 'group' }]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Test message'
|
|
}),
|
|
sendingStrategy: JSON.stringify({
|
|
type: 'sequential'
|
|
})
|
|
};
|
|
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
|
|
expect(result).to.have.property('success', false);
|
|
expect(result).to.have.property('error');
|
|
expect(result.error).to.include('account');
|
|
|
|
scheduleStub.restore();
|
|
});
|
|
|
|
it('should handle critical risk blocking', async function() {
|
|
// Mock critical risk that blocks execution
|
|
const riskStub = sinon.stub(taskEngine.riskService, 'evaluateOverallRisk').resolves('critical');
|
|
const actionStub = sinon.stub(taskEngine.riskService, 'executeRiskAction').resolves({
|
|
action: 'blocked',
|
|
reason: 'Critical security risk detected',
|
|
success: false
|
|
});
|
|
|
|
const mockTask = {
|
|
id: 1,
|
|
targetInfo: JSON.stringify({
|
|
targets: [{ id: 'group1', type: 'group' }]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Test message'
|
|
}),
|
|
sendingStrategy: JSON.stringify({
|
|
type: 'sequential'
|
|
})
|
|
};
|
|
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
|
|
expect(result).to.have.property('success', false);
|
|
expect(result).to.have.property('error');
|
|
expect(result.error).to.include('blocked');
|
|
|
|
riskStub.restore();
|
|
actionStub.restore();
|
|
});
|
|
});
|
|
|
|
describe('Performance Monitoring', function() {
|
|
it('should track execution metrics', async function() {
|
|
const mockTask = {
|
|
id: 1,
|
|
targetInfo: JSON.stringify({
|
|
targets: [{ id: 'group1', type: 'group' }]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Test message'
|
|
}),
|
|
sendingStrategy: JSON.stringify({
|
|
type: 'sequential'
|
|
})
|
|
};
|
|
|
|
// Mock successful send
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').resolves({
|
|
success: true,
|
|
messageId: 'msg_123',
|
|
executionTime: 1200
|
|
});
|
|
|
|
const startTime = Date.now();
|
|
const result = await taskEngine.executeTask(mockTask);
|
|
const endTime = Date.now();
|
|
|
|
expect(result).to.have.property('executionTime');
|
|
expect(result.executionTime).to.be.at.most(endTime - startTime + 100); // Allow some margin
|
|
|
|
sendStub.restore();
|
|
});
|
|
|
|
it('should provide service statistics', function() {
|
|
const stats = taskEngine.getServiceStats();
|
|
|
|
expect(stats).to.have.property('isRunning');
|
|
expect(stats).to.have.property('totalTasksExecuted');
|
|
expect(stats).to.have.property('successRate');
|
|
expect(stats).to.have.property('avgExecutionTime');
|
|
expect(stats).to.have.property('activeConnections');
|
|
});
|
|
});
|
|
|
|
describe('Configuration Management', function() {
|
|
it('should update execution configuration', async function() {
|
|
const newConfig = {
|
|
maxConcurrentTasks: 10,
|
|
defaultTimeout: 30000,
|
|
retryAttempts: 3,
|
|
enableBehaviorSimulation: true
|
|
};
|
|
|
|
await taskEngine.updateConfiguration(newConfig);
|
|
|
|
const config = taskEngine.getConfiguration();
|
|
expect(config.maxConcurrentTasks).to.equal(10);
|
|
expect(config.defaultTimeout).to.equal(30000);
|
|
});
|
|
|
|
it('should validate configuration parameters', function() {
|
|
const invalidConfig = {
|
|
maxConcurrentTasks: -1, // Invalid
|
|
defaultTimeout: 'invalid' // Invalid type
|
|
};
|
|
|
|
expect(() => {
|
|
taskEngine.updateConfiguration(invalidConfig);
|
|
}).to.throw();
|
|
});
|
|
});
|
|
|
|
describe('Integration with Message Queue', function() {
|
|
it('should process queued tasks', async function() {
|
|
const queuedTask = {
|
|
id: 'queue_task_1',
|
|
taskData: {
|
|
id: 1,
|
|
targetInfo: JSON.stringify({
|
|
targets: [{ id: 'group1', type: 'group' }]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Queued message'
|
|
}),
|
|
sendingStrategy: JSON.stringify({
|
|
type: 'sequential'
|
|
})
|
|
},
|
|
priority: 'normal'
|
|
};
|
|
|
|
// Mock queue processing
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').resolves({
|
|
success: true,
|
|
messageId: 'msg_queue_123'
|
|
});
|
|
|
|
const result = await taskEngine.processQueuedTask(queuedTask);
|
|
|
|
expect(result).to.have.property('success', true);
|
|
expect(result).to.have.property('jobId', queuedTask.id);
|
|
|
|
sendStub.restore();
|
|
});
|
|
|
|
it('should handle queue failures with retry', async function() {
|
|
const queuedTask = {
|
|
id: 'retry_task_1',
|
|
taskData: {
|
|
id: 1,
|
|
targetInfo: JSON.stringify({
|
|
targets: [{ id: 'group1', type: 'group' }]
|
|
}),
|
|
messageContent: JSON.stringify({
|
|
content: 'Retry message'
|
|
})
|
|
},
|
|
attempts: 0,
|
|
maxRetries: 3
|
|
};
|
|
|
|
// Mock failure on first attempt, success on second
|
|
let callCount = 0;
|
|
const sendStub = sinon.stub(taskEngine, 'sendTelegramMessage').callsFake(() => {
|
|
callCount++;
|
|
if (callCount === 1) {
|
|
return Promise.reject(new Error('Temporary failure'));
|
|
}
|
|
return Promise.resolve({ success: true, messageId: 'msg_retry_123' });
|
|
});
|
|
|
|
const result = await taskEngine.processQueuedTaskWithRetry(queuedTask);
|
|
|
|
expect(result).to.have.property('success', true);
|
|
expect(callCount).to.equal(2); // Failed once, succeeded on retry
|
|
|
|
sendStub.restore();
|
|
});
|
|
});
|
|
}); |