Fixing the round trip disabling dates

This commit is contained in:
Muluhabt
2026-07-28 16:12:44 +03:00
parent 83137ea0ef
commit 0dfb0cbe28
8 changed files with 241 additions and 8 deletions

View File

@@ -0,0 +1,26 @@
import { test, expect } from "@playwright/test";
import { PrismaClient } from "@prisma/client";
import { bookTrip } from "../../fixtures/booking-flow";
import { PROMO_EXPIRED } from "../../fixtures/data";
const prisma = new PrismaClient();
test.afterAll(async () => {
await prisma.$disconnect();
});
/**
* Round-trip variant of UA-11: an EXPIRED promo must not discount either leg of a round trip.
* Booked total should be the full two-leg fare (UA-6 baseline: cardBaseFareMinor * 2).
*/
test("round-trip: an expired promo code is ignored — full two-leg fare is booked", async ({ page }) => {
const r = await bookTrip(page, {
tripType: "ROUND_TRIP",
paymentMethod: "WALLET",
promoCode: PROMO_EXPIRED,
});
expect(r.confirmed).toBe(true);
const booking = await prisma.booking.findUniqueOrThrow({ where: { id: r.bookingId } });
expect(booking.bookingType).toBe("ROUND_TRIP");
expect(booking.totalMinor).toBe(r.cardBaseFareMinor * 2); // no discount applied
});

View File

@@ -0,0 +1,40 @@
import { test, expect } from "@playwright/test";
import { PrismaClient } from "@prisma/client";
import { bookTrip } from "../../fixtures/booking-flow";
const prisma = new PrismaClient();
test.afterAll(async () => {
await prisma.$disconnect();
});
/**
* Round-trip variant of UA-13 (C-1): a client-forged reviewedTotalMinor must be rejected server-side
* for round-trip bookings too, not just one-way. Forges both the per-seat fares (all passengers,
* both legs — seatFareMinor and returnSeatFareMinor) and reviewedTotalMinor to 1.
*/
test("round-trip: server rejects a client-forged reviewedTotalMinor=1 (C-1)", async ({ page }) => {
const r = await bookTrip(page, {
tripType: "ROUND_TRIP",
nationality: "Ethiopian",
paymentMethod: "WALLET",
tolerateBookingError: true,
mutateBookingBody: (body) => ({
...body,
reviewedTotalMinor: 1,
passengers: (body.passengers ?? []).map((p: any) => ({
...p,
seatFareMinor: 1,
returnSeatFareMinor: 1,
})),
}),
});
expect(r.cardBaseFareMinor).toBeGreaterThan(1000);
expect(r.reviewedTotalMinor).toBe(1);
expect(r.bookingStatus).toBeGreaterThanOrEqual(400);
expect(r.bookingStatus).toBeLessThan(500);
expect(r.bookingId).toBeFalsy();
const forged = await prisma.booking.findFirst({ where: { totalMinor: 1, bookingType: "ROUND_TRIP" } });
expect(forged).toBeNull();
});

View File

@@ -0,0 +1,38 @@
import { test, expect } from "@playwright/test";
import { PrismaClient } from "@prisma/client";
import { bookTrip } from "../../fixtures/booking-flow";
const prisma = new PrismaClient();
test.afterAll(async () => {
await prisma.$disconnect();
});
/**
* Round-trip variant of UA-2: a USD-display round-trip booking must keep the same currency
* coherence one-way already has — the passenger sees/agrees to a USD amount, but the stored
* charge basis (currency/totalMinor) stays honestly labeled ETB, for the COMBINED two-leg fare.
* UA-6 established the round-trip ETB baseline: totalMinor === cardBaseFareMinor * 2.
*/
test("round-trip: USD booking — combined two-leg charge basis stays coherently in ETB", async ({ page }) => {
const r = await bookTrip(page, {
tripType: "ROUND_TRIP",
nationality: "Other",
paymentMethod: "WALLET",
});
expect(r.displayCurrency).toBe("USD");
expect(r.confirmed).toBe(true);
const booking = await prisma.booking.findUniqueOrThrow({ where: { id: r.bookingId } });
const intent = await prisma.paymentIntent.findUniqueOrThrow({ where: { bookingId: r.bookingId } });
expect(booking.bookingType).toBe("ROUND_TRIP");
expect(booking.displayCurrency).toBe("USD");
// Stored charge basis: ETB, for the combined two-leg fare — not mislabeled, not single-leg.
expect(booking.currency).toBe("ETB");
expect(booking.totalMinor).toBe(r.cardBaseFareMinor * 2);
// The charge/intent moves exactly the stored ETB amount; booking is confirmed.
expect(intent.amountMinor).toBe(booking.totalMinor);
expect(booking.status).toBe("CONFIRMED");
});

