Add API test suite for Bird Voice Webhook endpoints, including comprehensive lifecycle tests for inbound call handling scenarios. Extend fixtures with department gate creation support and update OpenAPI spec validations.

This commit is contained in:
Jeppe Bundgaard
2026-04-13 17:08:37 +02:00
parent 2a8e8986e6
commit 8465ee794d
16 changed files with 3036 additions and 2106 deletions
+67 -15
View File
@@ -133,16 +133,10 @@ class ordersRoute
if ($targetUser->requiresReference() && empty($data['reference'])) {
$response->error('Reference is required by the customer', 400);
}
// Get the registration number
$reg_1 = $data['reg_1'];
// Get the registration numbers (If they are set, they 2-3 are optional)
$reg_2 = $data['reg_2'] ?? '';
$reg_3 = $data['reg_3'] ?? '';
// Strip the registration numbers of any whitespace
$reg_1 = preg_replace('/\s+/', '', $reg_1);
$reg_2 = preg_replace('/\s+/', '', $reg_2);
$reg_3 = preg_replace('/\s+/', '', $reg_3);
try {
$reg_1 = orders_input_normalizer::normalizeRegistrationNumber($data['reg_1']);
$reg_2 = orders_input_normalizer::normalizeRegistrationNumber($data['reg_2'] ?? '');
$reg_3 = orders_input_normalizer::normalizeRegistrationNumber($data['reg_3'] ?? '');
$createdAt = orders_input_normalizer::normalizeCreatedAt($data['created_at'] ?? date('Y-m-d H:i:s'));
$includeInInvoice = array_key_exists('include_in_invoice', $data)
? orders_input_normalizer::normalizeIncludeInInvoice($data['include_in_invoice'])
@@ -942,6 +936,7 @@ class ordersRoute
if (!is_array($data)) {
$data = [];
}
$data = $this->normalizeLegacyEditableFieldPayload($data, $response);
// Check if the required fields are set
if (!isset($data['id'])) {
$response->error('ID is required', 400);
@@ -973,6 +968,11 @@ class ordersRoute
// Include the order ID (Even though it is not editable)
'id',
'po',
'reference',
'notes',
'reg_1',
'reg_2',
'reg_3',
];
// Check if the $data contains any non-allowed keys
foreach ( $data as $key => $value ) {
@@ -987,13 +987,13 @@ class ordersRoute
}
// Registration numbers
if (isset($data['reg_1'])) {
$order->reg_1->set((string)$data['reg_1']);
$order->reg_1->set($this->normalizeRegistrationNumberOrError($data['reg_1']));
}
if (isset($data['reg_2'])) {
$order->reg_2->set((string)$data['reg_2']);
$order->reg_2->set($this->normalizeRegistrationNumberOrError($data['reg_2']));
}
if (isset($data['reg_3'])) {
$order->reg_3->set((string)$data['reg_3']);
$order->reg_3->set($this->normalizeRegistrationNumberOrError($data['reg_3']));
}
// Reference
if (isset($data['reference'])) {
@@ -1028,15 +1028,15 @@ class ordersRoute
}
// If the registration number is set, validate it
if (isset($data['reg_1'])) {
$order->reg_1->set($data['reg_1']);
$order->reg_1->set($this->normalizeRegistrationNumberOrError($data['reg_1']));
}
// If the registration number 2 is set, validate it
if (isset($data['reg_2'])) {
$order->reg_2->set($data['reg_2']);
$order->reg_2->set($this->normalizeRegistrationNumberOrError($data['reg_2']));
}
// If the registration number 3 is set, validate it
if (isset($data['reg_3'])) {
$order->reg_3->set($data['reg_3']);
$order->reg_3->set($this->normalizeRegistrationNumberOrError($data['reg_3']));
}
// If the PO is set, validate it
if (isset($data['po'])) {
@@ -1097,6 +1097,47 @@ class ordersRoute
}
}
private function normalizeLegacyEditableFieldPayload(array $data, response $response): array
{
if (!array_key_exists('field', $data) && !array_key_exists('value', $data)) {
return $data;
}
if (!array_key_exists('field', $data) || !array_key_exists('value', $data)) {
$response->error('Both legacy field and value are required', 400);
}
$field = is_string($data['field']) ? trim($data['field']) : '';
$allowedLegacyFields = ['reference', 'notes', 'reg_1', 'reg_2', 'reg_3'];
if ($field === '' || !in_array($field, $allowedLegacyFields, true)) {
$displayField = is_scalar($data['field']) || $data['field'] === null
? (string)$data['field']
: gettype($data['field']);
$response->error('Unsupported legacy order field: ' . $displayField, 400);
}
if (!array_key_exists($field, $data)) {
$data[$field] = $data['value'];
}
unset($data['field'], $data['value']);
return $data;
}
private function normalizeRegistrationNumberOrError(mixed $value): string
{
global $response;
try {
return orders_input_normalizer::normalizeRegistrationNumber($value);
} catch (\InvalidArgumentException $e) {
$response->error($e->getMessage(), 400);
}
}
/**
* @param array<int, array<string, mixed>> $orders
* @return array<int, array<string, mixed>>
@@ -1359,6 +1400,17 @@ class ordersRoute
if (!isset($data['reg_1'])) {
$response->error('Registration number 1 is required', 400);
}
try {
$data['reg_1'] = orders_input_normalizer::normalizeRegistrationNumber($data['reg_1']);
if (array_key_exists('reg_2', $data)) {
$data['reg_2'] = orders_input_normalizer::normalizeRegistrationNumber($data['reg_2']);
}
if (array_key_exists('reg_3', $data)) {
$data['reg_3'] = orders_input_normalizer::normalizeRegistrationNumber($data['reg_3']);
}
} catch (\InvalidArgumentException $e) {
$response->error($e->getMessage(), 400);
}
if (strlen($data['reg_1']) < 4) {
$response->error('Registration number 1 must be at least 4 characters', 400);
}