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>
372 lines
12 KiB
JavaScript
372 lines
12 KiB
JavaScript
import request from 'supertest';
|
|
import app from '../../../services/api-gateway/src/app.js';
|
|
import { connectDatabase, closeDatabase, clearDatabase } from '../../helpers/database.js';
|
|
import { createCampaign, createTelegramUser, createTemplate } from '../../helpers/factories.js';
|
|
|
|
describe('Campaign Workflow E2E Tests', () => {
|
|
let authToken;
|
|
let userId;
|
|
|
|
beforeAll(async () => {
|
|
await connectDatabase();
|
|
|
|
// Register and login
|
|
const registerResponse = await request(app)
|
|
.post('/api/v1/auth/register')
|
|
.send({
|
|
username: 'testuser',
|
|
email: 'test@example.com',
|
|
password: 'SecurePass123!',
|
|
fullName: 'Test User'
|
|
});
|
|
|
|
const loginResponse = await request(app)
|
|
.post('/api/v1/auth/login')
|
|
.send({
|
|
username: 'testuser',
|
|
password: 'SecurePass123!'
|
|
});
|
|
|
|
authToken = loginResponse.body.data.accessToken;
|
|
userId = loginResponse.body.data.user.id;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await clearDatabase();
|
|
await closeDatabase();
|
|
});
|
|
|
|
describe('Complete Campaign Lifecycle', () => {
|
|
let campaignId;
|
|
let templateId;
|
|
let segmentId;
|
|
let telegramUsers = [];
|
|
|
|
it('Step 1: Create message template', async () => {
|
|
const templateData = createTemplate({
|
|
name: 'Welcome Message',
|
|
category: 'onboarding',
|
|
content: {
|
|
en: 'Welcome {{firstName}}! Thanks for joining our community.',
|
|
es: '¡Bienvenido {{firstName}}! Gracias por unirte a nuestra comunidad.'
|
|
}
|
|
});
|
|
|
|
const response = await request(app)
|
|
.post('/api/v1/templates')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(templateData);
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body.success).toBe(true);
|
|
templateId = response.body.data.template.id;
|
|
});
|
|
|
|
it('Step 2: Import users', async () => {
|
|
// Create users via API
|
|
const usersData = [
|
|
createTelegramUser({ tags: ['new', 'premium'] }),
|
|
createTelegramUser({ tags: ['new', 'free'] }),
|
|
createTelegramUser({ tags: ['existing', 'premium'] }),
|
|
createTelegramUser({ tags: ['new', 'premium'] }),
|
|
createTelegramUser({ tags: ['new', 'free'] })
|
|
];
|
|
|
|
for (const userData of usersData) {
|
|
const response = await request(app)
|
|
.post('/api/v1/users')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(userData);
|
|
|
|
expect(response.status).toBe(201);
|
|
telegramUsers.push(response.body.data.user);
|
|
}
|
|
|
|
expect(telegramUsers.length).toBe(5);
|
|
});
|
|
|
|
it('Step 3: Create user segment', async () => {
|
|
const segmentData = {
|
|
name: 'New Premium Users',
|
|
description: 'Users who are new and have premium subscription',
|
|
criteria: [
|
|
{
|
|
field: 'tags',
|
|
operator: 'contains',
|
|
value: 'new',
|
|
logic: 'AND'
|
|
},
|
|
{
|
|
field: 'tags',
|
|
operator: 'contains',
|
|
value: 'premium',
|
|
logic: 'AND'
|
|
}
|
|
],
|
|
isDynamic: true
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post('/api/v1/segments')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(segmentData);
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body.success).toBe(true);
|
|
segmentId = response.body.data.segment.id;
|
|
|
|
// Verify segment users
|
|
const segmentUsersResponse = await request(app)
|
|
.get(`/api/v1/segments/${segmentId}/users`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(segmentUsersResponse.status).toBe(200);
|
|
expect(segmentUsersResponse.body.data.users.length).toBe(2); // Should match 2 users
|
|
});
|
|
|
|
it('Step 4: Create campaign targeting segment', async () => {
|
|
const campaignData = {
|
|
name: 'Welcome Campaign for Premium Users',
|
|
description: 'Onboarding campaign for new premium subscribers',
|
|
type: 'message',
|
|
content: {
|
|
messageTemplateId: templateId
|
|
},
|
|
targeting: {
|
|
segments: [segmentId]
|
|
},
|
|
settings: {
|
|
rateLimit: {
|
|
messagesPerSecond: 5,
|
|
messagesPerUser: 1
|
|
}
|
|
},
|
|
goals: {
|
|
targetAudience: 2,
|
|
conversionRate: 20,
|
|
revenue: 1000
|
|
}
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body.success).toBe(true);
|
|
campaignId = response.body.data.campaign.id;
|
|
});
|
|
|
|
it('Step 5: Test campaign execution', async () => {
|
|
// First, test with a single user
|
|
const testResponse = await request(app)
|
|
.post(`/api/v1/orchestrator/campaigns/${campaignId}/execute`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({
|
|
test: true,
|
|
testUsers: [telegramUsers[0].telegramId]
|
|
});
|
|
|
|
expect(testResponse.status).toBe(200);
|
|
expect(testResponse.body.success).toBe(true);
|
|
expect(testResponse.body.data.status).toBe('running');
|
|
});
|
|
|
|
it('Step 6: Activate and execute campaign', async () => {
|
|
// Update campaign status to active
|
|
const activateResponse = await request(app)
|
|
.put(`/api/v1/orchestrator/campaigns/${campaignId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ status: 'active' });
|
|
|
|
expect(activateResponse.status).toBe(200);
|
|
expect(activateResponse.body.data.campaign.status).toBe('active');
|
|
|
|
// Execute campaign
|
|
const executeResponse = await request(app)
|
|
.post(`/api/v1/orchestrator/campaigns/${campaignId}/execute`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ test: false });
|
|
|
|
expect(executeResponse.status).toBe(200);
|
|
expect(executeResponse.body.success).toBe(true);
|
|
expect(executeResponse.body.data).toHaveProperty('executionId');
|
|
});
|
|
|
|
it('Step 7: Monitor campaign execution', async () => {
|
|
// Get execution history
|
|
const executionsResponse = await request(app)
|
|
.get(`/api/v1/orchestrator/campaigns/${campaignId}/executions`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(executionsResponse.status).toBe(200);
|
|
expect(executionsResponse.body.data.executions.length).toBeGreaterThan(0);
|
|
|
|
const latestExecution = executionsResponse.body.data.executions[0];
|
|
expect(latestExecution).toHaveProperty('progress');
|
|
expect(latestExecution.progress.total).toBe(2); // Should target 2 users from segment
|
|
});
|
|
|
|
it('Step 8: View campaign statistics', async () => {
|
|
// Wait a bit for execution to complete
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
const statsResponse = await request(app)
|
|
.get(`/api/v1/orchestrator/campaigns/${campaignId}/statistics`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(statsResponse.status).toBe(200);
|
|
expect(statsResponse.body.success).toBe(true);
|
|
expect(statsResponse.body.data.statistics.overview).toHaveProperty('messagesSent');
|
|
expect(statsResponse.body.data.statistics.overview.messagesSent).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('Step 9: Create scheduled follow-up campaign', async () => {
|
|
// Create follow-up campaign
|
|
const followUpCampaign = {
|
|
name: 'Premium User Engagement',
|
|
description: 'Weekly engagement for premium users',
|
|
type: 'message',
|
|
content: {
|
|
customMessage: 'Hi {{firstName}}, here are this week\'s premium features!'
|
|
},
|
|
targeting: {
|
|
segments: [segmentId]
|
|
},
|
|
status: 'active'
|
|
};
|
|
|
|
const campaignResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(followUpCampaign);
|
|
|
|
const followUpId = campaignResponse.body.data.campaign.id;
|
|
|
|
// Schedule it
|
|
const scheduleData = {
|
|
campaignId: followUpId,
|
|
name: 'Weekly Premium Engagement',
|
|
type: 'recurring',
|
|
schedule: {
|
|
startDateTime: new Date(Date.now() + 86400000).toISOString(), // Start tomorrow
|
|
recurring: {
|
|
pattern: 'weekly',
|
|
frequency: {
|
|
interval: 1,
|
|
unit: 'week'
|
|
},
|
|
daysOfWeek: [1], // Monday
|
|
time: '10:00',
|
|
timezone: 'America/New_York'
|
|
}
|
|
}
|
|
};
|
|
|
|
const scheduleResponse = await request(app)
|
|
.post('/api/v1/scheduler/scheduled-campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(scheduleData);
|
|
|
|
expect(scheduleResponse.status).toBe(201);
|
|
expect(scheduleResponse.body.success).toBe(true);
|
|
expect(scheduleResponse.body.data.schedule.type).toBe('recurring');
|
|
});
|
|
|
|
it('Step 10: Setup webhook for campaign events', async () => {
|
|
const webhookData = {
|
|
name: 'Campaign Event Notifier',
|
|
url: 'https://example.com/webhooks/campaigns',
|
|
events: ['campaign.completed', 'campaign.failed', 'user.converted'],
|
|
headers: {
|
|
'X-Webhook-Secret': 'test-secret-key'
|
|
},
|
|
isActive: true
|
|
};
|
|
|
|
const response = await request(app)
|
|
.post('/api/v1/webhooks')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(webhookData);
|
|
|
|
expect(response.status).toBe(201);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.webhook.events).toContain('campaign.completed');
|
|
});
|
|
});
|
|
|
|
describe('A/B Testing Workflow', () => {
|
|
let campaignId;
|
|
let abTestId;
|
|
|
|
it('Should create campaign with A/B test', async () => {
|
|
// Create base campaign
|
|
const campaignData = createCampaign({
|
|
name: 'A/B Test Campaign',
|
|
status: 'active'
|
|
});
|
|
|
|
const campaignResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
campaignId = campaignResponse.body.data.campaign.id;
|
|
|
|
// Create A/B test
|
|
const abTestData = {
|
|
name: 'Subject Line Test',
|
|
campaignId: campaignId,
|
|
variants: [
|
|
{
|
|
name: 'Control',
|
|
weight: 50,
|
|
content: {
|
|
customMessage: '🎉 Special offer just for you!'
|
|
}
|
|
},
|
|
{
|
|
name: 'Variant A',
|
|
weight: 50,
|
|
content: {
|
|
customMessage: '💰 Save 50% today only!'
|
|
}
|
|
}
|
|
],
|
|
metrics: ['open_rate', 'click_rate', 'conversion_rate'],
|
|
sampleSize: 20 // 20% of audience
|
|
};
|
|
|
|
const abTestResponse = await request(app)
|
|
.post('/api/v1/ab-testing/experiments')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(abTestData);
|
|
|
|
expect(abTestResponse.status).toBe(201);
|
|
abTestId = abTestResponse.body.data.experiment.id;
|
|
|
|
// Start A/B test
|
|
const startResponse = await request(app)
|
|
.post(`/api/v1/ab-testing/experiments/${abTestId}/start`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(startResponse.status).toBe(200);
|
|
expect(startResponse.body.success).toBe(true);
|
|
});
|
|
|
|
it('Should analyze A/B test results', async () => {
|
|
// Wait for some data
|
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
|
|
const resultsResponse = await request(app)
|
|
.get(`/api/v1/ab-testing/experiments/${abTestId}/results`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(resultsResponse.status).toBe(200);
|
|
expect(resultsResponse.body.data.results).toHaveProperty('variants');
|
|
expect(resultsResponse.body.data.results).toHaveProperty('winner');
|
|
expect(resultsResponse.body.data.results).toHaveProperty('confidence');
|
|
});
|
|
});
|
|
}); |