View File

@@ -0,0 +1,48 @@
import { test, expect } from "@playwright/test";
import { PrismaClient } from "@prisma/client";
import { bookTrip } from "../../fixtures/booking-flow";
const prisma = new PrismaClient();
test.afterAll(async () => {
await prisma.$disconnect();
});
/**
* Round-trip variants of UA-4/UA-5: the "first child per booking is free" policy applies once to
* the whole booking (not once per leg) — the same free/paid child determination is used to price
* BOTH legs (createGuestRoundTripBooking computes paidChildrenCount once, reuses it for
* outboundTotalBase and returnTotalBase). So a free child rides free on both legs, and a paid
* child pays full fare on both legs. UA-6 established the round-trip baseline: one seated
* passenger produces 2 bookingSeat rows (one per leg, distinguished by `leg`).
*/
test("round-trip: first child under 5 travels free on BOTH legs, total = one adult fare per leg", async ({ page }) => {
const r = await bookTrip(page, { tripType: "ROUND_TRIP", adults: 1, children: 1, paymentMethod: "WALLET" });
expect(r.confirmed).toBe(true);
const booking = await prisma.booking.findUniqueOrThrow({ where: { id: r.bookingId } });
expect(booking.bookingType).toBe("ROUND_TRIP");
// Free child contributes nothing on either leg — same as an adult-only round trip (UA-6 baseline).
expect(booking.totalMinor).toBe(r.cardBaseFareMinor * 2);
// Only the adult is seated — once per leg (2 rows), free child never occupies a seat on either leg.
const seats = await prisma.bookingSeat.findMany({ where: { bookingId: r.bookingId } });
expect(seats.length).toBe(2);
expect(new Set(seats.map((s) => s.leg)).size).toBe(2);
});
test("round-trip: with 1 adult + 2 children, the second child pays full fare on BOTH legs", async ({ page }) => {
const r = await bookTrip(page, { tripType: "ROUND_TRIP", adults: 1, children: 2, paymentMethod: "WALLET" });
expect(r.confirmed).toBe(true);
const booking = await prisma.booking.findUniqueOrThrow({ where: { id: r.bookingId } });
expect(booking.bookingType).toBe("ROUND_TRIP");
// (adult + paid child) per leg = 2 fares/leg, both legs = 4 fares total.
expect(booking.totalMinor).toBe(r.cardBaseFareMinor * 4);
// Adult + paid second child are seated on both legs = 4 rows (2 passengers x 2 legs); free
// first child never occupies a seat on either leg.
const seats = await prisma.bookingSeat.findMany({ where: { bookingId: r.bookingId } });
expect(seats.length).toBe(4);
expect(new Set(seats.map((s) => s.leg)).size).toBe(2);
});

View File

@@ -0,0 +1,37 @@
import { test, expect } from "@playwright/test";
import { PrismaClient } from "@prisma/client";
import { bookTrip } from "../../fixtures/booking-flow";
import { PROMO_VALID } from "../../fixtures/data";
const prisma = new PrismaClient();
test.afterAll(async () => {
await prisma.$disconnect();
});
/**
* Round-trip variant of UA-8 (H-13): a VALID promo must discount the COMBINED two-leg total, not
* just one leg. createGuestRoundTripBooking is a separate implementation from one-way's
* createGuestBooking — it already had one confirmed divergence from the one-way H-13 fix (missing
* until a later patch), so this exists to catch any other one-way/round-trip discount divergence.
* UA-6 established the no-discount round-trip baseline: totalMinor === cardBaseFareMinor * 2 for a
* symmetric-distance route. PROMO_VALID is a flat 10% off (see fixtures/data.ts / seed-ui.ts).
*/
test("round-trip: a valid promo discounts the combined two-leg total (H-13 parity)", async ({ page }) => {
const r = await bookTrip(page, {
tripType: "ROUND_TRIP",
nationality: "Ethiopian",
paymentMethod: "WALLET",
promoCode: PROMO_VALID,
});
expect(r.confirmed).toBe(true);
const booking = await prisma.booking.findUniqueOrThrow({ where: { id: r.bookingId } });
expect(booking.bookingType).toBe("ROUND_TRIP");
const combinedBaseFareMinor = r.cardBaseFareMinor * 2; // UA-6 baseline: symmetric legs
const expectedDiscountMinor = Math.round(combinedBaseFareMinor * 0.1);
const expectedTotalMinor = combinedBaseFareMinor - expectedDiscountMinor;
expect(booking.totalMinor).toBeLessThan(combinedBaseFareMinor); // discount honored, not dropped
expect(booking.totalMinor).toBe(expectedTotalMinor);
});