Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
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>
This commit is contained in:
253
backend/test/services/AccountScheduler.test.js
Normal file
253
backend/test/services/AccountScheduler.test.js
Normal file
@@ -0,0 +1,253 @@
|
||||
const { expect } = require('chai');
|
||||
const TestSetup = require('../setup');
|
||||
const AccountScheduler = require('../../src/service/AccountScheduler');
|
||||
|
||||
describe('AccountScheduler Service', function() {
|
||||
let accountScheduler;
|
||||
let testDb;
|
||||
|
||||
before(async function() {
|
||||
this.timeout(10000);
|
||||
|
||||
// Setup test database and data
|
||||
await TestSetup.setupDatabase();
|
||||
await TestSetup.setupRedis();
|
||||
await TestSetup.createTestData();
|
||||
|
||||
testDb = TestSetup.getTestDb();
|
||||
accountScheduler = new AccountScheduler();
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await TestSetup.cleanup();
|
||||
});
|
||||
|
||||
describe('Initialization', function() {
|
||||
it('should initialize with default configuration', function() {
|
||||
expect(accountScheduler).to.be.instanceOf(AccountScheduler);
|
||||
expect(accountScheduler.schedulingStrategy).to.equal('health_priority');
|
||||
expect(accountScheduler.isRunning).to.be.false;
|
||||
});
|
||||
|
||||
it('should start and stop service correctly', async function() {
|
||||
await accountScheduler.start();
|
||||
expect(accountScheduler.isRunning).to.be.true;
|
||||
|
||||
await accountScheduler.stop();
|
||||
expect(accountScheduler.isRunning).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Account Selection', function() {
|
||||
beforeEach(async function() {
|
||||
await accountScheduler.start();
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await accountScheduler.stop();
|
||||
});
|
||||
|
||||
it('should select optimal account based on health priority', async function() {
|
||||
const taskRequirements = {
|
||||
tier: 'normal',
|
||||
messageCount: 5,
|
||||
urgency: 'medium'
|
||||
};
|
||||
|
||||
const account = await accountScheduler.selectOptimalAccount(taskRequirements);
|
||||
|
||||
expect(account).to.not.be.null;
|
||||
expect(account).to.have.property('accountId');
|
||||
expect(account).to.have.property('healthScore');
|
||||
expect(account.status).to.equal('active');
|
||||
});
|
||||
|
||||
it('should exclude limited and banned accounts', async function() {
|
||||
const taskRequirements = {
|
||||
excludeStatuses: ['limited', 'banned'],
|
||||
messageCount: 3
|
||||
};
|
||||
|
||||
const account = await accountScheduler.selectOptimalAccount(taskRequirements);
|
||||
|
||||
if (account) {
|
||||
expect(['active', 'warning']).to.include(account.status);
|
||||
}
|
||||
});
|
||||
|
||||
it('should respect account limits', async function() {
|
||||
const taskRequirements = {
|
||||
messageCount: 100, // Very high count
|
||||
checkLimits: true
|
||||
};
|
||||
|
||||
const account = await accountScheduler.selectOptimalAccount(taskRequirements);
|
||||
|
||||
if (account) {
|
||||
expect(account.todaySentCount + taskRequirements.messageCount).to.be.at.most(account.dailyLimit);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Load Balancing', function() {
|
||||
beforeEach(async function() {
|
||||
await accountScheduler.start();
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await accountScheduler.stop();
|
||||
});
|
||||
|
||||
it('should distribute tasks across multiple accounts', async function() {
|
||||
const selections = [];
|
||||
const taskRequirements = { messageCount: 1 };
|
||||
|
||||
// Select accounts multiple times
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const account = await accountScheduler.selectOptimalAccount(taskRequirements);
|
||||
if (account) {
|
||||
selections.push(account.accountId);
|
||||
}
|
||||
}
|
||||
|
||||
// Should have some variety in account selection
|
||||
const uniqueAccounts = new Set(selections);
|
||||
expect(uniqueAccounts.size).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should update account usage after task completion', async function() {
|
||||
const account = await accountScheduler.selectOptimalAccount({ messageCount: 5 });
|
||||
|
||||
if (account) {
|
||||
const initialUsage = account.todaySentCount;
|
||||
|
||||
await accountScheduler.updateAccountUsage(account.accountId, {
|
||||
sentCount: 5,
|
||||
success: true,
|
||||
executionTime: 1500
|
||||
});
|
||||
|
||||
// Verify usage was updated
|
||||
const updatedAccount = await accountScheduler.getAccountById(account.accountId);
|
||||
expect(updatedAccount.todaySentCount).to.equal(initialUsage + 5);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Risk Assessment', function() {
|
||||
beforeEach(async function() {
|
||||
await accountScheduler.start();
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await accountScheduler.stop();
|
||||
});
|
||||
|
||||
it('should calculate account risk score', async function() {
|
||||
const account = await accountScheduler.selectOptimalAccount({ messageCount: 1 });
|
||||
|
||||
if (account) {
|
||||
const riskScore = accountScheduler.calculateAccountRisk(account);
|
||||
|
||||
expect(riskScore).to.be.a('number');
|
||||
expect(riskScore).to.be.at.least(0);
|
||||
expect(riskScore).to.be.at.most(100);
|
||||
}
|
||||
});
|
||||
|
||||
it('should prefer lower risk accounts', async function() {
|
||||
// Set strategy to risk-based
|
||||
accountScheduler.setSchedulingStrategy('risk_balanced');
|
||||
|
||||
const account = await accountScheduler.selectOptimalAccount({
|
||||
messageCount: 1,
|
||||
riskTolerance: 'low'
|
||||
});
|
||||
|
||||
if (account) {
|
||||
expect(account.riskScore).to.be.at.most(50); // Low to medium risk
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', function() {
|
||||
it('should handle database connection errors gracefully', async function() {
|
||||
// Mock database error
|
||||
const originalQuery = testDb.query;
|
||||
testDb.query = () => Promise.reject(new Error('Database connection lost'));
|
||||
|
||||
const account = await accountScheduler.selectOptimalAccount({ messageCount: 1 });
|
||||
expect(account).to.be.null;
|
||||
|
||||
// Restore original query method
|
||||
testDb.query = originalQuery;
|
||||
});
|
||||
|
||||
it('should handle empty account pool', async function() {
|
||||
// Clear all accounts
|
||||
await testDb.query('DELETE FROM accounts_pool');
|
||||
|
||||
const account = await accountScheduler.selectOptimalAccount({ messageCount: 1 });
|
||||
expect(account).to.be.null;
|
||||
|
||||
// Restore test data
|
||||
await TestSetup.createTestData();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Strategy Configuration', function() {
|
||||
beforeEach(async function() {
|
||||
await accountScheduler.start();
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await accountScheduler.stop();
|
||||
});
|
||||
|
||||
it('should support different scheduling strategies', function() {
|
||||
const strategies = ['round_robin', 'health_priority', 'risk_balanced', 'random'];
|
||||
|
||||
strategies.forEach(strategy => {
|
||||
accountScheduler.setSchedulingStrategy(strategy);
|
||||
expect(accountScheduler.schedulingStrategy).to.equal(strategy);
|
||||
});
|
||||
});
|
||||
|
||||
it('should validate strategy parameters', function() {
|
||||
expect(() => {
|
||||
accountScheduler.setSchedulingStrategy('invalid_strategy');
|
||||
}).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance Monitoring', function() {
|
||||
beforeEach(async function() {
|
||||
await accountScheduler.start();
|
||||
});
|
||||
|
||||
afterEach(async function() {
|
||||
await accountScheduler.stop();
|
||||
});
|
||||
|
||||
it('should track selection performance metrics', async function() {
|
||||
const startTime = Date.now();
|
||||
|
||||
await accountScheduler.selectOptimalAccount({ messageCount: 1 });
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
// Selection should be reasonably fast (under 100ms)
|
||||
expect(duration).to.be.at.most(100);
|
||||
});
|
||||
|
||||
it('should provide service statistics', function() {
|
||||
const stats = accountScheduler.getServiceStats();
|
||||
|
||||
expect(stats).to.have.property('isRunning');
|
||||
expect(stats).to.have.property('strategy');
|
||||
expect(stats).to.have.property('totalSelections');
|
||||
expect(stats.isRunning).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
339
backend/test/services/RiskStrategyService.test.js
Normal file
339
backend/test/services/RiskStrategyService.test.js
Normal file
@@ -0,0 +1,339 @@
|
||||
const { expect } = require('chai');
|
||||
const TestSetup = require('../setup');
|
||||
const RiskStrategyService = require('../../src/service/RiskStrategyService');
|
||||
|
||||
describe('RiskStrategyService', function() {
|
||||
let riskService;
|
||||
let testDb;
|
||||
|
||||
before(async function() {
|
||||
this.timeout(10000);
|
||||
|
||||
await TestSetup.setupDatabase();
|
||||
await TestSetup.setupRedis();
|
||||
await TestSetup.createTestData();
|
||||
|
||||
testDb = TestSetup.getTestDb();
|
||||
riskService = new RiskStrategyService();
|
||||
await riskService.initialize();
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await TestSetup.cleanup();
|
||||
});
|
||||
|
||||
describe('Initialization', function() {
|
||||
it('should initialize with default rules', function() {
|
||||
expect(riskService.isInitialized).to.be.true;
|
||||
expect(riskService.activeRules.size).to.be.greaterThan(0);
|
||||
});
|
||||
|
||||
it('should load rules from database', async function() {
|
||||
const rules = await riskService.getAllRules();
|
||||
expect(rules).to.be.an('array');
|
||||
expect(rules.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Risk Evaluation', function() {
|
||||
const mockExecutionContext = {
|
||||
account: { accountId: 1, healthScore: 85, status: 'active' },
|
||||
target: { id: 'test_group', type: 'group' },
|
||||
message: { content: 'Test message', length: 12 },
|
||||
task: { id: 1, strategy: 'sequential' },
|
||||
timing: { hour: 14, dayOfWeek: 3 }
|
||||
};
|
||||
|
||||
it('should evaluate overall risk level', async function() {
|
||||
const riskLevel = await riskService.evaluateOverallRisk(mockExecutionContext);
|
||||
|
||||
expect(riskLevel).to.be.oneOf(['low', 'medium', 'high', 'critical']);
|
||||
});
|
||||
|
||||
it('should identify specific risks', async function() {
|
||||
const risks = await riskService.identifyRisks(mockExecutionContext);
|
||||
|
||||
expect(risks).to.be.an('array');
|
||||
risks.forEach(risk => {
|
||||
expect(risk).to.have.property('ruleId');
|
||||
expect(risk).to.have.property('severity');
|
||||
expect(risk).to.have.property('action');
|
||||
expect(risk).to.have.property('reason');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle account health risk', async function() {
|
||||
const lowHealthContext = {
|
||||
...mockExecutionContext,
|
||||
account: { accountId: 2, healthScore: 30, status: 'warning' }
|
||||
};
|
||||
|
||||
const risks = await riskService.identifyRisks(lowHealthContext);
|
||||
const healthRisk = risks.find(r => r.type === 'account' && r.category === 'health');
|
||||
|
||||
if (healthRisk) {
|
||||
expect(healthRisk.severity).to.be.oneOf(['medium', 'high']);
|
||||
expect(healthRisk.action).to.be.oneOf(['switched', 'delayed', 'blocked']);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle frequency limits', async function() {
|
||||
// Simulate high frequency context
|
||||
const highFreqContext = {
|
||||
...mockExecutionContext,
|
||||
frequency: { recentCount: 15, timeWindow: 3600 }
|
||||
};
|
||||
|
||||
const risks = await riskService.identifyRisks(highFreqContext);
|
||||
const freqRisk = risks.find(r => r.category === 'frequency');
|
||||
|
||||
if (freqRisk) {
|
||||
expect(freqRisk.action).to.be.oneOf(['delayed', 'blocked']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Risk Action Execution', function() {
|
||||
it('should execute delayed action', async function() {
|
||||
const risk = {
|
||||
ruleId: 1,
|
||||
action: 'delayed',
|
||||
severity: 'medium',
|
||||
parameters: { minDelay: 5000, maxDelay: 15000 }
|
||||
};
|
||||
|
||||
const result = await riskService.executeRiskAction(risk, {});
|
||||
|
||||
expect(result).to.have.property('action', 'delayed');
|
||||
expect(result).to.have.property('delay');
|
||||
expect(result.delay).to.be.at.least(5000);
|
||||
expect(result.delay).to.be.at.most(15000);
|
||||
});
|
||||
|
||||
it('should execute switch account action', async function() {
|
||||
const risk = {
|
||||
ruleId: 2,
|
||||
action: 'switched',
|
||||
severity: 'high',
|
||||
parameters: { reason: 'account_health_low' }
|
||||
};
|
||||
|
||||
const result = await riskService.executeRiskAction(risk, {
|
||||
account: { accountId: 1 }
|
||||
});
|
||||
|
||||
expect(result).to.have.property('action', 'switched');
|
||||
expect(result).to.have.property('originalAccount', 1);
|
||||
expect(result).to.have.property('reason');
|
||||
});
|
||||
|
||||
it('should execute block action', async function() {
|
||||
const risk = {
|
||||
ruleId: 3,
|
||||
action: 'blocked',
|
||||
severity: 'critical',
|
||||
parameters: { reason: 'critical_risk_detected' }
|
||||
};
|
||||
|
||||
const result = await riskService.executeRiskAction(risk, {});
|
||||
|
||||
expect(result).to.have.property('action', 'blocked');
|
||||
expect(result).to.have.property('reason');
|
||||
expect(result.success).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rule Management', function() {
|
||||
it('should create new rule', async function() {
|
||||
const ruleData = {
|
||||
name: 'Test Rule',
|
||||
type: 'behavior',
|
||||
category: 'test',
|
||||
conditions: { testCondition: true },
|
||||
action: 'warned',
|
||||
severity: 'low',
|
||||
priority: 50,
|
||||
enabled: true
|
||||
};
|
||||
|
||||
const rule = await riskService.createRule(ruleData);
|
||||
|
||||
expect(rule).to.have.property('id');
|
||||
expect(rule.name).to.equal('Test Rule');
|
||||
expect(rule.enabled).to.be.true;
|
||||
});
|
||||
|
||||
it('should update existing rule', async function() {
|
||||
const rules = await riskService.getAllRules();
|
||||
const ruleToUpdate = rules[0];
|
||||
|
||||
const updateData = {
|
||||
priority: 90,
|
||||
enabled: false
|
||||
};
|
||||
|
||||
const updatedRule = await riskService.updateRule(ruleToUpdate.id, updateData);
|
||||
|
||||
expect(updatedRule.priority).to.equal(90);
|
||||
expect(updatedRule.enabled).to.be.false;
|
||||
});
|
||||
|
||||
it('should delete rule', async function() {
|
||||
const rules = await riskService.getAllRules();
|
||||
const ruleToDelete = rules.find(r => r.name === 'Test Rule');
|
||||
|
||||
if (ruleToDelete) {
|
||||
const result = await riskService.deleteRule(ruleToDelete.id);
|
||||
expect(result).to.be.true;
|
||||
|
||||
const updatedRules = await riskService.getAllRules();
|
||||
const deletedRule = updatedRules.find(r => r.id === ruleToDelete.id);
|
||||
expect(deletedRule).to.be.undefined;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Risk Statistics', function() {
|
||||
it('should provide risk statistics', async function() {
|
||||
const stats = await riskService.getRiskStatistics('24h');
|
||||
|
||||
expect(stats).to.have.property('totalEvaluations');
|
||||
expect(stats).to.have.property('riskDistribution');
|
||||
expect(stats).to.have.property('actionDistribution');
|
||||
expect(stats).to.have.property('topTriggeredRules');
|
||||
|
||||
expect(stats.riskDistribution).to.have.property('low');
|
||||
expect(stats.riskDistribution).to.have.property('medium');
|
||||
expect(stats.riskDistribution).to.have.property('high');
|
||||
expect(stats.riskDistribution).to.have.property('critical');
|
||||
});
|
||||
|
||||
it('should track rule performance', async function() {
|
||||
const performance = await riskService.getRulePerformance();
|
||||
|
||||
expect(performance).to.be.an('array');
|
||||
performance.forEach(rule => {
|
||||
expect(rule).to.have.property('ruleId');
|
||||
expect(rule).to.have.property('triggerCount');
|
||||
expect(rule).to.have.property('avgProcessingTime');
|
||||
expect(rule).to.have.property('successRate');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Configuration Management', function() {
|
||||
it('should update risk thresholds', async function() {
|
||||
const newThresholds = {
|
||||
low: { min: 0, max: 30 },
|
||||
medium: { min: 31, max: 60 },
|
||||
high: { min: 61, max: 85 },
|
||||
critical: { min: 86, max: 100 }
|
||||
};
|
||||
|
||||
await riskService.updateRiskThresholds(newThresholds);
|
||||
|
||||
const config = riskService.getConfiguration();
|
||||
expect(config.riskThresholds).to.deep.equal(newThresholds);
|
||||
});
|
||||
|
||||
it('should enable/disable rule categories', async function() {
|
||||
await riskService.enableRuleCategory('behavior', false);
|
||||
|
||||
const config = riskService.getConfiguration();
|
||||
expect(config.enabledCategories.behavior).to.be.false;
|
||||
|
||||
// Re-enable for other tests
|
||||
await riskService.enableRuleCategory('behavior', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Integration Tests', function() {
|
||||
it('should work with message queue for async processing', async function() {
|
||||
const mockContext = {
|
||||
account: { accountId: 1, healthScore: 85 },
|
||||
target: { id: 'test_group' },
|
||||
message: { content: 'Test message' }
|
||||
};
|
||||
|
||||
// This would normally interact with Redis queue
|
||||
const queueResult = await riskService.queueRiskEvaluation(mockContext);
|
||||
expect(queueResult).to.have.property('queued', true);
|
||||
expect(queueResult).to.have.property('jobId');
|
||||
});
|
||||
|
||||
it('should provide real-time risk monitoring data', async function() {
|
||||
const monitoringData = await riskService.getRealTimeRiskData();
|
||||
|
||||
expect(monitoringData).to.have.property('currentRiskLevel');
|
||||
expect(monitoringData).to.have.property('activeThreats');
|
||||
expect(monitoringData).to.have.property('recentEvaluations');
|
||||
expect(monitoringData).to.have.property('systemHealth');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', function() {
|
||||
it('should handle malformed rule conditions', async function() {
|
||||
const invalidContext = {
|
||||
account: null, // Invalid account
|
||||
target: { id: 'test' },
|
||||
message: { content: 'test' }
|
||||
};
|
||||
|
||||
const risks = await riskService.identifyRisks(invalidContext);
|
||||
expect(risks).to.be.an('array'); // Should not throw error
|
||||
});
|
||||
|
||||
it('should handle database connection errors', async function() {
|
||||
// Mock database error
|
||||
const originalQuery = testDb.query;
|
||||
testDb.query = () => Promise.reject(new Error('Database error'));
|
||||
|
||||
try {
|
||||
await riskService.getAllRules();
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (error) {
|
||||
expect(error.message).to.include('Database error');
|
||||
}
|
||||
|
||||
// Restore
|
||||
testDb.query = originalQuery;
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance Tests', function() {
|
||||
it('should evaluate risks quickly', async function() {
|
||||
const context = {
|
||||
account: { accountId: 1, healthScore: 85 },
|
||||
target: { id: 'test_group' },
|
||||
message: { content: 'Test message' }
|
||||
};
|
||||
|
||||
const startTime = Date.now();
|
||||
await riskService.identifyRisks(context);
|
||||
const endTime = Date.now();
|
||||
|
||||
const duration = endTime - startTime;
|
||||
expect(duration).to.be.at.most(50); // Should complete within 50ms
|
||||
});
|
||||
|
||||
it('should handle concurrent evaluations', async function() {
|
||||
const promises = [];
|
||||
const context = {
|
||||
account: { accountId: 1, healthScore: 85 },
|
||||
target: { id: 'test_group' },
|
||||
message: { content: 'Test message' }
|
||||
};
|
||||
|
||||
// Create 10 concurrent evaluations
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(riskService.identifyRisks({ ...context, requestId: i }));
|
||||
}
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
expect(results).to.have.length(10);
|
||||
results.forEach(result => {
|
||||
expect(result).to.be.an('array');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
472
backend/test/services/TaskExecutionEngine.test.js
Normal file
472
backend/test/services/TaskExecutionEngine.test.js
Normal file
@@ -0,0 +1,472 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user