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,239 @@
// API Gateway Integration Tests
const TestEnvironment = require('./setup');
const TestHelpers = require('./helpers');
const apiGatewayApp = require('../../services/api-gateway/src/app');
describe('API Gateway Integration Tests', () => {
let testEnv;
let helpers;
let apiClient;
let testUser;
let apiKey;
const API_GATEWAY_PORT = 13000;
const API_BASE_URL = `http://localhost:${API_GATEWAY_PORT}/api/v1`;
beforeAll(async () => {
testEnv = new TestEnvironment();
helpers = new TestHelpers(testEnv);
// Setup test environment
await testEnv.setup();
// Setup mocks
helpers.setupMocks();
// Start API Gateway
await testEnv.startService('api-gateway', apiGatewayApp, API_GATEWAY_PORT);
// Create test user and API key
testUser = await testEnv.createTestUser();
apiKey = await testEnv.createTestApiKey(testUser.id);
// Create authenticated client
apiClient = await helpers.createAuthenticatedClient(`http://localhost:${API_GATEWAY_PORT}`, testUser);
});
afterAll(async () => {
helpers.cleanupMocks();
await testEnv.cleanup();
});
describe('Authentication', () => {
test('should authenticate with JWT token', async () => {
const response = await apiClient.get('/api/v1/auth/me');
const data = helpers.expectApiSuccess(response);
expect(data.user).toBeDefined();
expect(data.user.id).toBe(testUser.id);
expect(data.user.username).toBe(testUser.username);
});
test('should reject invalid token', async () => {
const invalidClient = require('axios').create({
baseURL: `http://localhost:${API_GATEWAY_PORT}`,
headers: {
'Authorization': 'Bearer invalid-token'
},
validateStatus: () => true
});
const response = await invalidClient.get('/api/v1/auth/me');
helpers.expectApiError(response, 401, 'Invalid token');
});
test('should authenticate with API key', async () => {
const apiKeyClient = require('axios').create({
baseURL: `http://localhost:${API_GATEWAY_PORT}`,
headers: {
'X-API-Key': apiKey.apiKey
},
validateStatus: () => true
});
const response = await apiKeyClient.get('/api/v1/auth/me');
const data = helpers.expectApiSuccess(response);
expect(data.user).toBeDefined();
expect(data.apiKey).toBeDefined();
});
});
describe('Rate Limiting', () => {
test('should enforce rate limits', async () => {
const requests = [];
// Make many requests quickly
for (let i = 0; i < 105; i++) {
requests.push(
apiClient.get('/api/v1/health').catch(e => e.response)
);
}
const responses = await Promise.all(requests);
// Check that some requests were rate limited
const rateLimited = responses.filter(r => r.status === 429);
expect(rateLimited.length).toBeGreaterThan(0);
// Verify rate limit headers
const limitedResponse = rateLimited[0];
expect(limitedResponse.headers['x-ratelimit-limit']).toBeDefined();
expect(limitedResponse.headers['x-ratelimit-remaining']).toBeDefined();
expect(limitedResponse.headers['x-ratelimit-reset']).toBeDefined();
});
});
describe('Service Routing', () => {
test('should route to orchestrator service', async () => {
const campaign = await helpers.createTestCampaign();
const response = await apiClient.get(`/api/v1/campaigns/${campaign.campaignId}`);
const data = helpers.expectApiSuccess(response);
expect(data.campaign).toBeDefined();
expect(data.campaign.campaignId).toBe(campaign.campaignId);
});
test('should route to analytics service', async () => {
const response = await apiClient.get('/api/v1/analytics/metrics', {
params: {
startDate: new Date(Date.now() - 86400000).toISOString(),
endDate: new Date().toISOString()
}
});
const data = helpers.expectApiSuccess(response);
expect(data.metrics).toBeDefined();
});
test('should handle service unavailable', async () => {
// Try to access a non-existent service endpoint
const response = await apiClient.get('/api/v1/nonexistent/endpoint');
helpers.expectApiError(response, 404);
});
});
describe('CORS', () => {
test('should handle CORS preflight requests', async () => {
const axios = require('axios');
const response = await axios.options(`http://localhost:${API_GATEWAY_PORT}/api/v1/health`, {
headers: {
'Origin': 'http://localhost:3008',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'authorization'
},
validateStatus: () => true
});
expect(response.status).toBe(204);
expect(response.headers['access-control-allow-origin']).toBe('http://localhost:3008');
expect(response.headers['access-control-allow-methods']).toContain('GET');
expect(response.headers['access-control-allow-headers']).toContain('authorization');
});
test('should reject unauthorized origins', async () => {
const axios = require('axios');
const response = await axios.get(`http://localhost:${API_GATEWAY_PORT}/api/v1/health`, {
headers: {
'Origin': 'http://unauthorized.com'
},
validateStatus: () => true
});
expect(response.headers['access-control-allow-origin']).toBeUndefined();
});
});
describe('Error Handling', () => {
test('should handle validation errors', async () => {
const response = await apiClient.post('/api/v1/campaigns', {
// Missing required fields
name: 'Test Campaign'
});
helpers.expectApiError(response, 400);
expect(response.data.errors).toBeDefined();
});
test('should handle internal server errors gracefully', async () => {
// Force an error by sending invalid data type
const response = await apiClient.post('/api/v1/campaigns', 'invalid-data-type');
helpers.expectApiError(response, 400);
expect(response.data.error).toBeDefined();
});
});
describe('Health Checks', () => {
test('should return health status', async () => {
const response = await apiClient.get('/api/v1/health');
const data = helpers.expectApiSuccess(response);
expect(data.status).toBe('healthy');
expect(data.timestamp).toBeDefined();
expect(data.version).toBeDefined();
});
test('should return detailed service health', async () => {
const response = await apiClient.get('/api/v1/health/services');
const data = helpers.expectApiSuccess(response);
expect(data.services).toBeDefined();
expect(data.services['api-gateway']).toBeDefined();
expect(data.services['api-gateway'].status).toBe('healthy');
});
});
describe('Request Logging', () => {
test('should log requests with correlation ID', async () => {
const correlationId = 'test-correlation-' + Date.now();
const response = await apiClient.get('/api/v1/health', {
headers: {
'X-Correlation-ID': correlationId
}
});
helpers.expectApiSuccess(response);
expect(response.headers['x-correlation-id']).toBe(correlationId);
});
});
describe('API Versioning', () => {
test('should support multiple API versions', async () => {
// Test v1
const v1Response = await apiClient.get('/api/v1/health');
helpers.expectApiSuccess(v1Response);
// Test v2 (if implemented)
const v2Response = await apiClient.get('/api/v2/health');
if (v2Response.status === 200) {
const data = helpers.expectApiSuccess(v2Response);
expect(data.apiVersion).toBe('v2');
} else {
// v2 not implemented yet
expect(v2Response.status).toBe(404);
}
});
});
});

