feat(gateway): include production Telegram user gateway
Version the NestJS TDLib gateway with Docker deployment, migrations, automated tests and platform integration documentation while excluding all account sessions, credentials, runtime databases and backups.
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* Full API endpoint routing & validation test
|
||||
* Tests every route's registration, validation, response format, and error handling.
|
||||
* Uses mocked services - no DB/Redis required.
|
||||
*/
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import {
|
||||
INestApplication, Controller, Get, Post, Put, Delete,
|
||||
Body, Param, Query, UseGuards, SetMetadata, ValidationPipe,
|
||||
} from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
|
||||
import { AllExceptionsFilter } from '../src/common/filters/all-exceptions.filter';
|
||||
import { ResponseInterceptor } from '../src/common/interceptors/response.interceptor';
|
||||
import { RequestIdInterceptor } from '../src/common/interceptors/request-id.interceptor';
|
||||
import { TenantGuard } from '../src/common/guards/tenant.guard';
|
||||
|
||||
// ============ Build controllers directly (no DB) ============
|
||||
|
||||
// For testing, we build a simple module with all controllers
|
||||
// and mock their services. This validates routing, validation, auth guards.
|
||||
|
||||
function Public() { return SetMetadata('isPublic', true); }
|
||||
|
||||
// --- Mock Services ---
|
||||
const makeMock = (methods: string[]) => {
|
||||
const mock: any = {};
|
||||
for (const m of methods) mock[m] = jest.fn().mockResolvedValue({ ok: true });
|
||||
return mock;
|
||||
};
|
||||
|
||||
const mockTenantsService = makeMock(['create','findAll','findById','update','remove']);
|
||||
const mockAuthService = makeMock(['register','login','validateUserById']);
|
||||
const mockTgAccountsService = makeMock(['create','findAll','findById','update','remove']);
|
||||
const mockTgAuthService = makeMock(['setPhoneNumber','submitCode','submitPassword','resendCode','logout','getAuthState']);
|
||||
const mockTgProxiesService = makeMock(['create','findAll','findById','update','remove','bindToAccount','unbindFromAccount']);
|
||||
const mockTgChatsService = makeMock(['getChats','getChat','getChatHistory','searchChats','syncChatsFromTdlib','openChat','closeChat']);
|
||||
const mockTgContactsService = makeMock(['getContacts','importContacts','removeContacts','searchUser','syncedContacts']);
|
||||
const mockTgDialogsService = makeMock(['openByPhone','openByUsername','openByUserId','getMe']);
|
||||
const mockTgGroupsService = makeMock(['getGroup','createGroup','getMembers','getGroupInviteLink']);
|
||||
const mockTgChannelsService = makeMock(['getChannel','createChannel','joinChannel','getChannelMembers']);
|
||||
const mockTgMessagesService = makeMock(['sendMessage','sendMessageDirect','editMessage','forwardMessage','deleteMessage','searchMessages','getSendJobStatus']);
|
||||
const mockTgFilesService = makeMock(['getFiles','getFileById','downloadFile','uploadFile','getFile']);
|
||||
const mockTgWebhooksService = makeMock(['create','findAll','findById','update','remove','getDeliveries']);
|
||||
const mockAdminService = { getHealth: jest.fn().mockReturnValue({ status:'ok' }), getMetrics: jest.fn().mockReturnValue({}), getClientStatus: jest.fn().mockReturnValue({}) };
|
||||
|
||||
// --- Import real controllers ---
|
||||
import { TenantsController } from '../src/modules/tenants/tenants.controller';
|
||||
import { AuthController } from '../src/modules/auth/auth.controller';
|
||||
import { TgAccountsController } from '../src/modules/tg-accounts/tg-accounts.controller';
|
||||
import { TgAuthController } from '../src/modules/tg-auth/tg-auth.controller';
|
||||
import { TgProxiesController } from '../src/modules/tg-proxies/tg-proxies.controller';
|
||||
import { TgChatsController } from '../src/modules/tg-chats/tg-chats.controller';
|
||||
import { TgContactsController } from '../src/modules/tg-contacts/tg-contacts.controller';
|
||||
import { TgDialogsController } from '../src/modules/tg-dialogs/tg-dialogs.controller';
|
||||
import { TgGroupsController } from '../src/modules/tg-groups/tg-groups.controller';
|
||||
import { TgChannelsController } from '../src/modules/tg-channels/tg-channels.controller';
|
||||
import { TgMessagesController } from '../src/modules/tg-messages/tg-messages.controller';
|
||||
import { TgFilesController } from '../src/modules/tg-files/tg-files.controller';
|
||||
import { TgWebhooksController } from '../src/modules/tg-webhooks/tg-webhooks.controller';
|
||||
import { AdminController } from '../src/modules/admin/admin.controller';
|
||||
|
||||
// --- Import real services (as tokens) ---
|
||||
import { AdminService } from '../src/modules/admin/admin.service';
|
||||
import { TenantsService } from '../src/modules/tenants/tenants.service';
|
||||
import { AuthService } from '../src/modules/auth/auth.service';
|
||||
import { TgAccountsService } from '../src/modules/tg-accounts/tg-accounts.service';
|
||||
import { TgAuthService } from '../src/modules/tg-auth/tg-auth.service';
|
||||
import { TgProxiesService } from '../src/modules/tg-proxies/tg-proxies.service';
|
||||
import { TgChatsService } from '../src/modules/tg-chats/tg-chats.service';
|
||||
import { TgContactsService } from '../src/modules/tg-contacts/tg-contacts.service';
|
||||
import { TgDialogsService } from '../src/modules/tg-dialogs/tg-dialogs.service';
|
||||
import { TgGroupsService } from '../src/modules/tg-groups/tg-groups.service';
|
||||
import { TgChannelsService } from '../src/modules/tg-channels/tg-channels.service';
|
||||
import { TgMessagesService } from '../src/modules/tg-messages/tg-messages.service';
|
||||
import { TgFilesService } from '../src/modules/tg-files/tg-files.service';
|
||||
import { TgWebhooksService } from '../src/modules/tg-webhooks/tg-webhooks.service';
|
||||
|
||||
@Controller()
|
||||
class FakeController {}
|
||||
|
||||
// Mock AuthGuard: always passes, sets user
|
||||
class MockJwtAuthGuard { canActivate(ctx: any) {
|
||||
const req = ctx.switchToHttp().getRequest();
|
||||
req.user = { id: 'user-1', email: 'test@test.com', tenantId: 'tenant-1', role: 'admin' };
|
||||
return true;
|
||||
}}
|
||||
|
||||
// Mock TenantGuard: always passes
|
||||
class MockTenantGuard { canActivate(ctx: any) {
|
||||
ctx.switchToHttp().getRequest().tenantId = 'tenant-1';
|
||||
return true;
|
||||
}}
|
||||
|
||||
describe('=== Telegram User Gateway - Full API Endpoint Test ===', () => {
|
||||
let app: INestApplication;
|
||||
const accountId = 'acc-test-1';
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [
|
||||
AdminController,
|
||||
TenantsController,
|
||||
AuthController,
|
||||
TgAccountsController,
|
||||
TgAuthController,
|
||||
TgProxiesController,
|
||||
TgChatsController,
|
||||
TgContactsController,
|
||||
TgDialogsController,
|
||||
TgGroupsController,
|
||||
TgChannelsController,
|
||||
TgMessagesController,
|
||||
TgFilesController,
|
||||
TgWebhooksController,
|
||||
],
|
||||
providers: [
|
||||
{ provide: AdminService, useValue: mockAdminService },
|
||||
{ provide: TenantsService, useValue: mockTenantsService },
|
||||
{ provide: AuthService, useValue: mockAuthService },
|
||||
{ provide: TgAccountsService, useValue: mockTgAccountsService },
|
||||
{ provide: TgAuthService, useValue: mockTgAuthService },
|
||||
{ provide: TgProxiesService, useValue: mockTgProxiesService },
|
||||
{ provide: TgChatsService, useValue: mockTgChatsService },
|
||||
{ provide: TgContactsService, useValue: mockTgContactsService },
|
||||
{ provide: TgDialogsService, useValue: mockTgDialogsService },
|
||||
{ provide: TgGroupsService, useValue: mockTgGroupsService },
|
||||
{ provide: TgChannelsService, useValue: mockTgChannelsService },
|
||||
{ provide: TgMessagesService, useValue: mockTgMessagesService },
|
||||
{ provide: TgFilesService, useValue: mockTgFilesService },
|
||||
{ provide: TgWebhooksService, useValue: mockTgWebhooksService },
|
||||
],
|
||||
})
|
||||
.overrideGuard(TenantGuard).useClass(MockTenantGuard)
|
||||
.overrideGuard(require('../src/common/guards/jwt-auth.guard').JwtAuthGuard).useClass(MockJwtAuthGuard)
|
||||
.compile();
|
||||
|
||||
app = module.createNestApplication();
|
||||
app.setGlobalPrefix('api');
|
||||
app.useGlobalFilters(new AllExceptionsFilter());
|
||||
app.useGlobalInterceptors(new RequestIdInterceptor(), new ResponseInterceptor());
|
||||
app.useGlobalPipes(new ValidationPipe());
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => { await app?.close(); });
|
||||
|
||||
// Helper to do authenticated requests
|
||||
const req = (method: string, path: string, body?: any) => {
|
||||
const s = request(app.getHttpServer());
|
||||
let r: request.Test;
|
||||
switch (method) {
|
||||
case 'GET': r = s.get(path); break;
|
||||
case 'POST': r = s.post(path); break;
|
||||
case 'PUT': r = s.put(path); break;
|
||||
case 'DELETE': r = s.delete(path); break;
|
||||
default: r = s.get(path);
|
||||
}
|
||||
r.set('Authorization', 'Bearer test-token');
|
||||
r.set('x-tenant-id', 'tenant-1');
|
||||
if (body && method !== 'GET') r.send(body);
|
||||
return r;
|
||||
};
|
||||
|
||||
// =====================================================
|
||||
// MODULE 1: Admin (3 endpoints)
|
||||
// =====================================================
|
||||
describe('[1] Admin Module', () => {
|
||||
it('GET /api/admin/health (public)', () =>
|
||||
request(app.getHttpServer()).get('/api/admin/health').expect(200));
|
||||
|
||||
it('GET /api/admin/metrics', () =>
|
||||
req('GET', '/api/admin/metrics').expect(200));
|
||||
|
||||
it('GET /api/admin/status', () =>
|
||||
req('GET', '/api/admin/status').expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 2: Tenants (5 endpoints)
|
||||
// =====================================================
|
||||
describe('[2] Tenants Module', () => {
|
||||
it('POST /api/tenants Create (public, validates)', async () => {
|
||||
await request(app.getHttpServer()).post('/api/tenants').send({}).expect(400); // missing name
|
||||
await request(app.getHttpServer()).post('/api/tenants').send({ name: 't1' }).expect(201);
|
||||
});
|
||||
it('GET /api/tenants List', () => req('GET','/api/tenants').expect(200));
|
||||
it('GET /api/tenants/:id Get by ID', () => req('GET','/api/tenants/t-1').expect(200));
|
||||
it('PUT /api/tenants/:id Update', () => req('PUT','/api/tenants/t-1', { name:'x' }).expect(200));
|
||||
it('DELETE /api/tenants/:id Remove', () => req('DELETE','/api/tenants/t-1').expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 3: Auth (2 endpoints)
|
||||
// =====================================================
|
||||
describe('[3] Auth Module', () => {
|
||||
it('POST /api/auth/register (public, validates)', async () => {
|
||||
mockAuthService.register.mockResolvedValue({ user:{}, token:'tok' });
|
||||
await request(app.getHttpServer()).post('/api/auth/register').send({}).expect(400); // no fields
|
||||
await request(app.getHttpServer()).post('/api/auth/register')
|
||||
.send({ email:'a@b.com', password:'12345678', tenantId:'t1' }).expect(201);
|
||||
});
|
||||
it('POST /api/auth/login (public, validates)', async () => {
|
||||
mockAuthService.login.mockResolvedValue({ user:{}, token:'tok' });
|
||||
await request(app.getHttpServer()).post('/api/auth/login').send({}).expect(400); // no fields
|
||||
await request(app.getHttpServer()).post('/api/auth/login')
|
||||
.send({ email:'a@b.com', password:'12345678', tenantId:'t1' }).expect(201);
|
||||
});
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 4: TG Accounts (5 endpoints)
|
||||
// =====================================================
|
||||
describe('[4] TG Accounts Module', () => {
|
||||
it('POST /api/tg-accounts Create (validates)', async () => {
|
||||
mockTgAccountsService.create.mockResolvedValue({ id:accountId, phoneNumber:'+86' });
|
||||
await req('POST','/api/tg-accounts', {}).expect(400); // no phoneNumber
|
||||
await req('POST','/api/tg-accounts', { phoneNumber:'+8613800138000' }).expect(201);
|
||||
});
|
||||
it('GET /api/tg-accounts List', () => req('GET','/api/tg-accounts').expect(200));
|
||||
it('GET /api/tg-accounts/:id Get', () => req('GET',`/api/tg-accounts/${accountId}`).expect(200));
|
||||
it('PUT /api/tg-accounts/:id Update', () => req('PUT',`/api/tg-accounts/${accountId}`, { isActive:true }).expect(200));
|
||||
it('DELETE /api/tg-accounts/:id Remove', () => req('DELETE',`/api/tg-accounts/${accountId}`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 5: TG Auth (6 endpoints)
|
||||
// =====================================================
|
||||
describe('[5] TG Auth Module (Login State Machine)', () => {
|
||||
it('POST /tg-accounts/:id/auth/phone Set phone', () =>
|
||||
req('POST',`/api/tg-accounts/${accountId}/auth/phone`, { phoneNumber:'+86' }).expect(201));
|
||||
it('POST /tg-accounts/:id/auth/code Submit code', () =>
|
||||
req('POST',`/api/tg-accounts/${accountId}/auth/code`, { code:'12345' }).expect(201));
|
||||
it('POST /tg-accounts/:id/auth/password Submit 2FA password', () =>
|
||||
req('POST',`/api/tg-accounts/${accountId}/auth/password`, { password:'pass' }).expect(201));
|
||||
it('POST /tg-accounts/:id/auth/resend Resend code', () =>
|
||||
req('POST',`/api/tg-accounts/${accountId}/auth/resend`).expect(201));
|
||||
it('POST /tg-accounts/:id/auth/state Get auth state', () =>
|
||||
req('POST',`/api/tg-accounts/${accountId}/auth/state`).expect(201));
|
||||
it('POST /tg-accounts/:id/auth/logout Logout', () =>
|
||||
req('POST',`/api/tg-accounts/${accountId}/auth/logout`).expect(201));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 6: TG Proxies (7 endpoints)
|
||||
// =====================================================
|
||||
describe('[6] TG Proxies Module', () => {
|
||||
const proxyId = 'proxy-test-1';
|
||||
it('POST /api/tg-proxies Create (validates)', async () => {
|
||||
await req('POST','/api/tg-proxies', {}).expect(400);
|
||||
mockTgProxiesService.create.mockResolvedValue({ id:proxyId });
|
||||
await req('POST','/api/tg-proxies', { name:'p', type:'socks5', host:'1.1.1.1', port:1080 }).expect(201);
|
||||
});
|
||||
it('GET /api/tg-proxies List', () => req('GET','/api/tg-proxies').expect(200));
|
||||
it('GET /api/tg-proxies/:id Get', () => req('GET',`/api/tg-proxies/${proxyId}`).expect(200));
|
||||
it('PUT /api/tg-proxies/:id Update', () => req('PUT',`/api/tg-proxies/${proxyId}`, { name:'x' }).expect(200));
|
||||
it('POST /api/tg-accounts/:id/proxy Bind', () => req('POST',`/api/tg-accounts/${accountId}/proxy`, { proxyId }).expect(201));
|
||||
it('DELETE /api/tg-accounts/:id/proxy/:bid Unbind', () => req('DELETE',`/api/tg-accounts/${accountId}/proxy/${proxyId}`).expect(200));
|
||||
it('DELETE /api/tg-proxies/:id Delete', () => req('DELETE',`/api/tg-proxies/${proxyId}`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 7: TG Chats (7 endpoints)
|
||||
// =====================================================
|
||||
describe('[7] TG Chats Module', () => {
|
||||
it('GET /tg-accounts/:id/chats List', () => req('GET',`/api/tg-accounts/${accountId}/chats`).expect(200));
|
||||
it('GET /tg-accounts/:id/chats/sync Sync', () => req('GET',`/api/tg-accounts/${accountId}/chats/sync`).expect(200));
|
||||
it('GET /tg-accounts/:id/chats/search Search', () => req('GET',`/api/tg-accounts/${accountId}/chats/search?q=hi`).expect(200));
|
||||
it('GET /tg-accounts/:id/chats/:chatId Detail', () => req('GET',`/api/tg-accounts/${accountId}/chats/999`).expect(200));
|
||||
it('GET /tg-accounts/:id/chats/:chatId/history History', () => req('GET',`/api/tg-accounts/${accountId}/chats/999/history`).expect(200));
|
||||
it('POST /tg-accounts/:id/chats/:chatId/open Open', () => req('POST',`/api/tg-accounts/${accountId}/chats/999/open`).expect(201));
|
||||
it('POST /tg-accounts/:id/chats/:chatId/close Close', () => req('POST',`/api/tg-accounts/${accountId}/chats/999/close`).expect(201));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 8: TG Contacts (5 endpoints)
|
||||
// =====================================================
|
||||
describe('[8] TG Contacts Module', () => {
|
||||
it('GET /tg-accounts/:id/contacts Get contacts', () => req('GET',`/api/tg-accounts/${accountId}/contacts`).expect(200));
|
||||
it('GET /tg-accounts/:id/contacts/synced Synced', () => req('GET',`/api/tg-accounts/${accountId}/contacts/synced`).expect(200));
|
||||
it('POST /tg-accounts/:id/contacts/import Import', () => req('POST',`/api/tg-accounts/${accountId}/contacts/import`, { contacts:[{phoneNumber:'+86'}] }).expect(201));
|
||||
it('DELETE /tg-accounts/:id/contacts/remove Remove', () => req('DELETE',`/api/tg-accounts/${accountId}/contacts/remove`, { userIds:[1] }).expect(200));
|
||||
it('GET /tg-accounts/:id/contacts/search Search', () => req('GET',`/api/tg-accounts/${accountId}/contacts/search?q=+86`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 9: TG Dialogs (4 endpoints)
|
||||
// =====================================================
|
||||
describe('[9] TG Dialogs Module', () => {
|
||||
it('GET /tg-accounts/:id/dialogs/me Get me', () => req('GET',`/api/tg-accounts/${accountId}/dialogs/me`).expect(200));
|
||||
it('POST /tg-accounts/:id/dialogs/open-by-phone By phone', () => req('POST',`/api/tg-accounts/${accountId}/dialogs/open-by-phone`, { phoneNumber:'+86' }).expect(201));
|
||||
it('POST /tg-accounts/:id/dialogs/open-by-username By username', () => req('POST',`/api/tg-accounts/${accountId}/dialogs/open-by-username`, { username:'test' }).expect(201));
|
||||
it('POST /tg-accounts/:id/dialogs/open-by-user/:id By user ID', () => req('POST',`/api/tg-accounts/${accountId}/dialogs/open-by-user/456`).expect(201));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 10: TG Groups (4 endpoints)
|
||||
// =====================================================
|
||||
describe('[10] TG Groups Module', () => {
|
||||
it('POST /tg-accounts/:id/groups Create', () => req('POST',`/api/tg-accounts/${accountId}/groups`, { title:'G' }).expect(201));
|
||||
it('GET /tg-accounts/:id/groups/:chatId Get', () => req('GET',`/api/tg-accounts/${accountId}/groups/111`).expect(200));
|
||||
it('GET /tg-accounts/:id/groups/:chatId/members Members', () => req('GET',`/api/tg-accounts/${accountId}/groups/111/members`).expect(200));
|
||||
it('GET /tg-accounts/:id/groups/:chatId/invite-link Invite', () => req('GET',`/api/tg-accounts/${accountId}/groups/111/invite-link`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 11: TG Channels (4 endpoints)
|
||||
// =====================================================
|
||||
describe('[11] TG Channels Module', () => {
|
||||
it('POST /tg-accounts/:id/channels Create', () => req('POST',`/api/tg-accounts/${accountId}/channels`, { title:'C' }).expect(201));
|
||||
it('GET /tg-accounts/:id/channels/:chatId Get', () => req('GET',`/api/tg-accounts/${accountId}/channels/222`).expect(200));
|
||||
it('POST /tg-accounts/:id/channels/join Join', () => req('POST',`/api/tg-accounts/${accountId}/channels/join`, { inviteLink:'https://t.me/a' }).expect(201));
|
||||
it('GET /tg-accounts/:id/channels/:chatId/members Members', () => req('GET',`/api/tg-accounts/${accountId}/channels/222/members`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 12: TG Messages (7 endpoints)
|
||||
// =====================================================
|
||||
describe('[12] TG Messages Module', () => {
|
||||
it('POST /tg-accounts/:id/messages/send Send (queued)', () => req('POST',`/api/tg-accounts/${accountId}/messages/send`, { chatId:123, text:'hi' }).expect(201));
|
||||
it('POST /tg-accounts/:id/messages/send-direct Send (direct)', () => req('POST',`/api/tg-accounts/${accountId}/messages/send-direct`, { chatId:123, text:'hi' }).expect(201));
|
||||
it('POST /tg-accounts/:id/messages/edit Edit', () => req('POST',`/api/tg-accounts/${accountId}/messages/edit`, { chatId:123, messageId:456 }).expect(201));
|
||||
it('POST /tg-accounts/:id/messages/forward Forward', () => req('POST',`/api/tg-accounts/${accountId}/messages/forward`, { fromChatId:1, messageId:2, toChatId:3 }).expect(201));
|
||||
it('POST /tg-accounts/:id/messages/delete Delete', () => req('POST',`/api/tg-accounts/${accountId}/messages/delete`, { chatId:123, messageIds:[456] }).expect(201));
|
||||
it('POST /tg-accounts/:id/messages/search Search', () => req('POST',`/api/tg-accounts/${accountId}/messages/search`, { query:'hello' }).expect(201));
|
||||
it('GET /tg-accounts/:id/messages/jobs/:jid Job status', () => req('GET',`/api/tg-accounts/${accountId}/messages/jobs/job-1`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 13: TG Files (4 endpoints)
|
||||
// =====================================================
|
||||
describe('[13] TG Files Module', () => {
|
||||
it('GET /tg-accounts/:id/files List', () => req('GET',`/api/tg-accounts/${accountId}/files`).expect(200));
|
||||
it('GET /tg-accounts/:id/files/:fid Get', () => req('GET',`/api/tg-accounts/${accountId}/files/file-1`).expect(200));
|
||||
it('GET /tg-accounts/:id/files/tdlib/:fid TDLib info', () => req('GET',`/api/tg-accounts/${accountId}/files/tdlib/999`).expect(200));
|
||||
it('POST /tg-accounts/:id/files/download/:fid Download', () => req('POST',`/api/tg-accounts/${accountId}/files/download/999`).expect(201));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// MODULE 14: TG Webhooks (6 endpoints)
|
||||
// =====================================================
|
||||
describe('[14] TG Webhooks Module', () => {
|
||||
const whId = 'wh-test-1';
|
||||
it('POST /tg-accounts/:id/webhooks Create (validates)', async () => {
|
||||
await req('POST',`/api/tg-accounts/${accountId}/webhooks`, {}).expect(400);
|
||||
mockTgWebhooksService.create.mockResolvedValue({ id:whId });
|
||||
await req('POST',`/api/tg-accounts/${accountId}/webhooks`, { url:'https://example.com/hook', events:['message.received'] }).expect(201);
|
||||
});
|
||||
it('GET /tg-accounts/:id/webhooks List', () => req('GET',`/api/tg-accounts/${accountId}/webhooks`).expect(200));
|
||||
it('GET /tg-accounts/:id/webhooks/:id Get', () => req('GET',`/api/tg-accounts/${accountId}/webhooks/${whId}`).expect(200));
|
||||
it('PUT /tg-accounts/:id/webhooks/:id Update', () => req('PUT',`/api/tg-accounts/${accountId}/webhooks/${whId}`, { isActive:false }).expect(200));
|
||||
it('GET /tg-accounts/:id/webhooks/:id/deliveries Deliveries', () => req('GET',`/api/tg-accounts/${accountId}/webhooks/${whId}/deliveries`).expect(200));
|
||||
it('DELETE /tg-accounts/:id/webhooks/:id Delete', () => req('DELETE',`/api/tg-accounts/${accountId}/webhooks/${whId}`).expect(200));
|
||||
});
|
||||
|
||||
// =====================================================
|
||||
// CROSS-CUTTING: Response format, error handling
|
||||
// =====================================================
|
||||
describe('[Format] Response Wrapper & Error Handling', () => {
|
||||
it('all responses have { success, data, timestamp }', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/api/admin/health');
|
||||
expect(res.body).toHaveProperty('success', true);
|
||||
expect(res.body).toHaveProperty('data');
|
||||
expect(res.body).toHaveProperty('timestamp');
|
||||
});
|
||||
|
||||
it('error responses have { success:false, errorCode, message }', async () => {
|
||||
const res = await request(app.getHttpServer())
|
||||
.post('/api/auth/register')
|
||||
.send({ email: 'bad' });
|
||||
expect(res.body.success).toBe(false);
|
||||
expect(res.body).toHaveProperty('errorCode');
|
||||
expect(res.body).toHaveProperty('message');
|
||||
});
|
||||
|
||||
it('unknown routes return 404', async () => {
|
||||
await request(app.getHttpServer()).get('/api/does-not-exist').expect(404);
|
||||
});
|
||||
|
||||
it('x-request-id is set in response headers', async () => {
|
||||
const res = await request(app.getHttpServer()).get('/api/admin/health');
|
||||
expect(res.headers['x-request-id']).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user