1035 lines
44 KiB
PHP
1035 lines
44 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use classes\limited_backoffice_service;
|
|
|
|
usesApiSuite();
|
|
|
|
function limited_backoffice_manager_session(array $departmentIds, array $extraPermissions = []): array
|
|
{
|
|
$permissions = [
|
|
limited_backoffice_service::PERMISSION_ACCESS,
|
|
limited_backoffice_service::PERMISSION_MANAGE_PRICES,
|
|
limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES,
|
|
];
|
|
foreach ($departmentIds as $departmentId) {
|
|
$permissions[] = 'department_access_' . (int)$departmentId;
|
|
}
|
|
|
|
return api_fixtures()->createUserSession(array_values(array_unique(array_merge($permissions, $extraPermissions))));
|
|
}
|
|
|
|
function limited_backoffice_all_role_permissions(): array
|
|
{
|
|
return [
|
|
'user',
|
|
'permissions_list_own',
|
|
'list_orders',
|
|
'add_order',
|
|
'edit_order',
|
|
'delete_order',
|
|
'list_order_items',
|
|
'add_order_items',
|
|
'edit_order_items',
|
|
'delete_order_items',
|
|
'charge_order',
|
|
'list_bookings',
|
|
'list_own_bookings',
|
|
'edit_bookings',
|
|
'add_booking',
|
|
'complete_bookings',
|
|
'resend_booking_confirmations',
|
|
'department_timebookings_entries_get',
|
|
'department_timebookings_entries_post',
|
|
'department_timebookings_entries_put',
|
|
'statistics_orders_new',
|
|
'statistics_bookings_new',
|
|
];
|
|
}
|
|
|
|
function limited_backoffice_price_insert(int $departmentId, int $productId, int $price): void
|
|
{
|
|
$statement = api_test_runtime()->db()->prepare(
|
|
'INSERT INTO `product_department_prices` (`department_id`, `product_id`, `price`)
|
|
VALUES (?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE `price` = VALUES(`price`)'
|
|
);
|
|
$statement->bind_param('iii', $departmentId, $productId, $price);
|
|
$statement->execute();
|
|
$statement->close();
|
|
|
|
api_fixtures()->cleanupDeleteWhere('product_department_prices', [
|
|
'department_id' => $departmentId,
|
|
'product_id' => $productId,
|
|
]);
|
|
}
|
|
|
|
function limited_backoffice_price_value(int $departmentId, int $productId): ?int
|
|
{
|
|
$row = api_test_runtime()->queryOne(
|
|
'SELECT `price` FROM `product_department_prices` WHERE `department_id` = ' . $departmentId .
|
|
' AND `product_id` = ' . $productId . ' LIMIT 1'
|
|
);
|
|
|
|
return $row === null ? null : (int)$row['price'];
|
|
}
|
|
|
|
function limited_backoffice_cleanup_created_employee(int $employeeId): void
|
|
{
|
|
$row = api_test_runtime()->queryOne(
|
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ' . $employeeId . ' LIMIT 1'
|
|
);
|
|
$groupId = (int)($row['managed_group_id'] ?? 0);
|
|
|
|
api_fixtures()->cleanupDeleteWhere('limited_backoffice_employees', ['user_id' => $employeeId]);
|
|
api_fixtures()->cleanupDeleteWhere('tokens', ['user_id' => $employeeId]);
|
|
api_fixtures()->cleanupDeleteById('users', $employeeId);
|
|
if ($groupId > 0) {
|
|
api_fixtures()->cleanupDeleteWhere('groups_permissions', ['group_id' => $groupId]);
|
|
api_fixtures()->cleanupDeleteById('groups', $groupId);
|
|
}
|
|
}
|
|
|
|
function limited_backoffice_without_users_deleted_at(callable $callback): void
|
|
{
|
|
$db = api_test_runtime()->db();
|
|
$column = $db->query("SHOW COLUMNS FROM `users` LIKE 'deleted_at'");
|
|
if ($column === false) {
|
|
throw new RuntimeException('Unable to inspect users.deleted_at test column.');
|
|
}
|
|
|
|
$hadColumn = (int)$column->num_rows > 0;
|
|
if ($hadColumn) {
|
|
$db->query('ALTER TABLE `users` DROP COLUMN `deleted_at`');
|
|
}
|
|
|
|
try {
|
|
$callback();
|
|
} finally {
|
|
if ($hadColumn) {
|
|
$db->query('ALTER TABLE `users` ADD COLUMN `deleted_at` DATETIME NULL');
|
|
}
|
|
}
|
|
}
|
|
|
|
function limited_backoffice_without_department_prices_updated_at(callable $callback): void
|
|
{
|
|
$db = api_test_runtime()->db();
|
|
$column = $db->query("SHOW COLUMNS FROM `product_department_prices` LIKE 'updated_at'");
|
|
if ($column === false) {
|
|
throw new RuntimeException('Unable to inspect product_department_prices.updated_at test column.');
|
|
}
|
|
|
|
$hadColumn = (int)$column->num_rows > 0;
|
|
if ($hadColumn) {
|
|
$db->query('ALTER TABLE `product_department_prices` DROP COLUMN `updated_at`');
|
|
}
|
|
|
|
try {
|
|
$callback();
|
|
} finally {
|
|
if ($hadColumn) {
|
|
$db->query(
|
|
'ALTER TABLE `product_department_prices`
|
|
ADD COLUMN `updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
AFTER `created_at`'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
it('lists and updates explicit prices only for assigned departments', function (): void {
|
|
api_test_covers('GET /limited-backoffice/departments', 'happy');
|
|
api_test_covers('GET /limited-backoffice/departments/{departmentId}/prices', 'happy');
|
|
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/prices', 'happy');
|
|
|
|
$productDeletedAtColumn = api_test_runtime()->db()->query("SHOW COLUMNS FROM `products` LIKE 'deleted_at'");
|
|
expect($productDeletedAtColumn)->not->toBeFalse();
|
|
expect((int)$productDeletedAtColumn->num_rows)->toBe(0);
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Prices Own']);
|
|
$otherDepartment = api_fixtures()->createDepartment(['name' => 'Limited Prices Other']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Washes']);
|
|
$product = api_fixtures()->createProduct([
|
|
'name' => 'Limited Wash Product',
|
|
'category' => $category['id'],
|
|
'price' => 98765,
|
|
]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
api_fixtures()->linkDepartmentCategory((int)$otherDepartment['id'], (int)$category['id']);
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$product['id'], 1234);
|
|
limited_backoffice_price_insert((int)$otherDepartment['id'], (int)$product['id'], 4321);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
$departments = api_client()->get('/limited-backoffice/departments', $session['headers']);
|
|
$departments
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(array_column($departments->data(), 'id'))
|
|
->toContain((int)$department['id'])
|
|
->not->toContain((int)$otherDepartment['id']);
|
|
|
|
$prices = api_client()->get('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', $session['headers']);
|
|
$prices
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($prices->body)->not->toContain('98765');
|
|
expect($prices->data()['categories'][0]['products'][0])
|
|
->toMatchArray([
|
|
'id' => (int)$product['id'],
|
|
'name' => 'Limited Wash Product',
|
|
'price' => 1234,
|
|
]);
|
|
|
|
$updated = api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$product['id'], 'price' => '2222'],
|
|
],
|
|
], $session['headers']);
|
|
|
|
$updated
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$product['id']))->toBe(2222);
|
|
expect(limited_backoffice_price_value((int)$otherDepartment['id'], (int)$product['id']))->toBe(4321);
|
|
});
|
|
|
|
it('updates department prices when the price table has no updated_at column', function (): void {
|
|
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/prices', 'schema compatibility');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Prices Legacy Schema']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Prices Legacy Category']);
|
|
$products = [
|
|
api_fixtures()->createProduct(['name' => 'Legacy Price One', 'category' => $category['id']]),
|
|
api_fixtures()->createProduct(['name' => 'Legacy Price Two', 'category' => $category['id']]),
|
|
api_fixtures()->createProduct(['name' => 'Legacy Price Three', 'category' => $category['id']]),
|
|
];
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
|
|
foreach ($products as $index => $product) {
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$product['id'], 100 + $index);
|
|
}
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
limited_backoffice_without_department_prices_updated_at(function () use ($department, $products, $session): void {
|
|
$updated = api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$products[0]['id'], 'price' => '999999'],
|
|
['product_id' => (int)$products[1]['id'], 'price' => '999999'],
|
|
['product_id' => (int)$products[2]['id'], 'price' => '99999'],
|
|
],
|
|
], $session['headers']);
|
|
|
|
$updated
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$products[0]['id']))->toBe(999999);
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$products[1]['id']))->toBe(999999);
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$products[2]['id']))->toBe(99999);
|
|
});
|
|
});
|
|
|
|
it('deduplicates products from duplicate department category links', function (): void {
|
|
api_test_covers('GET /limited-backoffice/departments/{departmentId}/prices', 'dedupe');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Duplicate Products']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Duplicate Category']);
|
|
$firstProduct = api_fixtures()->createProduct([
|
|
'name' => 'Duplicate Price A',
|
|
'category' => $category['id'],
|
|
]);
|
|
$secondProduct = api_fixtures()->createProduct([
|
|
'name' => 'Duplicate Price B',
|
|
'category' => $category['id'],
|
|
]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$firstProduct['id'], 111);
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$secondProduct['id'], 222);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
$response = api_client()->get('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', $session['headers']);
|
|
$response
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$productIds = [];
|
|
foreach ($response->data()['categories'] as $departmentCategory) {
|
|
foreach ($departmentCategory['products'] as $departmentProduct) {
|
|
$productIds[] = (int)$departmentProduct['id'];
|
|
}
|
|
}
|
|
|
|
expect($productIds)->toBe([(int)$firstProduct['id'], (int)$secondProduct['id']]);
|
|
});
|
|
|
|
it('deduplicates missing product setup gaps from duplicate department category links', function (): void {
|
|
api_test_covers('GET /limited-backoffice/departments/{departmentId}/prices', 'dedupe failure');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Duplicate Setup Gap']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Duplicate Setup Category']);
|
|
$product = api_fixtures()->createProduct([
|
|
'name' => 'Duplicate Missing Product',
|
|
'category' => $category['id'],
|
|
'price' => 88888,
|
|
]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
$response = api_client()->get('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', $session['headers']);
|
|
$response
|
|
->assertStatus(409)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Department price setup is incomplete.');
|
|
|
|
expect(array_column($response->data()['missing_products'], 'id'))->toBe([(int)$product['id']]);
|
|
expect($response->body)->not->toContain('88888');
|
|
});
|
|
|
|
it('rejects cross-department price access, body spoofing, and outside products', function (): void {
|
|
api_test_covers('GET /limited-backoffice/departments/{departmentId}/prices', 'auth');
|
|
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/prices', 'auth');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Scope Own']);
|
|
$otherDepartment = api_fixtures()->createDepartment(['name' => 'Limited Scope Other']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Scope Category']);
|
|
$otherCategory = api_fixtures()->createCategory(['name' => 'Limited Scope Other Category']);
|
|
$product = api_fixtures()->createProduct(['category' => $category['id']]);
|
|
$outsideProduct = api_fixtures()->createProduct(['category' => $otherCategory['id']]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
api_fixtures()->linkDepartmentCategory((int)$otherDepartment['id'], (int)$otherCategory['id']);
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$product['id'], 100);
|
|
limited_backoffice_price_insert((int)$otherDepartment['id'], (int)$outsideProduct['id'], 200);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
api_client()->get('/limited-backoffice/departments/' . (int)$otherDepartment['id'] . '/prices', $session['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMissingPermissions(['department_access_' . (int)$otherDepartment['id']]);
|
|
|
|
api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'department_id' => (int)$otherDepartment['id'],
|
|
'prices' => [
|
|
['product_id' => (int)$product['id'], 'price' => 111],
|
|
],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Department ID in body does not match the route.');
|
|
|
|
api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$product['id'], 'price' => 111],
|
|
['product_id' => (int)$outsideProduct['id'], 'price' => 111],
|
|
],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Product is not available for this department.');
|
|
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$product['id']))->toBe(100);
|
|
expect(limited_backoffice_price_value((int)$otherDepartment['id'], (int)$outsideProduct['id']))->toBe(200);
|
|
});
|
|
|
|
it('fails price setup gaps without exposing product defaults', function (): void {
|
|
api_test_covers('GET /limited-backoffice/departments/{departmentId}/prices', 'failure');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Setup Gap']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Setup Gap Category']);
|
|
$product = api_fixtures()->createProduct([
|
|
'name' => 'Setup Gap Product',
|
|
'category' => $category['id'],
|
|
'price' => 87654,
|
|
]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
$response = api_client()->get('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', $session['headers']);
|
|
$response
|
|
->assertStatus(409)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Department price setup is incomplete.');
|
|
|
|
expect($response->body)->not->toContain('87654');
|
|
expect($response->data()['missing_products'][0]['id'] ?? null)->toBe((int)$product['id']);
|
|
});
|
|
|
|
it('defaults missing custom-only department prices to sentinel without exposing fallback prices', function (): void {
|
|
api_test_covers('GET /limited-backoffice/departments', 'happy');
|
|
api_test_covers('GET /limited-backoffice/departments/{departmentId}/prices', 'happy');
|
|
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/prices', 'happy');
|
|
|
|
$department = api_fixtures()->createDepartment([
|
|
'name' => 'Limited Custom Pricing Only',
|
|
'custom_pricing_only' => 1,
|
|
]);
|
|
$otherDepartment = api_fixtures()->createDepartment(['name' => 'Limited Other Pricing']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Custom Pricing Category']);
|
|
$product = api_fixtures()->createProduct([
|
|
'name' => 'Custom Missing Product',
|
|
'category' => $category['id'],
|
|
'price' => 87654,
|
|
]);
|
|
$otherProduct = api_fixtures()->createProduct([
|
|
'name' => 'Custom Missing Other Product',
|
|
'category' => $category['id'],
|
|
'price' => 76543,
|
|
]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
api_fixtures()->linkDepartmentCategory((int)$otherDepartment['id'], (int)$category['id']);
|
|
limited_backoffice_price_insert((int)$otherDepartment['id'], (int)$product['id'], 4321);
|
|
limited_backoffice_price_insert((int)$otherDepartment['id'], (int)$otherProduct['id'], 5432);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
$departments = api_client()->get('/limited-backoffice/departments', $session['headers']);
|
|
$departments
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
expect($departments->data()[0]['custom_pricing_only'] ?? null)->toBeTrue();
|
|
|
|
$response = api_client()->get('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', $session['headers']);
|
|
$response
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($response->body)->not->toContain('87654');
|
|
expect($response->body)->not->toContain('76543');
|
|
expect($response->body)->not->toContain('4321');
|
|
expect($response->body)->not->toContain('5432');
|
|
expect($response->data()['department']['custom_pricing_only'] ?? null)->toBeTrue();
|
|
$products = [];
|
|
foreach ($response->data()['categories'] as $departmentCategory) {
|
|
foreach ($departmentCategory['products'] as $departmentProduct) {
|
|
$products[(int)$departmentProduct['id']] = $departmentProduct;
|
|
}
|
|
}
|
|
expect($products[(int)$product['id']]['price'] ?? null)->toBe(\objects\products_o::CUSTOM_PRICING_MISSING_PRICE);
|
|
expect($products[(int)$otherProduct['id']]['price'] ?? null)->toBe(\objects\products_o::CUSTOM_PRICING_MISSING_PRICE);
|
|
|
|
$updated = api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$product['id'], 'price' => 2222],
|
|
],
|
|
], $session['headers']);
|
|
$updated
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$updatedProducts = [];
|
|
foreach ($updated->data()['categories'] as $departmentCategory) {
|
|
foreach ($departmentCategory['products'] as $departmentProduct) {
|
|
$updatedProducts[(int)$departmentProduct['id']] = $departmentProduct;
|
|
}
|
|
}
|
|
expect($updated->body)->not->toContain('87654');
|
|
expect($updated->body)->not->toContain('76543');
|
|
expect($updated->body)->not->toContain('4321');
|
|
expect($updated->body)->not->toContain('5432');
|
|
expect($updatedProducts[(int)$product['id']]['price'] ?? null)->toBe(2222);
|
|
expect($updatedProducts[(int)$otherProduct['id']]['price'] ?? null)->toBe(\objects\products_o::CUSTOM_PRICING_MISSING_PRICE);
|
|
});
|
|
|
|
it('rejects invalid price batches and leaves existing prices unchanged', function (): void {
|
|
api_test_covers('PUT /limited-backoffice/departments/{departmentId}/prices', 'validation');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Invalid Prices']);
|
|
$category = api_fixtures()->createCategory(['name' => 'Limited Invalid Prices Category']);
|
|
$firstProduct = api_fixtures()->createProduct(['category' => $category['id']]);
|
|
$secondProduct = api_fixtures()->createProduct(['category' => $category['id']]);
|
|
api_fixtures()->linkDepartmentCategory((int)$department['id'], (int)$category['id']);
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$firstProduct['id'], 100);
|
|
limited_backoffice_price_insert((int)$department['id'], (int)$secondProduct['id'], 200);
|
|
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
foreach ([null, '', 'abc', -1] as $invalidPrice) {
|
|
api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$firstProduct['id'], 'price' => 999],
|
|
['product_id' => (int)$secondProduct['id'], 'price' => $invalidPrice],
|
|
],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false);
|
|
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$firstProduct['id']))->toBe(100);
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$secondProduct['id']))->toBe(200);
|
|
}
|
|
|
|
api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$firstProduct['id'], 'price' => 999],
|
|
['product_id' => (int)$firstProduct['id'], 'price' => 888],
|
|
['product_id' => (int)$secondProduct['id'], 'price' => 777],
|
|
],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Duplicate product price rows are not allowed.');
|
|
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$firstProduct['id']))->toBe(100);
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$secondProduct['id']))->toBe(200);
|
|
|
|
api_client()->put('/limited-backoffice/departments/' . (int)$department['id'] . '/prices', [
|
|
'prices' => [
|
|
['product_id' => (int)$firstProduct['id'], 'price' => 999],
|
|
],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Price is required for every department product.');
|
|
|
|
expect(limited_backoffice_price_value((int)$department['id'], (int)$firstProduct['id']))->toBe(100);
|
|
});
|
|
|
|
it('creates updates lists and deactivates scoped employees without exposing raw permissions', function (): void {
|
|
limited_backoffice_without_users_deleted_at(function (): void {
|
|
api_test_covers('GET /limited-backoffice/roles', 'happy');
|
|
api_test_covers('GET /limited-backoffice/employees', 'happy');
|
|
api_test_covers('POST /limited-backoffice/employees', 'happy');
|
|
api_test_covers('PUT /limited-backoffice/employees/{employeeId}', 'happy');
|
|
api_test_covers('DELETE /limited-backoffice/employees/{employeeId}', 'happy');
|
|
|
|
$usersDeletedAtColumn = api_test_runtime()->db()->query("SHOW COLUMNS FROM `users` LIKE 'deleted_at'");
|
|
expect($usersDeletedAtColumn)->not->toBeFalse();
|
|
expect((int)$usersDeletedAtColumn->num_rows)->toBe(0);
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Employee Department']);
|
|
$session = limited_backoffice_manager_session([(int)$department['id']], limited_backoffice_all_role_permissions());
|
|
|
|
$roles = api_client()->get('/limited-backoffice/roles', $session['headers']);
|
|
$roles
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect(array_column($roles->data(), 'key'))->toBe(['viewer', 'cashier', 'booking_coordinator', 'operations_lead', 'department_admin']);
|
|
$rolesByKey = array_column($roles->data(), null, 'key');
|
|
expect($rolesByKey['viewer']['permission_groups'] ?? null)->toBe([
|
|
[
|
|
'key' => 'account',
|
|
'capabilities' => ['sign_in', 'view_own_permissions'],
|
|
],
|
|
]);
|
|
$departmentAdminGroups = array_column($rolesByKey['department_admin']['permission_groups'] ?? [], 'capabilities', 'key');
|
|
expect($departmentAdminGroups['limited_backoffice'] ?? null)->toBe([
|
|
'open_limited_backoffice',
|
|
'manage_department_prices',
|
|
'manage_employee_access',
|
|
]);
|
|
expect($roles->body)->not->toContain('department_access_');
|
|
$rolePayload = $roles->data();
|
|
$rolePayloadStrings = [];
|
|
array_walk_recursive($rolePayload, static function ($value) use (&$rolePayloadStrings): void {
|
|
if (is_string($value)) {
|
|
$rolePayloadStrings[] = $value;
|
|
}
|
|
});
|
|
foreach ([
|
|
'list_orders',
|
|
'add_order',
|
|
'edit_order',
|
|
'delete_order',
|
|
'list_order_items',
|
|
'add_order_items',
|
|
'edit_order_items',
|
|
'delete_order_items',
|
|
'charge_order',
|
|
'list_bookings',
|
|
'list_own_bookings',
|
|
'edit_bookings',
|
|
'add_booking',
|
|
'complete_bookings',
|
|
'resend_booking_confirmations',
|
|
'department_timebookings_entries_get',
|
|
'department_timebookings_entries_post',
|
|
'department_timebookings_entries_put',
|
|
'statistics_orders_new',
|
|
'statistics_bookings_new',
|
|
'limited_backoffice_access',
|
|
'limited_backoffice_prices_manage',
|
|
'limited_backoffice_employees_manage',
|
|
] as $rawPermission) {
|
|
expect($rolePayloadStrings)->not->toContain($rawPermission);
|
|
}
|
|
|
|
$created = api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Limited Cashier',
|
|
'email' => 'limited-cashier@example.test',
|
|
'phone_country_code' => 45,
|
|
'phone' => 12345678,
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'cashier',
|
|
'department_ids' => [(int)$department['id']],
|
|
], $session['headers']);
|
|
|
|
$created
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$employeeId = (int)($created->data()['id'] ?? 0);
|
|
expect($employeeId)->toBeGreaterThan(0);
|
|
limited_backoffice_cleanup_created_employee($employeeId);
|
|
expect($created->data()['user_id'] ?? null)->toBe($employeeId);
|
|
expect($created->data()['email'] ?? null)->toBe('limited-cashier@example.test');
|
|
expect($created->data()['phone_country_code'] ?? null)->toBe(45);
|
|
expect($created->data()['phone'] ?? null)->toBe(12345678);
|
|
expect($created->body)->not->toContain('department_access_');
|
|
expect($created->body)->not->toContain('permissions');
|
|
|
|
$groupRow = api_test_runtime()->queryOne(
|
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ' . $employeeId . ' LIMIT 1'
|
|
);
|
|
$groupId = (int)($groupRow['managed_group_id'] ?? 0);
|
|
$permissionRows = api_test_runtime()->db()->query(
|
|
'SELECT `permission` FROM `groups_permissions` WHERE `group_id` = ' . $groupId
|
|
)->fetch_all(MYSQLI_ASSOC);
|
|
$permissions = array_column($permissionRows, 'permission');
|
|
expect($permissions)
|
|
->toContain('department_access_' . (int)$department['id'])
|
|
->toContain('employee_public_data')
|
|
->toContain('add_order')
|
|
->not->toContain('superuser');
|
|
|
|
$publicEmployees = api_client()->get('/public/employees');
|
|
$publicEmployeeIds = array_map('intval', array_column($publicEmployees->data(), 'id'));
|
|
expect($publicEmployeeIds)->toContain($employeeId);
|
|
|
|
$updated = api_client()->put('/limited-backoffice/employees/' . $employeeId, [
|
|
'display_name' => 'Limited Lead',
|
|
'email' => 'limited-lead@example.test',
|
|
'phone_country_code' => 358,
|
|
'phone' => 87654321,
|
|
'role_key' => 'operations_lead',
|
|
'department_ids' => [(int)$department['id']],
|
|
], $session['headers']);
|
|
$updated
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($updated->data()['display_name'] ?? null)->toBe('Limited Lead');
|
|
expect($updated->data()['email'] ?? null)->toBe('limited-lead@example.test');
|
|
expect($updated->data()['phone_country_code'] ?? null)->toBe(358);
|
|
expect($updated->data()['phone'] ?? null)->toBe(87654321);
|
|
expect($updated->data()['role']['key'] ?? null)->toBe('operations_lead');
|
|
|
|
$list = api_client()->get('/limited-backoffice/employees', $session['headers']);
|
|
$ids = array_column($list->data(), 'id');
|
|
expect($ids)->toContain($employeeId);
|
|
$listedEmployee = null;
|
|
foreach ($list->data() as $employee) {
|
|
if ((int)($employee['id'] ?? 0) === $employeeId) {
|
|
$listedEmployee = $employee;
|
|
break;
|
|
}
|
|
}
|
|
expect($listedEmployee)->not->toBeNull();
|
|
expect($listedEmployee['user_id'] ?? null)->toBe($employeeId);
|
|
expect($listedEmployee['email'] ?? null)->toBe('limited-lead@example.test');
|
|
expect($listedEmployee['phone_country_code'] ?? null)->toBe(358);
|
|
expect($listedEmployee['phone'] ?? null)->toBe(87654321);
|
|
expect($list->body)->not->toContain('department_access_');
|
|
|
|
$deactivated = api_client()->delete('/limited-backoffice/employees/' . $employeeId, null, $session['headers']);
|
|
$deactivated
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
expect($deactivated->data()['active'] ?? true)->toBeFalse();
|
|
$userRow = api_test_runtime()->queryOne('SELECT `password`, `group_id` FROM `users` WHERE `id` = ' . $employeeId);
|
|
expect($userRow)->not->toBeNull();
|
|
expect(array_key_exists('password', $userRow ?? []))->toBeTrue();
|
|
expect($userRow['password'])->toBeNull();
|
|
expect((int)($userRow['group_id'] ?? -1))->toBe(0);
|
|
$publicEmployeesAfterDeactivation = api_client()->get('/public/employees');
|
|
$publicEmployeeIdsAfterDeactivation = array_map('intval', array_column($publicEmployeesAfterDeactivation->data(), 'id'));
|
|
expect($publicEmployeeIdsAfterDeactivation)->not->toContain($employeeId);
|
|
$employeeRow = api_test_runtime()->queryOne(
|
|
'SELECT `deactivated_at` FROM `limited_backoffice_employees` WHERE `user_id` = ' . $employeeId . ' LIMIT 1'
|
|
);
|
|
expect($employeeRow['deactivated_at'] ?? null)->not->toBeNull();
|
|
});
|
|
});
|
|
|
|
it('caps limited employee permissions to the manager permissions and selected departments', function (): void {
|
|
api_test_covers('POST /limited-backoffice/employees', 'auth');
|
|
api_test_covers('GET /limited-backoffice/roles', 'auth');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Permission Cap']);
|
|
$session = limited_backoffice_manager_session([(int)$department['id']], [
|
|
'list_orders',
|
|
]);
|
|
|
|
$roles = api_client()->get('/limited-backoffice/roles', $session['headers']);
|
|
$rolesByKey = array_column($roles->data(), null, 'key');
|
|
$operationsLeadGroups = array_column($rolesByKey['operations_lead']['permission_groups'] ?? [], 'capabilities', 'key');
|
|
expect($operationsLeadGroups['account'] ?? null)->toBe(['sign_in']);
|
|
expect($operationsLeadGroups['orders'] ?? null)->toBe(['view_orders']);
|
|
expect($roles->body)->not->toContain('create_orders');
|
|
expect($roles->body)->not->toContain('view_order_statistics');
|
|
|
|
$created = api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Limited Capped Lead',
|
|
'email' => 'limited-capped@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'operations_lead',
|
|
'department_ids' => [(int)$department['id']],
|
|
], $session['headers']);
|
|
$created
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$employeeId = (int)($created->data()['id'] ?? 0);
|
|
expect($employeeId)->toBeGreaterThan(0);
|
|
limited_backoffice_cleanup_created_employee($employeeId);
|
|
|
|
$groupRow = api_test_runtime()->queryOne(
|
|
'SELECT `managed_group_id` FROM `limited_backoffice_employees` WHERE `user_id` = ' . $employeeId . ' LIMIT 1'
|
|
);
|
|
$groupId = (int)($groupRow['managed_group_id'] ?? 0);
|
|
$permissionRows = api_test_runtime()->db()->query(
|
|
'SELECT `permission` FROM `groups_permissions` WHERE `group_id` = ' . $groupId
|
|
)->fetch_all(MYSQLI_ASSOC);
|
|
$permissions = array_column($permissionRows, 'permission');
|
|
|
|
expect($permissions)
|
|
->toContain('user')
|
|
->toContain('employee_public_data')
|
|
->toContain('list_orders')
|
|
->toContain('department_access_' . (int)$department['id'])
|
|
->not->toContain('add_order')
|
|
->not->toContain('delete_order')
|
|
->not->toContain('statistics_orders_new')
|
|
->not->toContain(limited_backoffice_service::PERMISSION_MANAGE_EMPLOYEES);
|
|
});
|
|
|
|
it('includes limited employees in the regular employee list and protects raw user edits', function (): void {
|
|
api_test_covers('GET /users', 'limited backoffice employee list');
|
|
api_test_covers('PUT /users', 'limited backoffice guard');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Regular List']);
|
|
$managerSession = limited_backoffice_manager_session([(int)$department['id']], [
|
|
'permissions_list_own',
|
|
]);
|
|
|
|
$regularEmployee = api_fixtures()->createUser([
|
|
'customer_number' => 0,
|
|
'display_name' => 'Regular Backoffice Employee',
|
|
], ['employee_public_data']);
|
|
|
|
$created = api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Limited Listed Employee',
|
|
'email' => 'limited-listed@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'viewer',
|
|
'department_ids' => [(int)$department['id']],
|
|
], $managerSession['headers']);
|
|
$created
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$employeeId = (int)($created->data()['id'] ?? 0);
|
|
expect($employeeId)->toBeGreaterThan(0);
|
|
limited_backoffice_cleanup_created_employee($employeeId);
|
|
|
|
$adminSession = api_fixtures()->createUserSession([
|
|
'list_users',
|
|
'edit_user',
|
|
]);
|
|
|
|
$withoutLimited = api_client()->get('/users?page=1&limit=50&filters=customer_number:0', $adminSession['headers']);
|
|
$withoutLimitedIds = array_map('intval', array_column($withoutLimited->data(), 'id'));
|
|
expect($withoutLimitedIds)->toContain((int)$regularEmployee['id']);
|
|
expect($withoutLimitedIds)->not->toContain($employeeId);
|
|
|
|
$withLimited = api_client()->get(
|
|
'/users?page=1&limit=50&filters=customer_number:0&include_limited_backoffice_employees=true',
|
|
$adminSession['headers']
|
|
);
|
|
$usersById = array_column($withLimited->data(), null, 'id');
|
|
expect(array_keys($usersById))->toContain((int)$regularEmployee['id']);
|
|
expect(array_keys($usersById))->toContain($employeeId);
|
|
expect($usersById[$employeeId]['limited_backoffice_managed'] ?? null)->toBeTrue();
|
|
expect($usersById[(int)$regularEmployee['id']]['limited_backoffice_managed'] ?? null)->toBeFalse();
|
|
|
|
$userRow = api_test_runtime()->queryOne(
|
|
'SELECT `customer_number`, `group_id` FROM `users` WHERE `id` = ' . $employeeId . ' LIMIT 1'
|
|
);
|
|
$customerNumber = (int)($userRow['customer_number'] ?? 0);
|
|
$groupId = (int)($userRow['group_id'] ?? 0);
|
|
|
|
api_client()->put('/users', [
|
|
'id' => $employeeId,
|
|
'customer_number' => $customerNumber + 1,
|
|
'role' => $groupId,
|
|
'display_name' => 'Blocked Customer Change',
|
|
], $adminSession['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Limited backoffice managed users cannot change customer number.');
|
|
|
|
api_client()->put('/users', [
|
|
'id' => $employeeId,
|
|
'customer_number' => $customerNumber,
|
|
'role' => 0,
|
|
'display_name' => 'Blocked Role Change',
|
|
], $adminSession['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Limited backoffice managed users cannot change role.');
|
|
|
|
api_client()->put('/users', [
|
|
'id' => $employeeId,
|
|
'customer_number' => $customerNumber,
|
|
'role' => $groupId,
|
|
'display_name' => 'Edited Limited Listed Employee',
|
|
], $adminSession['headers'])
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$updatedUserRow = api_test_runtime()->queryOne(
|
|
'SELECT `customer_number`, `group_id`, `display_name` FROM `users` WHERE `id` = ' . $employeeId . ' LIMIT 1'
|
|
);
|
|
expect((int)($updatedUserRow['customer_number'] ?? 0))->toBe($customerNumber);
|
|
expect((int)($updatedUserRow['group_id'] ?? 0))->toBe($groupId);
|
|
expect($updatedUserRow['display_name'] ?? null)->toBe('Edited Limited Listed Employee');
|
|
});
|
|
|
|
it('accepts employees without optional phone details', function (): void {
|
|
api_test_covers('POST /limited-backoffice/employees', 'happy');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Employee No Phone']);
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
$created = api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Limited No Phone',
|
|
'email' => 'limited-no-phone@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'viewer',
|
|
'department_ids' => [(int)$department['id']],
|
|
], $session['headers']);
|
|
|
|
$created
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$employeeId = (int)($created->data()['id'] ?? 0);
|
|
expect($employeeId)->toBeGreaterThan(0);
|
|
limited_backoffice_cleanup_created_employee($employeeId);
|
|
expect(array_key_exists('phone_country_code', $created->data()))->toBeTrue();
|
|
expect(array_key_exists('phone', $created->data()))->toBeTrue();
|
|
expect($created->data()['phone_country_code'])->toBeNull();
|
|
expect($created->data()['phone'])->toBeNull();
|
|
});
|
|
|
|
it('rejects employee scopes roles raw permissions self edits superusers and shared groups', function (): void {
|
|
api_test_covers('POST /limited-backoffice/employees', 'validation');
|
|
api_test_covers('PUT /limited-backoffice/employees/{employeeId}', 'validation');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Employee Own']);
|
|
$otherDepartment = api_fixtures()->createDepartment(['name' => 'Limited Employee Other']);
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Outside Employee',
|
|
'email' => 'outside@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'cashier',
|
|
'department_ids' => [(int)$otherDepartment['id']],
|
|
], $session['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMissingPermissions(['department_access_' . (int)$otherDepartment['id']]);
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Raw Employee',
|
|
'email' => 'raw@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'cashier',
|
|
'department_ids' => [(int)$department['id']],
|
|
'permissions' => ['superuser'],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Raw permission and group assignment is not allowed.');
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
'display_name' => 'Unknown Role Employee',
|
|
'email' => 'unknown-role@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'superuser',
|
|
'department_ids' => [(int)$department['id']],
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Unknown role.');
|
|
|
|
api_client()->put('/limited-backoffice/employees/' . (int)$session['user']['id'], [
|
|
'display_name' => 'Self Edit',
|
|
], $session['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Managers cannot edit themselves.');
|
|
|
|
$superuser = api_fixtures()->createUser(['group_id' => 1]);
|
|
api_test_runtime()->db()->query(
|
|
'INSERT INTO `limited_backoffice_employees`
|
|
(`user_id`, `managed_group_id`, `role_key`, `department_ids`, `created_by_user_id`)
|
|
VALUES (' . (int)$superuser['id'] . ", 1, 'department_admin', '[" . (int)$department['id'] . "]', " . (int)$session['user']['id'] . ')'
|
|
);
|
|
api_fixtures()->cleanupDeleteWhere('limited_backoffice_employees', ['user_id' => (int)$superuser['id']]);
|
|
|
|
api_client()->put('/limited-backoffice/employees/' . (int)$superuser['id'], [
|
|
'display_name' => 'Edited Superuser',
|
|
], $session['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Cannot manage superuser accounts.');
|
|
|
|
$sharedGroup = api_fixtures()->createGroup();
|
|
$firstSharedUser = api_fixtures()->createUser(['group_id' => $sharedGroup['id']]);
|
|
api_fixtures()->createUser(['group_id' => $sharedGroup['id']]);
|
|
$departmentJson = '[' . (int)$department['id'] . ']';
|
|
api_test_runtime()->db()->query(
|
|
'INSERT INTO `limited_backoffice_employees`
|
|
(`user_id`, `managed_group_id`, `role_key`, `department_ids`, `created_by_user_id`)
|
|
VALUES (' . (int)$firstSharedUser['id'] . ', ' . (int)$sharedGroup['id'] . ", 'viewer', '" . $departmentJson . "', " . (int)$session['user']['id'] . ')'
|
|
);
|
|
api_fixtures()->cleanupDeleteWhere('limited_backoffice_employees', ['user_id' => (int)$firstSharedUser['id']]);
|
|
|
|
api_client()->put('/limited-backoffice/employees/' . (int)$firstSharedUser['id'], [
|
|
'display_name' => 'Edited Shared Group',
|
|
], $session['headers'])
|
|
->assertStatus(403)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Cannot manage shared groups.');
|
|
});
|
|
|
|
it('rejects invalid limited backoffice employee contact details', function (): void {
|
|
api_test_covers('POST /limited-backoffice/employees', 'validation');
|
|
api_test_covers('PUT /limited-backoffice/employees/{employeeId}', 'validation');
|
|
|
|
$department = api_fixtures()->createDepartment(['name' => 'Limited Employee Contact Validation']);
|
|
$session = limited_backoffice_manager_session([(int)$department['id']]);
|
|
$basePayload = [
|
|
'display_name' => 'Contact Employee',
|
|
'email' => 'contact@example.test',
|
|
'password' => 'Secret123!',
|
|
'role_key' => 'viewer',
|
|
'department_ids' => [(int)$department['id']],
|
|
];
|
|
|
|
api_client()->post('/limited-backoffice/employees', array_diff_key($basePayload, ['email' => true]), $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Email is required.');
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
...$basePayload,
|
|
'email' => 'not-an-email',
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Email must be a valid email address.');
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
...$basePayload,
|
|
'phone_country_code' => 45,
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Phone country code and phone number must be provided together.');
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
...$basePayload,
|
|
'phone_country_code' => 1,
|
|
'phone' => 12345678,
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Phone country code is not supported.');
|
|
|
|
api_client()->post('/limited-backoffice/employees', [
|
|
...$basePayload,
|
|
'phone_country_code' => 45,
|
|
'phone' => '12ab',
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Phone values must contain digits only.');
|
|
|
|
$created = api_client()->post('/limited-backoffice/employees', $basePayload, $session['headers'])
|
|
->assertStatus(200)
|
|
->assertEnvelope()
|
|
->assertSuccess();
|
|
|
|
$employeeId = (int)($created->data()['id'] ?? 0);
|
|
expect($employeeId)->toBeGreaterThan(0);
|
|
limited_backoffice_cleanup_created_employee($employeeId);
|
|
|
|
api_client()->put('/limited-backoffice/employees/' . $employeeId, [
|
|
'email' => '',
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Email is required.');
|
|
|
|
api_client()->put('/limited-backoffice/employees/' . $employeeId, [
|
|
'phone_country_code' => 45,
|
|
'phone' => '123',
|
|
], $session['headers'])
|
|
->assertStatus(400)
|
|
->assertEnvelope()
|
|
->assertSuccess(false)
|
|
->assertMessage('Phone number must be 4-15 digits.');
|
|
});
|