View File

@@ -0,0 +1,365 @@
// Compliance Integration Tests
const TestEnvironment = require('./setup');
const TestHelpers = require('./helpers');
const apiGatewayApp = require('../../services/api-gateway/src/app');
const complianceGuardApp = require('../../services/compliance-guard/src/app');
describe('Compliance Integration Tests', () => {
let testEnv;
let helpers;
let apiClient;
let testUser;
const SERVICES = {
'api-gateway': { app: apiGatewayApp, port: 13000 },
'compliance-guard': { app: complianceGuardApp, port: 13006 }
};
beforeAll(async () => {
testEnv = new TestEnvironment();
helpers = new TestHelpers(testEnv);
// Setup test environment
await testEnv.setup();
// Setup mocks
helpers.setupMocks();
// Start services
for (const [name, config] of Object.entries(SERVICES)) {
await testEnv.startService(name, config.app, config.port);
}
// Create test user
testUser = await testEnv.createTestUser();
// Create authenticated client
apiClient = await helpers.createAuthenticatedClient(
`http://localhost:${SERVICES['api-gateway'].port}`,
testUser
);
});
afterAll(async () => {
helpers.cleanupMocks();
await testEnv.cleanup();
});
describe('Data Protection', () => {
test('should encrypt sensitive data', async () => {
const sensitiveData = {
userId: testUser.id,
data: {
ssn: '123-45-6789',
creditCard: '4111111111111111',
email: 'test@example.com'
}
};
const response = await apiClient.post(
'/api/v1/compliance/data/encrypt',
sensitiveData
);
const { encrypted } = helpers.expectApiSuccess(response);
expect(encrypted).toBeDefined();
expect(encrypted.data).not.toEqual(sensitiveData.data);
expect(encrypted.data).toContain('encrypted:');
});
test('should decrypt data for authorized users', async () => {
// First encrypt
const originalData = {
message: 'Secret message',
timestamp: new Date().toISOString()
};
const encryptResponse = await apiClient.post(
'/api/v1/compliance/data/encrypt',
{ userId: testUser.id, data: originalData }
);
const { encrypted } = helpers.expectApiSuccess(encryptResponse);
// Then decrypt
const decryptResponse = await apiClient.post(
'/api/v1/compliance/data/decrypt',
{ userId: testUser.id, data: encrypted.data }
);
const { decrypted } = helpers.expectApiSuccess(decryptResponse);
expect(decrypted).toEqual(originalData);
});
test('should prevent unauthorized decryption', async () => {
// Create another user
const otherUser = await testEnv.createTestUser();
const otherClient = await helpers.createAuthenticatedClient(
`http://localhost:${SERVICES['api-gateway'].port}`,
otherUser
);
// Try to decrypt data encrypted by first user
const response = await otherClient.post(
'/api/v1/compliance/data/decrypt',
{ userId: testUser.id, data: 'encrypted:somedata' }
);
helpers.expectApiError(response, 403, 'Unauthorized');
});
});
describe('Privacy Rights', () => {
test('should handle data export request', async () => {
// Create some test data
await helpers.createTestCampaign({ userId: testUser.id });
await helpers.createTestTask({ userId: testUser.id });
const response = await apiClient.post(
'/api/v1/compliance/privacy/export',
{ userId: testUser.id }
);
const { exportId, status } = helpers.expectApiSuccess(response, 202);
expect(exportId).toBeDefined();
expect(status).toBe('processing');
// Wait for export to complete
await helpers.sleep(2000);
// Check export status
const statusResponse = await apiClient.get(
`/api/v1/compliance/privacy/export/${exportId}`
);
const exportData = helpers.expectApiSuccess(statusResponse);
expect(['completed', 'processing']).toContain(exportData.status);
if (exportData.status === 'completed') {
expect(exportData.downloadUrl).toBeDefined();
}
});
test('should handle data deletion request', async () => {
// Create test data
const campaign = await helpers.createTestCampaign({ userId: testUser.id });
// Request deletion
const response = await apiClient.post(
'/api/v1/compliance/privacy/delete',
{
userId: testUser.id,
scope: 'all',
reason: 'User requested account deletion'
}
);
const { deletionId, status } = helpers.expectApiSuccess(response, 202);
expect(deletionId).toBeDefined();
expect(status).toBe('scheduled');
// Wait for deletion
await helpers.sleep(2000);
// Verify data was deleted
const campaignResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}`
);
helpers.expectApiError(campaignResponse, 404);
});
test('should respect data retention policies', async () => {
const response = await apiClient.get('/api/v1/compliance/retention/policies');
const { policies } = helpers.expectApiSuccess(response);
expect(policies).toBeDefined();
expect(policies).toHaveProperty('messages');
expect(policies).toHaveProperty('userdata');
expect(policies).toHaveProperty('analytics');
expect(policies.messages.retentionDays).toBeGreaterThan(0);
expect(policies.userdata.retentionDays).toBeGreaterThan(0);
});
});
describe('Consent Management', () => {
test('should record user consent', async () => {
const consentData = {
userId: testUser.id,
purposes: [
{
purpose: 'marketing',
granted: true,
timestamp: new Date().toISOString()
},
{
purpose: 'analytics',
granted: true,
timestamp: new Date().toISOString()
},
{
purpose: 'personalization',
granted: false,
timestamp: new Date().toISOString()
}
],
ipAddress: '192.168.1.1',
userAgent: 'Test Client/1.0'
};
const response = await apiClient.post(
'/api/v1/compliance/consent/record',
consentData
);
const { consentId } = helpers.expectApiSuccess(response, 201);
expect(consentId).toBeDefined();
});
test('should check consent before processing', async () => {
// Check consent status
const response = await apiClient.get(
`/api/v1/compliance/consent/check/${testUser.id}`,
{
params: { purpose: 'marketing' }
}
);
const { granted, timestamp } = helpers.expectApiSuccess(response);
expect(typeof granted).toBe('boolean');
if (granted) {
expect(timestamp).toBeDefined();
}
});
test('should update consent preferences', async () => {
const updateData = {
userId: testUser.id,
updates: [
{
purpose: 'marketing',
granted: false,
timestamp: new Date().toISOString()
}
]
};
const response = await apiClient.put(
'/api/v1/compliance/consent/update',
updateData
);
helpers.expectApiSuccess(response);
// Verify update
const checkResponse = await apiClient.get(
`/api/v1/compliance/consent/check/${testUser.id}`,
{
params: { purpose: 'marketing' }
}
);
const { granted } = helpers.expectApiSuccess(checkResponse);
expect(granted).toBe(false);
});
});
describe('Audit Logging', () => {
test('should log compliance events', async () => {
// Perform some compliance actions
await apiClient.post('/api/v1/compliance/data/encrypt', {
userId: testUser.id,
data: { test: 'data' }
});
await apiClient.get(`/api/v1/compliance/consent/check/${testUser.id}`, {
params: { purpose: 'analytics' }
});
// Get audit logs
const response = await apiClient.get('/api/v1/compliance/audit/logs', {
params: {
userId: testUser.id,
startDate: new Date(Date.now() - 3600000).toISOString(),
endDate: new Date().toISOString()
}
});
const { logs, total } = helpers.expectApiSuccess(response);
expect(logs).toBeDefined();
expect(logs.length).toBeGreaterThan(0);
expect(total).toBeGreaterThan(0);
// Verify log structure
const log = logs[0];
expect(log).toHaveProperty('timestamp');
expect(log).toHaveProperty('action');
expect(log).toHaveProperty('userId');
expect(log).toHaveProperty('details');
});
test('should generate compliance reports', async () => {
const response = await apiClient.post('/api/v1/compliance/audit/report', {
type: 'monthly',
month: new Date().getMonth() + 1,
year: new Date().getFullYear()
});
const { reportId, status } = helpers.expectApiSuccess(response, 202);
expect(reportId).toBeDefined();
expect(status).toBe('generating');
});
});
describe('Regulatory Compliance', () => {
test('should validate GDPR compliance', async () => {
const response = await apiClient.get('/api/v1/compliance/regulatory/gdpr/status');
const { compliant, issues } = helpers.expectApiSuccess(response);
expect(typeof compliant).toBe('boolean');
expect(Array.isArray(issues)).toBe(true);
if (!compliant) {
expect(issues.length).toBeGreaterThan(0);
issues.forEach(issue => {
expect(issue).toHaveProperty('category');
expect(issue).toHaveProperty('description');
expect(issue).toHaveProperty('severity');
});
}
});
test('should validate CCPA compliance', async () => {
const response = await apiClient.get('/api/v1/compliance/regulatory/ccpa/status');
const { compliant, issues } = helpers.expectApiSuccess(response);
expect(typeof compliant).toBe('boolean');
expect(Array.isArray(issues)).toBe(true);
});
test('should handle Do Not Sell requests', async () => {
const response = await apiClient.post('/api/v1/compliance/privacy/donotsell', {
userId: testUser.id,
optOut: true
});
helpers.expectApiSuccess(response);
// Verify opt-out status
const statusResponse = await apiClient.get(
`/api/v1/compliance/privacy/donotsell/${testUser.id}`
);
const { optedOut } = helpers.expectApiSuccess(statusResponse);
expect(optedOut).toBe(true);
});
});
});

View File

@@ -0,0 +1,319 @@
// Test Helpers
const jwt = require('jsonwebtoken');
const axios = require('axios');
const { v4: uuidv4 } = require('uuid');
class TestHelpers {
constructor(testEnv) {
this.testEnv = testEnv;
this.apiClient = null;
}
// Create authenticated API client
async createAuthenticatedClient(baseURL, user) {
const token = this.generateJWT(user);
return axios.create({
baseURL,
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
validateStatus: () => true // Don't throw on any status
});
}
// Generate JWT token for testing
generateJWT(user) {
return jwt.sign(
{
userId: user.id,
username: user.username,
email: user.email
},
process.env.JWT_SECRET || 'test-jwt-secret',
{ expiresIn: '1h' }
);
}
// Create test campaign
async createTestCampaign(data = {}) {
const defaultData = {
campaignId: uuidv4(),
name: `Test Campaign ${Date.now()}`,
description: 'Test campaign for integration testing',
targetAudience: {
groups: ['test-group-1', 'test-group-2'],
tags: ['test', 'automated'],
filters: {}
},
messages: [
{
content: 'Test message content',
type: 'text',
delay: 0
}
],
schedule: {
startTime: new Date(),
endTime: new Date(Date.now() + 86400000), // +1 day
timezone: 'UTC'
},
goals: {
impressions: 1000,
clicks: 100,
conversions: 10
},
status: 'draft'
};
const campaign = { ...defaultData, ...data };
// Save to MongoDB
const Campaign = require('../../services/orchestrator/src/models/Campaign');
return await Campaign.create(campaign);
}
// Create test task
async createTestTask(data = {}) {
const defaultData = {
taskId: uuidv4(),
type: 'SEND_MESSAGE',
payload: {
message: 'Test message',
chatId: 'test-chat-123',
options: {}
},
priority: 'medium',
status: 'pending',
retryCount: 0,
maxRetries: 3
};
const task = { ...defaultData, ...data };
// Save to MongoDB
const Task = require('../../services/orchestrator/src/models/Task');
return await Task.create(task);
}
// Wait for condition with timeout
async waitForCondition(checkFn, timeout = 5000, interval = 100) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
if (await checkFn()) {
return true;
}
await this.sleep(interval);
}
return false;
}
// Sleep helper
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Mock external services
setupMocks() {
// Mock Telegram System API
const nock = require('nock');
const telegramMock = nock(process.env.TELEGRAM_SYSTEM_URL || 'http://localhost:8080')
.persist()
.post('/api/messages/send')
.reply(200, {
success: true,
messageId: uuidv4(),
timestamp: new Date().toISOString()
})
.get('/api/accounts')
.reply(200, {
accounts: [
{
id: 'test-account-1',
phone: '+1234567890',
status: 'active'
}
]
})
.get('/api/groups')
.reply(200, {
groups: [
{
id: 'test-group-1',
title: 'Test Group 1',
members_count: 100
}
]
});
// Mock Claude API
const claudeMock = nock('https://api.anthropic.com')
.persist()
.post('/v1/messages')
.reply(200, {
id: 'msg_' + uuidv4(),
type: 'message',
content: [
{
type: 'text',
text: 'Mocked Claude response for testing'
}
]
});
// Mock OpenAI API
const openaiMock = nock('https://api.openai.com')
.persist()
.post('/v1/moderations')
.reply(200, {
id: 'modr-' + uuidv4(),
results: [
{
flagged: false,
categories: {
hate: false,
harassment: false,
violence: false
}
}
]
});
return { telegramMock, claudeMock, openaiMock };
}
// Clean up mocks
cleanupMocks() {
const nock = require('nock');
nock.cleanAll();
}
// Verify API response
expectApiSuccess(response, statusCode = 200) {
expect(response.status).toBe(statusCode);
expect(response.data).toBeDefined();
if (response.data.error) {
throw new Error(`API Error: ${response.data.error}`);
}
return response.data;
}
// Verify API error
expectApiError(response, statusCode, errorMessage) {
expect(response.status).toBe(statusCode);
if (errorMessage) {
expect(response.data.error).toContain(errorMessage);
}
return response.data;
}
// Create test webhook
async createTestWebhook(url, events = ['message.sent', 'campaign.completed']) {
return {
id: uuidv4(),
url,
events,
secret: 'test-webhook-secret',
active: true,
createdAt: new Date()
};
}
// Simulate webhook delivery
async simulateWebhookDelivery(webhook, event, data) {
const crypto = require('crypto');
const payload = {
id: uuidv4(),
event,
data,
timestamp: new Date().toISOString()
};
const signature = crypto
.createHmac('sha256', webhook.secret)
.update(JSON.stringify(payload))
.digest('hex');
return axios.post(webhook.url, payload, {
headers: {
'X-Webhook-Signature': signature,
'Content-Type': 'application/json'
},
validateStatus: () => true
});
}
// Generate test data
generateTestData(type, count = 10) {
const data = [];
for (let i = 0; i < count; i++) {
switch (type) {
case 'messages':
data.push({
messageId: uuidv4(),
content: `Test message ${i + 1}`,
chatId: `chat-${i + 1}`,
timestamp: new Date(Date.now() - i * 60000) // 1 min apart
});
break;
case 'events':
data.push({
eventId: uuidv4(),
type: ['message.sent', 'message.delivered', 'message.read'][i % 3],
data: { messageId: uuidv4() },
timestamp: new Date()
});
break;
case 'metrics':
data.push({
timestamp: new Date(Date.now() - i * 3600000), // 1 hour apart
impressions: Math.floor(Math.random() * 1000),
clicks: Math.floor(Math.random() * 100),
conversions: Math.floor(Math.random() * 10)
});
break;
}
}
return data;
}
// Verify database state
async verifyDatabaseState(expectations) {
const results = {};
if (expectations.mongodb) {
for (const [collection, query] of Object.entries(expectations.mongodb)) {
const Model = require(`../../services/orchestrator/src/models/${collection}`);
results[collection] = await Model.find(query);
}
}
if (expectations.postgres) {
for (const [table, query] of Object.entries(expectations.postgres)) {
const result = await this.testEnv.pgPool.query(
`SELECT * FROM ${table} WHERE ${query}`
);
results[table] = result.rows;
}
}
if (expectations.redis) {
const redis = this.testEnv.getRedisClient();
for (const key of expectations.redis) {
results[`redis:${key}`] = await redis.get(key);
}
}
return results;
}
}
module.exports = TestHelpers;

View File

@@ -0,0 +1,408 @@
// Orchestration Integration Tests
const TestEnvironment = require('./setup');
const TestHelpers = require('./helpers');
const apiGatewayApp = require('../../services/api-gateway/src/app');
const orchestratorApp = require('../../services/orchestrator/src/app');
const claudeAgentApp = require('../../services/claude-agent/src/app');
const gramjsAdapterApp = require('../../services/gramjs-adapter/src/app');
describe('Orchestration Integration Tests', () => {
let testEnv;
let helpers;
let apiClient;
let testUser;
const SERVICES = {
'api-gateway': { app: apiGatewayApp, port: 13000 },
'orchestrator': { app: orchestratorApp, port: 13001 },
'claude-agent': { app: claudeAgentApp, port: 13002 },
'gramjs-adapter': { app: gramjsAdapterApp, port: 13003 }
};
beforeAll(async () => {
testEnv = new TestEnvironment();
helpers = new TestHelpers(testEnv);
// Setup test environment
await testEnv.setup();
// Setup mocks
helpers.setupMocks();
// Start all services
for (const [name, config] of Object.entries(SERVICES)) {
await testEnv.startService(name, config.app, config.port);
}
// Wait for services to be ready
await helpers.sleep(2000);
// Create test user
testUser = await testEnv.createTestUser();
// Create authenticated client
apiClient = await helpers.createAuthenticatedClient(
`http://localhost:${SERVICES['api-gateway'].port}`,
testUser
);
});
afterAll(async () => {
helpers.cleanupMocks();
await testEnv.cleanup();
});
describe('Campaign Orchestration', () => {
test('should create and execute a campaign', async () => {
// Create campaign
const campaignData = {
name: 'Integration Test Campaign',
description: 'Testing full orchestration flow',
targetAudience: {
groups: ['test-group-1'],
tags: ['integration-test']
},
messages: [
{
content: 'Hello from integration test!',
type: 'text',
delay: 0
},
{
content: 'This is the second message',
type: 'text',
delay: 1000
}
],
schedule: {
startTime: new Date(),
endTime: new Date(Date.now() + 3600000),
timezone: 'UTC'
},
goals: {
impressions: 100,
clicks: 10
}
};
const createResponse = await apiClient.post('/api/v1/campaigns', campaignData);
const { campaign } = helpers.expectApiSuccess(createResponse, 201);
expect(campaign.campaignId).toBeDefined();
expect(campaign.status).toBe('draft');
// Start campaign
const startResponse = await apiClient.post(
`/api/v1/campaigns/${campaign.campaignId}/start`
);
helpers.expectApiSuccess(startResponse);
// Wait for campaign to start processing
await helpers.sleep(2000);
// Check campaign status
const statusResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}`
);
const updatedCampaign = helpers.expectApiSuccess(statusResponse).campaign;
expect(['active', 'completed']).toContain(updatedCampaign.status);
// Check that tasks were created
const tasksResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}/tasks`
);
const { tasks } = helpers.expectApiSuccess(tasksResponse);
expect(tasks).toBeDefined();
expect(tasks.length).toBeGreaterThan(0);
expect(tasks[0].type).toBe('SEND_MESSAGE');
});
test('should handle campaign pause and resume', async () => {
// Create and start campaign
const campaign = await helpers.createTestCampaign({
status: 'active'
});
// Pause campaign
const pauseResponse = await apiClient.post(
`/api/v1/campaigns/${campaign.campaignId}/pause`
);
helpers.expectApiSuccess(pauseResponse);
// Verify paused status
const pausedResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}`
);
const pausedCampaign = helpers.expectApiSuccess(pausedResponse).campaign;
expect(pausedCampaign.status).toBe('paused');
// Resume campaign
const resumeResponse = await apiClient.post(
`/api/v1/campaigns/${campaign.campaignId}/resume`
);
helpers.expectApiSuccess(resumeResponse);
// Verify active status
const activeResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}`
);
const activeCampaign = helpers.expectApiSuccess(activeResponse).campaign;
expect(activeCampaign.status).toBe('active');
});
test('should handle campaign cancellation', async () => {
// Create and start campaign
const campaign = await helpers.createTestCampaign({
status: 'active'
});
// Cancel campaign
const cancelResponse = await apiClient.post(
`/api/v1/campaigns/${campaign.campaignId}/cancel`
);
helpers.expectApiSuccess(cancelResponse);
// Verify cancelled status
const cancelledResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}`
);
const cancelledCampaign = helpers.expectApiSuccess(cancelledResponse).campaign;
expect(cancelledCampaign.status).toBe('cancelled');
});
});
describe('Task Execution', () => {
test('should execute message sending task', async () => {
// Create a message task
const task = await helpers.createTestTask({
type: 'SEND_MESSAGE',
payload: {
message: 'Test message from integration test',
chatId: 'test-chat-123',
options: {
parseMode: 'HTML'
}
}
});
// Execute task
const executeResponse = await apiClient.post(
`/api/v1/tasks/${task.taskId}/execute`
);
helpers.expectApiSuccess(executeResponse);
// Wait for execution
await helpers.sleep(1000);
// Check task status
const statusResponse = await apiClient.get(
`/api/v1/tasks/${task.taskId}`
);
const executedTask = helpers.expectApiSuccess(statusResponse).task;
expect(['completed', 'processing']).toContain(executedTask.status);
expect(executedTask.result).toBeDefined();
});
test('should handle task retry on failure', async () => {
// Create a task that will fail
const task = await helpers.createTestTask({
type: 'SEND_MESSAGE',
payload: {
message: 'Test message',
chatId: 'invalid-chat-id', // This should cause a failure
options: {}
},
maxRetries: 3
});
// Execute task
const executeResponse = await apiClient.post(
`/api/v1/tasks/${task.taskId}/execute`
);
helpers.expectApiSuccess(executeResponse);
// Wait for retries
await helpers.sleep(3000);
// Check task status
const statusResponse = await apiClient.get(
`/api/v1/tasks/${task.taskId}`
);
const retriedTask = helpers.expectApiSuccess(statusResponse).task;
expect(retriedTask.retryCount).toBeGreaterThan(0);
expect(['failed', 'pending']).toContain(retriedTask.status);
});
});
describe('Claude Agent Integration', () => {
test('should generate campaign strategy using Claude', async () => {
const strategyRequest = {
goal: 'Increase user engagement',
targetAudience: {
demographics: {
age: '25-35',
interests: ['technology', 'startups']
},
size: 1000
},
constraints: {
budget: 1000,
duration: '7 days'
}
};
const response = await apiClient.post(
'/api/v1/ai/strategy/generate',
strategyRequest
);
const { strategy } = helpers.expectApiSuccess(response);
expect(strategy).toBeDefined();
expect(strategy.messages).toBeDefined();
expect(strategy.schedule).toBeDefined();
expect(strategy.expectedOutcomes).toBeDefined();
});
test('should analyze campaign performance', async () => {
// Create a completed campaign with some metrics
const campaign = await helpers.createTestCampaign({
status: 'completed',
metrics: {
impressions: 500,
clicks: 50,
conversions: 5
}
});
const analysisRequest = {
campaignId: campaign.campaignId,
metrics: campaign.metrics
};
const response = await apiClient.post(
'/api/v1/ai/analysis/campaign',
analysisRequest
);
const { analysis } = helpers.expectApiSuccess(response);
expect(analysis).toBeDefined();
expect(analysis.performance).toBeDefined();
expect(analysis.recommendations).toBeDefined();
expect(analysis.insights).toBeDefined();
});
});
describe('Message Queue Integration', () => {
test('should process messages through RabbitMQ', async () => {
if (!testEnv.getRabbitChannel()) {
console.log('Skipping RabbitMQ test - not available');
return;
}
const channel = testEnv.getRabbitChannel();
const testQueue = 'test-integration-queue';
const testMessage = {
type: 'TEST_MESSAGE',
data: {
value: 'Integration test message',
timestamp: new Date().toISOString()
}
};
// Create queue
await channel.assertQueue(testQueue, { durable: false });
// Set up consumer
const receivedMessages = [];
await channel.consume(testQueue, (msg) => {
if (msg) {
receivedMessages.push(JSON.parse(msg.content.toString()));
channel.ack(msg);
}
});
// Send message
channel.sendToQueue(
testQueue,
Buffer.from(JSON.stringify(testMessage))
);
// Wait for message to be processed
await helpers.sleep(1000);
// Verify message was received
expect(receivedMessages).toHaveLength(1);
expect(receivedMessages[0]).toEqual(testMessage);
// Cleanup
await channel.deleteQueue(testQueue);
});
});
describe('End-to-End Workflow', () => {
test('should complete full marketing workflow', async () => {
// 1. Generate strategy with Claude
const strategyResponse = await apiClient.post('/api/v1/ai/strategy/generate', {
goal: 'Product launch announcement',
targetAudience: {
demographics: { interests: ['tech'] },
size: 500
}
});
const { strategy } = helpers.expectApiSuccess(strategyResponse);
// 2. Create campaign based on strategy
const campaignData = {
name: 'Product Launch Campaign',
description: 'Generated from AI strategy',
targetAudience: strategy.targetAudience,
messages: strategy.messages,
schedule: strategy.schedule,
goals: strategy.expectedOutcomes
};
const createResponse = await apiClient.post('/api/v1/campaigns', campaignData);
const { campaign } = helpers.expectApiSuccess(createResponse, 201);
// 3. Start campaign
await apiClient.post(`/api/v1/campaigns/${campaign.campaignId}/start`);
// 4. Wait for some execution
await helpers.sleep(3000);
// 5. Get analytics
const analyticsResponse = await apiClient.get(
`/api/v1/analytics/campaigns/${campaign.campaignId}/metrics`
);
const { metrics } = helpers.expectApiSuccess(analyticsResponse);
expect(metrics).toBeDefined();
expect(metrics.totalMessages).toBeGreaterThanOrEqual(0);
// 6. Get AI analysis
const analysisResponse = await apiClient.post('/api/v1/ai/analysis/campaign', {
campaignId: campaign.campaignId,
metrics: metrics
});
const { analysis } = helpers.expectApiSuccess(analysisResponse);
expect(analysis).toBeDefined();
expect(analysis.performance).toBeDefined();
// 7. Stop campaign
await apiClient.post(`/api/v1/campaigns/${campaign.campaignId}/cancel`);
// Verify final state
const finalResponse = await apiClient.get(
`/api/v1/campaigns/${campaign.campaignId}`
);
const finalCampaign = helpers.expectApiSuccess(finalResponse).campaign;
expect(finalCampaign.status).toBe('cancelled');
});
});
});

View File

@@ -0,0 +1,241 @@
// Integration Test Setup
const mongoose = require('mongoose');
const { MongoMemoryServer } = require('mongodb-memory-server');
const Redis = require('ioredis-mock');
const amqp = require('amqplib');
const { Pool } = require('pg');
const { Client } = require('@elastic/elasticsearch');
const { v4: uuidv4 } = require('uuid');
class TestEnvironment {
constructor() {
this.mongoServer = null;
this.redisClient = null;
this.rabbitConnection = null;
this.pgPool = null;
this.esClient = null;
this.servers = new Map();
}
async setup() {
console.log('Setting up test environment...');
// Setup MongoDB
this.mongoServer = await MongoMemoryServer.create();
const mongoUri = this.mongoServer.getUri();
await mongoose.connect(mongoUri);
// Setup Redis Mock
this.redisClient = new Redis({
data: {}
});
// Setup PostgreSQL (using test database)
this.pgPool = new Pool({
host: process.env.TEST_POSTGRES_HOST || 'localhost',
port: process.env.TEST_POSTGRES_PORT || 5432,
user: process.env.TEST_POSTGRES_USER || 'test_user',
password: process.env.TEST_POSTGRES_PASSWORD || 'test_pass',
database: process.env.TEST_POSTGRES_DB || 'marketing_test'
});
// Create test database schema
await this.setupPostgresSchema();
// Setup RabbitMQ connection
if (process.env.TEST_RABBITMQ_URL) {
try {
this.rabbitConnection = await amqp.connect(process.env.TEST_RABBITMQ_URL);
this.rabbitChannel = await this.rabbitConnection.createChannel();
} catch (error) {
console.warn('RabbitMQ not available for tests:', error.message);
}
}
// Setup Elasticsearch Mock
this.esClient = {
index: jest.fn().mockResolvedValue({ body: { _id: uuidv4() } }),
search: jest.fn().mockResolvedValue({ body: { hits: { hits: [] } } }),
bulk: jest.fn().mockResolvedValue({ body: { errors: false } }),
ping: jest.fn().mockResolvedValue(true)
};
// Set environment variables for services
process.env.MONGODB_URI = mongoUri;
process.env.REDIS_HOST = 'localhost';
process.env.NODE_ENV = 'test';
process.env.JWT_SECRET = 'test-jwt-secret-key-for-testing-only';
process.env.ENCRYPTION_KEY = 'test-encryption-key-32-chars-long';
console.log('Test environment setup complete');
}
async setupPostgresSchema() {
const schemas = [
// Users table
`CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
// API Keys table
`CREATE TABLE IF NOT EXISTS api_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES users(id),
key_hash VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255),
permissions JSONB DEFAULT '[]',
last_used_at TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP
)`,
// Telegram Accounts table
`CREATE TABLE IF NOT EXISTS telegram_accounts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
phone_number VARCHAR(20) UNIQUE NOT NULL,
api_id VARCHAR(255),
api_hash VARCHAR(255),
session_data TEXT,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`
];
for (const schema of schemas) {
await this.pgPool.query(schema);
}
}
async startService(name, app, port) {
return new Promise((resolve) => {
const server = app.listen(port, () => {
console.log(`Test ${name} service started on port ${port}`);
this.servers.set(name, server);
resolve(server);
});
});
}
async cleanup() {
console.log('Cleaning up test environment...');
// Close all servers
for (const [name, server] of this.servers) {
await new Promise((resolve) => {
server.close(resolve);
});
console.log(`Closed ${name} server`);
}
// Cleanup databases
if (mongoose.connection.readyState === 1) {
await mongoose.disconnect();
}
if (this.mongoServer) {
await this.mongoServer.stop();
}
if (this.pgPool) {
await this.pgPool.end();
}
if (this.rabbitConnection) {
await this.rabbitChannel.close();
await this.rabbitConnection.close();
}
if (this.redisClient) {
this.redisClient.disconnect();
}
console.log('Test environment cleanup complete');
}
// Helper methods for tests
async createTestUser(userData = {}) {
const defaultData = {
username: `testuser_${Date.now()}`,
email: `test_${Date.now()}@example.com`,
password: 'TestPassword123!'
};
const user = { ...defaultData, ...userData };
// Hash password
const bcrypt = require('bcryptjs');
const passwordHash = await bcrypt.hash(user.password, 10);
// Insert into database
const result = await this.pgPool.query(
'INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING *',
[user.username, user.email, passwordHash]
);
return {
...result.rows[0],
password: user.password // Return plain password for testing
};
}
async createTestApiKey(userId) {
const crypto = require('crypto');
const apiKey = `test_${crypto.randomBytes(16).toString('hex')}`;
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
const result = await this.pgPool.query(
'INSERT INTO api_keys (user_id, key_hash, name, permissions) VALUES ($1, $2, $3, $4) RETURNING *',
[userId, keyHash, 'Test API Key', JSON.stringify(['read', 'write'])]
);
return {
...result.rows[0],
apiKey // Return plain key for testing
};
}
async createTestTelegramAccount(data = {}) {
const defaultData = {
phone_number: `+1234567${Date.now().toString().slice(-4)}`,
api_id: '12345',
api_hash: 'test_api_hash',
session_data: 'test_session_data'
};
const account = { ...defaultData, ...data };
const result = await this.pgPool.query(
'INSERT INTO telegram_accounts (phone_number, api_id, api_hash, session_data) VALUES ($1, $2, $3, $4) RETURNING *',
[account.phone_number, account.api_id, account.api_hash, account.session_data]
);
return result.rows[0];
}
getRedisClient() {
return this.redisClient;
}
getMongoConnection() {
return mongoose.connection;
}
getPostgresPool() {
return this.pgPool;
}
getRabbitChannel() {
return this.rabbitChannel;
}
getElasticsearchClient() {
return this.esClient;
}
}
module.exports = TestEnvironment;