chore: remediate CI blockers for production readiness

This commit is contained in:
Ho Ngoc Hai
2026-05-07 13:08:20 +07:00
parent f82806e06d
commit b35ec55126
32 changed files with 401 additions and 113 deletions

View File

@@ -0,0 +1,69 @@
import { test, expect, registerUser, loginSeedAdmin } from '../fixtures';
import { createListing } from '../fixtures/listings.fixture';
test.describe('User-to-admin listing moderation flow', () => {
test('user creates listing, submits review, admin approves, and listing becomes active', async ({ request }) => {
const { accessToken: userToken } = await registerUser(request);
const title = `E2E User Admin Flow ${Date.now()}`;
const { listing } = await createListing(request, userToken, {
title,
address: `${Date.now()} Nguyễn Huệ`,
});
const listingId = listing.listingId as string;
expect(listingId).toBeTruthy();
expect(listing.status).toBe('DRAFT');
const submitRes = await request.patch(`listings/${listingId}/status`, {
data: { status: 'PENDING_REVIEW' },
headers: { Authorization: `Bearer ${userToken}` },
});
expect(submitRes.status()).toBe(200);
const submitBody = await submitRes.json();
expect(submitBody).toEqual(expect.objectContaining({ status: 'PENDING_REVIEW' }));
const { accessToken: adminToken } = await loginSeedAdmin(request);
const queueRes = await request.get('admin/moderation', {
params: { page: 1, limit: 100 },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(queueRes.status()).toBe(200);
const queue = await queueRes.json();
expect(queue.data).toEqual(
expect.arrayContaining([
expect.objectContaining({
listingId,
propertyTitle: title,
}),
]),
);
const approveRes = await request.post('admin/moderation/approve', {
data: {
listingId,
moderationNotes: 'E2E admin approval',
},
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(approveRes.status()).toBe(201);
const approveBody = await approveRes.json();
expect(approveBody).toEqual(expect.objectContaining({ listingId, status: 'ACTIVE' }));
const detailRes = await request.get(`listings/${listingId}`);
expect(detailRes.status()).toBe(200);
const detail = await detailRes.json();
expect(detail.id).toBe(listingId);
expect(detail.status).toBe('ACTIVE');
const queueAfterApproveRes = await request.get('admin/moderation', {
params: { page: 1, limit: 100 },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(queueAfterApproveRes.status()).toBe(200);
const queueAfterApprove = await queueAfterApproveRes.json();
expect(queueAfterApprove.data).not.toEqual(
expect.arrayContaining([expect.objectContaining({ listingId })]),
);
});
});

View File

@@ -50,6 +50,16 @@ export async function loginUser(
return res.json();
}
/** Logs in the seeded admin created by prisma/seed.ts for E2E admin happy paths. */
export async function loginSeedAdmin(request: APIRequestContext): Promise<TokenPair> {
const phone = process.env['E2E_ADMIN_PHONE'] ?? '0876677771';
const password = process.env['SEED_DEFAULT_PASSWORD'];
if (!password) {
throw new Error('SEED_DEFAULT_PASSWORD is required to log in the seeded admin user');
}
return loginUser(request, phone, password);
}
/**
* Extended test fixture that provides a pre-authenticated API context.
*

View File

@@ -1,5 +1,5 @@
export { test, expect } from './auth.fixture';
export { createTestUser, registerUser, loginUser } from './auth.fixture';
export { createTestUser, registerUser, loginUser, loginSeedAdmin } from './auth.fixture';
export type { TokenPair } from './auth.fixture';
export { createTestListing, createListing } from './listings.fixture';
export { buildVnpayCallbackData, buildMomoCallbackData } from './payments.fixture';

View File

@@ -43,26 +43,14 @@ export default async function globalSetup() {
env: { ...process.env, DATABASE_URL: databaseUrl },
};
// Apply schema to test database.
// Prisma 7 removed datasource.url from schema — the URL is in prisma.config.ts
// which picks it up from DATABASE_URL env var set above.
// For local dev, the test DB is typically set up manually or via pg_dump.
console.log('[E2E globalSetup] Verifying test database schema...');
try {
execSync('npx prisma db push --accept-data-loss --config prisma/prisma.config.ts', execOpts);
} catch (err) {
console.warn('[E2E globalSetup] prisma db push failed (may be expected in Prisma 7):', (err as Error).message);
console.log('[E2E globalSetup] Continuing — assuming test DB schema is already set up.');
}
// Apply committed migrations only. `db push --accept-data-loss` hides
// migration drift and can mutate the test schema outside review.
console.log('[E2E globalSetup] Applying test database migrations...');
execSync('npx prisma migrate deploy --config prisma/prisma.config.ts', execOpts);
// Seed database (upserts are idempotent)
console.log('[E2E globalSetup] Seeding test database...');
try {
execSync('npx prisma db seed --config prisma/prisma.config.ts', execOpts);
} catch (err) {
console.warn('[E2E globalSetup] Seed failed (may be expected if Prisma 7 config changed):', (err as Error).message);
console.log('[E2E globalSetup] Continuing — assuming test DB is already seeded.');
}
execSync('npx prisma db seed --config prisma/prisma.config.ts', execOpts);
console.log('[E2E globalSetup] Test database ready.\n');
}