feat: implement project development module, transfer management features, and industrial AVM model integration
This commit is contained in:
46
e2e/api/admin-payments.spec.ts
Normal file
46
e2e/api/admin-payments.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { test, expect, registerUser } from '../fixtures';
|
||||
|
||||
/**
|
||||
* Admin Payments E2E tests (TEC-2749).
|
||||
*
|
||||
* Verifies authorization on POST /admin/payments/:id/confirm-transfer.
|
||||
* Full happy-path flow (confirm → payment.COMPLETED + audit log) requires
|
||||
* a seeded admin + pending bank-transfer payment and is exercised in
|
||||
* the handler unit tests.
|
||||
*/
|
||||
test.describe('Admin Payments API — Authorization', () => {
|
||||
let regularToken: string;
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
const { accessToken } = await registerUser(request);
|
||||
regularToken = accessToken;
|
||||
});
|
||||
|
||||
test.describe('POST /admin/payments/:id/confirm-transfer — Confirm bank transfer', () => {
|
||||
test('rejects unauthenticated request', async ({ request }) => {
|
||||
const res = await request.post('admin/payments/test-payment-id/confirm-transfer', {
|
||||
data: { bankReference: 'FT123456' },
|
||||
});
|
||||
|
||||
expect(res.status()).toBe(401);
|
||||
});
|
||||
|
||||
test('rejects non-admin user', async ({ request }) => {
|
||||
const res = await request.post('admin/payments/test-payment-id/confirm-transfer', {
|
||||
data: { bankReference: 'FT123456' },
|
||||
headers: { Authorization: `Bearer ${regularToken}` },
|
||||
});
|
||||
|
||||
expect(res.status()).toBe(403);
|
||||
});
|
||||
|
||||
test('rejects non-admin user with empty body', async ({ request }) => {
|
||||
const res = await request.post('admin/payments/test-payment-id/confirm-transfer', {
|
||||
data: {},
|
||||
headers: { Authorization: `Bearer ${regularToken}` },
|
||||
});
|
||||
|
||||
expect(res.status()).toBe(403);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -287,4 +287,46 @@ test.describe('Listings API', () => {
|
||||
expect(res.status()).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('GET /listings/:id/price-history — Price history timeline', () => {
|
||||
test('returns empty array when listing has no price changes', async ({ request }) => {
|
||||
const { listing } = await createListing(request, accessToken);
|
||||
|
||||
const res = await request.get(`listings/${listing.listingId}/price-history`);
|
||||
|
||||
expect(res.status()).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(Array.isArray(body)).toBe(true);
|
||||
expect(body).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('returns a price history entry after price update', async ({ request }) => {
|
||||
const { listing } = await createListing(request, accessToken);
|
||||
|
||||
const updateRes = await request.patch(`listings/${listing.listingId}`, {
|
||||
data: { priceVND: '6000000000' },
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
expect(updateRes.status()).toBe(200);
|
||||
|
||||
// Event bus is async — poll briefly for the snapshot to land.
|
||||
const deadline = Date.now() + 5000;
|
||||
let body: Array<{ oldPrice: string; newPrice: string; source: string; changedAt: string }> = [];
|
||||
while (Date.now() < deadline) {
|
||||
const res = await request.get(`listings/${listing.listingId}/price-history`);
|
||||
expect(res.status()).toBe(200);
|
||||
body = await res.json();
|
||||
if (body.length > 0) break;
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
|
||||
expect(body.length).toBeGreaterThanOrEqual(1);
|
||||
const entry = body[0]!;
|
||||
expect(entry).toHaveProperty('oldPrice');
|
||||
expect(entry).toHaveProperty('newPrice');
|
||||
expect(entry).toHaveProperty('source');
|
||||
expect(entry).toHaveProperty('changedAt');
|
||||
expect(entry.source).toBe('manual_update');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user