test: improve e2e tests and add COMPLETE_EXCHANGE permission tests

- Fix E2E test assertion for buy/sell direction change
- Add data-testid to date buttons for reliable e2e selection
- Update e2e tests to use data-testid instead of fragile weekday matching
- Add tests for regular user cannot complete/no-show trades (COMPLETE_EXCHANGE permission)
This commit is contained in:
counterweight 2025-12-23 11:00:32 +01:00
parent ef01a970d5
commit ca3a08a236
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
3 changed files with 81 additions and 34 deletions

View file

@ -769,6 +769,68 @@ class TestAdminCompleteTrade:
assert response.status_code == 400
assert "not yet started" in response.json()["detail"]
@pytest.mark.asyncio
async def test_regular_user_cannot_complete_trade(
self, client_factory, regular_user, admin_user
):
"""Regular user cannot complete trades (requires COMPLETE_EXCHANGE permission)."""
# Create a past trade in DB
async with client_factory.get_db_session() as db:
past_time = datetime.now(UTC) - timedelta(hours=1)
exchange = Exchange(
user_id=regular_user["user"]["id"],
slot_start=past_time,
slot_end=past_time + timedelta(minutes=15),
direction=TradeDirection.BUY,
eur_amount=10000,
sats_amount=500000,
market_price_eur=20000.0,
agreed_price_eur=21000.0,
premium_percentage=5,
status=ExchangeStatus.BOOKED,
)
db.add(exchange)
await db.commit()
await db.refresh(exchange)
trade_id = exchange.id
# Regular user tries to complete
async with client_factory.create(cookies=regular_user["cookies"]) as client:
response = await client.post(f"/api/admin/trades/{trade_id}/complete")
assert response.status_code == 403
@pytest.mark.asyncio
async def test_regular_user_cannot_mark_no_show(
self, client_factory, regular_user, admin_user
):
"""Regular user cannot mark trades as no-show (requires COMPLETE_EXCHANGE permission)."""
# Create a past trade in DB
async with client_factory.get_db_session() as db:
past_time = datetime.now(UTC) - timedelta(hours=1)
exchange = Exchange(
user_id=regular_user["user"]["id"],
slot_start=past_time,
slot_end=past_time + timedelta(minutes=15),
direction=TradeDirection.BUY,
eur_amount=10000,
sats_amount=500000,
market_price_eur=20000.0,
agreed_price_eur=21000.0,
premium_percentage=5,
status=ExchangeStatus.BOOKED,
)
db.add(exchange)
await db.commit()
await db.refresh(exchange)
trade_id = exchange.id
# Regular user tries to mark as no-show
async with client_factory.create(cookies=regular_user["cookies"]) as client:
response = await client.post(f"/api/admin/trades/{trade_id}/no-show")
assert response.status_code == 403
class TestAdminNoShowTrade:
"""Test admin marking trades as no-show."""

View file

@ -462,6 +462,7 @@ export default function ExchangePage() {
return (
<button
key={dateStr}
data-testid={`date-${dateStr}`}
onClick={() => handleDateSelect(date)}
disabled={isDisabled}
style={{

View file

@ -79,11 +79,17 @@ test.describe("Exchange Page - Regular User Access", () => {
test("clicking buy/sell changes direction", async ({ page }) => {
await page.goto("/exchange");
// Click Sell BTC
// Initially in buy mode - summary shows BTC first: "You buy [sats], you sell €X"
// Verify buy mode is initially active
const buyButton = page.getByRole("button", { name: "Buy BTC" });
await expect(buyButton).toBeVisible();
// Click Sell BTC to switch direction
await page.getByRole("button", { name: "Sell BTC" }).click();
// The summary should mention "You send" for sell
await expect(page.getByText(/You send/)).toBeVisible();
// In sell mode, the summary shows EUR first: "You buy €X, you sell [sats]"
// We can verify by checking the summary text contains "You buy €" (EUR comes first)
await expect(page.getByText(/You buy €\d/)).toBeVisible();
});
test("exchange page shows date selection", async ({ page }) => {
@ -112,17 +118,9 @@ test.describe("Exchange Page - With Availability", () => {
test("shows available slots when availability is set", async ({ page }) => {
await page.goto("/exchange");
// Get tomorrow's display name to click the correct button
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
// Click tomorrow's date using the weekday name
// Wait for the button to be enabled (availability loading must complete)
const dateButton = page
.locator("button")
.filter({ hasText: new RegExp(`^${weekday}`) })
.first();
// Use data-testid for reliable date selection
const tomorrowStr = getTomorrowDateStr();
const dateButton = page.getByTestId(`date-${tomorrowStr}`);
await expect(dateButton).toBeEnabled({ timeout: 15000 });
await dateButton.click();
@ -140,16 +138,9 @@ test.describe("Exchange Page - With Availability", () => {
test("clicking slot shows confirmation form", async ({ page }) => {
await page.goto("/exchange");
// Get tomorrow's display name
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
// Click tomorrow's date - wait for button to be enabled first
const dateButton = page
.locator("button")
.filter({ hasText: new RegExp(`^${weekday}`) })
.first();
// Use data-testid for reliable date selection
const tomorrowStr = getTomorrowDateStr();
const dateButton = page.getByTestId(`date-${tomorrowStr}`);
await expect(dateButton).toBeEnabled({ timeout: 15000 });
await dateButton.click();
@ -169,16 +160,9 @@ test.describe("Exchange Page - With Availability", () => {
test("confirmation shows trade details", async ({ page }) => {
await page.goto("/exchange");
// Get tomorrow's display name
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const weekday = tomorrow.toLocaleDateString("en-US", { weekday: "short" });
// Click tomorrow's date - wait for button to be enabled first
const dateButton = page
.locator("button")
.filter({ hasText: new RegExp(`^${weekday}`) })
.first();
// Use data-testid for reliable date selection
const tomorrowStr = getTomorrowDateStr();
const dateButton = page.getByTestId(`date-${tomorrowStr}`);
await expect(dateButton).toBeEnabled({ timeout: 15000 });
await dateButton.click();