chore: remediate CI blockers for production readiness
This commit is contained in:
69
e2e/api/user-admin-listing-flow.spec.ts
Normal file
69
e2e/api/user-admin-listing-flow.spec.ts
Normal 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 })]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user