chore: perauset customer project
This commit is contained in:
12
e2e/tests/steps/audit-log.steps.ts
Normal file
12
e2e/tests/steps/audit-log.steps.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Then } from '@cucumber/cucumber'
|
||||
import { strict as assert } from 'assert'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
Then('the audit log should contain an {string} entry for table {string}', function (this: PerausetWorld, action: string, tableName: string) {
|
||||
assert.ok(this.lastBody, 'No response body')
|
||||
assert.ok(Array.isArray(this.lastBody.items), `Expected body.items to be an array, got: ${JSON.stringify(this.lastBody)}`)
|
||||
const match = this.lastBody.items.find(
|
||||
(entry: any) => entry.action === action && entry.tableName === tableName
|
||||
)
|
||||
assert.ok(match, `No audit log entry with action="${action}" for table="${tableName}" found in: ${JSON.stringify(this.lastBody.items.slice(0, 5))}`)
|
||||
})
|
||||
6
e2e/tests/steps/auth.steps.ts
Normal file
6
e2e/tests/steps/auth.steps.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Given } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
Given('I login as {string}', async function (this: PerausetWorld, email: string) {
|
||||
await this.login(email)
|
||||
})
|
||||
64
e2e/tests/steps/boats.steps.ts
Normal file
64
e2e/tests/steps/boats.steps.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { When } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I create a boat route with name {string} origin {string} destination {string}', async function (this: PerausetWorld, name: string, origin: string, destination: string) {
|
||||
await this.post('/boats/routes', {
|
||||
name,
|
||||
origin,
|
||||
destination,
|
||||
})
|
||||
})
|
||||
|
||||
When('I create a boat with name {string} capacity {int}', async function (this: PerausetWorld, name: string, capacity: number) {
|
||||
await this.post('/boats', {
|
||||
name,
|
||||
passengerCapacity: capacity,
|
||||
})
|
||||
})
|
||||
|
||||
When('I create a boat trip for the route and boat departing {string}', async function (this: PerausetWorld, departureAt: string) {
|
||||
const routeId = (this as any).routeId
|
||||
const boatId = (this as any).boatId
|
||||
await this.post('/boats/trips', {
|
||||
routeId,
|
||||
boatId,
|
||||
scheduledDepartureAt: departureAt,
|
||||
})
|
||||
})
|
||||
|
||||
When('I open the boat trip', async function (this: PerausetWorld) {
|
||||
const tripId = (this as any).tripId
|
||||
await this.post(`/boats/trips/${tripId}/open`, {
|
||||
tripId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I request a seat on the boat trip', async function (this: PerausetWorld) {
|
||||
const tripId = (this as any).tripId
|
||||
await this.post(`/boats/trips/${tripId}/request-seat`, {
|
||||
tripId,
|
||||
requestedSeats: 1,
|
||||
})
|
||||
})
|
||||
|
||||
When('I confirm the boat request', async function (this: PerausetWorld) {
|
||||
const boatRequestId = (this as any).boatRequestId
|
||||
await this.post(`/boats/requests/${boatRequestId}/confirm`, {
|
||||
requestId: boatRequestId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I create a boat trip for the route without boat departing {string}', async function (this: PerausetWorld, departureAt: string) {
|
||||
const routeId = (this as any).routeId
|
||||
await this.post('/boats/trips', {
|
||||
routeId,
|
||||
scheduledDepartureAt: departureAt,
|
||||
})
|
||||
})
|
||||
|
||||
When('I cancel the boat trip', async function (this: PerausetWorld) {
|
||||
const tripId = (this as any).tripId
|
||||
await this.post(`/boats/trips/${tripId}/cancel`, {
|
||||
tripId,
|
||||
})
|
||||
})
|
||||
141
e2e/tests/steps/browser/common.steps.ts
Normal file
141
e2e/tests/steps/browser/common.steps.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber'
|
||||
import { expect } from '@playwright/test'
|
||||
import { BrowserWorld } from '../../support/browser-world.js'
|
||||
import { browserConfig } from '../../support/browser-config.js'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Given
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Given('I am on the login page', async function (this: BrowserWorld) {
|
||||
await this.navigateTo('/login')
|
||||
})
|
||||
|
||||
Given('I am logged in as {string}', async function (this: BrowserWorld, email: string) {
|
||||
await this.login(email)
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// When — navigation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
When('I navigate to {string}', async function (this: BrowserWorld, path: string) {
|
||||
await this.navigateTo(path)
|
||||
})
|
||||
|
||||
When('I click {string}', async function (this: BrowserWorld, name: string) {
|
||||
const page = this.getPage()
|
||||
// Try button first, then link
|
||||
const button = page.getByRole('button', { name })
|
||||
if (await button.isVisible().catch(() => false)) {
|
||||
await button.click()
|
||||
} else {
|
||||
await page.getByRole('link', { name }).click()
|
||||
}
|
||||
await page.waitForLoadState('networkidle').catch(() => {})
|
||||
})
|
||||
|
||||
When('I click {string} in the sidebar', async function (this: BrowserWorld, name: string) {
|
||||
const page = this.getPage()
|
||||
// Mantine NavLink renders as an <a> WITHOUT href (navigation is via onClick), so
|
||||
// it has no implicit "link" role — target the label text inside the navbar.
|
||||
// Some labels are duplicated (e.g. "My Stays" maps to both /my/stays and /stays
|
||||
// due to the nav.stays == nav.my_stays i18n collision); first() picks the
|
||||
// personal-section entry, which appears first in DOM order.
|
||||
const navItem = page
|
||||
.locator('.mantine-AppShell-navbar')
|
||||
.getByText(name, { exact: true })
|
||||
.first()
|
||||
await navItem.scrollIntoViewIfNeeded()
|
||||
await navItem.click()
|
||||
await page.waitForLoadState('networkidle').catch(() => {})
|
||||
})
|
||||
|
||||
When('I click tab {string}', async function (this: BrowserWorld, name: string) {
|
||||
const page = this.getPage()
|
||||
await page.getByRole('tab', { name }).click()
|
||||
await page.waitForLoadState('networkidle').catch(() => {})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// When — form interactions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
When(
|
||||
'I fill in {string} with {string}',
|
||||
async function (this: BrowserWorld, label: string, value: string) {
|
||||
await this.fillField(label, value)
|
||||
}
|
||||
)
|
||||
|
||||
When(
|
||||
'I select {string} from {string}',
|
||||
async function (this: BrowserWorld, value: string, label: string) {
|
||||
await this.selectOption(label, value)
|
||||
}
|
||||
)
|
||||
|
||||
When(
|
||||
'I pick date {string} for {string}',
|
||||
async function (this: BrowserWorld, day: string, label: string) {
|
||||
const page = this.getPage()
|
||||
// Close any open calendar/dropdown first
|
||||
await page.keyboard.press('Escape')
|
||||
await page.waitForTimeout(300)
|
||||
// Mantine DatePickerInput: the label text and button are wrapped in the same container
|
||||
// Use a CSS selector to find the right DatePickerInput by its label
|
||||
const wrapper = page.locator(`text="${label}" >> xpath=ancestor::div[contains(@class, "mantine-DatePickerInput-root") or contains(@class, "mantine-InputWrapper-root")][1]`)
|
||||
const btn = wrapper.locator('button').first()
|
||||
if (await btn.count() > 0) {
|
||||
await btn.click({ timeout: 10_000 })
|
||||
} else {
|
||||
// Fallback: just try getByLabel
|
||||
await page.getByLabel(label).click({ timeout: 10_000 })
|
||||
}
|
||||
// Wait for calendar popover to appear
|
||||
await page.waitForTimeout(500)
|
||||
// Click the day number — try table buttons with matching text
|
||||
await page.locator('table button').filter({ hasText: new RegExp(`^${day}$`) }).first().click()
|
||||
// Close the calendar popover
|
||||
await page.waitForTimeout(300)
|
||||
}
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Then — assertions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Then('I should see {string}', async function (this: BrowserWorld, text: string) {
|
||||
await this.expectText(text)
|
||||
})
|
||||
|
||||
Then('I should not see {string}', async function (this: BrowserWorld, text: string) {
|
||||
await this.expectNoText(text)
|
||||
})
|
||||
|
||||
Then('I should see heading {string}', async function (this: BrowserWorld, text: string) {
|
||||
await this.expectHeading(text)
|
||||
})
|
||||
|
||||
Then('I should be on {string}', async function (this: BrowserWorld, path: string) {
|
||||
const page = this.getPage()
|
||||
await page.waitForURL((url) => url.pathname.includes(path), { timeout: 10_000 })
|
||||
expect(page.url()).toContain(path)
|
||||
})
|
||||
|
||||
Then(
|
||||
'the sidebar should show {string}',
|
||||
async function (this: BrowserWorld, text: string) {
|
||||
const page = this.getPage()
|
||||
const sidebar = page.locator('.mantine-AppShell-navbar')
|
||||
await expect(sidebar.getByText(text)).toBeVisible({ timeout: 10_000 })
|
||||
}
|
||||
)
|
||||
|
||||
Then(
|
||||
'the {string} field should contain {string}',
|
||||
async function (this: BrowserWorld, label: string, value: string) {
|
||||
const page = this.getPage()
|
||||
await expect(page.getByLabel(label)).toHaveValue(value, { timeout: 5_000 })
|
||||
}
|
||||
)
|
||||
38
e2e/tests/steps/finance.steps.ts
Normal file
38
e2e/tests/steps/finance.steps.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { When } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I create a finance record {string} with amount {int} and type {string}', async function (this: PerausetWorld, name: string, amount: number, recordType: string) {
|
||||
await this.post('/finance', {
|
||||
name,
|
||||
amount,
|
||||
recordType,
|
||||
category: 'operations',
|
||||
})
|
||||
})
|
||||
|
||||
When('I list finance records', async function (this: PerausetWorld) {
|
||||
await this.get('/finance')
|
||||
})
|
||||
|
||||
When('I update the finance record with name {string}', async function (this: PerausetWorld, name: string) {
|
||||
const financeId = (this as any).financeId
|
||||
await this.put(`/finance/${financeId}`, {
|
||||
financeId,
|
||||
name,
|
||||
})
|
||||
})
|
||||
|
||||
When('I link the finance record to entity type {string}', async function (this: PerausetWorld, entityType: string) {
|
||||
const financeId = (this as any).financeId
|
||||
const entityId = (this as any).itemId
|
||||
await this.post('/finance/links', {
|
||||
financeId,
|
||||
entityType,
|
||||
entityId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I list finance links for the current finance record', async function (this: PerausetWorld) {
|
||||
const financeId = (this as any).financeId
|
||||
await this.get(`/finance/links?financeId=${financeId}`)
|
||||
})
|
||||
10
e2e/tests/steps/health-check.steps.ts
Normal file
10
e2e/tests/steps/health-check.steps.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { When, Then } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I send a GET request to {string}', async function (this: PerausetWorld, path: string) {
|
||||
await this.get(path)
|
||||
})
|
||||
|
||||
Then('the response status should be {int}', function (this: PerausetWorld, status: number) {
|
||||
this.expectStatus(status)
|
||||
})
|
||||
49
e2e/tests/steps/inventory.steps.ts
Normal file
49
e2e/tests/steps/inventory.steps.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { When } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I create an inventory item {string} with unit {string} quantity {int} and minimum {int}', async function (this: PerausetWorld, name: string, unit: string, currentQuantity: number, minimumQuantity: number) {
|
||||
await this.post('/inventory', {
|
||||
name,
|
||||
unit,
|
||||
currentQuantity,
|
||||
minimumQuantity,
|
||||
category: 'kitchen',
|
||||
})
|
||||
})
|
||||
|
||||
When('I update the inventory item with name {string}', async function (this: PerausetWorld, name: string) {
|
||||
const itemId = (this as any).itemId
|
||||
await this.put(`/inventory/${itemId}`, {
|
||||
itemId,
|
||||
name,
|
||||
})
|
||||
})
|
||||
|
||||
When('I list inventory items', async function (this: PerausetWorld) {
|
||||
await this.get('/inventory')
|
||||
})
|
||||
|
||||
When('I request inventory for item with quantity {int} unit {string} and purpose {string}', async function (this: PerausetWorld, quantity: number, unit: string, purpose: string) {
|
||||
const itemId = (this as any).itemId
|
||||
await this.post('/inventory/requests', {
|
||||
itemId,
|
||||
quantity,
|
||||
unit,
|
||||
purpose,
|
||||
})
|
||||
})
|
||||
|
||||
When('I review the inventory request with status {string}', async function (this: PerausetWorld, status: string) {
|
||||
const requestId = (this as any).requestId
|
||||
await this.post(`/inventory/requests/${requestId}/review`, {
|
||||
requestId,
|
||||
status,
|
||||
})
|
||||
})
|
||||
|
||||
When('I fulfill the inventory request', async function (this: PerausetWorld) {
|
||||
const requestId = (this as any).requestId
|
||||
await this.post(`/inventory/requests/${requestId}/fulfill`, {
|
||||
requestId,
|
||||
})
|
||||
})
|
||||
22
e2e/tests/steps/kitchen.steps.ts
Normal file
22
e2e/tests/steps/kitchen.steps.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { When } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I create a meal service of type {string} on {string} with headcount {int}', async function (this: PerausetWorld, serviceType: string, serviceDate: string, plannedHeadcount: number) {
|
||||
await this.post('/kitchen/meal-services', {
|
||||
serviceType,
|
||||
serviceDate,
|
||||
plannedHeadcount,
|
||||
})
|
||||
})
|
||||
|
||||
When('I list meal services', async function (this: PerausetWorld) {
|
||||
await this.get('/kitchen/meal-services')
|
||||
})
|
||||
|
||||
When('I upsert my dietary profile as vegan with nut allergy', async function (this: PerausetWorld) {
|
||||
await this.put('/kitchen/dietary-profile', {
|
||||
isVegan: true,
|
||||
hasNutAllergy: true,
|
||||
allergyNotes: 'Severe nut allergy',
|
||||
})
|
||||
})
|
||||
29
e2e/tests/steps/notifications.steps.ts
Normal file
29
e2e/tests/steps/notifications.steps.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Given, When } from '@cucumber/cucumber'
|
||||
import { strict as assert } from 'assert'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
import { openDb } from '../support/db.js'
|
||||
|
||||
Given('a test notification exists for the current user', async function (this: PerausetWorld) {
|
||||
const userId = await this.getSessionUserId()
|
||||
const db = openDb()
|
||||
try {
|
||||
const row = db
|
||||
.prepare(
|
||||
`INSERT INTO notification (user_id, type, title, body)
|
||||
VALUES (?, 'test', 'Test Notification', 'This is a test notification')
|
||||
RETURNING notification_id`
|
||||
)
|
||||
.get(userId) as { notification_id: string }
|
||||
;(this as any).notificationId = row.notification_id
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
})
|
||||
|
||||
When('I mark the notification as read', async function (this: PerausetWorld) {
|
||||
const notificationId = (this as any).notificationId
|
||||
assert.ok(notificationId, 'No notification ID stored')
|
||||
await this.post(`/notifications/${notificationId}/read`, {
|
||||
notificationId,
|
||||
})
|
||||
})
|
||||
59
e2e/tests/steps/retreats.steps.ts
Normal file
59
e2e/tests/steps/retreats.steps.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { When } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I create a retreat {string} with slug {string} from {string} to {string}', async function (this: PerausetWorld, name: string, slug: string, startAt: string, endAt: string) {
|
||||
await this.post('/retreats', {
|
||||
slug,
|
||||
name,
|
||||
startAt,
|
||||
endAt,
|
||||
})
|
||||
})
|
||||
|
||||
When('I list retreats', async function (this: PerausetWorld) {
|
||||
await this.get('/retreats')
|
||||
})
|
||||
|
||||
When('I update the retreat with name {string} and capacity {int}', async function (this: PerausetWorld, name: string, capacity: number) {
|
||||
const retreatId = (this as any).retreatId
|
||||
await this.put(`/retreats/${retreatId}`, {
|
||||
retreatId,
|
||||
name,
|
||||
capacity,
|
||||
})
|
||||
})
|
||||
|
||||
When('I publish the retreat', async function (this: PerausetWorld) {
|
||||
const retreatId = (this as any).retreatId
|
||||
await this.post(`/retreats/${retreatId}/publish`, {
|
||||
retreatId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I add a person {string} with role {string} to the retreat', async function (this: PerausetWorld, fullName: string, roleType: string) {
|
||||
const retreatId = (this as any).retreatId
|
||||
await this.post(`/retreats/${retreatId}/persons`, {
|
||||
retreatId,
|
||||
fullName,
|
||||
roleType,
|
||||
email: `${fullName.toLowerCase().replace(/\s+/g, '.')}@test.org`,
|
||||
})
|
||||
})
|
||||
|
||||
When('I add a schedule item {string} from {string} to {string} at {string}', async function (this: PerausetWorld, title: string, startsAt: string, endsAt: string, location: string) {
|
||||
const retreatId = (this as any).retreatId
|
||||
await this.post(`/retreats/${retreatId}/schedule`, {
|
||||
retreatId,
|
||||
title,
|
||||
startsAt,
|
||||
endsAt,
|
||||
location,
|
||||
})
|
||||
})
|
||||
|
||||
When('I cancel the retreat', async function (this: PerausetWorld) {
|
||||
const retreatId = (this as any).retreatId
|
||||
await this.post(`/retreats/${retreatId}/cancel`, {
|
||||
retreatId,
|
||||
})
|
||||
})
|
||||
22
e2e/tests/steps/roles.steps.ts
Normal file
22
e2e/tests/steps/roles.steps.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { When, Then } from '@cucumber/cucumber'
|
||||
import { strict as assert } from 'assert'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
import { getUserId } from '../support/db.js'
|
||||
|
||||
When('I set roles {string} for user {string}', async function (this: PerausetWorld, roles: string, email: string) {
|
||||
// Look up userId from DB if not already cached
|
||||
let userId = this.createdUsers[email]
|
||||
if (!userId) {
|
||||
userId = getUserId(email)
|
||||
this.createdUsers[email] = userId
|
||||
}
|
||||
const rolesArray = roles.split(',').map((r) => r.trim())
|
||||
await this.put(`/users/${userId}/roles`, { memberRoles: rolesArray })
|
||||
})
|
||||
|
||||
Then('the response body field {string} should contain {string}', function (this: PerausetWorld, field: string, value: string) {
|
||||
assert.ok(this.lastBody, 'No response body')
|
||||
const arr = this.lastBody[field]
|
||||
assert.ok(Array.isArray(arr), `Expected body.${field} to be an array, got: ${JSON.stringify(arr)}`)
|
||||
assert.ok(arr.includes(value), `Expected body.${field} to contain "${value}", got: ${JSON.stringify(arr)}`)
|
||||
})
|
||||
51
e2e/tests/steps/rooms.steps.ts
Normal file
51
e2e/tests/steps/rooms.steps.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { When, Given } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
import { openDb } from '../support/db.js'
|
||||
|
||||
When('I create a room with code {string} name {string} type {string} capacity {int}', async function (this: PerausetWorld, code: string, name: string, roomType: string, capacity: number) {
|
||||
await this.post('/rooms', {
|
||||
code,
|
||||
name,
|
||||
roomType,
|
||||
capacity,
|
||||
})
|
||||
})
|
||||
|
||||
When('I allocate the room to the stay from {string} to {string}', async function (this: PerausetWorld, startsAt: string, endsAt: string) {
|
||||
const roomId = (this as any).roomId
|
||||
const stayId = (this as any).stayId
|
||||
await this.post('/rooms/allocations', {
|
||||
roomId,
|
||||
stayId,
|
||||
startsAt,
|
||||
endsAt,
|
||||
})
|
||||
})
|
||||
|
||||
When('I allocate the room to stored stay {string} from {string} to {string}', async function (this: PerausetWorld, stayKey: string, startsAt: string, endsAt: string) {
|
||||
const roomId = (this as any).roomId
|
||||
const stayId = (this as any)[stayKey]
|
||||
await this.post('/rooms/allocations', {
|
||||
roomId,
|
||||
stayId,
|
||||
startsAt,
|
||||
endsAt,
|
||||
})
|
||||
})
|
||||
|
||||
Given('the room allocation is active in the database', async function (this: PerausetWorld) {
|
||||
const allocationId = (this as any).allocationId
|
||||
const db = openDb()
|
||||
try {
|
||||
db.prepare(`UPDATE room_allocation SET status = 'active' WHERE allocation_id = ?`).run(allocationId)
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
})
|
||||
|
||||
When('I release the room allocation', async function (this: PerausetWorld) {
|
||||
const allocationId = (this as any).allocationId
|
||||
await this.post(`/rooms/allocations/${allocationId}/release`, {
|
||||
allocationId,
|
||||
})
|
||||
})
|
||||
19
e2e/tests/steps/shared.steps.ts
Normal file
19
e2e/tests/steps/shared.steps.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { When, Then } from '@cucumber/cucumber'
|
||||
import { strict as assert } from 'assert'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
|
||||
When('I send a POST request to {string} with body:', async function (this: PerausetWorld, path: string, body: string) {
|
||||
await this.post(path, JSON.parse(body))
|
||||
})
|
||||
|
||||
Then('I store the response field {string} as {string}', function (this: PerausetWorld, field: string, key: string) {
|
||||
assert.ok(this.lastBody, 'No response body')
|
||||
const value = this.lastBody[field]
|
||||
assert.ok(value !== undefined && value !== null, `Response body field "${field}" is missing. Body: ${JSON.stringify(this.lastBody)}`)
|
||||
;(this as any)[key] = value
|
||||
})
|
||||
|
||||
Then('the response body field {string} should be truthy', function (this: PerausetWorld, field: string) {
|
||||
assert.ok(this.lastBody, 'No response body')
|
||||
assert.ok(this.lastBody[field], `Expected body.${field} to be truthy, got: ${JSON.stringify(this.lastBody[field])}`)
|
||||
})
|
||||
57
e2e/tests/steps/stays.steps.ts
Normal file
57
e2e/tests/steps/stays.steps.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { When, Given } from '@cucumber/cucumber'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
import { openDb } from '../support/db.js'
|
||||
|
||||
When('I submit a stay request with type {string} from {string} to {string}', async function (this: PerausetWorld, requestType: string, startAt: string, endAt: string) {
|
||||
await this.post('/stays/requests', {
|
||||
requestType,
|
||||
requestedStartAt: startAt,
|
||||
requestedEndAt: endAt,
|
||||
})
|
||||
})
|
||||
|
||||
When('I approve the stay request', async function (this: PerausetWorld) {
|
||||
const requestId = (this as any).requestId
|
||||
await this.post(`/stays/requests/${requestId}/approve`, {
|
||||
requestId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I reject the stay request with reason {string}', async function (this: PerausetWorld, reason: string) {
|
||||
const requestId = (this as any).requestId
|
||||
await this.post(`/stays/requests/${requestId}/reject`, {
|
||||
requestId,
|
||||
reviewNotes: reason,
|
||||
})
|
||||
})
|
||||
|
||||
When('I create a stay from the approved request', async function (this: PerausetWorld) {
|
||||
const requestId = (this as any).requestId
|
||||
await this.post('/stays', {
|
||||
requestId,
|
||||
})
|
||||
})
|
||||
|
||||
Given('the stay is confirmed in the database', async function (this: PerausetWorld) {
|
||||
const stayId = (this as any).stayId
|
||||
const db = openDb()
|
||||
try {
|
||||
db.prepare(`UPDATE stay SET status = 'confirmed' WHERE stay_id = ?`).run(stayId)
|
||||
} finally {
|
||||
db.close()
|
||||
}
|
||||
})
|
||||
|
||||
When('I check in the stay', async function (this: PerausetWorld) {
|
||||
const stayId = (this as any).stayId
|
||||
await this.post(`/stays/${stayId}/check-in`, {
|
||||
stayId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I check out the stay', async function (this: PerausetWorld) {
|
||||
const stayId = (this as any).stayId
|
||||
await this.post(`/stays/${stayId}/check-out`, {
|
||||
stayId,
|
||||
})
|
||||
})
|
||||
63
e2e/tests/steps/tasks.steps.ts
Normal file
63
e2e/tests/steps/tasks.steps.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { When } from '@cucumber/cucumber'
|
||||
import { strict as assert } from 'assert'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
import { getUserId } from '../support/db.js'
|
||||
|
||||
When('I create a task template with title {string} category {string}', async function (this: PerausetWorld, title: string, category: string) {
|
||||
await this.post('/tasks/templates', {
|
||||
title,
|
||||
category,
|
||||
})
|
||||
})
|
||||
|
||||
When('I create a task instance with title {string} category {string}', async function (this: PerausetWorld, title: string, category: string) {
|
||||
await this.post('/tasks', {
|
||||
title,
|
||||
category,
|
||||
})
|
||||
})
|
||||
|
||||
When('I assign the task to user {string}', async function (this: PerausetWorld, email: string) {
|
||||
const taskId = (this as any).taskId
|
||||
|
||||
// Look up userId from DB
|
||||
let userId = this.createdUsers[email]
|
||||
if (!userId) {
|
||||
userId = getUserId(email)
|
||||
this.createdUsers[email] = userId
|
||||
}
|
||||
|
||||
await this.post(`/tasks/${taskId}/assign`, {
|
||||
taskId,
|
||||
userId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I start the task', async function (this: PerausetWorld) {
|
||||
const taskId = (this as any).taskId
|
||||
await this.post(`/tasks/${taskId}/start`, {
|
||||
taskId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I complete the task', async function (this: PerausetWorld) {
|
||||
const taskId = (this as any).taskId
|
||||
await this.post(`/tasks/${taskId}/complete`, {
|
||||
taskId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I claim the task', async function (this: PerausetWorld) {
|
||||
const taskId = (this as any).taskId
|
||||
await this.post(`/tasks/${taskId}/claim`, {
|
||||
taskId,
|
||||
})
|
||||
})
|
||||
|
||||
When('I mark the task as blocked with reason {string}', async function (this: PerausetWorld, reason: string) {
|
||||
const taskId = (this as any).taskId
|
||||
await this.post(`/tasks/${taskId}/block`, {
|
||||
taskId,
|
||||
reason,
|
||||
})
|
||||
})
|
||||
37
e2e/tests/steps/users.steps.ts
Normal file
37
e2e/tests/steps/users.steps.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber'
|
||||
import { strict as assert } from 'assert'
|
||||
import type { PerausetWorld } from '../support/world.js'
|
||||
import { ensureUser } from '../support/db.js'
|
||||
|
||||
Given('a user {string} exists', function (this: PerausetWorld, email: string) {
|
||||
this.createdUsers[email] = ensureUser(email, [])
|
||||
})
|
||||
|
||||
Given('a user {string} exists with roles {string}', function (this: PerausetWorld, email: string, roles: string) {
|
||||
const rolesArray = roles.split(',').map((r) => r.trim())
|
||||
this.createdUsers[email] = ensureUser(email, rolesArray)
|
||||
})
|
||||
|
||||
When('I get my own user profile', async function (this: PerausetWorld) {
|
||||
const userId = await this.getSessionUserId()
|
||||
await this.get(`/users/${userId}`)
|
||||
})
|
||||
|
||||
When('I update my profile with displayName {string}', async function (this: PerausetWorld, displayName: string) {
|
||||
const userId = await this.getSessionUserId()
|
||||
await this.put(`/users/${userId}/profile`, { displayName })
|
||||
})
|
||||
|
||||
Then('the response body should be a list with field {string}', function (this: PerausetWorld, field: string) {
|
||||
assert.ok(this.lastBody, 'No response body')
|
||||
assert.ok(Array.isArray(this.lastBody[field]), `Expected body.${field} to be an array, got: ${JSON.stringify(this.lastBody[field])}`)
|
||||
})
|
||||
|
||||
Then('the response body field {string} should be {string}', function (this: PerausetWorld, field: string, value: string) {
|
||||
assert.ok(this.lastBody, 'No response body')
|
||||
assert.equal(
|
||||
this.lastBody[field],
|
||||
value,
|
||||
`Expected body.${field} to be "${value}", got: "${this.lastBody[field]}"`
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user