diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 00e999672f..99886261eb 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -1861,4 +1861,352 @@ trait UsersBase // TODO add test for session delete // TODO add test for all sessions delete + + /** + * @depends testCreateUser + */ + public function testListUsersWithSelect(array $data): void + { + /** + * Test Query::select with regular attributes + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'email'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + // Verify only selected attributes are present + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + + // Verify other attributes are not present + $this->assertArrayNotHasKey('status', $user); + $this->assertArrayNotHasKey('registration', $user); + $this->assertArrayNotHasKey('passwordUpdate', $user); + $this->assertArrayNotHasKey('phone', $user); + + /** + * Test Query::select with subQuery attributes (sessions, tokens) + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'sessions', 'tokens'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + + // SubQuery attributes should not be directly present in select but data should be available + // The implementation removes subQuery attributes from select and handles them via skipFilters + $this->assertArrayNotHasKey('sessions', $user); + $this->assertArrayNotHasKey('tokens', $user); + + // Verify other attributes are not present + $this->assertArrayNotHasKey('email', $user); + $this->assertArrayNotHasKey('status', $user); + + /** + * Test Query::select with mixed attributes (regular + subQuery) + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['name', 'email', 'memberships', 'targets'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + + // SubQuery attributes (memberships) should not be in select result + $this->assertArrayNotHasKey('memberships', $user); + + // targets should be available since subQueryTargets is not skipped by default + $this->assertArrayHasKey('targets', $user); + + /** + * Test Query::select with only subQuery attributes + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['sessions', 'challenges', 'authenticators'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + // When only subQuery attributes are selected, all attributes should be returned + // since no select query is passed to database + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + + /** + * Test Query::select with system attributes + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', '$createdAt', '$updatedAt', 'name'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('$createdAt', $user); + $this->assertArrayHasKey('$updatedAt', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayNotHasKey('email', $user); + + /** + * Test Query::select with multiple select queries (should be consolidated) + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name'])->toString(), + Query::select(['email', 'status'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + // All attributes from both select queries should be present + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + $this->assertArrayHasKey('status', $user); + $this->assertArrayNotHasKey('registration', $user); + + /** + * Test Query::select with targets (should not be skipped by default) + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'targets'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('targets', $user); // targets should be available + + /** + * Test invalid select attributes + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['nonExistentAttribute'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + $this->assertStringContainsString('not found', $response['body']['message']); + + /** + * Test Query::select with mixed valid and invalid attributes + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'invalidAttribute'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + $this->assertStringContainsString('not found', $response['body']['message']); + + /** + * Test Query::select combined with other queries + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'email'])->toString(), + Query::equal('status', [true])->toString(), + Query::limit(5)->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + $this->assertLessThanOrEqual(5, count($response['body']['users'])); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + $this->assertArrayNotHasKey('status', $user); + $this->assertArrayNotHasKey('registration', $user); + } + + /** + * @depends testCreateUser + */ + public function testListUsersSelectEdgeCases(array $data): void + { + /** + * Test empty select query - should return all attributes + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select([])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + $this->assertArrayHasKey('status', $user); + + /** + * Test select with duplicate attributes - should deduplicate + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'name', '$id', 'email'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('email', $user); + $this->assertArrayNotHasKey('status', $user); + + /** + * Test select with only targets (should be available since subQueryTargets not skipped by default) + */ + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['targets'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['users']); + + // Since targets is the only selected attribute and it's a subQuery attribute, + // but subQueryTargets is not skipped by default, all attributes should be returned + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('targets', $user); + + /** + * Core functionality test: verify subQuery attributes handling + * This test specifically validates the PR requirements + */ + + // Test that sessions/tokens are handled correctly (removed from select, added to skipFilters) + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'sessions'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + // sessions should not be present as it's removed from select and handled via skipFilters + $this->assertArrayNotHasKey('sessions', $user); + $this->assertArrayNotHasKey('email', $user); // Not selected + + // Test that targets are NOT removed (since subQueryTargets is not skipped by default) + $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'queries' => [ + Query::select(['$id', 'name', 'targets'])->toString() + ] + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $user = $response['body']['users'][0]; + $this->assertArrayHasKey('$id', $user); + $this->assertArrayHasKey('name', $user); + $this->assertArrayHasKey('targets', $user); // Should be present + } }