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>
370 lines
12 KiB
JavaScript
370 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 } from '../../helpers/factories.js';
|
|
|
|
describe('Campaigns API Integration Tests', () => {
|
|
let authToken;
|
|
|
|
beforeAll(async () => {
|
|
await connectDatabase();
|
|
|
|
// Login to get auth token
|
|
const loginResponse = await request(app)
|
|
.post('/api/v1/auth/login')
|
|
.send({
|
|
username: 'admin',
|
|
password: 'password123'
|
|
});
|
|
|
|
authToken = loginResponse.body.data.accessToken;
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await clearDatabase();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await closeDatabase();
|
|
});
|
|
|
|
describe('GET /api/v1/orchestrator/campaigns', () => {
|
|
it('should list campaigns', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data).toHaveProperty('campaigns');
|
|
expect(Array.isArray(response.body.data.campaigns)).toBe(true);
|
|
expect(response.body.data).toHaveProperty('pagination');
|
|
});
|
|
|
|
it('should filter campaigns by status', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v1/orchestrator/campaigns?status=active')
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
response.body.data.campaigns.forEach(campaign => {
|
|
expect(campaign.status).toBe('active');
|
|
});
|
|
});
|
|
|
|
it('should paginate campaigns', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v1/orchestrator/campaigns?page=1&limit=5')
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.data.campaigns.length).toBeLessThanOrEqual(5);
|
|
expect(response.body.data.pagination.page).toBe(1);
|
|
expect(response.body.data.pagination.limit).toBe(5);
|
|
});
|
|
|
|
it('should require authentication', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v1/orchestrator/campaigns');
|
|
|
|
expect(response.status).toBe(401);
|
|
expect(response.body.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('POST /api/v1/orchestrator/campaigns', () => {
|
|
it('should create a new campaign', async () => {
|
|
const campaignData = createCampaign();
|
|
|
|
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);
|
|
expect(response.body.data.campaign).toHaveProperty('id');
|
|
expect(response.body.data.campaign.name).toBe(campaignData.name);
|
|
expect(response.body.data.campaign.type).toBe(campaignData.type);
|
|
});
|
|
|
|
it('should validate required fields', async () => {
|
|
const response = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({
|
|
// Missing required fields
|
|
description: 'Test campaign'
|
|
});
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toContain('required');
|
|
});
|
|
|
|
it('should validate campaign type', async () => {
|
|
const campaignData = createCampaign({ type: 'invalid_type' });
|
|
|
|
const response = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toContain('type');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/v1/orchestrator/campaigns/:id', () => {
|
|
let campaignId;
|
|
|
|
beforeEach(async () => {
|
|
// Create a campaign
|
|
const campaignData = createCampaign();
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
campaignId = createResponse.body.data.campaign.id;
|
|
});
|
|
|
|
it('should get campaign by ID', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v1/orchestrator/campaigns/${campaignId}`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.campaign.id).toBe(campaignId);
|
|
});
|
|
|
|
it('should return 404 for non-existent campaign', async () => {
|
|
const response = await request(app)
|
|
.get('/api/v1/orchestrator/campaigns/nonexistent123')
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(404);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toContain('not found');
|
|
});
|
|
});
|
|
|
|
describe('PUT /api/v1/orchestrator/campaigns/:id', () => {
|
|
let campaignId;
|
|
|
|
beforeEach(async () => {
|
|
// Create a campaign
|
|
const campaignData = createCampaign();
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
campaignId = createResponse.body.data.campaign.id;
|
|
});
|
|
|
|
it('should update campaign', async () => {
|
|
const updates = {
|
|
name: 'Updated Campaign Name',
|
|
description: 'Updated description'
|
|
};
|
|
|
|
const response = await request(app)
|
|
.put(`/api/v1/orchestrator/campaigns/${campaignId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(updates);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.campaign.name).toBe(updates.name);
|
|
expect(response.body.data.campaign.description).toBe(updates.description);
|
|
});
|
|
|
|
it('should not update campaign type', async () => {
|
|
const response = await request(app)
|
|
.put(`/api/v1/orchestrator/campaigns/${campaignId}`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ type: 'different_type' });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toContain('type');
|
|
});
|
|
});
|
|
|
|
describe('POST /api/v1/orchestrator/campaigns/:id/execute', () => {
|
|
let campaignId;
|
|
|
|
beforeEach(async () => {
|
|
// Create users for targeting
|
|
const users = await Promise.all([
|
|
createTelegramUser(),
|
|
createTelegramUser(),
|
|
createTelegramUser()
|
|
]);
|
|
|
|
// Create a campaign with users
|
|
const campaignData = createCampaign({
|
|
status: 'active',
|
|
targeting: {
|
|
includedUsers: users.map(u => u.telegramId)
|
|
}
|
|
});
|
|
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
campaignId = createResponse.body.data.campaign.id;
|
|
});
|
|
|
|
it('should execute campaign', async () => {
|
|
const response = await request(app)
|
|
.post(`/api/v1/orchestrator/campaigns/${campaignId}/execute`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ test: false });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data).toHaveProperty('executionId');
|
|
expect(response.body.data.status).toBe('running');
|
|
});
|
|
|
|
it('should execute campaign in test mode', async () => {
|
|
const response = await request(app)
|
|
.post(`/api/v1/orchestrator/campaigns/${campaignId}/execute`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({
|
|
test: true,
|
|
testUsers: ['testUser123']
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data).toHaveProperty('executionId');
|
|
});
|
|
|
|
it('should not execute draft campaign', async () => {
|
|
// Create draft campaign
|
|
const draftCampaign = createCampaign({ status: 'draft' });
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(draftCampaign);
|
|
|
|
const draftId = createResponse.body.data.campaign.id;
|
|
|
|
const response = await request(app)
|
|
.post(`/api/v1/orchestrator/campaigns/${draftId}/execute`)
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send({ test: false });
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toContain('not active');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/v1/orchestrator/campaigns/:id/statistics', () => {
|
|
let campaignId;
|
|
|
|
beforeEach(async () => {
|
|
// Create and execute a campaign
|
|
const campaignData = createCampaign({
|
|
status: 'active',
|
|
statistics: {
|
|
messagesSent: 100,
|
|
delivered: 95,
|
|
read: 80,
|
|
clicked: 20,
|
|
conversions: 10
|
|
}
|
|
});
|
|
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
campaignId = createResponse.body.data.campaign.id;
|
|
});
|
|
|
|
it('should get campaign statistics', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v1/orchestrator/campaigns/${campaignId}/statistics`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.statistics).toHaveProperty('overview');
|
|
expect(response.body.data.statistics.overview).toHaveProperty('deliveryRate');
|
|
expect(response.body.data.statistics.overview).toHaveProperty('readRate');
|
|
expect(response.body.data.statistics.overview).toHaveProperty('conversionRate');
|
|
});
|
|
|
|
it('should filter statistics by date range', async () => {
|
|
const response = await request(app)
|
|
.get(`/api/v1/orchestrator/campaigns/${campaignId}/statistics?dateRange=last7days`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.statistics).toHaveProperty('timeline');
|
|
});
|
|
});
|
|
|
|
describe('DELETE /api/v1/orchestrator/campaigns/:id', () => {
|
|
let campaignId;
|
|
|
|
beforeEach(async () => {
|
|
// Create a campaign
|
|
const campaignData = createCampaign({ status: 'draft' });
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(campaignData);
|
|
|
|
campaignId = createResponse.body.data.campaign.id;
|
|
});
|
|
|
|
it('should delete campaign', async () => {
|
|
const response = await request(app)
|
|
.delete(`/api/v1/orchestrator/campaigns/${campaignId}`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.message).toContain('deleted');
|
|
|
|
// Verify deletion
|
|
const getResponse = await request(app)
|
|
.get(`/api/v1/orchestrator/campaigns/${campaignId}`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(getResponse.status).toBe(404);
|
|
});
|
|
|
|
it('should not delete active campaign', async () => {
|
|
// Create active campaign
|
|
const activeCampaign = createCampaign({ status: 'active' });
|
|
const createResponse = await request(app)
|
|
.post('/api/v1/orchestrator/campaigns')
|
|
.set('Authorization', `Bearer ${authToken}`)
|
|
.send(activeCampaign);
|
|
|
|
const activeId = createResponse.body.data.campaign.id;
|
|
|
|
const response = await request(app)
|
|
.delete(`/api/v1/orchestrator/campaigns/${activeId}`)
|
|
.set('Authorization', `Bearer ${authToken}`);
|
|
|
|
expect(response.status).toBe(400);
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.error).toContain('Cannot delete active campaign');
|
|
});
|
|
});
|
|
}); |