Initial commit: Telegram Management System
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:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View File

@@ -0,0 +1,282 @@
import { jest } from '@jest/globals';
import CampaignService from '../../../../services/orchestrator/src/services/campaignService.js';
import Campaign from '../../../../services/orchestrator/src/models/Campaign.js';
import { createCampaign } from '../../../helpers/factories.js';
// Mock dependencies
jest.mock('../../../../services/orchestrator/src/models/Campaign.js');
jest.mock('../../../../services/orchestrator/src/services/messagingService.js');
jest.mock('../../../../services/orchestrator/src/services/analyticsService.js');
describe('CampaignService', () => {
let campaignService;
let mockMessagingService;
let mockAnalyticsService;
beforeEach(() => {
mockMessagingService = {
sendMessage: jest.fn(),
validateMessage: jest.fn()
};
mockAnalyticsService = {
trackEvent: jest.fn(),
updateCampaignStats: jest.fn()
};
campaignService = new CampaignService(mockMessagingService, mockAnalyticsService);
jest.clearAllMocks();
});
describe('createCampaign', () => {
it('should create a new campaign', async () => {
const campaignData = createCampaign();
const savedCampaign = { ...campaignData, _id: 'camp123', save: jest.fn() };
Campaign.mockImplementation(() => savedCampaign);
savedCampaign.save.mockResolvedValue(savedCampaign);
const result = await campaignService.createCampaign(campaignData, 'user123');
expect(Campaign).toHaveBeenCalledWith({
...campaignData,
createdBy: 'user123'
});
expect(savedCampaign.save).toHaveBeenCalled();
expect(result).toEqual(savedCampaign);
});
it('should validate required fields', async () => {
const invalidData = { name: '' };
await expect(campaignService.createCampaign(invalidData, 'user123'))
.rejects.toThrow('Campaign name is required');
});
it('should track campaign creation', async () => {
const campaignData = createCampaign();
const savedCampaign = { ...campaignData, _id: 'camp123', save: jest.fn() };
Campaign.mockImplementation(() => savedCampaign);
savedCampaign.save.mockResolvedValue(savedCampaign);
await campaignService.createCampaign(campaignData, 'user123');
expect(mockAnalyticsService.trackEvent).toHaveBeenCalledWith({
event: 'campaign.created',
campaignId: 'camp123',
userId: 'user123',
campaignType: campaignData.type
});
});
});
describe('executeCampaign', () => {
it('should execute campaign successfully', async () => {
const campaign = {
_id: 'camp123',
status: 'active',
targeting: {
includedUsers: ['user1', 'user2', 'user3']
},
content: {
customMessage: 'Test message'
},
settings: {
rateLimit: {
messagesPerSecond: 10
}
},
save: jest.fn()
};
Campaign.findById.mockResolvedValue(campaign);
mockMessagingService.sendMessage.mockResolvedValue({ success: true });
const result = await campaignService.executeCampaign('camp123', {
test: false
});
expect(result.campaignId).toBe('camp123');
expect(result.status).toBe('completed');
expect(result.progress.total).toBe(3);
expect(mockMessagingService.sendMessage).toHaveBeenCalledTimes(3);
});
it('should handle test mode execution', async () => {
const campaign = {
_id: 'camp123',
status: 'active',
targeting: {
includedUsers: ['user1', 'user2', 'user3']
},
content: {
customMessage: 'Test message'
},
save: jest.fn()
};
Campaign.findById.mockResolvedValue(campaign);
mockMessagingService.sendMessage.mockResolvedValue({ success: true });
const result = await campaignService.executeCampaign('camp123', {
test: true,
testUsers: ['testUser1']
});
expect(result.isTest).toBe(true);
expect(mockMessagingService.sendMessage).toHaveBeenCalledTimes(1);
expect(mockMessagingService.sendMessage).toHaveBeenCalledWith(
'testUser1',
expect.any(Object)
);
});
it('should respect rate limits', async () => {
const campaign = {
_id: 'camp123',
status: 'active',
targeting: {
includedUsers: Array(100).fill(null).map((_, i) => `user${i}`)
},
content: {
customMessage: 'Test message'
},
settings: {
rateLimit: {
messagesPerSecond: 10
}
},
save: jest.fn()
};
Campaign.findById.mockResolvedValue(campaign);
mockMessagingService.sendMessage.mockResolvedValue({ success: true });
const startTime = Date.now();
await campaignService.executeCampaign('camp123', { test: false });
const endTime = Date.now();
// With 100 users and 10 messages per second, it should take at least 9 seconds
// (allowing for some margin)
expect(endTime - startTime).toBeGreaterThanOrEqual(9000);
});
it('should handle campaign not found', async () => {
Campaign.findById.mockResolvedValue(null);
await expect(campaignService.executeCampaign('invalid123'))
.rejects.toThrow('Campaign not found');
});
it('should handle inactive campaign', async () => {
const campaign = {
_id: 'camp123',
status: 'draft'
};
Campaign.findById.mockResolvedValue(campaign);
await expect(campaignService.executeCampaign('camp123'))
.rejects.toThrow('Campaign is not active');
});
});
describe('updateCampaign', () => {
it('should update campaign successfully', async () => {
const campaign = {
_id: 'camp123',
name: 'Old Name',
description: 'Old Description',
save: jest.fn(),
toObject: jest.fn(() => ({ _id: 'camp123', name: 'New Name' }))
};
Campaign.findById.mockResolvedValue(campaign);
campaign.save.mockResolvedValue(campaign);
const updates = {
name: 'New Name',
description: 'New Description'
};
const result = await campaignService.updateCampaign('camp123', updates);
expect(campaign.name).toBe('New Name');
expect(campaign.description).toBe('New Description');
expect(campaign.save).toHaveBeenCalled();
});
it('should not allow updating campaign in progress', async () => {
const campaign = {
_id: 'camp123',
status: 'executing'
};
Campaign.findById.mockResolvedValue(campaign);
await expect(campaignService.updateCampaign('camp123', { name: 'New' }))
.rejects.toThrow('Cannot update campaign while it is executing');
});
});
describe('deleteCampaign', () => {
it('should delete campaign successfully', async () => {
const campaign = {
_id: 'camp123',
status: 'draft',
deleteOne: jest.fn()
};
Campaign.findById.mockResolvedValue(campaign);
await campaignService.deleteCampaign('camp123');
expect(campaign.deleteOne).toHaveBeenCalled();
});
it('should not allow deleting active campaign', async () => {
const campaign = {
_id: 'camp123',
status: 'active'
};
Campaign.findById.mockResolvedValue(campaign);
await expect(campaignService.deleteCampaign('camp123'))
.rejects.toThrow('Cannot delete active campaign');
});
});
describe('getCampaignStatistics', () => {
it('should return campaign statistics', async () => {
const campaign = {
_id: 'camp123',
statistics: {
messagesSent: 100,
delivered: 95,
read: 80,
clicked: 20,
conversions: 10
}
};
Campaign.findById.mockResolvedValue(campaign);
const stats = await campaignService.getCampaignStatistics('camp123');
expect(stats).toEqual({
overview: {
messagesSent: 100,
delivered: 95,
deliveryRate: 95,
read: 80,
readRate: 84.21,
clicked: 20,
clickRate: 25,
conversions: 10,
conversionRate: 12.5
}
});
});
});
});