الملفات
back_end_oudelaa/test/app.e2e-spec.ts

410 أسطر
14 KiB
TypeScript

import { INestApplication, ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
Object.assign(process.env, {
NODE_ENV: 'test',
EMAIL_ENABLED: 'false',
REDIS_ENABLED: 'false',
REDIS_SOCKET_ADAPTER_ENABLED: 'false',
QUEUE_ENABLED: 'false',
REQUEST_LOGGING_ENABLED: 'false',
FEED_CACHE_ENABLED: 'false',
BCRYPT_SALT_ROUNDS: '8',
PUBLIC_BASE_URL: process.env.PUBLIC_BASE_URL ?? 'http://127.0.0.1:4000',
STORAGE_PUBLIC_BASE_URL:
process.env.STORAGE_PUBLIC_BASE_URL ?? process.env.PUBLIC_BASE_URL ?? 'http://127.0.0.1:4000',
MONGODB_URI: process.env.MONGODB_URI ?? 'mongodb://127.0.0.1:27017/oudelaa-e2e',
JWT_ACCESS_SECRET: process.env.JWT_ACCESS_SECRET ?? 'test-access-secret-123456',
JWT_REFRESH_SECRET: process.env.JWT_REFRESH_SECRET ?? 'test-refresh-secret-123456',
SUPERADMIN_EMAIL: process.env.SUPERADMIN_EMAIL ?? 'superadmin@example.com',
SUPERADMIN_PASSWORD: process.env.SUPERADMIN_PASSWORD ?? 'StrongPass123!',
SUPERADMIN_ACCESS_SECRET:
process.env.SUPERADMIN_ACCESS_SECRET ?? 'test-superadmin-access-123456',
SUPERADMIN_REFRESH_SECRET:
process.env.SUPERADMIN_REFRESH_SECRET ?? 'test-superadmin-refresh-123456',
});
jest.setTimeout(120000);
const tinyPngBuffer = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9WlH0i8AAAAASUVORK5CYII=',
'base64',
);
describe('Oudelaa smoke (e2e)', () => {
let app: INestApplication;
const ts = Date.now();
const primary = {
email: `smoke_primary_${ts}@example.com`,
password: 'StrongPass123!',
accessToken: '',
userId: '',
username: '',
};
const secondary = {
email: `smoke_secondary_${ts}@example.com`,
password: 'StrongPass123!',
accessToken: '',
userId: '',
username: '',
};
const tertiary = {
email: `smoke_tertiary_${ts}@example.com`,
password: 'StrongPass123!',
accessToken: '',
userId: '',
username: '',
};
const superAdmin = {
accessToken: '',
refreshToken: '',
secondAccessToken: '',
secondRefreshToken: '',
};
let postId = '';
let editableCommentId = '';
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
const configService = app.get(ConfigService);
app.setGlobalPrefix(configService.get<string>('globalPrefix', 'api/v1'));
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: { enableImplicitConversion: true },
}),
);
await app.init();
});
afterAll(async () => {
if (app) {
await app.close();
}
});
const registerAndVerify = async (user: {
email: string;
password: string;
accessToken: string;
userId: string;
username: string;
}) => {
const registerResponse = await request(app.getHttpServer())
.post('/api/v1/auth/register-basic')
.send({
email: user.email,
password: user.password,
confirmPassword: user.password,
})
.expect(201);
const verifyResponse = await request(app.getHttpServer())
.post('/api/v1/auth/verify-email')
.send({
email: user.email,
code: registerResponse.body.debugCode,
})
.expect(200);
user.accessToken = verifyResponse.body.accessToken;
user.userId = verifyResponse.body.user._id || verifyResponse.body.user.id;
user.username = verifyResponse.body.user.username;
};
it('/api/v1/health (GET)', () => {
return request(app.getHttpServer()).get('/api/v1/health').expect(200);
});
it('registers and verifies smoke users', async () => {
await registerAndVerify(primary);
await registerAndVerify(secondary);
await registerAndVerify(tertiary);
expect(primary.accessToken).toBeTruthy();
expect(secondary.username).toBeTruthy();
expect(tertiary.userId).toBeTruthy();
});
it('updates profile setup with avatar upload', async () => {
const response = await request(app.getHttpServer())
.patch('/api/v1/users/me/profile-setup')
.set('Authorization', `Bearer ${primary.accessToken}`)
.field('stageName', 'Smoke Artist')
.field('bio', 'Smoke profile bio')
.field('location', 'Riyadh, Saudi Arabia')
.field('latitude', '24.7136')
.field('longitude', '46.6753')
.attach('avatarFile', tinyPngBuffer, { filename: 'avatar.png', contentType: 'image/png' })
.expect(200);
expect(response.body.stageName).toBe('Smoke Artist');
expect(response.body.avatar).toMatch(/^https?:\/\//);
});
it('updates music setup and discovers talents', async () => {
await request(app.getHttpServer())
.patch('/api/v1/users/me/music-setup')
.set('Authorization', `Bearer ${primary.accessToken}`)
.send({
musicRoles: ['instrumentalist', 'composer'],
musicGenres: ['Tarab'],
experienceLevel: 'intermediate',
favoriteInstruments: ['Oud'],
favoriteMaqamat: ['Rast'],
})
.expect(200);
const discoverResponse = await request(app.getHttpServer())
.get('/api/v1/users/discover?musicRole=instrumentalist&hasAvatarOnly=true&limit=8')
.set('Authorization', `Bearer ${secondary.accessToken}`)
.expect(200);
expect(discoverResponse.body.items).toBeInstanceOf(Array);
expect(discoverResponse.body.roleBuckets).toBeInstanceOf(Array);
expect(discoverResponse.body.pagination).toBeTruthy();
const overviewResponse = await request(app.getHttpServer())
.get(`/api/v1/users/${primary.userId}/profile-overview`)
.set('Authorization', `Bearer ${secondary.accessToken}`)
.expect(200);
expect(overviewResponse.body.stats).toBeTruthy();
expect(overviewResponse.body.contentCounts).toBeTruthy();
expect(overviewResponse.body.viewerState).toBeTruthy();
});
it('creates an image post with tag and mention', async () => {
const response = await request(app.getHttpServer())
.post('/api/v1/posts')
.set('Authorization', `Bearer ${primary.accessToken}`)
.field('content', `Smoke image post with @${secondary.username} #smoke`)
.field('visibility', 'public')
.field('taggedUserIds', secondary.userId)
.field('mentionUsernames', secondary.username)
.field('location', 'Riyadh, Saudi Arabia')
.field('latitude', '24.7136')
.field('longitude', '46.6753')
.attach('imageFiles', tinyPngBuffer, { filename: 'post.png', contentType: 'image/png' })
.expect(201);
postId = response.body._id || response.body.id;
expect(postId).toBeTruthy();
expect(response.body.postType).toBe('image');
expect(response.body.imageUrls).toBeInstanceOf(Array);
expect(response.body.imageUrls[0]).toMatch(/^https?:\/\//);
expect(response.body.mentionUsernames).toContain(secondary.username.toLowerCase());
});
it('returns unified pagination in feed', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/feed/me?includeSuggestions=false&limit=10')
.set('Authorization', `Bearer ${primary.accessToken}`)
.expect(200);
expect(response.body.items).toBeInstanceOf(Array);
expect(response.body.pagination).toEqual(
expect.objectContaining({
mode: 'cursor',
limit: 10,
}),
);
});
it('creates mention notification for mentioned post user', async () => {
const response = await request(app.getHttpServer())
.get('/api/v1/notifications')
.set('Authorization', `Bearer ${secondary.accessToken}`)
.expect(200);
const mention = (response.body.items ?? []).find(
(item: any) => item.type === 'mention' && (item.referenceId === postId || item.deepLink === `/posts/${postId}`),
);
expect(response.body.pagination).toBeTruthy();
expect(mention).toBeTruthy();
});
it('creates comment mention notification for a third user', async () => {
await request(app.getHttpServer())
.post('/api/v1/comments')
.set('Authorization', `Bearer ${secondary.accessToken}`)
.send({
postId,
content: `Great take @${tertiary.username}`,
mentionUsernames: [tertiary.username],
})
.expect(201);
const commentsResponse = await request(app.getHttpServer())
.get(`/api/v1/comments/post/${postId}?page=1&limit=10`)
.set('Authorization', `Bearer ${primary.accessToken}`)
.expect(200);
expect(commentsResponse.body.pagination).toEqual(
expect.objectContaining({
mode: 'offset',
limit: 10,
}),
);
const notificationsResponse = await request(app.getHttpServer())
.get('/api/v1/notifications')
.set('Authorization', `Bearer ${tertiary.accessToken}`)
.expect(200);
const mention = (notificationsResponse.body.items ?? []).find(
(item: any) => item.type === 'mention' && item.resourceType === 'comment',
);
expect(mention).toBeTruthy();
});
it('updates own comment content and mention usernames', async () => {
const createResponse = await request(app.getHttpServer())
.post('/api/v1/comments')
.set('Authorization', `Bearer ${secondary.accessToken}`)
.send({
postId,
content: 'Needs edit',
})
.expect(201);
editableCommentId = createResponse.body._id || createResponse.body.id;
const updatedContent = `Edited with @${primary.username}`;
const updateResponse = await request(app.getHttpServer())
.patch(`/api/v1/comments/${editableCommentId}`)
.set('Authorization', `Bearer ${secondary.accessToken}`)
.send({
content: updatedContent,
mentionUsernames: [primary.username],
})
.expect(200);
expect(updateResponse.body.content).toBe(updatedContent);
expect(updateResponse.body.mentionUsernames).toContain(primary.username.toLowerCase());
const commentsResponse = await request(app.getHttpServer())
.get(`/api/v1/comments/post/${postId}?page=1&limit=20`)
.set('Authorization', `Bearer ${primary.accessToken}`)
.expect(200);
const updatedComment = (commentsResponse.body.items ?? []).find(
(item: any) => (item._id || item.id) === editableCommentId,
);
expect(updatedComment?.content).toBe(updatedContent);
const notificationsResponse = await request(app.getHttpServer())
.get('/api/v1/notifications?resourceType=comment')
.set('Authorization', `Bearer ${primary.accessToken}`)
.expect(200);
const mention = (notificationsResponse.body.items ?? []).find(
(item: any) =>
item.type === 'mention' &&
item.resourceType === 'comment' &&
item.previewText === updatedContent,
);
expect(mention).toBeTruthy();
});
it('supports superadmin sessions refresh rotation and notifications access', async () => {
const loginOne = await request(app.getHttpServer())
.post('/api/v1/auth/superadmin/login')
.send({
email: process.env.SUPERADMIN_EMAIL,
password: process.env.SUPERADMIN_PASSWORD,
})
.expect(200);
superAdmin.accessToken = loginOne.body.accessToken;
superAdmin.refreshToken = loginOne.body.refreshToken;
const sessionsAfterFirstLogin = await request(app.getHttpServer())
.get('/api/v1/auth/superadmin/sessions')
.set('Authorization', `Bearer ${superAdmin.accessToken}`)
.expect(200);
const countAfterFirstLogin = (sessionsAfterFirstLogin.body.items ?? []).length;
const loginTwo = await request(app.getHttpServer())
.post('/api/v1/auth/superadmin/login')
.send({
email: process.env.SUPERADMIN_EMAIL,
password: process.env.SUPERADMIN_PASSWORD,
})
.expect(200);
superAdmin.secondAccessToken = loginTwo.body.accessToken;
superAdmin.secondRefreshToken = loginTwo.body.refreshToken;
const initialSessions = await request(app.getHttpServer())
.get('/api/v1/auth/superadmin/sessions')
.set('Authorization', `Bearer ${superAdmin.accessToken}`)
.expect(200);
const initialItems = initialSessions.body.items ?? [];
expect(initialItems).toHaveLength(countAfterFirstLogin + 1);
const recentSessionIds = initialItems
.slice(0, 2)
.map((item: { id?: string }) => item.id)
.filter((id: string | undefined): id is string => Boolean(id));
const refreshResponse = await request(app.getHttpServer())
.post('/api/v1/auth/superadmin/refresh')
.send({ refreshToken: superAdmin.refreshToken })
.expect(200);
superAdmin.accessToken = refreshResponse.body.accessToken;
superAdmin.refreshToken = refreshResponse.body.refreshToken;
const refreshedSessions = await request(app.getHttpServer())
.get('/api/v1/auth/superadmin/sessions')
.set('Authorization', `Bearer ${superAdmin.accessToken}`)
.expect(200);
const refreshedItems = refreshedSessions.body.items ?? [];
expect(refreshedItems).toHaveLength(initialItems.length);
const notificationsResponse = await request(app.getHttpServer())
.get('/api/v1/notifications/superadmin?limit=10')
.set('Authorization', `Bearer ${superAdmin.accessToken}`)
.expect(200);
expect(notificationsResponse.body.items).toBeInstanceOf(Array);
const secondarySession = refreshedItems.find(
(item: { id?: string }) => item.id && recentSessionIds.includes(item.id),
);
expect(secondarySession?.id).toBeTruthy();
await request(app.getHttpServer())
.post(`/api/v1/auth/superadmin/sessions/${secondarySession.id}/revoke`)
.set('Authorization', `Bearer ${superAdmin.accessToken}`)
.expect(201);
const sessionsAfterRevoke = await request(app.getHttpServer())
.get('/api/v1/auth/superadmin/sessions')
.set('Authorization', `Bearer ${superAdmin.accessToken}`)
.expect(200);
expect(sessionsAfterRevoke.body.items ?? []).toHaveLength(initialItems.length - 1);
});
});