Resolve merge conflict with master in SelfserveLaneCommandApiTest.php

This commit is contained in:
copilot-swe-agent[bot]
2026-06-02 08:15:18 +00:00
committed by GitHub
6 changed files with 174 additions and 1 deletions
+26
View File
@@ -5874,6 +5874,32 @@ paths:
'200': '200':
description: Success description: Success
/order-bookings/booking-confirmation/resend:
post:
tags:
- Bookings
summary: Resend order booking confirmation
description: Resends the customer booking confirmation email for an order booking. Requires `resend_booking_confirmations` and access to the booking's department.
operationId: resendOrderBookingConfirmation
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [id]
properties:
id: {type: integer}
responses:
'200':
description: Booking confirmation resent successfully
content:
application/json:
schema: {}
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
/order-bookings/complete: /order-bookings/complete:
post: post:
tags: tags:
+18 -1
View File
@@ -51,6 +51,23 @@ collect_logs() {
docker compose $compose_files cp php1:/var/log/php "$log_dir/php-logs" >/dev/null 2>&1 || true docker compose $compose_files cp php1:/var/log/php "$log_dir/php-logs" >/dev/null 2>&1 || true
} }
retry_command() {
max_attempts="$1"
shift
attempt=1
while :; do
"$@" && return 0
status="$?"
if [ "$attempt" -ge "$max_attempts" ]; then
return "$status"
fi
sleep_seconds=$((attempt * 5))
echo "Command failed with status $status; retrying in ${sleep_seconds}s (attempt $((attempt + 1))/$max_attempts): $*" >&2
sleep "$sleep_seconds"
attempt=$((attempt + 1))
done
}
cleanup() { cleanup() {
status="$?" status="$?"
collect_logs "$status" collect_logs "$status"
@@ -70,7 +87,7 @@ cleanup() {
} }
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
docker compose $compose_files up -d redis mysql-debug php1 retry_command "${PHP_CI_DOCKER_RETRIES:-3}" docker compose $compose_files up -d redis mysql-debug php1
docker compose $compose_files exec -T php1 sh -lc ' docker compose $compose_files exec -T php1 sh -lc '
set -eu set -eu
+26
View File
@@ -6239,6 +6239,32 @@ paths:
'200': '200':
description: Success description: Success
/order-bookings/booking-confirmation/resend:
post:
tags:
- Bookings
summary: Resend order booking confirmation
description: Resends the customer booking confirmation email for an order booking. Requires `resend_booking_confirmations` and access to the booking's department.
operationId: resendOrderBookingConfirmation
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [id]
properties:
id: {type: integer}
responses:
'200':
description: Booking confirmation resent successfully
content:
application/json:
schema: {}
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
/order-bookings/complete: /order-bookings/complete:
post: post:
tags: tags:
@@ -3,6 +3,7 @@
namespace routes; namespace routes;
use classes\authentication; use classes\authentication;
use classes\email;
use classes\order_bookings_counts_cache; use classes\order_bookings_counts_cache;
use classes\order_bookings_list_cache; use classes\order_bookings_list_cache;
use classes\redis; use classes\redis;
@@ -368,6 +369,29 @@ class orderBookingRoute
] ]
); );
$this->post('/order-bookings/booking-confirmation/resend', function () {
global $response;
$object = self::getTargetObject();
if (!$object || !$object->exists()) {
$response->error('Order booking does not exist.', 400);
}
self::requirePermission('resend_booking_confirmations');
self::requireDepartmentAccess((int)$object->department->value());
(new email())->sendOrderBookingConfirmationEmail($object);
$response->success([
'message' => 'Booking confirmation resent successfully.',
'booking' => $object->asArray(),
]);
},
[
'resend_booking_confirmations' => 'Permission for department admins to resend order booking confirmation emails.'
]
);
$this->post('/order-bookings/complete', function () { $this->post('/order-bookings/complete', function () {
// Require the user to be logged in // Require the user to be logged in
global $response; global $response;
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
putenv('EMAIL_FAKE_MODE=1');
usesApiSuite();
it('allows department admins to resend order booking confirmations', function (): void {
$customer = api_fixtures()->createUser([
'display_name' => 'Resend Booking Confirmation Customer',
'email' => 'resend-booking-confirmation@example.test',
]);
$branding = api_fixtures()->createBranding([
'name' => 'Resend Booking Confirmation Brand',
'address' => 'Resend Booking Confirmation Address 1',
]);
$department = api_fixtures()->createDepartment([
'name' => 'Resend Booking Confirmation Department',
'branding' => $branding['id'],
]);
$booking = api_fixtures()->createOrderBooking([
'customer_number' => $customer['customer_number'],
'department' => $department['id'],
'reference' => 'RESEND-CONFIRMATION',
'reg_1' => 'RESEND1',
]);
$session = api_fixtures()->createUserSession([
'resend_booking_confirmations',
'department_access_' . $department['id'],
]);
$response = api_client()->post('/order-bookings/booking-confirmation/resend', [
'id' => $booking['id'],
], $session['headers']);
$response
->assertStatus(200)
->assertEnvelope()
->assertSuccess();
expect($response->data())
->toBeArray()
->toHaveKey('message', 'Booking confirmation resent successfully.')
->toHaveKey('booking')
->and($response->data()['booking'])
->toBeArray()
->and($response->data()['booking']['id'] ?? null)
->toBe($booking['id'])
->and($response->data()['booking']['reference'] ?? null)
->toBe('RESEND-CONFIRMATION');
});
it('requires department access when resending order booking confirmations', function (): void {
$customer = api_fixtures()->createUser([
'display_name' => 'Resend Booking Confirmation Foreign Customer',
]);
$department = api_fixtures()->createDepartment([
'name' => 'Resend Booking Confirmation Foreign Department',
]);
$booking = api_fixtures()->createOrderBooking([
'customer_number' => $customer['customer_number'],
'department' => $department['id'],
]);
$session = api_fixtures()->createUserSession([
'resend_booking_confirmations',
]);
$response = api_client()->post('/order-bookings/booking-confirmation/resend', [
'id' => $booking['id'],
], $session['headers']);
$response
->assertStatus(403)
->assertEnvelope()
->assertSuccess(false)
->assertMissingPermissions(['department_access_' . $department['id']]);
});
@@ -64,6 +64,7 @@ it('allows customer self-serve permission to execute START without department ac
'lane_id' => (int)$scenario['lane']['id'], 'lane_id' => (int)$scenario['lane']['id'],
'command' => 'START', 'command' => 'START',
'license_plate' => (string)$scenario['vehicle']['reg'], 'license_plate' => (string)$scenario['vehicle']['reg'],
'defer_relay_side_effects' => true,
], api_fixtures()->bearerHeaders($token)); ], api_fixtures()->bearerHeaders($token));
$response $response
@@ -169,6 +170,7 @@ it('still allows elevated operators with department access to execute lane comma
'lane_id' => (int)$scenario['lane']['id'], 'lane_id' => (int)$scenario['lane']['id'],
'command' => 'START', 'command' => 'START',
'license_plate' => 'OP' . (int)$scenario['lane']['id'], 'license_plate' => 'OP' . (int)$scenario['lane']['id'],
'defer_relay_side_effects' => true,
], $session['headers']); ], $session['headers']);
$response $response