diff --git a/services/nginx/app/objects/users_o.php b/services/nginx/app/objects/users_o.php index ff3466ee..bbd29456 100644 --- a/services/nginx/app/objects/users_o.php +++ b/services/nginx/app/objects/users_o.php @@ -723,7 +723,7 @@ class users_o extends db $user_id = $this->id; } $userIds = $this->attributeSiblingUserIds((int)$user_id); - $sql = "SELECT MIN(id) AS id, MIN(user_id) AS user_id, attribute, MIN(created_at) AS created_at + $sql = "SELECT MIN(id) AS id, MIN(user_id) AS user_id, attribute, NULL AS created_at FROM customer_attributes WHERE user_id IN (" . implode(',', $userIds) . ") GROUP BY attribute diff --git a/services/nginx/app/tests/Api/AuthApiTest.php b/services/nginx/app/tests/Api/AuthApiTest.php index 826b6228..e4b28635 100644 --- a/services/nginx/app/tests/Api/AuthApiTest.php +++ b/services/nginx/app/tests/Api/AuthApiTest.php @@ -106,6 +106,7 @@ it('includes economic runtime config for uncached auth sessions', function (): v $session = api_fixtures()->createUserSession(['list_departments'], [ 'display_name' => 'Fresh Session User', ]); + api_fixtures()->addCustomerAttribute((int)$session['user']['id'], 'restrictSpotFree'); $response = api_client()->get('/auth/session', $session['headers']); @@ -120,7 +121,9 @@ it('includes economic runtime config for uncached auth sessions', function (): v ->and($response->data()['runtime_config']['economic']['transaction_draft_customer_number'] ?? null) ->toBe(556677) ->and($response->data()['runtime_config']['economic']['default_distribution_department_id'] ?? null) - ->toBe(65); + ->toBe(65) + ->and($response->data()['permissions'] ?? []) + ->toContain('has_attribute_restrictSpotFree'); }); it('rejects invalid auth session tokens', function (): void { diff --git a/services/nginx/app/tests/Unit/Users/CustomerAttributesLegacySchemaCompatibilityTest.php b/services/nginx/app/tests/Unit/Users/CustomerAttributesLegacySchemaCompatibilityTest.php new file mode 100644 index 00000000..93108cbf --- /dev/null +++ b/services/nginx/app/tests/Unit/Users/CustomerAttributesLegacySchemaCompatibilityTest.php @@ -0,0 +1,108 @@ +> */ + private array $rows; + + /** @param list> $rows */ + public function __construct(array $rows) + { + $this->rows = array_values($rows); + $this->num_rows = count($rows); + } + + /** @return array|null */ + public function fetch_assoc(): ?array + { + if ($this->rows === []) { + return null; + } + + return array_shift($this->rows); + } + + /** @return list> */ + public function rows(): array + { + return $this->rows; + } +} + +final class CustomerAttributesLegacySchemaDbStub +{ + /** @var list */ + public array $queries = []; + + public function query(string $sql): CustomerAttributesLegacySchemaResultStub + { + $this->queries[] = $sql; + + if (str_contains($sql, 'SELECT customer_number FROM users')) { + return new CustomerAttributesLegacySchemaResultStub([ + ['customer_number' => 5501], + ]); + } + + if (str_contains($sql, 'SELECT id FROM users WHERE customer_number')) { + return new CustomerAttributesLegacySchemaResultStub([ + ['id' => 12], + ['id' => 13], + ]); + } + + if (str_contains($sql, 'FROM customer_attributes')) { + $columnReferences = str_replace('NULL AS created_at', '', $sql); + if (str_contains($columnReferences, 'created_at')) { + throw new RuntimeException("Unknown column 'created_at' in 'SELECT'"); + } + + return new CustomerAttributesLegacySchemaResultStub([ + ['id' => 4, 'user_id' => 12, 'attribute' => 'onlyTankCleaning', 'created_at' => null], + ['id' => 7, 'user_id' => 13, 'attribute' => 'restrictSpotFree', 'created_at' => null], + ]); + } + + return new CustomerAttributesLegacySchemaResultStub([]); + } + + /** @return list> */ + public function fetch_all(CustomerAttributesLegacySchemaResultStub $result): array + { + return $result->rows(); + } +} + +it('loads sibling customer attributes when the legacy table has no created_at column', function (): void { + $hadDb = array_key_exists('db', $GLOBALS); + $previousDb = $hadDb ? $GLOBALS['db'] : null; + $db = new CustomerAttributesLegacySchemaDbStub(); + $GLOBALS['db'] = $db; + + try { + $attributes = (new users_o())->getUserAttributes(12); + + expect(array_column($attributes, 'attribute')) + ->toBe(['onlyTankCleaning', 'restrictSpotFree']) + ->and(array_column($attributes, 'created_at')) + ->toBe([null, null]); + + $attributeQuery = $db->queries[2] ?? ''; + expect($attributeQuery) + ->toContain('SELECT MIN(id) AS id, MIN(user_id) AS user_id, attribute, NULL AS created_at') + ->toContain('WHERE user_id IN (12,13)') + ->not->toContain('MIN(created_at)'); + } finally { + if ($hadDb) { + $GLOBALS['db'] = $previousDb; + } else { + unset($GLOBALS['db']); + } + } +});