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,254 @@
import request from 'supertest';
import app from '../../../services/api-gateway/src/app.js';
import { connectDatabase, closeDatabase, clearDatabase } from '../../helpers/database.js';
import { createUser } from '../../helpers/factories.js';
import bcrypt from 'bcryptjs';
describe('Auth API Integration Tests', () => {
beforeAll(async () => {
await connectDatabase();
});
afterEach(async () => {
await clearDatabase();
});
afterAll(async () => {
await closeDatabase();
});
describe('POST /api/v1/auth/login', () => {
it('should login with valid credentials', async () => {
// Create a user in the database
const password = 'Test123!@#';
const hashedPassword = await bcrypt.hash(password, 10);
const userData = createUser({ password: hashedPassword });
// Save user to database (you'll need to import and use your User model)
// const user = await User.create(userData);
const response = await request(app)
.post('/api/v1/auth/login')
.send({
username: userData.username,
password: password
});
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('accessToken');
expect(response.body.data).toHaveProperty('refreshToken');
expect(response.body.data.user).toHaveProperty('id');
expect(response.body.data.user.username).toBe(userData.username);
});
it('should fail with invalid credentials', async () => {
const response = await request(app)
.post('/api/v1/auth/login')
.send({
username: 'nonexistent',
password: 'wrongpassword'
});
expect(response.status).toBe(401);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Invalid credentials');
});
it('should fail with missing credentials', async () => {
const response = await request(app)
.post('/api/v1/auth/login')
.send({
username: 'testuser'
// missing password
});
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toContain('required');
});
it('should handle rate limiting', async () => {
// Make multiple requests to trigger rate limit
const requests = Array(11).fill(null).map(() =>
request(app)
.post('/api/v1/auth/login')
.send({
username: 'testuser',
password: 'password'
})
);
const responses = await Promise.all(requests);
const rateLimited = responses.some(res => res.status === 429);
expect(rateLimited).toBe(true);
});
});
describe('POST /api/v1/auth/register', () => {
it('should register new user', async () => {
const userData = {
username: 'newuser',
email: 'newuser@example.com',
password: 'SecurePass123!',
fullName: 'New User'
};
const response = await request(app)
.post('/api/v1/auth/register')
.send(userData);
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.message).toBe('Registration successful');
});
it('should fail with duplicate username', async () => {
const userData = {
username: 'existinguser',
email: 'test@example.com',
password: 'SecurePass123!'
};
// First registration
await request(app)
.post('/api/v1/auth/register')
.send(userData);
// Duplicate registration
const response = await request(app)
.post('/api/v1/auth/register')
.send(userData);
expect(response.status).toBe(409);
expect(response.body.success).toBe(false);
expect(response.body.error).toContain('already exists');
});
it('should validate email format', async () => {
const response = await request(app)
.post('/api/v1/auth/register')
.send({
username: 'testuser',
email: 'invalid-email',
password: 'SecurePass123!'
});
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toContain('email');
});
});
describe('POST /api/v1/auth/refresh', () => {
let accessToken, refreshToken;
beforeEach(async () => {
// Login to get tokens
const loginResponse = await request(app)
.post('/api/v1/auth/login')
.send({
username: 'admin',
password: 'password123'
});
accessToken = loginResponse.body.data.accessToken;
refreshToken = loginResponse.body.data.refreshToken;
});
it('should refresh access token', async () => {
const response = await request(app)
.post('/api/v1/auth/refresh')
.send({
refreshToken: refreshToken
});
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('accessToken');
expect(response.body.data.accessToken).not.toBe(accessToken);
});
it('should fail with invalid refresh token', async () => {
const response = await request(app)
.post('/api/v1/auth/refresh')
.send({
refreshToken: 'invalid-refresh-token'
});
expect(response.status).toBe(401);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Invalid refresh token');
});
});
describe('GET /api/v1/auth/me', () => {
let accessToken;
beforeEach(async () => {
// Login to get token
const loginResponse = await request(app)
.post('/api/v1/auth/login')
.send({
username: 'admin',
password: 'password123'
});
accessToken = loginResponse.body.data.accessToken;
});
it('should get current user info', async () => {
const response = await request(app)
.get('/api/v1/auth/me')
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty('id');
expect(response.body.data).toHaveProperty('role');
});
it('should fail without token', async () => {
const response = await request(app)
.get('/api/v1/auth/me');
expect(response.status).toBe(401);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('No token provided');
});
});
describe('POST /api/v1/auth/logout', () => {
let accessToken;
beforeEach(async () => {
// Login to get token
const loginResponse = await request(app)
.post('/api/v1/auth/login')
.send({
username: 'admin',
password: 'password123'
});
accessToken = loginResponse.body.data.accessToken;
});
it('should logout successfully', async () => {
const response = await request(app)
.post('/api/v1/auth/logout')
.set('Authorization', `Bearer ${accessToken}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.message).toBe('Logged out successfully');
// Verify token is blacklisted
const meResponse = await request(app)
.get('/api/v1/auth/me')
.set('Authorization', `Bearer ${accessToken}`);
expect(meResponse.status).toBe(401);
});
});
});

View File

@@ -0,0 +1,370 @@
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');
});
});
});