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'); }); }); });