2020-01-11 13:58:02 +00:00
< ? php
2020-01-12 06:35:37 +00:00
namespace Tests\E2E\Services\Account ;
2020-01-11 13:58:02 +00:00
2022-12-22 03:12:45 +00:00
use Appwrite\Tests\Retry ;
2020-01-13 15:15:52 +00:00
use Tests\E2E\Client ;
2020-01-11 13:58:02 +00:00
use Tests\E2E\Scopes\ProjectCustom ;
2024-03-06 17:34:21 +00:00
use Tests\E2E\Scopes\Scope ;
2020-01-11 13:58:02 +00:00
use Tests\E2E\Scopes\SideClient ;
2022-07-12 16:44:58 +00:00
use Utopia\Database\DateTime ;
2022-12-14 15:42:25 +00:00
use Utopia\Database\Helpers\ID ;
2024-01-25 16:53:51 +00:00
use Utopia\Database\Query ;
2023-03-01 12:00:36 +00:00
use Utopia\Database\Validator\Datetime as DatetimeValidator ;
2022-08-14 14:22:38 +00:00
2022-02-04 10:23:06 +00:00
use function sleep ;
2020-01-11 13:58:02 +00:00
class AccountCustomClientTest extends Scope
{
use AccountBase ;
use ProjectCustom ;
use SideClient ;
2020-01-13 15:15:52 +00:00
2026-02-05 22:54:14 +00:00
/**
* Static cache for account data across tests
*/
private static array $accountData = [];
private static array $sessionData = [];
private static array $updatedNameData = [];
private static array $updatedPasswordData = [];
private static array $updatedEmailData = [];
private static array $updatedPrefsData = [];
private static array $verificationData = [];
private static array $verifiedData = [];
private static array $recoveryData = [];
private static array $phoneData = [];
private static array $phoneSessionData = [];
private static array $phonePasswordData = [];
private static array $phoneUpdatedData = [];
private static array $phoneVerificationData = [];
private static array $magicUrlData = [];
private static array $magicUrlSessionData = [];
/**
* Helper to set up an account with session
*/
protected function setupAccountWithSession () : array
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $sessionData [ $cacheKey ])) {
return self :: $sessionData [ $cacheKey ];
}
// First create an account
$accountData = $this -> setupAccount ();
$email = $accountData [ 'email' ];
$password = $accountData [ 'password' ];
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'email' => $email ,
'password' => $password ,
]);
$sessionId = $response [ 'body' ][ '$id' ];
$session = $response [ 'cookies' ][ 'a_session_' . $projectId ];
self :: $sessionData [ $cacheKey ] = array_merge ( $accountData , [
'sessionId' => $sessionId ,
'session' => $session ,
]);
return self :: $sessionData [ $cacheKey ];
}
2026-02-06 07:24:28 +00:00
/**
* Helper to create a fresh account with session ( bypasses cache ) .
* Use this when you need a predictable session / log count for testing .
*/
protected function createFreshAccountWithSession () : array
{
$projectId = $this -> getProject ()[ '$id' ];
// Use more entropy to avoid collisions in parallel test execution
$email = uniqid ( '' , true ) . getmypid () . bin2hex ( random_bytes ( 4 )) . '@localhost.test' ;
$password = 'password' ;
$name = 'User Name' ;
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$id = $response [ 'body' ][ '$id' ];
// Create session
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$sessionId = $response [ 'body' ][ '$id' ];
$session = $response [ 'cookies' ][ 'a_session_' . $projectId ];
return [
'id' => $id ,
'email' => $email ,
'password' => $password ,
'name' => $name ,
'sessionId' => $sessionId ,
'session' => $session ,
];
}
2026-02-05 22:54:14 +00:00
/**
* Helper to set up a basic account
*/
protected function setupAccount () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $accountData [ $cacheKey ])) {
return self :: $accountData [ $cacheKey ];
}
$email = uniqid () . 'user@localhost.test' ;
$password = 'password' ;
$name = 'User Name' ;
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$id = $response [ 'body' ][ '$id' ];
self :: $accountData [ $cacheKey ] = [
'id' => $id ,
'email' => $email ,
'password' => $password ,
'name' => $name ,
];
return self :: $accountData [ $cacheKey ];
}
/**
* Helper to set up account with updated name
*/
protected function setupAccountWithUpdatedName () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $updatedNameData [ $cacheKey ])) {
return self :: $updatedNameData [ $cacheKey ];
}
$data = $this -> setupAccountWithSession ();
$session = $data [ 'session' ];
$newName = 'Lorem' ;
$this -> client -> call ( Client :: METHOD_PATCH , '/account/name' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'name' => $newName
]);
self :: $updatedNameData [ $cacheKey ] = array_merge ( $data , [ 'name' => $newName ]);
return self :: $updatedNameData [ $cacheKey ];
}
/**
* Helper to set up account with updated password
*/
protected function setupAccountWithUpdatedPassword () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $updatedPasswordData [ $cacheKey ])) {
return self :: $updatedPasswordData [ $cacheKey ];
}
$data = $this -> setupAccountWithUpdatedName ();
$session = $data [ 'session' ];
$password = $data [ 'password' ];
$this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'password' => 'new-password' ,
'oldPassword' => $password ,
]);
self :: $updatedPasswordData [ $cacheKey ] = array_merge ( $data , [ 'password' => 'new-password' ]);
return self :: $updatedPasswordData [ $cacheKey ];
}
/**
* Helper to set up account with updated email
*/
protected function setupAccountWithUpdatedEmail () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $updatedEmailData [ $cacheKey ])) {
return self :: $updatedEmailData [ $cacheKey ];
}
$data = $this -> setupAccountWithUpdatedPassword ();
$session = $data [ 'session' ];
$newEmail = uniqid () . 'new@localhost.test' ;
$this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'email' => $newEmail ,
'password' => 'new-password' ,
]);
self :: $updatedEmailData [ $cacheKey ] = array_merge ( $data , [ 'email' => $newEmail ]);
return self :: $updatedEmailData [ $cacheKey ];
}
/**
* Helper to set up account with updated prefs
*/
protected function setupAccountWithUpdatedPrefs () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $updatedPrefsData [ $cacheKey ])) {
return self :: $updatedPrefsData [ $cacheKey ];
}
$data = $this -> setupAccountWithUpdatedEmail ();
$session = $data [ 'session' ];
$this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'prefs' => [
'prefKey1' => 'prefValue1' ,
'prefKey2' => 'prefValue2' ,
]
]);
self :: $updatedPrefsData [ $cacheKey ] = $data ;
return self :: $updatedPrefsData [ $cacheKey ];
}
/**
* Helper to set up account with verification created
*/
protected function setupAccountWithVerification () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $verificationData [ $cacheKey ])) {
return self :: $verificationData [ $cacheKey ];
}
$data = $this -> setupAccountWithUpdatedPrefs ();
$email = $data [ 'email' ];
$session = $data [ 'session' ];
$this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'url' => 'http://localhost/verification' ,
]);
$lastEmail = $this -> getLastEmailByAddress ( $email );
$tokens = $this -> extractQueryParamsFromEmailLink ( $lastEmail [ 'html' ]);
$verification = $tokens [ 'secret' ];
self :: $verificationData [ $cacheKey ] = array_merge ( $data , [ 'verification' => $verification ]);
return self :: $verificationData [ $cacheKey ];
}
/**
* Helper to set up account with verified email
*/
protected function setupAccountWithVerifiedEmail () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $verifiedData [ $cacheKey ])) {
return self :: $verifiedData [ $cacheKey ];
}
$data = $this -> setupAccountWithVerification ();
$id = $data [ 'id' ];
$session = $data [ 'session' ];
$verification = $data [ 'verification' ];
$this -> client -> call ( Client :: METHOD_PUT , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'userId' => $id ,
'secret' => $verification ,
]);
self :: $verifiedData [ $cacheKey ] = $data ;
return self :: $verifiedData [ $cacheKey ];
}
/**
* Helper to set up account with recovery token
*/
protected function setupAccountWithRecovery () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $recoveryData [ $cacheKey ])) {
return self :: $recoveryData [ $cacheKey ];
}
$data = $this -> setupAccountWithVerifiedEmail ();
$email = $data [ 'email' ];
$this -> client -> call ( Client :: METHOD_POST , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'email' => $email ,
'url' => 'http://localhost/recovery' ,
]);
2026-02-24 01:43:15 +00:00
$lastEmail = $this -> getLastEmailByAddress ( $email , function ( $email ) {
$this -> assertStringContainsString ( 'Password Reset' , $email [ 'subject' ]);
});
2026-02-05 22:54:14 +00:00
$tokens = $this -> extractQueryParamsFromEmailLink ( $lastEmail [ 'html' ]);
$recovery = $tokens [ 'secret' ];
self :: $recoveryData [ $cacheKey ] = array_merge ( $data , [ 'recovery' => $recovery ]);
return self :: $recoveryData [ $cacheKey ];
}
/**
* Helper to set up phone account
*/
protected function setupPhoneAccount () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $phoneData [ $cacheKey ])) {
return self :: $phoneData [ $cacheKey ];
}
2026-02-09 01:55:41 +00:00
// Ensure phone auth is enabled (may have been disabled by testPhoneVerification in parallel)
$this -> ensurePhoneAuthEnabled ();
// Use a truly unique phone number for parallel test safety
// Combine microtime, PID, and random digits to avoid collisions across parallel processes
$number = '+1' . substr ( str_replace ( '.' , '' , microtime ( true )) . getmypid () . random_int ( 100 , 999 ), - 9 );
2026-02-05 22:54:14 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => ID :: unique (),
'phone' => $number ,
]);
2026-02-09 01:20:42 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-05 22:54:14 +00:00
$userId = $response [ 'body' ][ 'userId' ];
$smsRequest = $this -> getLastRequestForProject (
$projectId ,
Scope :: REQUEST_TYPE_SMS ,
[
'header_X-Username' => 'username' ,
'header_X-Key' => 'password' ,
'method' => 'POST' ,
],
probe : function ( array $request ) use ( $number ) {
$this -> assertEquals ( $number , $request [ 'data' ][ 'to' ] ? ? null );
}
);
2026-02-09 01:20:42 +00:00
$this -> assertNotEmpty ( $smsRequest , 'SMS request not found for phone number: ' . $number );
2026-02-05 22:54:14 +00:00
self :: $phoneData [ $cacheKey ] = [
'token' => $smsRequest [ 'data' ][ 'message' ],
'id' => $userId ,
'number' => $number ,
];
return self :: $phoneData [ $cacheKey ];
}
/**
* Helper to set up phone session
*/
protected function setupPhoneSession () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $phoneSessionData [ $cacheKey ])) {
return self :: $phoneSessionData [ $cacheKey ];
}
2026-02-09 04:53:21 +00:00
// Try up to 3 times with fresh phone accounts if session creation fails
$maxRetries = 3 ;
$lastError = '' ;
for ( $attempt = 0 ; $attempt < $maxRetries ; $attempt ++ ) {
// Force fresh phone account on retry
if ( $attempt > 0 ) {
unset ( self :: $phoneData [ $cacheKey ]);
\usleep ( 500000 ); // 500ms between retries
}
2026-02-05 22:54:14 +00:00
2026-02-09 04:53:21 +00:00
$data = $this -> setupPhoneAccount ();
$id = $data [ 'id' ];
// Extract OTP token - try the raw message first, then first word
$rawMessage = $data [ 'token' ];
$token = \trim ( $rawMessage );
if ( \str_contains ( $token , ' ' )) {
$token = \explode ( ' ' , $token )[ 0 ];
}
2026-02-09 01:20:42 +00:00
2026-02-09 04:53:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => $id ,
'secret' => $token ,
]);
if ( $response [ 'headers' ][ 'status-code' ] === 201 ) {
$session = $response [ 'cookies' ][ 'a_session_' . $projectId ];
self :: $phoneSessionData [ $cacheKey ] = array_merge ( $data , [ 'session' => $session ]);
return self :: $phoneSessionData [ $cacheKey ];
}
2026-02-05 22:54:14 +00:00
2026-02-09 04:53:21 +00:00
$lastError = 'Attempt ' . ( $attempt + 1 ) . ': Phone session creation failed (status ' . $response [ 'headers' ][ 'status-code' ] . '). Token: "' . $token . '", Raw message: "' . $rawMessage . '", UserId: ' . $id ;
}
2026-02-05 22:54:14 +00:00
2026-02-09 04:53:21 +00:00
$this -> fail ( $lastError );
2026-02-05 22:54:14 +00:00
}
/**
* Helper to set up phone account converted to password
*/
protected function setupPhoneConvertedToPassword () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $phonePasswordData [ $cacheKey ])) {
return self :: $phonePasswordData [ $cacheKey ];
}
$data = $this -> setupPhoneSession ();
$session = $data [ 'session' ];
$email = uniqid () . 'new@localhost.test' ;
$password = 'new-password' ;
2026-02-09 01:20:42 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
2026-02-05 22:54:14 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'email' => $email ,
'password' => $password ,
]);
2026-02-09 01:20:42 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Re-login with email to get a fresh session after credential change
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$session = $response [ 'cookies' ][ 'a_session_' . $projectId ];
2026-02-05 22:54:14 +00:00
self :: $phonePasswordData [ $cacheKey ] = array_merge ( $data , [
'email' => $email ,
'password' => $password ,
2026-02-09 01:20:42 +00:00
'session' => $session ,
2026-02-05 22:54:14 +00:00
]);
return self :: $phonePasswordData [ $cacheKey ];
}
/**
* Helper to set up phone account with updated phone
*/
protected function setupPhoneUpdated () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $phoneUpdatedData [ $cacheKey ])) {
return self :: $phoneUpdatedData [ $cacheKey ];
}
$data = $this -> setupPhoneConvertedToPassword ();
$session = $data [ 'session' ];
2026-02-09 01:55:41 +00:00
// Use a truly unique phone number to avoid target conflicts across parallel test runs
$newPhone = '+456' . substr ( str_replace ( '.' , '' , microtime ( true )) . getmypid () . random_int ( 100 , 999 ), - 8 );
2026-02-05 22:54:14 +00:00
2026-02-09 01:20:42 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/phone' , array_merge ([
2026-02-05 22:54:14 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]), [
'phone' => $newPhone ,
'password' => 'new-password'
]);
2026-02-09 01:20:42 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-05 22:54:14 +00:00
self :: $phoneUpdatedData [ $cacheKey ] = array_merge ( $data , [ 'phone' => $newPhone ]);
return self :: $phoneUpdatedData [ $cacheKey ];
}
/**
* Helper to set up phone verification
*/
protected function setupPhoneVerification () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $phoneVerificationData [ $cacheKey ])) {
return self :: $phoneVerificationData [ $cacheKey ];
}
$data = $this -> setupPhoneUpdated ();
$session = $data [ 'session' ];
$phone = $data [ 'phone' ];
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'cookie' => 'a_session_' . $projectId . '=' . $session ,
]));
2026-02-09 01:20:42 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-05 22:54:14 +00:00
$tokenCreatedAt = $response [ 'body' ][ '$createdAt' ];
$smsRequest = $this -> getLastRequestForProject (
$projectId ,
Scope :: REQUEST_TYPE_SMS ,
[
'header_X-Username' => 'username' ,
'header_X-Key' => 'password' ,
'method' => 'POST' ,
],
probe : function ( array $request ) use ( $tokenCreatedAt , $phone ) {
if ( ! empty ( $phone )) {
$this -> assertEquals ( $phone , $request [ 'data' ][ 'to' ] ? ? null );
}
$tokenRecievedAt = $request [ 'time' ];
$this -> assertGreaterThan ( $tokenCreatedAt , $tokenRecievedAt );
}
);
2026-02-09 01:20:42 +00:00
$this -> assertNotEmpty ( $smsRequest , 'SMS request not found for phone verification' );
2026-02-05 22:54:14 +00:00
self :: $phoneVerificationData [ $cacheKey ] = array_merge ( $data , [
'token' => \substr ( $smsRequest [ 'data' ][ 'message' ], 0 , 6 )
]);
return self :: $phoneVerificationData [ $cacheKey ];
}
/**
* Helper to set up magic URL account
*/
protected function setupMagicUrl () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $magicUrlData [ $cacheKey ])) {
return self :: $magicUrlData [ $cacheKey ];
}
$email = \time () . 'user@appwrite.io' ;
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/magic-url' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => ID :: unique (),
'email' => $email ,
]);
$userId = $response [ 'body' ][ 'userId' ];
$lastEmail = $this -> getLastEmailByAddress ( $email );
$token = substr ( $lastEmail [ 'text' ], strpos ( $lastEmail [ 'text' ], '&secret=' , 0 ) + 8 , 64 );
self :: $magicUrlData [ $cacheKey ] = [
'token' => $token ,
'id' => $userId ,
'email' => $email ,
];
return self :: $magicUrlData [ $cacheKey ];
}
/**
* Helper to set up magic URL session
*/
protected function setupMagicUrlSession () : array
{
$projectId = $this -> getProject ()[ '$id' ];
$cacheKey = $projectId ;
if ( ! empty ( self :: $magicUrlSessionData [ $cacheKey ])) {
return self :: $magicUrlSessionData [ $cacheKey ];
}
$data = $this -> setupMagicUrl ();
$id = $data [ 'id' ];
$token = $data [ 'token' ];
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/magic-url' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => $id ,
'secret' => $token ,
]);
$sessionId = $response [ 'body' ][ '$id' ];
$session = $response [ 'cookies' ][ 'a_session_' . $projectId ];
self :: $magicUrlSessionData [ $cacheKey ] = array_merge ( $data , [
'sessionId' => $sessionId ,
'session' => $session ,
]);
return self :: $magicUrlSessionData [ $cacheKey ];
}
/**
* Helper to create an anonymous session ( returns new session each time )
*/
protected function createAnonymousSession () : string
{
$projectId = $this -> getProject ()[ '$id' ];
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/anonymous' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]);
return $response [ 'cookies' ][ 'a_session_' . $projectId ];
}
2026-02-09 01:55:41 +00:00
/**
* Helper to delete any existing user with the given email .
* Used to prevent parallel test conflicts when tests share
* hardcoded emails ( e . g . from mock OAuth providers ) .
*/
protected function deleteUserByEmail ( string $email ) : void
{
$projectId = $this -> getProject ()[ '$id' ];
$apiKey = $this -> getProject ()[ 'apiKey' ];
$response = $this -> client -> call ( Client :: METHOD_GET , '/users' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $apiKey ,
], [
'queries' => [
Query :: equal ( 'email' , [ $email ]) -> toString (),
],
]);
if ( $response [ 'headers' ][ 'status-code' ] === 200 ) {
foreach ( $response [ 'body' ][ 'users' ] ? ? [] as $user ) {
$this -> client -> call ( Client :: METHOD_DELETE , '/users/' . $user [ '$id' ], [
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
'x-appwrite-key' => $apiKey ,
]);
}
}
}
/**
* Helper to ensure phone auth is enabled for the project .
* Needed because testPhoneVerification disables it and other
* parallel tests may need it .
*/
protected function ensurePhoneAuthEnabled () : void
{
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/auth/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
2026-04-22 15:02:22 +00:00
'x-appwrite-response-format' => '1.9.1' ,
2026-02-09 01:55:41 +00:00
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'status' => true ,
]);
}
2026-02-05 22:54:14 +00:00
public function testCreateAccountSession () : void
{
$data = $this -> setupAccount ();
$email = $data [ 'email' ];
$password = $data [ 'password' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertNotFalse ( \DateTime :: createFromFormat ( 'Y-m-d\TH:i:s.uP' , $response [ 'body' ][ 'expire' ]));
$sessionId = $response [ 'body' ][ '$id' ];
2023-12-11 16:52:14 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2023-10-26 08:43:15 +00:00
2026-04-08 13:58:57 +00:00
$accountResponse = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 200 , $accountResponse [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $email , $accountResponse [ 'body' ][ 'email' ]);
2023-10-26 08:43:15 +00:00
// apiKey is only available in custom client test
$apiKey = $this -> getProject ()[ 'apiKey' ];
if ( ! empty ( $apiKey )) {
$userId = $response [ 'body' ][ 'userId' ];
$response = $this -> client -> call ( Client :: METHOD_GET , '/users/' . $userId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $apiKey ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertArrayHasKey ( 'accessedAt' , $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'accessedAt' ]);
}
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
$this -> assertNotFalse ( \DateTime :: createFromFormat ( 'Y-m-d\TH:i:s.uP' , $response [ 'body' ][ 'expire' ]));
2024-02-25 06:24:06 +00:00
/**
* Test for FAILURE
*/
2023-11-15 11:57:27 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2024-02-25 06:24:06 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-11-15 11:57:27 +00:00
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email . 'x' ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password . 'x' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => '' ,
'password' => '' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testGetAccount () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithSession ();
$email = $data [ 'email' ];
$name = $data [ 'name' ];
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
$this -> assertEquals ( $response [ 'body' ][ 'name' ], $name );
$this -> assertArrayHasKey ( 'accessedAt' , $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'accessedAt' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'content-type' => 'application/json' ,
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session . 'xx' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testGetAccountPrefs () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithSession ();
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertEmpty ( $response [ 'body' ]);
$this -> assertCount ( 0 , $response [ 'body' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testGetAccountSessions () : void
2023-10-26 08:43:15 +00:00
{
2026-02-06 07:24:28 +00:00
// Use fresh account for predictable session count
$data = $this -> createFreshAccountWithSession ();
2026-02-05 22:54:14 +00:00
$session = $data [ 'session' ];
$sessionId = $data [ 'sessionId' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2026-02-06 07:24:28 +00:00
$this -> assertEquals ( 1 , $response [ 'body' ][ 'total' ]);
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $sessionId , $response [ 'body' ][ 'sessions' ][ 0 ][ '$id' ]);
2024-03-04 08:23:39 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'sessions' ][ 0 ][ 'secret' ]);
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( 'Windows' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'osName' ]);
$this -> assertEquals ( 'WIN' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'osCode' ]);
$this -> assertEquals ( '10' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'osVersion' ]);
$this -> assertEquals ( 'browser' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'clientType' ]);
$this -> assertEquals ( 'Chrome' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'clientName' ]);
$this -> assertEquals ( 'CH' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'clientCode' ]);
$this -> assertEquals ( '70.0' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'clientVersion' ]);
$this -> assertEquals ( 'Blink' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'clientEngine' ]);
$this -> assertEquals ( 'desktop' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'deviceName' ]);
$this -> assertEquals ( '' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'deviceBrand' ]);
$this -> assertEquals ( '' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'deviceModel' ]);
$this -> assertEquals ( $response [ 'body' ][ 'sessions' ][ 0 ][ 'ip' ], filter_var ( $response [ 'body' ][ 'sessions' ][ 0 ][ 'ip' ], FILTER_VALIDATE_IP ));
$this -> assertEquals ( '--' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'countryCode' ]);
$this -> assertEquals ( 'Unknown' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'countryName' ]);
$this -> assertEquals ( true , $response [ 'body' ][ 'sessions' ][ 0 ][ 'current' ]);
$this -> assertNotFalse ( \DateTime :: createFromFormat ( 'Y-m-d\TH:i:s.uP' , $response [ 'body' ][ 'sessions' ][ 0 ][ 'expire' ]));
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testGetAccountLogs () : void
2023-10-26 08:43:15 +00:00
{
2026-02-06 07:24:28 +00:00
// Use fresh account for predictable log count
$data = $this -> createFreshAccountWithSession ();
2026-02-05 22:54:14 +00:00
$session = $data [ 'session' ];
2026-05-21 04:47:45 +00:00
$headers = array_merge ([
2023-10-26 08:43:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2026-05-21 04:47:45 +00:00
]);
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
/**
* Test for SUCCESS
*/
$this -> assertEventually ( function () use ( $headers ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/logs' , $headers );
2026-02-20 08:28:09 +00:00
2026-05-21 04:47:45 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $response [ 'body' ][ 'logs' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'logs' ]);
$logCount = count ( $response [ 'body' ][ 'logs' ]);
$this -> assertContains ( $logCount , [ 1 , 2 ]);
$this -> assertIsNumeric ( $response [ 'body' ][ 'total' ]);
$this -> assertEquals ( 'session.create' , $response [ 'body' ][ 'logs' ][ 0 ][ 'event' ]);
$this -> assertEquals ( 'Windows' , $response [ 'body' ][ 'logs' ][ 0 ][ 'osName' ]);
$this -> assertEquals ( 'WIN' , $response [ 'body' ][ 'logs' ][ 0 ][ 'osCode' ]);
$this -> assertEquals ( '10' , $response [ 'body' ][ 'logs' ][ 0 ][ 'osVersion' ]);
$this -> assertEquals ( 'browser' , $response [ 'body' ][ 'logs' ][ 0 ][ 'clientType' ]);
$this -> assertEquals ( 'Chrome' , $response [ 'body' ][ 'logs' ][ 0 ][ 'clientName' ]);
$this -> assertEquals ( 'CH' , $response [ 'body' ][ 'logs' ][ 0 ][ 'clientCode' ]);
$this -> assertEquals ( '70.0' , $response [ 'body' ][ 'logs' ][ 0 ][ 'clientVersion' ]);
$this -> assertEquals ( 'Blink' , $response [ 'body' ][ 'logs' ][ 0 ][ 'clientEngine' ]);
$this -> assertEquals ( 'desktop' , $response [ 'body' ][ 'logs' ][ 0 ][ 'deviceName' ]);
$this -> assertEquals ( '' , $response [ 'body' ][ 'logs' ][ 0 ][ 'deviceBrand' ]);
$this -> assertEquals ( '' , $response [ 'body' ][ 'logs' ][ 0 ][ 'deviceModel' ]);
$this -> assertEquals ( filter_var ( $response [ 'body' ][ 'logs' ][ 0 ][ 'ip' ], FILTER_VALIDATE_IP ), $response [ 'body' ][ 'logs' ][ 0 ][ 'ip' ]);
$this -> assertEquals ( '--' , $response [ 'body' ][ 'logs' ][ 0 ][ 'countryCode' ]);
$this -> assertEquals ( 'Unknown' , $response [ 'body' ][ 'logs' ][ 0 ][ 'countryName' ]);
if ( $logCount === 2 ) {
$this -> assertEquals ( 'user.create' , $response [ 'body' ][ 'logs' ][ 1 ][ 'event' ]);
$this -> assertEquals ( filter_var ( $response [ 'body' ][ 'logs' ][ 1 ][ 'ip' ], FILTER_VALIDATE_IP ), $response [ 'body' ][ 'logs' ][ 1 ][ 'ip' ]);
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'logs' ][ 1 ][ 'time' ]));
}
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$responseLimit = $this -> client -> call ( Client :: METHOD_GET , '/account/logs' , $headers , [
'queries' => [
Query :: limit ( 1 ) -> toString ()
]
]);
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$this -> assertEquals ( 200 , $responseLimit [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $responseLimit [ 'body' ][ 'logs' ]);
$this -> assertNotEmpty ( $responseLimit [ 'body' ][ 'logs' ]);
$this -> assertCount ( 1 , $responseLimit [ 'body' ][ 'logs' ]);
$this -> assertIsNumeric ( $responseLimit [ 'body' ][ 'total' ]);
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'logs' ][ 0 ], $responseLimit [ 'body' ][ 'logs' ][ 0 ]);
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$responseOffset = $this -> client -> call ( Client :: METHOD_GET , '/account/logs' , $headers , [
'queries' => [
Query :: offset ( 1 ) -> toString ()
]
]);
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$this -> assertEquals ( 200 , $responseOffset [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $responseOffset [ 'body' ][ 'logs' ]);
$this -> assertCount ( $logCount - 1 , $responseOffset [ 'body' ][ 'logs' ]);
$this -> assertIsNumeric ( $responseOffset [ 'body' ][ 'total' ]);
2026-02-20 08:28:09 +00:00
2026-05-21 04:47:45 +00:00
if ( $logCount === 2 ) {
$this -> assertEquals ( $response [ 'body' ][ 'logs' ][ 1 ], $responseOffset [ 'body' ][ 'logs' ][ 0 ]);
}
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$responseLimitOffset = $this -> client -> call ( Client :: METHOD_GET , '/account/logs' , $headers , [
'queries' => [
Query :: offset ( 1 ) -> toString (),
Query :: limit ( 1 ) -> toString ()
]
]);
2023-10-26 08:43:15 +00:00
2026-05-21 04:47:45 +00:00
$this -> assertEquals ( 200 , $responseLimitOffset [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $responseLimitOffset [ 'body' ][ 'logs' ]);
$this -> assertCount ( min ( 1 , $logCount - 1 ), $responseLimitOffset [ 'body' ][ 'logs' ]);
$this -> assertIsNumeric ( $responseLimitOffset [ 'body' ][ 'total' ]);
if ( $logCount === 2 ) {
$this -> assertEquals ( $response [ 'body' ][ 'logs' ][ 1 ], $responseLimitOffset [ 'body' ][ 'logs' ][ 0 ]);
}
});
2026-02-20 08:28:09 +00:00
2025-10-21 08:41:41 +00:00
/**
2025-10-29 09:08:08 +00:00
* Test for total = false
2025-10-21 08:41:41 +00:00
*/
2026-05-21 04:47:45 +00:00
$logsWithIncludeTotalFalse = $this -> client -> call ( Client :: METHOD_GET , '/account/logs' , $headers , [
2025-10-29 09:08:08 +00:00
'total' => false
2025-10-21 08:41:41 +00:00
]);
$this -> assertEquals ( 200 , $logsWithIncludeTotalFalse [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $logsWithIncludeTotalFalse [ 'body' ]);
$this -> assertIsArray ( $logsWithIncludeTotalFalse [ 'body' ][ 'logs' ]);
$this -> assertIsInt ( $logsWithIncludeTotalFalse [ 'body' ][ 'total' ]);
$this -> assertEquals ( 0 , $logsWithIncludeTotalFalse [ 'body' ][ 'total' ]);
$this -> assertGreaterThan ( 0 , count ( $logsWithIncludeTotalFalse [ 'body' ][ 'logs' ]));
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/logs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
// TODO Add tests for OAuth2 session creation
2026-02-05 22:54:14 +00:00
public function testUpdateAccountName () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithSession ();
$email = $data [ 'email' ];
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
$newName = 'Lorem' ;
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/name' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'name' => $newName
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
$this -> assertEquals ( $response [ 'body' ][ 'name' ], $newName );
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/name' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/name' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), []);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/name' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'name' => 'ocSRq1d3QphHivJyUmYY7WMnrxyjdk5YvVwcDqx2zS0coxESN8RmsQwLWw5Whnf0WbVohuFWTRAaoKgCOO0Y0M7LwgFnZmi8881Y72222222222222222222222222222'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
#[Retry(count: 1)]
2026-02-05 22:54:14 +00:00
public function testUpdateAccountPassword () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithUpdatedName ();
$email = $data [ 'email' ];
$password = $data [ 'password' ];
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
2025-06-18 08:00:24 +00:00
for ( $i = 0 ; $i < 5 ; $i ++ ) {
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-24 01:00:07 +00:00
usleep ( 500000 );
2025-06-18 08:00:24 +00:00
}
2025-06-18 20:56:58 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$allSessions = array_map ( fn ( $sessionDetails ) => $sessionDetails [ '$id' ], $response [ 'body' ][ 'sessions' ]);
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => 'new-password' ,
'oldPassword' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
2025-06-18 20:56:58 +00:00
$currentSessionId = $data [ 'sessionId' ] ? ? '' ;
2025-06-18 08:00:24 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 1 , $response [ 'body' ][ 'total' ]);
// checking the current session or not
2025-06-18 20:56:58 +00:00
$this -> assertEquals ( $currentSessionId , $response [ 'body' ][ 'sessions' ][ 0 ][ '$id' ]);
2025-06-18 08:00:24 +00:00
$this -> assertTrue ( $response [ 'body' ][ 'sessions' ][ 0 ][ 'current' ]);
2025-06-18 20:56:58 +00:00
// checking for all non active sessions are cleared
foreach ( $allSessions as $sessionId ) {
if ( $currentSessionId === $sessionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
} else {
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/' . $sessionId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
}
}
$newPassword = 'new-password' ;
// updating the invalidateSession to false to check sessions are not invalidated
$this -> updateProjectinvalidateSessionsProperty ( false );
for ( $i = 0 ; $i < 5 ; $i ++ ) {
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $newPassword ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2026-02-24 01:00:07 +00:00
usleep ( 500000 );
2025-06-18 20:56:58 +00:00
}
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions' , array_merge ([
2023-10-26 08:43:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-06-18 20:56:58 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$allSessions = array_map ( fn ( $sessionDetails ) => $sessionDetails [ '$id' ], $response [ 'body' ][ 'sessions' ]);
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2023-10-26 08:43:15 +00:00
]), [
2025-06-18 20:56:58 +00:00
'password' => $newPassword ,
'oldPassword' => $newPassword ,
2023-10-26 08:43:15 +00:00
]);
2025-06-18 20:56:58 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
foreach ( $allSessions as $sessionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/' . $sessionId , headers : array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
}
// setting invalidateSession to true to check the sessions are cleared or not
$this -> updateProjectinvalidateSessionsProperty ( true );
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => $newPassword ,
'oldPassword' => $newPassword ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$allSessions = array_map ( fn ( $sessionDetails ) => $sessionDetails [ '$id' ], $response [ 'body' ][ 'sessions' ]);
foreach ( $allSessions as $sessionId ) {
if ( $currentSessionId !== $sessionId ) {
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/' . $sessionId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
}
}
2023-10-26 08:43:15 +00:00
2025-06-18 20:59:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $newPassword ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), []);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Existing user tries to update password by passing wrong old password -> SHOULD FAIL
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => 'new-password' ,
'oldPassword' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Existing user tries to update password without passing old password -> SHOULD FAIL
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => 'new-password'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testUpdateAccountEmail () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithUpdatedPassword ();
2023-10-26 08:43:15 +00:00
$newEmail = uniqid () . 'new@localhost.test' ;
2026-02-05 22:54:14 +00:00
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'email' => $newEmail ,
'password' => 'new-password' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $newEmail );
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), []);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
// Test if we can create a new account with the old email
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-12-15 10:49:35 +00:00
'x-appwrite-dev-key' => $this -> getProject ()[ 'devKey' ] ? ? ''
2023-10-26 08:43:15 +00:00
]), [
'userId' => ID :: unique (),
2024-08-01 18:36:21 +00:00
'email' => $data [ 'email' ],
'password' => $data [ 'password' ],
'name' => $data [ 'name' ],
2023-10-26 08:43:15 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $data [ 'email' ]);
$this -> assertEquals ( $response [ 'body' ][ 'name' ], $data [ 'name' ]);
}
2026-02-05 22:54:14 +00:00
public function testUpdateAccountPrefs () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithUpdatedEmail ();
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'prefs' => [
'prefKey1' => 'prefValue1' ,
'prefKey2' => 'prefValue2' ,
]
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertEquals ( 'prefValue1' , $response [ 'body' ][ 'prefs' ][ 'prefKey1' ]);
$this -> assertEquals ( 'prefValue2' , $response [ 'body' ][ 'prefs' ][ 'prefKey2' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'prefs' => '{}'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'prefs' => '[]'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'prefs' => '{"test": "value"}'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Prefs size exceeded
*/
$prefsObject = [ " longValue " => str_repeat ( " 🍰 " , 100000 )];
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'prefs' => $prefsObject
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
// Now let's test the same thing, but with normal symbol instead of multi-byte cake emoji
$prefsObject = [ " longValue " => str_repeat ( " - " , 100000 )];
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/prefs' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'prefs' => $prefsObject
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testCreateAccountVerification () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithUpdatedPrefs ();
$email = $data [ 'email' ];
$name = $data [ 'name' ];
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'url' => 'http://localhost/verification' ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'expire' ]));
2023-10-26 08:43:15 +00:00
2026-01-10 13:33:20 +00:00
$lastEmail = $this -> getLastEmailByAddress ( $email );
2023-10-26 08:43:15 +00:00
2026-01-10 13:33:20 +00:00
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: ' . $email );
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $name , $lastEmail [ 'to' ][ 0 ][ 'name' ]);
2025-10-06 16:44:29 +00:00
$this -> assertEquals ( 'Account Verification for ' . $this -> getProject ()[ 'name' ], $lastEmail [ 'subject' ]);
2025-07-24 14:09:23 +00:00
$this -> assertStringContainsStringIgnoringCase ( 'Verify your email to activate your ' . $this -> getProject ()[ 'name' ] . ' account.' , $lastEmail [ 'text' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:52:59 +00:00
$tokens = $this -> extractQueryParamsFromEmailLink ( $lastEmail [ 'html' ]);
2025-03-29 12:51:25 +00:00
$verification = $tokens [ 'secret' ];
2025-07-21 07:59:38 +00:00
$expectedExpire = DateTime :: formatTz ( $response [ 'body' ][ 'expire' ]);
2025-03-29 12:51:25 +00:00
$this -> assertEquals ( $expectedExpire , $tokens [ 'expire' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:51:25 +00:00
// Secret check
$this -> assertArrayHasKey ( 'secret' , $tokens );
$this -> assertNotEmpty ( $tokens [ 'secret' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:51:25 +00:00
// User ID check
$this -> assertArrayHasKey ( 'userId' , $tokens );
$this -> assertNotEmpty ( $tokens [ 'userId' ]);
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'url' => 'localhost/verification' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'url' => 'http://remotehost/verification' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testUpdateAccountVerification () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithVerification ();
$id = $data [ 'id' ];
$session = $data [ 'session' ];
$verification = $data [ 'verification' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'userId' => $id ,
'secret' => $verification ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'userId' => ID :: custom ( 'ewewe' ),
'secret' => $verification ,
]);
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'userId' => $id ,
'secret' => 'sdasdasdasd' ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testDeleteAccountSession () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithVerifiedEmail ();
$email = $data [ 'email' ];
$password = $data [ 'password' ];
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
$sessionNewId = $response [ 'body' ][ '$id' ];
2023-12-11 16:52:14 +00:00
$sessionNew = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2023-10-26 08:43:15 +00:00
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $sessionNew ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/account/sessions/' . $sessionNewId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $sessionNew ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $sessionNew ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testDeleteAccountSessionCurrent () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithVerifiedEmail ();
$email = $data [ 'email' ];
$password = $data [ 'password' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-12-11 16:52:14 +00:00
$sessionNew = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2023-10-26 08:43:15 +00:00
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $sessionNew ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/account/sessions/current' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $sessionNew ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $sessionNew ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testDeleteAccountSessions () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithVerifiedEmail ();
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/account/sessions' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testCreateAccountRecovery () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithVerifiedEmail ();
$email = $data [ 'email' ];
$name = $data [ 'name' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'url' => 'http://localhost/recovery' ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'expire' ]));
2023-10-26 08:43:15 +00:00
2026-02-24 01:43:15 +00:00
$lastEmail = $this -> getLastEmailByAddress ( $email , function ( $email ) {
$this -> assertStringContainsString ( 'Password Reset' , $email [ 'subject' ]);
});
2023-10-26 08:43:15 +00:00
2026-01-10 13:33:20 +00:00
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: ' . $email );
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $name , $lastEmail [ 'to' ][ 0 ][ 'name' ]);
2025-10-06 16:44:29 +00:00
$this -> assertEquals ( 'Password Reset for ' . $this -> getProject ()[ 'name' ], $lastEmail [ 'subject' ]);
2025-07-24 14:09:23 +00:00
$this -> assertStringContainsStringIgnoringCase ( 'Reset your ' . $this -> getProject ()[ 'name' ] . ' password using the link.' , $lastEmail [ 'text' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:52:59 +00:00
$tokens = $this -> extractQueryParamsFromEmailLink ( $lastEmail [ 'html' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:51:25 +00:00
// Secret check
$this -> assertArrayHasKey ( 'secret' , $tokens );
$this -> assertNotEmpty ( $tokens [ 'secret' ]);
$this -> assertNotFalse ( $response [ 'body' ][ 'secret' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:51:25 +00:00
// User ID check
$this -> assertArrayHasKey ( 'userId' , $tokens );
$this -> assertNotEmpty ( $tokens [ 'userId' ]);
$this -> assertNotFalse ( $response [ 'body' ][ 'userId' ]);
2023-10-26 08:43:15 +00:00
2025-03-29 12:51:25 +00:00
// Expire check
$this -> assertArrayHasKey ( 'expire' , $tokens );
$this -> assertNotEmpty ( $tokens [ 'expire' ]);
$this -> assertEquals (
2025-07-21 07:59:38 +00:00
DateTime :: formatTz ( $response [ 'body' ][ 'expire' ]),
2025-03-29 12:51:25 +00:00
$tokens [ 'expire' ]
);
2023-10-26 08:43:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'url' => 'localhost/recovery' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'url' => 'http://remotehost/recovery' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => 'not-found@localhost.test' ,
'url' => 'http://localhost/recovery' ,
]);
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
}
#[Retry(count: 1)]
2026-02-05 22:54:14 +00:00
public function testUpdateAccountRecovery () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithRecovery ();
$id = $data [ 'id' ];
$recovery = $data [ 'recovery' ];
2023-12-05 11:00:26 +00:00
$newPassword = 'test-recovery' ;
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $id ,
'secret' => $recovery ,
2023-12-05 11:00:26 +00:00
'password' => $newPassword ,
2023-10-26 08:43:15 +00:00
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: custom ( 'ewewe' ),
'secret' => $recovery ,
2023-12-05 11:00:26 +00:00
'password' => $newPassword ,
2023-10-26 08:43:15 +00:00
]);
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/recovery' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $id ,
'secret' => 'sdasdasdasd' ,
2023-12-05 11:00:26 +00:00
'password' => $newPassword ,
2023-10-26 08:43:15 +00:00
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
}
2025-10-31 12:22:47 +00:00
public function testSessionAlert () : void
2024-06-24 13:39:47 +00:00
{
2024-06-26 08:42:01 +00:00
$email = uniqid () . 'session-alert@appwrite.io' ;
$password = 'password123' ;
$name = 'Session Alert Tester' ;
2024-06-24 13:39:47 +00:00
2024-06-26 08:42:01 +00:00
// Enable session alerts
2024-06-24 13:39:47 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/auth/session-alerts' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
2026-04-21 15:34:44 +00:00
'x-appwrite-response-format' => '1.9.1' ,
2024-06-24 13:39:47 +00:00
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
2024-07-03 08:02:16 +00:00
'alerts' => true ,
2024-06-24 13:39:47 +00:00
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2024-06-26 08:42:01 +00:00
// Create a new account
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-12-15 10:49:35 +00:00
'x-appwrite-dev-key' => $this -> getProject ()[ 'devKey' ] ? ? ''
2024-06-26 08:42:01 +00:00
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2024-07-16 12:31:32 +00:00
// Create first session for the new account
2024-06-24 13:39:47 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-06-26 14:46:12 +00:00
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' ,
2024-06-24 13:39:47 +00:00
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2024-07-16 12:31:32 +00:00
// Create second session for the new account
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36' ,
]), [
'email' => $email ,
'password' => $password ,
]);
2024-06-26 08:42:01 +00:00
// Check the alert email
2026-01-10 13:33:20 +00:00
$lastEmail = $this -> getLastEmailByAddress ( $email );
2024-06-24 13:39:47 +00:00
2026-01-10 13:33:20 +00:00
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: ' . $email );
2024-07-16 12:31:32 +00:00
$this -> assertStringContainsString ( 'Security alert: new session' , $lastEmail [ 'subject' ]);
2024-06-24 13:39:47 +00:00
$this -> assertStringContainsString ( $response [ 'body' ][ 'ip' ], $lastEmail [ 'text' ]); // IP Address
2024-06-26 14:46:12 +00:00
$this -> assertStringContainsString ( 'Unknown' , $lastEmail [ 'text' ]); // Country
$this -> assertStringContainsString ( $response [ 'body' ][ 'clientName' ], $lastEmail [ 'text' ]); // Client name
2025-10-31 12:22:47 +00:00
$this -> assertStringNotContainsStringIgnoringCase ( 'Appwrite logo' , $lastEmail [ 'html' ]);
2024-08-01 18:36:21 +00:00
// Verify no alert sent in OTP login
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
2026-01-10 13:49:28 +00:00
'email' => 'otpuser3@appwrite.io'
2024-08-01 18:36:21 +00:00
]);
$this -> assertEquals ( $response [ 'headers' ][ 'status-code' ], 201 );
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$createdAt' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'userId' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'expire' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'phrase' ]);
2025-07-24 14:09:23 +00:00
$this -> assertStringContainsStringIgnoringCase ( 'New login detected on ' . $this -> getProject ()[ 'name' ], $lastEmail [ 'text' ]);
2024-08-01 18:36:21 +00:00
$userId = $response [ 'body' ][ 'userId' ];
2026-01-10 13:49:28 +00:00
$lastEmail = $this -> getLastEmailByAddress ( 'otpuser3@appwrite.io' );
2024-08-01 18:36:21 +00:00
2026-01-10 13:49:28 +00:00
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: otpuser3@appwrite.io' );
2024-08-01 18:36:21 +00:00
$this -> assertEquals ( 'OTP for ' . $this -> getProject ()[ 'name' ] . ' Login' , $lastEmail [ 'subject' ]);
2025-03-29 12:51:25 +00:00
// Find 6 concurrent digits in email text - OTP
2024-08-01 18:36:21 +00:00
preg_match_all ( " / \ b \ d { 6} \ b/ " , $lastEmail [ 'text' ], $matches );
chore: bump PHPStan to level 4 and fix all new errors
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:
- `app/controllers/general.php`: path-traversal guard was negating
`\substr(...)` before the strict comparison (`!\substr(...) === $base`
was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
`DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
result as an array with `deviceName/deviceBrand/deviceModel` keys.
Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
key was checking `$resourceKey === 'functions'` when `$resourceKey`
is `'functionId'|'siteId'` — always false. Switched to the intended
`$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
to `string|false` to match `openssl_encrypt`; this lets callers'
`=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
`array_key_exists('from', [])` branch in the Msg91 provider (empty
array literal; branch was unreachable).
Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
`markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
Installer State, `$source` on MigrationsWorker, `$account2` on two
GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
`assertNotNull` assertions with `addToAssertionCount(1)` or
`assertNotEmpty` where the runtime type was already known.
2026-04-19 12:01:20 +00:00
$code = $matches [ 0 ][ 0 ] ? ? '' ;
2024-08-01 18:36:21 +00:00
$this -> assertNotEmpty ( $code );
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $userId ,
'secret' => $code
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $userId , $response [ 'body' ][ 'userId' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'expire' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
$lastEmailId = $lastEmail [ 'id' ];
2026-01-10 13:49:28 +00:00
$lastEmail = $this -> getLastEmailByAddress ( 'otpuser3@appwrite.io' );
2024-08-01 18:36:21 +00:00
$this -> assertEquals ( $lastEmailId , $lastEmail [ 'id' ]);
2024-06-24 13:39:47 +00:00
}
2026-02-05 22:54:14 +00:00
public function testCreateOAuth2AccountSession () : void
2020-01-13 15:15:52 +00:00
{
2026-02-05 22:54:14 +00:00
// Just ensure we have a session set up
$this -> setupAccountWithSession ();
2020-01-13 15:15:52 +00:00
$provider = 'mock' ;
$appId = '1' ;
$secret = '123456' ;
/**
* Test for SUCCESS
*/
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
2020-01-13 15:15:52 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
2023-01-17 01:17:37 +00:00
'enabled' => true ,
2020-01-13 15:15:52 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2020-01-13 15:15:52 +00:00
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/oauth2/' . $provider , array_merge ([
2020-01-13 15:15:52 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
2020-02-17 07:16:11 +00:00
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2020-01-13 15:15:52 +00:00
]), [
2020-02-16 11:41:03 +00:00
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
2026-03-16 15:31:08 +00:00
], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://localhost/v1/mock/tests/general/oauth2' , $response [ 'headers' ][ 'location' ]);
$oauthClient = new Client ();
$oauthClient -> setEndpoint ( '' );
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://appwrite:/v1/account/sessions/oauth2/callback/mock/' . $this -> getProject ()[ '$id' ] . '?code=' , $response [ 'headers' ][ 'location' ]);
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://appwrite:/v1/account/sessions/oauth2/mock/redirect?code=' , $response [ 'headers' ][ 'location' ]);
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertArrayHasKey ( 'a_session_' . $this -> getProject ()[ '$id' ] . '_legacy' , $response [ 'cookies' ]);
$this -> assertArrayHasKey ( 'a_session_' . $this -> getProject ()[ '$id' ], $response [ 'cookies' ]);
$oauthUserCookie = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
$this -> assertNotEmpty ( $oauthUserCookie );
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'success' , $response [ 'body' ][ 'result' ]);
// Ensure user is authenticated
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $oauthUserCookie ,
2020-01-13 15:15:52 +00:00
]);
2026-03-16 15:31:08 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'useroauth@localhost.test' , $response [ 'body' ][ 'email' ]);
$oauthUserId = $response [ 'body' ][ '$id' ];
$this -> assertNotEmpty ( $oauthUserId );
// Ensure session looks as expected
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , [
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $oauthUserCookie ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $oauthUserId , $response [ 'body' ][ 'userId' ]);
$this -> assertEquals ( 'mock' , $response [ 'body' ][ 'provider' ]);
// Same sign-in again, but this time with oauth2 token flow
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/tokens/oauth2/' . $provider , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://localhost/v1/mock/tests/general/oauth2' , $response [ 'headers' ][ 'location' ]);
$oauthClient = new Client ();
$oauthClient -> setEndpoint ( '' );
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://appwrite:/v1/account/sessions/oauth2/callback/mock/' . $this -> getProject ()[ '$id' ] . '?code=' , $response [ 'headers' ][ 'location' ]);
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://appwrite:/v1/account/sessions/oauth2/mock/redirect?code=' , $response [ 'headers' ][ 'location' ]);
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertStringStartsWith ( 'http://localhost/v1/mock/tests/general/oauth2/success?secret=' , $response [ 'headers' ][ 'location' ]);
$oauthParamsString = \parse_url ( $response [ 'headers' ][ 'location' ], PHP_URL_QUERY );
$oauthParams = [];
\parse_str ( $oauthParamsString , $oauthParams );
$this -> assertNotEmpty ( $oauthParams [ 'secret' ]);
$this -> assertNotEmpty ( $oauthParams [ 'userId' ]);
$response = $oauthClient -> call ( Client :: METHOD_GET , $response [ 'headers' ][ 'location' ], followRedirects : false );
2020-01-13 15:15:52 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'success' , $response [ 'body' ][ 'result' ]);
2026-03-16 15:31:08 +00:00
// Claim session
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], [
'userId' => $oauthParams [ 'userId' ],
'secret' => $oauthParams [ 'secret' ],
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'mock' , $response [ 'body' ][ 'provider' ]);
$this -> assertArrayHasKey ( 'a_session_' . $this -> getProject ()[ '$id' ] . '_legacy' , $response [ 'cookies' ]);
$this -> assertArrayHasKey ( 'a_session_' . $this -> getProject ()[ '$id' ], $response [ 'cookies' ]);
$oauthUserCookie = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
$this -> assertNotEmpty ( $oauthUserCookie );
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , [
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $oauthUserCookie ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'useroauth@localhost.test' , $response [ 'body' ][ 'email' ]);
$oauthUserId = $response [ 'body' ][ '$id' ];
$this -> assertNotEmpty ( $oauthUserId );
// Ensure session looks as expected
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , [
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $oauthUserCookie ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $oauthUserId , $response [ 'body' ][ 'userId' ]);
$this -> assertEquals ( 'mock' , $response [ 'body' ][ 'provider' ]);
2023-02-14 05:56:14 +00:00
/**
* Test for Failure when disabled
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
'enabled' => false ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-02-14 05:56:14 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/oauth2/' . $provider , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
]);
2023-02-14 06:07:06 +00:00
$this -> assertEquals ( 412 , $response [ 'headers' ][ 'status-code' ]);
2020-01-13 15:15:52 +00:00
}
2020-12-27 20:32:39 +00:00
2026-02-05 22:54:14 +00:00
public function testCreateOidcOAuth2Token () : void
2025-10-02 09:28:55 +00:00
{
$provider = 'oidc' ;
$appId = '1' ;
// Valid well-known configuration
$secret = ' {
" wellKnownEndpoint " : " https://accounts.google.com/.well-known/openid-configuration " ,
" authorizationEndpoint " : " https://accounts.google.com/o/oauth2/v2/auth " ,
" tokenEndpoint " : " https://oauth2.googleapis.com/token " ,
" userinfoEndpoint " : " https://openidconnect.googleapis.com/v1/userinfo "
} ' ;
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
'enabled' => true ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/tokens/oauth2/' . $provider , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'provider' => $provider ,
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
], true , false );
$this -> assertEquals ( 301 , $response [ 'headers' ][ 'status-code' ]);
// Invalid well-known configuration
$secret = '{}' ;
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
'enabled' => true ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/tokens/oauth2/' . $provider , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'provider' => $provider ,
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
]);
$this -> assertEquals ( 500 , $response [ 'headers' ][ 'status-code' ]);
2026-02-09 00:25:19 +00:00
// Clean up - disable the OIDC provider to avoid polluting other parallel tests
$this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'enabled' => false ,
]);
2025-10-02 09:28:55 +00:00
}
2026-02-05 22:54:14 +00:00
public function testBlockedAccount () : void
2020-12-27 20:32:39 +00:00
{
2022-05-16 10:11:37 +00:00
$email = uniqid () . 'user@localhost.test' ;
2020-12-27 20:32:39 +00:00
$password = 'password' ;
$name = 'User Name (blocked)' ;
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2020-12-27 20:32:39 +00:00
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$id = $response [ 'body' ][ '$id' ];
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2020-12-27 20:32:39 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2020-12-27 20:32:39 +00:00
$sessionId = $response [ 'body' ][ '$id' ];
2023-12-08 23:17:13 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2020-12-27 20:32:39 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2020-12-27 20:32:39 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2020-12-27 20:32:39 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/users/' . $id . '/status' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
2021-07-14 11:02:12 +00:00
'status' => false ,
2020-12-27 20:32:39 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2020-12-27 20:32:39 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2026-03-06 17:43:36 +00:00
$this -> assertEquals ( 403 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2022-05-16 10:11:37 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2026-03-06 17:43:36 +00:00
$this -> assertEquals ( 403 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
}
2026-02-05 22:54:14 +00:00
public function testSelfBlockedAccount () : void
2022-05-16 10:11:37 +00:00
{
$email = uniqid () . 'user55@localhost.test' ;
$password = 'password' ;
$name = 'User Name (self blocked)' ;
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2022-05-16 10:11:37 +00:00
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$id = $response [ 'body' ][ '$id' ];
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2022-05-16 10:11:37 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
2023-12-08 23:17:13 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/status' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
], [
'status' => false ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-05-23 17:40:31 +00:00
$this -> assertStringContainsString ( 'a_session_' . $this -> getProject ()[ '$id' ] . '=deleted' , $response [ 'headers' ][ 'set-cookie' ]);
$this -> assertEquals ( '[]' , $response [ 'headers' ][ 'x-fallback-cookies' ]);
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2020-12-27 20:32:39 +00:00
]));
2026-03-06 17:43:36 +00:00
$this -> assertEquals ( 403 , $response [ 'headers' ][ 'status-code' ]);
2020-12-27 20:32:39 +00:00
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2020-12-27 20:32:39 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2026-03-06 17:43:36 +00:00
$this -> assertEquals ( 403 , $response [ 'headers' ][ 'status-code' ]);
2020-12-27 20:32:39 +00:00
}
2020-12-28 20:31:55 +00:00
2026-02-05 22:54:14 +00:00
public function testCreateJWT () : void
2020-12-28 20:31:55 +00:00
{
2022-05-16 10:11:37 +00:00
$email = uniqid () . 'user@localhost.test' ;
2020-12-28 20:31:55 +00:00
$password = 'password' ;
$name = 'User Name (JWT)' ;
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2020-12-28 20:31:55 +00:00
'email' => $email ,
'password' => $password ,
'name' => $name ,
]);
$id = $response [ 'body' ][ '$id' ];
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2022-05-16 10:11:37 +00:00
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2020-12-28 20:31:55 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
$sessionId = $response [ 'body' ][ '$id' ];
2023-12-08 23:17:13 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2020-12-28 20:31:55 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2020-12-28 20:31:55 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/jwt' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2020-12-28 20:31:55 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-12-23 15:31:17 +00:00
$this -> assertEquals ( 119 , $response [ 'headers' ][ 'x-ratelimit-remaining' ]);
2020-12-28 20:31:55 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ 'jwt' ]);
$this -> assertIsString ( $response [ 'body' ][ 'jwt' ]);
$jwt = $response [ 'body' ][ 'jwt' ];
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-jwt' => 'wrong-token' ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-jwt' => $jwt ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/account/sessions/' . $sessionId , array_merge ([
2020-12-28 20:31:55 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2020-12-28 20:31:55 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-jwt' => $jwt ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
2025-12-22 17:19:50 +00:00
// Test JWT with custom duration
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/jwt' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'duration' => 5
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'jwt' ]);
$jwt = $response [ 'body' ][ 'jwt' ];
// Ensure JWT works before expiration
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-jwt' => $jwt ,
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Wait for JWT to expire
\sleep ( 6 );
// Ensure JWT no longer works after expiration
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-jwt' => $jwt ,
]));
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2020-12-28 20:31:55 +00:00
}
2021-02-16 13:49:21 +00:00
2026-02-05 22:54:14 +00:00
public function testCreateAnonymousAccount () : void
2021-02-16 13:49:21 +00:00
{
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/anonymous' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]);
2021-02-23 22:43:05 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2021-05-20 13:41:55 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2023-10-26 08:43:15 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2021-02-16 13:49:21 +00:00
2023-12-08 23:17:13 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2021-02-16 13:49:21 +00:00
2023-07-07 00:12:39 +00:00
\usleep ( 1000 * 30 ); // wait for 30ms to let the shutdown update accessedAt
$apiKey = $this -> getProject ()[ 'apiKey' ];
$userId = $response [ 'body' ][ 'userId' ];
$response = $this -> client -> call ( Client :: METHOD_GET , '/users/' . $userId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $apiKey ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-07-07 00:12:39 +00:00
$this -> assertArrayHasKey ( 'accessedAt' , $response [ 'body' ]);
2023-08-23 01:34:23 +00:00
2023-07-07 00:12:39 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ 'accessedAt' ]);
2021-02-16 13:49:21 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/anonymous' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 13:49:21 +00:00
]);
2021-02-23 22:43:05 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
}
2026-02-05 22:54:14 +00:00
public function testCreateAnonymousAccountVerification () : void
2025-09-22 08:56:23 +00:00
{
2026-02-05 22:54:14 +00:00
$session = $this -> createAnonymousSession ();
2025-09-22 08:56:23 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'url' => 'http://localhost/verification' ,
]);
2025-10-03 10:59:39 +00:00
$this -> assertEquals ( 400 , $response [ 'body' ][ 'code' ]);
2025-09-22 08:56:23 +00:00
$this -> assertEquals ( 'user_email_not_found' , $response [ 'body' ][ 'type' ]);
}
2026-02-05 22:54:14 +00:00
public function testUpdateAnonymousAccountPassword () : void
2021-02-16 13:49:21 +00:00
{
2026-02-05 22:54:14 +00:00
$session = $this -> createAnonymousSession ();
2021-02-16 13:49:21 +00:00
/**
2021-05-06 13:21:36 +00:00
* Test for FAILURE
2021-02-16 13:49:21 +00:00
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 13:49:21 +00:00
]), [
2021-02-16 14:14:36 +00:00
'oldPassword' => '' ,
2021-02-16 13:49:21 +00:00
]);
2021-05-06 13:21:36 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
}
2026-02-05 22:54:14 +00:00
public function testUpdateAnonymousAccountEmail () : void
2021-02-16 14:14:36 +00:00
{
2026-02-05 22:54:14 +00:00
$session = $this -> createAnonymousSession ();
2022-05-16 10:11:37 +00:00
$email = uniqid () . 'new@localhost.test' ;
2021-02-16 14:14:36 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 14:14:36 +00:00
]), [
'email' => $email ,
'password' => '' ,
]);
2021-04-16 16:50:16 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 14:14:36 +00:00
}
2026-02-05 22:54:14 +00:00
public function testConvertAnonymousAccount () : void
2021-02-16 13:49:21 +00:00
{
2026-02-05 22:54:14 +00:00
$session = $this -> createAnonymousSession ();
2022-05-16 10:11:37 +00:00
$email = uniqid () . 'new@localhost.test' ;
2021-02-16 13:49:21 +00:00
$password = 'new-password' ;
2021-02-16 15:01:15 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2021-02-16 15:01:15 +00:00
'email' => $email ,
'password' => $password
]);
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 15:01:15 +00:00
]), [
'email' => $email ,
'password' => $password ,
]);
2024-03-04 09:36:02 +00:00
$this -> assertEquals ( 409 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 15:01:15 +00:00
2021-02-16 13:49:21 +00:00
/**
* Test for SUCCESS
*/
2022-05-16 10:11:37 +00:00
$email = uniqid () . 'new@localhost.test' ;
2021-02-16 15:01:15 +00:00
2021-02-16 13:49:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 13:49:21 +00:00
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2021-02-16 13:49:21 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2021-02-16 14:20:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-07-06 18:50:13 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2023-07-11 04:03:02 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2023-07-06 18:50:13 +00:00
]), [
'url' => 'http://localhost'
]);
2023-07-11 04:03:02 +00:00
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 13:49:21 +00:00
}
2021-02-16 15:51:08 +00:00
2026-02-05 22:54:14 +00:00
public function testConvertAnonymousAccountOAuth2 () : void
2021-02-16 15:51:08 +00:00
{
2026-02-09 01:55:41 +00:00
// Clean up any existing user with the mock OAuth email to prevent
// conflicts with parallel tests that also use the mock provider
$this -> deleteUserByEmail ( 'useroauth@localhost.test' );
2026-02-05 22:54:14 +00:00
$session = $this -> createAnonymousSession ();
2021-02-16 15:51:08 +00:00
$provider = 'mock' ;
$appId = '1' ;
$secret = '123456' ;
/**
* Test for SUCCESS
*/
2021-07-17 21:21:33 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-07-17 21:21:33 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2021-07-17 21:21:33 +00:00
$userId = $response [ 'body' ][ '$id' ] ? ? '' ;
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
2021-02-16 15:51:08 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
2023-01-17 01:17:37 +00:00
'enabled' => true ,
2021-02-16 15:51:08 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 15:51:08 +00:00
2026-02-09 01:55:41 +00:00
// Delete any user with the mock OAuth email right before the OAuth call
// to minimize the race window with parallel tests
$this -> deleteUserByEmail ( 'useroauth@localhost.test' );
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/oauth2/' . $provider , array_merge ([
2021-02-16 15:51:08 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 15:51:08 +00:00
]), [
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
]);
2022-02-04 10:23:06 +00:00
2021-02-16 15:51:08 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2025-12-07 20:29:45 +00:00
$sessionCookieKey = 'a_session_' . $this -> getProject ()[ '$id' ];
$this -> assertArrayHasKey (
$sessionCookieKey ,
$response [ 'cookies' ],
" Failed asserting that session cookie ' $sessionCookieKey ' is set. Cookies: " . json_encode ( $response [ 'cookies' ])
);
$session = $response [ 'cookies' ][ $sessionCookieKey ];
2022-05-16 10:11:37 +00:00
2021-02-16 15:51:08 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-02-16 15:51:08 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2021-07-17 21:21:33 +00:00
$this -> assertEquals ( $response [ 'body' ][ '$id' ], $userId );
2024-06-05 18:23:31 +00:00
$this -> assertEquals ( 'User Name' , $response [ 'body' ][ 'name' ]);
$this -> assertEquals ( 'useroauth@localhost.test' , $response [ 'body' ][ 'email' ]);
2022-02-04 10:23:06 +00:00
// Since we only support one oauth user, let's also check updateSession here
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2022-02-04 10:23:06 +00:00
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2024-03-04 08:23:39 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2022-02-04 10:23:06 +00:00
$this -> assertEquals ( '123456' , $response [ 'body' ][ 'providerAccessToken' ]);
$this -> assertEquals ( 'tuvwxyz' , $response [ 'body' ][ 'providerRefreshToken' ]);
2022-07-13 14:02:49 +00:00
$this -> assertGreaterThan ( DateTime :: addSeconds ( new \DateTime (), 14400 - 5 ), $response [ 'body' ][ 'providerAccessTokenExpiry' ]); // 5 seconds allowed networking delay
2022-02-04 10:23:06 +00:00
$initialExpiry = $response [ 'body' ][ 'providerAccessTokenExpiry' ];
2022-05-16 10:11:37 +00:00
2022-02-04 10:23:06 +00:00
sleep ( 3 );
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/sessions/current' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2022-02-04 10:23:06 +00:00
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( '123456' , $response [ 'body' ][ 'providerAccessToken' ]);
$this -> assertEquals ( 'tuvwxyz' , $response [ 'body' ][ 'providerRefreshToken' ]);
$this -> assertNotEquals ( $initialExpiry , $response [ 'body' ][ 'providerAccessTokenExpiry' ]);
2021-02-16 15:51:08 +00:00
2025-12-18 12:44:04 +00:00
// Clean up - delete the user
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/users/' . $userId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testOAuthUnverifiedEmailCannotLinkToExistingAccount () : void
2025-12-18 12:44:04 +00:00
{
$provider = 'mock-unverified' ;
$appId = '1' ;
$secret = '123456' ;
// First, create a user with the same email that the unverified OAuth will try to use
$email = 'useroauthunverified@localhost.test' ;
$password = 'password' ;
2026-02-09 01:55:41 +00:00
// Clean up any existing user with this email from parallel tests
$this -> deleteUserByEmail ( $email );
2025-12-18 12:44:04 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], [
'userId' => ID :: unique (),
'email' => $email ,
'password' => $password ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$existingUserId = $response [ 'body' ][ '$id' ];
// Enable the mock-unverified provider
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
'enabled' => true ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Attempt OAuth login with unverified email - should fail because existing user has same email
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/oauth2/' . $provider , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'failure' , $response [ 'body' ][ 'result' ]);
// Clean up - delete the user
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/users/' . $existingUserId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testOAuthVerifiedEmailCanLinkToExistingAccount () : void
2025-12-18 12:44:04 +00:00
{
$provider = 'mock' ;
$appId = '1' ;
$secret = '123456' ;
$email = 'useroauth@localhost.test' ;
2026-02-09 01:55:41 +00:00
// Clean up any existing user with this email from parallel tests
$this -> deleteUserByEmail ( $email );
2025-12-18 12:44:04 +00:00
// Create a user with the same email that the verified OAuth will try to use
$response = $this -> client -> call ( Client :: METHOD_POST , '/account' , [
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], [
'userId' => ID :: unique (),
'email' => $email ,
'password' => 'password' ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$existingUserId = $response [ 'body' ][ '$id' ];
// Enable the mock provider
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/oauth2' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'provider' => $provider ,
'appId' => $appId ,
'secret' => $secret ,
'enabled' => true ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
// Attempt OAuth login with verified email - should succeed and link to existing account
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/oauth2/' . $provider , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'success' => 'http://localhost/v1/mock/tests/general/oauth2/success' ,
'failure' => 'http://localhost/v1/mock/tests/general/oauth2/failure' ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'success' , $response [ 'body' ][ 'result' ]);
// Verify the OAuth identity was linked to the existing user
$sessionCookieKey = 'a_session_' . $this -> getProject ()[ '$id' ];
$session = $response [ 'cookies' ][ $sessionCookieKey ];
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( $existingUserId , $response [ 'body' ][ '$id' ]);
$this -> assertEquals ( $email , $response [ 'body' ][ 'email' ]);
// Clean up - delete the user
$response = $this -> client -> call ( Client :: METHOD_DELETE , '/users/' . $existingUserId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
]));
$this -> assertEquals ( 204 , $response [ 'headers' ][ 'status-code' ]);
2021-02-16 15:51:08 +00:00
}
2021-06-16 10:41:15 +00:00
2026-02-05 22:54:14 +00:00
public function testGetSessionByID () : void
2022-05-16 10:11:37 +00:00
{
2026-02-05 22:54:14 +00:00
$session = $this -> createAnonymousSession ();
2021-06-16 10:41:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-06-16 10:41:15 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2024-03-04 08:23:39 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-06-05 18:23:31 +00:00
$this -> assertEquals ( 'anonymous' , $response [ 'body' ][ 'provider' ]);
2021-06-16 10:41:15 +00:00
$sessionID = $response [ 'body' ][ '$id' ];
2022-05-16 10:11:37 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/' . $sessionID , array_merge ([
2021-06-16 10:41:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-06-16 10:41:15 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2024-03-04 08:23:39 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-06-05 18:23:31 +00:00
$this -> assertEquals ( 'anonymous' , $response [ 'body' ][ 'provider' ]);
2021-06-16 10:41:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/97823askjdkasd80921371980' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2022-05-16 10:11:37 +00:00
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
2021-06-16 10:41:15 +00:00
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
2021-06-16 10:41:15 +00:00
}
2022-04-26 10:07:33 +00:00
2026-02-05 22:54:14 +00:00
public function testUpdateAccountNameSearch () : void
2022-04-26 10:07:33 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithUpdatedName ();
$id = $data [ 'id' ];
2022-04-26 10:07:33 +00:00
$newName = 'Lorem' ;
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/users' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
2022-08-15 19:35:50 +00:00
'search' => $newName ,
2022-04-26 10:07:33 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-04-26 10:07:33 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'users' ]);
2026-02-06 07:27:47 +00:00
// In parallel execution, there may be more users with name 'Lorem'
$this -> assertGreaterThanOrEqual ( 1 , count ( $response [ 'body' ][ 'users' ]));
// Find our user in the results
$foundUser = null ;
foreach ( $response [ 'body' ][ 'users' ] as $user ) {
if ( $user [ '$id' ] === $id ) {
$foundUser = $user ;
break ;
}
}
$this -> assertNotNull ( $foundUser , 'User should be found in search results' );
$this -> assertEquals ( $newName , $foundUser [ 'name' ]);
2022-04-26 10:07:33 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/users' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
2022-08-15 19:35:50 +00:00
'search' => $id ,
2022-04-26 10:07:33 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-04-26 10:07:33 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'users' ]);
$this -> assertCount ( 1 , $response [ 'body' ][ 'users' ]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( $newName , $response [ 'body' ][ 'users' ][ 0 ][ 'name' ]);
2022-04-26 10:07:33 +00:00
}
2026-02-05 22:54:14 +00:00
public function testUpdateAccountEmailSearch () : void
2022-04-26 10:07:33 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupAccountWithUpdatedEmail ();
$id = $data [ 'id' ];
$email = $data [ 'email' ];
2022-04-26 10:07:33 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_GET , '/users' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
2022-08-15 19:35:50 +00:00
'search' => '"' . $email . '"' ,
2022-04-26 10:07:33 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-04-26 10:07:33 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'users' ]);
$this -> assertCount ( 1 , $response [ 'body' ][ 'users' ]);
$this -> assertEquals ( $response [ 'body' ][ 'users' ][ 0 ][ 'email' ], $email );
$response = $this -> client -> call ( Client :: METHOD_GET , '/users' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
2022-08-15 19:35:50 +00:00
'search' => $id ,
2022-04-26 10:07:33 +00:00
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-04-26 10:07:33 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'users' ]);
$this -> assertCount ( 1 , $response [ 'body' ][ 'users' ]);
$this -> assertEquals ( $response [ 'body' ][ 'users' ][ 0 ][ 'email' ], $email );
}
2022-06-08 12:50:31 +00:00
2026-02-05 22:54:14 +00:00
public function testCreatePhone () : void
2022-06-08 12:50:31 +00:00
{
$number = '+123456789' ;
/**
* Test for SUCCESS
*/
2023-11-30 11:35:52 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/phone' , array_merge ([
2022-06-08 12:50:31 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: unique (),
2022-08-14 18:09:24 +00:00
'phone' => $number ,
2022-06-08 12:50:31 +00:00
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2023-10-10 13:36:53 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'expire' ]));
2022-06-08 12:50:31 +00:00
$userId = $response [ 'body' ][ 'userId' ];
2026-01-13 09:01:16 +00:00
$smsRequest = $this -> getLastRequestForProject (
$this -> getProject ()[ '$id' ],
Scope :: REQUEST_TYPE_SMS ,
[
'header_X-Username' => 'username' ,
'header_X-Key' => 'password' ,
'method' => 'POST' ,
],
probe : function ( array $request ) use ( $number ) {
$this -> assertEquals ( 'Appwrite Mock Message Sender' , $request [ 'headers' ][ 'User-Agent' ] ? ? null );
$this -> assertEquals ( 'username' , $request [ 'headers' ][ 'X-Username' ] ? ? null );
$this -> assertEquals ( 'password' , $request [ 'headers' ][ 'X-Key' ] ? ? null );
$this -> assertEquals ( 'POST' , $request [ 'method' ] ? ? null );
$this -> assertEquals ( '+123456789' , $request [ 'data' ][ 'from' ] ? ? null );
$this -> assertEquals ( $number , $request [ 'data' ][ 'to' ] ? ? null );
}
);
2022-09-28 05:45:07 +00:00
2024-02-20 12:54:17 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique ()
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
}
2026-02-05 22:54:14 +00:00
public function testCreateSessionWithPhone () : void
2022-06-08 12:50:31 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupPhoneAccount ();
$id = $data [ 'id' ];
chore: bump PHPStan to level 4 and fix all new errors
Raises `phpstan.neon` level from 3 to 4 and fixes the 549 new errors
that level 4 surfaces across 157 files. Fixes are root-cause — no
`@phpstan-ignore`, no `@var` casts, no baseline entries, no widened
types. A handful of latent bugs were fixed along the way:
- `app/controllers/general.php`: path-traversal guard was negating
`\substr(...)` before the strict comparison (`!\substr(...) === $base`
was always `false === $base`). Rewritten as `\substr(...) !== $base`.
- `src/Appwrite/Platform/Modules/Databases/Http/Databases/Logs/XList.php`
and `.../TablesDB/Logs/XList.php`: were importing the raw Matomo
`DeviceDetector` (whose `getDevice()` returns `?int`) but treating the
result as an array with `deviceName/deviceBrand/deviceModel` keys.
Swapped to `Appwrite\Detector\Detector`, matching the wrapper already
used a few lines below for `$os`/`$client`.
- `src/Appwrite/Platform/Modules/Functions/Workers/Builds.php`: a match
key was checking `$resourceKey === 'functions'` when `$resourceKey`
is `'functionId'|'siteId'` — always false. Switched to the intended
`$resource->getCollection() === 'functions'` check.
- `src/Appwrite/OpenSSL/OpenSSL.php`: `encrypt()` return type tightened
to `string|false` to match `openssl_encrypt`; this lets callers'
`=== false` error handling remain meaningful.
- `app/controllers/api/messaging.php`: removed a dead
`array_key_exists('from', [])` branch in the Msg91 provider (empty
array literal; branch was unreachable).
Large cleanup categories across the 549 fixes:
- Removed redundant `?? default` on array offsets and expressions that
PHPStan now knows are non-nullable.
- Removed unreachable statements (mostly `return;` after `throw` or
`markTestSkipped()`).
- Removed redundant `is_array`/`is_string`/`is_bool`/`instanceof` checks
on already-narrowed types.
- Added `default =>` arms (or throwing arms) to non-exhaustive matches
on `string`/`mixed` input.
- Removed dead `$document === false` branches where method return types
were tightened to non-nullable `Document`.
- Removed unused properties (`$version` on Etsy/Zoom OAuth2, `$paths` on
Installer State, `$source` on MigrationsWorker, `$account2` on two
GraphQL auth tests), unused traits (`ApiVectorsDB`, `DatabaseFixture`),
and an unused `cleanupStaleExecutions` task method.
- Replaced `assertTrue(true)` and redundant `assertIsArray`/`assertIsString`/
`assertNotNull` assertions with `addToAssertionCount(1)` or
`assertNotEmpty` where the runtime type was already known.
2026-04-19 12:01:20 +00:00
$token = explode ( " " , $data [ 'token' ])[ 0 ];
2026-02-05 22:54:14 +00:00
$number = $data [ 'number' ];
2022-06-08 12:50:31 +00:00
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: custom ( 'ewewe' ),
2022-06-08 12:50:31 +00:00
'secret' => $token ,
]);
2023-10-12 13:38:32 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $id ,
'secret' => 'sdasdasdasd' ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $id ,
'secret' => $token ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'userId' ]);
2023-12-08 23:17:13 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2022-06-08 12:50:31 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2023-02-05 20:39:41 +00:00
$this -> assertEquals ( true , ( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2022-06-08 12:50:31 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'phone' ], $number );
$this -> assertTrue ( $response [ 'body' ][ 'phoneVerification' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $id ,
'secret' => $token ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testConvertPhoneToPassword () : void
2022-06-08 12:50:31 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupPhoneSession ();
2022-06-08 12:50:31 +00:00
$session = $data [ 'session' ];
$email = uniqid () . 'new@localhost.test' ;
$password = 'new-password' ;
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2022-06-08 12:50:31 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
2022-06-14 08:17:50 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
2022-06-08 12:50:31 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => $password ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
}
2026-02-05 22:54:14 +00:00
public function testUpdatePhone () : void
2022-06-08 12:50:31 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupPhoneConvertedToPassword ();
2026-02-09 01:55:41 +00:00
$newPhone = '+456' . substr ( str_replace ( '.' , '' , microtime ( true )) . getmypid () . random_int ( 100 , 999 ), - 8 );
2026-02-05 22:54:14 +00:00
$session = $data [ 'session' ];
2022-06-08 12:50:31 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
2022-08-14 18:09:24 +00:00
'phone' => $newPhone ,
2022-06-08 12:50:31 +00:00
'password' => 'new-password'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2022-06-08 12:50:31 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'phone' ], $newPhone );
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), []);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2022-06-08 12:50:31 +00:00
}
2026-02-05 22:54:14 +00:00
public function testCreateSession () : void
2023-10-11 13:21:20 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupPhoneUpdated ();
2023-12-05 11:00:26 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/users/' . $data [ 'id' ] . '/tokens' , [
2023-10-11 13:21:20 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
2023-11-02 13:15:29 +00:00
], [
'expire' => 60
2023-10-11 13:21:20 +00:00
]);
2023-12-05 11:00:26 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-11 13:21:20 +00:00
$userId = $response [ 'body' ][ 'userId' ];
$secret = $response [ 'body' ][ 'secret' ];
/**
* Test for SUCCESS
*/
2024-01-09 18:05:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , [
2023-10-11 13:21:20 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], [
'userId' => $userId ,
2023-12-05 11:00:26 +00:00
'secret' => $secret
2023-10-11 13:21:20 +00:00
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-12-05 11:00:26 +00:00
$this -> assertEquals ( $data [ 'id' ], $response [ 'body' ][ 'userId' ]);
2023-10-11 13:21:20 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'expire' ]);
2023-10-12 13:38:32 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2025-01-27 13:58:46 +00:00
$this -> assertEquals ( 'browser' , $response [ 'body' ][ 'clientType' ]);
$this -> assertEquals ( 'CH' , $response [ 'body' ][ 'clientCode' ]);
$this -> assertEquals ( 'Chrome' , $response [ 'body' ][ 'clientName' ]);
// Forwarded User Agent with API Key
$response = $this -> client -> call ( Client :: METHOD_POST , '/users/' . $data [ 'id' ] . '/tokens' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
'expire' => 60
]);
$userId = $response [ 'body' ][ 'userId' ];
$secret = $response [ 'body' ][ 'secret' ];
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
'x-forwarded-user-agent' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
], [
'userId' => $userId ,
'secret' => $secret
]);
$this -> assertEquals ( 'browser' , $response [ 'body' ][ 'clientType' ]);
$this -> assertEquals ( 'CM' , actual : $response [ 'body' ][ 'clientCode' ]);
$this -> assertEquals ( 'Chrome Mobile' , $response [ 'body' ][ 'clientName' ]);
// Forwarded User Agent without API Key
$response = $this -> client -> call ( Client :: METHOD_POST , '/users/' . $data [ 'id' ] . '/tokens' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-appwrite-key' => $this -> getProject ()[ 'apiKey' ],
], [
'expire' => 60
]);
$userId = $response [ 'body' ][ 'userId' ];
$secret = $response [ 'body' ][ 'secret' ];
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , [
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'x-forwarded-user-agent' => 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
], [
'userId' => $userId ,
'secret' => $secret
]);
$this -> assertEquals ( 'browser' , $response [ 'body' ][ 'clientType' ]);
$this -> assertEquals ( 'CH' , $response [ 'body' ][ 'clientCode' ]);
$this -> assertEquals ( 'Chrome' , $response [ 'body' ][ 'clientName' ]);
2023-10-11 13:21:20 +00:00
/**
* Test for FAILURE
*/
// Invalid userId
2024-01-09 18:05:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , [
2023-10-11 13:21:20 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], [
'userId' => ID :: custom ( 'ewewe' ),
'secret' => $secret ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
// Invalid secret
2024-01-09 18:05:21 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/token' , [
2023-10-11 13:21:20 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
], [
'userId' => $userId ,
'secret' => '123456' ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testPhoneVerification () : void
2022-06-08 12:50:31 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupPhoneUpdated ();
$session = $data [ 'session' ];
2022-06-08 12:50:31 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2025-06-26 18:47:59 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ '$createdAt' ]);
2023-10-13 15:10:55 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'expire' ]));
2022-06-08 12:50:31 +00:00
2025-06-26 18:47:59 +00:00
$tokenCreatedAt = $response [ 'body' ][ '$createdAt' ];
2026-01-13 09:01:16 +00:00
$phone = $data [ 'phone' ] ? ? '' ;
$smsQuery = [
'header_X-Username' => 'username' ,
'header_X-Key' => 'password' ,
'method' => 'POST' ,
];
$smsRequest = $this -> getLastRequestForProject (
$this -> getProject ()[ '$id' ],
Scope :: REQUEST_TYPE_SMS ,
$smsQuery ,
probe : function ( array $request ) use ( $tokenCreatedAt , $phone ) {
$this -> assertArrayHasKey ( 'data' , $request );
$this -> assertArrayHasKey ( 'time' , $request );
$this -> assertArrayHasKey ( 'message' , $request [ 'data' ], " Last request missing message: " . \json_encode ( $request ));
if ( ! empty ( $phone )) {
$this -> assertEquals ( $phone , $request [ 'data' ][ 'to' ] ? ? null );
}
// Ensure we are not using token from last sms login
$tokenRecievedAt = $request [ 'time' ];
$this -> assertGreaterThan ( $tokenCreatedAt , $tokenRecievedAt );
}
);
2024-02-25 12:05:14 +00:00
2025-03-05 18:40:41 +00:00
/**
* Test for FAILURE
*/
// disable phone sessions
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/projects/' . $this -> getProject ()[ '$id' ] . '/auth/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => 'console' ,
2026-04-22 15:02:22 +00:00
'x-appwrite-response-format' => '1.9.1' ,
2025-03-05 18:40:41 +00:00
'cookie' => 'a_session_console=' . $this -> getRoot ()[ 'session' ],
]), [
'status' => false ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( false , $response [ 'body' ][ 'authPhone' ]);
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/verification/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
$this -> assertEquals ( 501 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( " Phone authentication is disabled for this project " , $response [ 'body' ][ 'message' ]);
2026-02-09 01:55:41 +00:00
// Re-enable phone auth so other parallel tests are not affected
$this -> ensurePhoneAuthEnabled ();
2022-06-08 12:50:31 +00:00
}
2026-02-05 22:54:14 +00:00
public function testUpdatePhoneVerification () : void
2022-06-08 12:50:31 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupPhoneVerification ();
$id = $data [ 'id' ];
$session = $data [ 'session' ];
$secret = $data [ 'token' ];
2022-06-08 12:50:31 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'userId' => $id ,
2022-09-19 08:09:48 +00:00
'secret' => $secret ,
2022-06-08 12:50:31 +00:00
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
2022-08-14 10:33:36 +00:00
'userId' => ID :: custom ( 'ewewe' ),
2022-09-19 08:09:48 +00:00
'secret' => $secret ,
2022-06-08 12:50:31 +00:00
]);
$this -> assertEquals ( 404 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/verification/phone' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'userId' => $id ,
'secret' => '999999' ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
}
2023-10-26 08:43:15 +00:00
2026-02-05 22:54:14 +00:00
public function testCreateMagicUrl () : void
2023-10-26 08:43:15 +00:00
{
2026-02-06 08:48:55 +00:00
// Use uniqid for uniqueness in parallel test execution
$email = 'magic-' . uniqid () . '-' . \time () . '@appwrite.io' ;
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
2023-11-30 11:35:52 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/magic-url' , array_merge ([
2023-10-26 08:43:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => $email ,
// 'url' => 'http://localhost/magiclogin',
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
2024-02-01 10:41:01 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'phrase' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'expire' ]));
2023-10-26 08:43:15 +00:00
$userId = $response [ 'body' ][ 'userId' ];
2026-01-10 13:33:20 +00:00
$lastEmail = $this -> getLastEmailByAddress ( $email );
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: ' . $email );
2024-01-10 10:37:27 +00:00
$this -> assertEquals ( $this -> getProject ()[ 'name' ] . ' Login' , $lastEmail [ 'subject' ]);
2025-07-24 14:09:23 +00:00
$this -> assertStringContainsStringIgnoringCase ( 'Sign in to ' . $this -> getProject ()[ 'name' ] . ' with your secure link. Expires in 1 hour.' , $lastEmail [ 'text' ]);
2024-01-13 09:55:44 +00:00
$this -> assertStringNotContainsStringIgnoringCase ( 'security phrase' , $lastEmail [ 'text' ]);
2023-10-26 08:43:15 +00:00
2024-01-10 10:27:38 +00:00
$token = substr ( $lastEmail [ 'text' ], strpos ( $lastEmail [ 'text' ], '&secret=' , 0 ) + 8 , 64 );
2023-10-26 08:43:15 +00:00
$expireTime = strpos ( $lastEmail [ 'text' ], 'expire=' . urlencode ( $response [ 'body' ][ 'expire' ]), 0 );
$this -> assertNotFalse ( $expireTime );
$secretTest = strpos ( $lastEmail [ 'text' ], 'secret=' . $response [ 'body' ][ 'secret' ], 0 );
$this -> assertNotFalse ( $secretTest );
$userIDTest = strpos ( $lastEmail [ 'text' ], 'userId=' . $response [ 'body' ][ 'userId' ], 0 );
$this -> assertNotFalse ( $userIDTest );
/**
* Test for FAILURE
*/
2023-11-30 11:35:52 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/magic-url' , array_merge ([
2023-10-26 08:43:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => $email ,
'url' => 'localhost/magiclogin' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-11-30 11:35:52 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/magic-url' , array_merge ([
2023-10-26 08:43:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => $email ,
'url' => 'http://remotehost/magiclogin' ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-11-30 11:35:52 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/magic-url' , array_merge ([
2023-10-26 08:43:15 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
]);
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2024-01-13 09:55:44 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/magic-url' , array_merge ([
2022-06-08 12:50:31 +00:00
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
2024-01-13 09:55:44 +00:00
'userId' => ID :: unique (),
'email' => $email ,
2024-02-01 10:41:01 +00:00
'phrase' => true
2022-06-08 12:50:31 +00:00
]);
2024-01-13 09:55:44 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-02-01 10:41:01 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ 'phrase' ]);
2024-01-13 09:55:44 +00:00
2026-02-24 01:43:15 +00:00
$phrase = $response [ 'body' ][ 'phrase' ];
$lastEmail = $this -> getLastEmailByAddress ( $email , function ( $email ) use ( $phrase ) {
$this -> assertStringContainsStringIgnoringCase ( $phrase , $email [ 'text' ]);
});
2026-01-10 13:33:20 +00:00
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: ' . $email );
2026-02-24 01:43:15 +00:00
$this -> assertStringContainsStringIgnoringCase ( $phrase , $lastEmail [ 'text' ]);
2023-10-26 08:43:15 +00:00
}
2026-02-05 22:54:14 +00:00
public function testCreateSessionWithMagicUrl () : void
2023-10-26 08:43:15 +00:00
{
2026-02-09 05:20:36 +00:00
$projectId = $this -> getProject ()[ '$id' ];
// Get a fresh magic URL token - the cached one may have been consumed by setupMagicUrlSession
$email = \uniqid () . 'magicurl@localhost.test' ;
$tokenResponse = $this -> client -> call ( Client :: METHOD_POST , '/account/tokens/magic-url' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $projectId ,
]), [
'userId' => ID :: unique (),
'email' => $email ,
]);
$this -> assertEquals ( 201 , $tokenResponse [ 'headers' ][ 'status-code' ]);
$id = $tokenResponse [ 'body' ][ 'userId' ];
$lastEmail = $this -> getLastEmailByAddress ( $email );
$this -> assertNotEmpty ( $lastEmail , 'Email not found for address: ' . $email );
$token = substr ( $lastEmail [ 'text' ], strpos ( $lastEmail [ 'text' ], '&secret=' , 0 ) + 8 , 64 );
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/magic-url' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
2026-02-09 05:20:36 +00:00
'x-appwrite-project' => $projectId ,
2023-10-26 08:43:15 +00:00
]), [
'userId' => $id ,
'secret' => $token ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ 'userId' ]);
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
$sessionId = $response [ 'body' ][ '$id' ];
2023-12-11 19:41:58 +00:00
$session = $response [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_GET , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
$this -> assertTrue ( $response [ 'body' ][ 'emailVerification' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/magic-url' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: custom ( 'ewewe' ),
'secret' => $token ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/sessions/magic-url' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => $id ,
'secret' => 'sdasdasdasd' ,
]);
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
}
2026-02-05 22:54:14 +00:00
public function testUpdateAccountPasswordWithMagicUrl () : void
2023-10-26 08:43:15 +00:00
{
2026-02-05 22:54:14 +00:00
$data = $this -> setupMagicUrlSession ();
$email = $data [ 'email' ];
$session = $data [ 'session' ];
2023-10-26 08:43:15 +00:00
/**
* Test for SUCCESS
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => 'new-password'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertIsArray ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-06-05 18:23:31 +00:00
$this -> assertTrue (( new DatetimeValidator ()) -> isValid ( $response [ 'body' ][ 'registration' ]));
2023-10-26 08:43:15 +00:00
$this -> assertEquals ( $response [ 'body' ][ 'email' ], $email );
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => 'new-password' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$this -> assertEmpty ( $response [ 'body' ][ 'secret' ]);
/**
* Test for FAILURE
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]));
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), []);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 400 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Existing user tries to update password by passing wrong old password -> SHOULD FAIL
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => 'new-password' ,
'oldPassword' => 'wrong-password' ,
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
/**
* Existing user tries to update password without passing old password -> SHOULD FAIL
*/
$response = $this -> client -> call ( Client :: METHOD_PATCH , '/account/password' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session ,
]), [
'password' => 'new-password'
]);
2023-10-26 14:04:47 +00:00
$this -> assertEquals ( 401 , $response [ 'headers' ][ 'status-code' ]);
2023-10-26 08:43:15 +00:00
}
2024-10-22 01:35:11 +00:00
public function testCreatePushTarget () : void
{
2024-10-22 01:54:34 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/targets/push' , \array_merge ([
2024-10-22 01:35:11 +00:00
'content-type' => 'application/json' ,
2024-10-22 01:54:34 +00:00
'x-appwrite-project' => $this -> getProject ()[ '$id' ]
], $this -> getHeaders ()), [
2024-10-22 01:35:11 +00:00
'targetId' => ID :: unique (),
'identifier' => 'test-identifier' ,
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
$this -> assertEquals ( 'test-identifier' , $response [ 'body' ][ 'identifier' ]);
}
public function testUpdatePushTarget () : void
{
2024-10-22 01:54:34 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/targets/push' , \array_merge ([
2024-10-22 01:35:11 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-10-22 01:54:34 +00:00
], $this -> getHeaders ()), [
2024-10-22 01:35:11 +00:00
'targetId' => ID :: unique (),
2024-10-22 01:54:34 +00:00
'identifier' => 'test-identifier-2' ,
2024-10-22 01:35:11 +00:00
]);
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $response [ 'body' ][ '$id' ]);
2024-10-22 01:54:34 +00:00
$this -> assertEquals ( 'test-identifier-2' , $response [ 'body' ][ 'identifier' ]);
2024-10-22 01:35:11 +00:00
2024-10-22 01:54:34 +00:00
$response = $this -> client -> call ( Client :: METHOD_PUT , '/account/targets/' . $response [ 'body' ][ '$id' ] . '/push' , \array_merge ([
2024-10-22 01:35:11 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2024-10-22 01:54:34 +00:00
], $this -> getHeaders ()), [
2024-10-22 01:35:11 +00:00
'identifier' => 'test-identifier-updated' ,
]);
$this -> assertEquals ( 200 , $response [ 'headers' ][ 'status-code' ]);
$this -> assertEquals ( 'test-identifier-updated' , $response [ 'body' ][ 'identifier' ]);
$this -> assertEquals ( false , $response [ 'body' ][ 'expired' ]);
}
2025-12-08 20:48:40 +00:00
2025-12-10 20:20:52 +00:00
public function testMFARecoveryCodeChallenge () : void
2025-12-10 00:07:43 +00:00
{
2025-12-10 20:20:52 +00:00
// Generate recovery codes using existing authenticated session
2025-12-10 00:07:43 +00:00
$response = $this -> client -> call ( Client :: METHOD_POST , '/account/mfa/recovery-codes' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), []);
2025-12-08 20:48:40 +00:00
2025-12-10 00:07:43 +00:00
$this -> assertEquals ( 201 , $response [ 'headers' ][ 'status-code' ]);
2025-12-08 20:48:40 +00:00
$this -> assertNotEmpty ( $response [ 'body' ][ 'recoveryCodes' ]);
$recoveryCodes = $response [ 'body' ][ 'recoveryCodes' ];
$this -> assertGreaterThan ( 0 , count ( $recoveryCodes ));
// Create recovery code challenge
2025-12-10 00:07:43 +00:00
$challenge = $this -> client -> call ( Client :: METHOD_POST , '/account/mfa/challenge' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), [
2025-12-08 20:48:40 +00:00
'factor' => 'recoveryCode'
]);
$this -> assertEquals ( 201 , $challenge [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $challenge [ 'body' ][ '$id' ]);
$challengeId = $challenge [ 'body' ][ '$id' ];
// Test SUCCESS: Verify with valid recovery code (this tests the bug fix)
2025-12-10 00:07:43 +00:00
$verification = $this -> client -> call ( Client :: METHOD_PUT , '/account/mfa/challenge' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), [
2025-12-08 20:48:40 +00:00
'challengeId' => $challengeId ,
'otp' => $recoveryCodes [ 0 ]
]);
$this -> assertEquals ( 200 , $verification [ 'headers' ][ 'status-code' ]);
$this -> assertArrayHasKey ( 'factors' , $verification [ 'body' ]);
$this -> assertContains ( 'recoveryCode' , $verification [ 'body' ][ 'factors' ]);
// Test that the code was consumed (can't use again)
2025-12-10 00:07:43 +00:00
$challenge2 = $this -> client -> call ( Client :: METHOD_POST , '/account/mfa/challenge' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), [
2025-12-08 20:48:40 +00:00
'factor' => 'recoveryCode'
]);
$this -> assertEquals ( 201 , $challenge2 [ 'headers' ][ 'status-code' ]);
2025-12-10 00:07:43 +00:00
$verification2 = $this -> client -> call ( Client :: METHOD_PUT , '/account/mfa/challenge' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), [
2025-12-08 20:48:40 +00:00
'challengeId' => $challenge2 [ 'body' ][ '$id' ],
'otp' => $recoveryCodes [ 0 ] // Same code should fail
]);
$this -> assertEquals ( 401 , $verification2 [ 'headers' ][ 'status-code' ]);
// Test FAILURE: Invalid recovery code
2025-12-10 00:07:43 +00:00
$challenge3 = $this -> client -> call ( Client :: METHOD_POST , '/account/mfa/challenge' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), [
2025-12-08 20:48:40 +00:00
'factor' => 'recoveryCode'
]);
$this -> assertEquals ( 201 , $challenge3 [ 'headers' ][ 'status-code' ]);
2025-12-10 00:07:43 +00:00
$verification3 = $this -> client -> call ( Client :: METHOD_PUT , '/account/mfa/challenge' , array_merge ([
2025-12-08 20:48:40 +00:00
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
2025-12-10 20:20:52 +00:00
], $this -> getHeaders ()), [
2025-12-08 20:48:40 +00:00
'challengeId' => $challenge3 [ 'body' ][ '$id' ],
'otp' => 'invalid-code-123'
]);
$this -> assertEquals ( 401 , $verification3 [ 'headers' ][ 'status-code' ]);
}
2026-05-06 13:50:18 +00:00
public function testRefreshEmailPasswordSession () : void
{
$email = uniqid () . 'user@localhost.test' ;
$account = $this -> client -> call ( Client :: METHOD_POST , '/account' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'userId' => ID :: unique (),
'email' => $email ,
'password' => 'password' ,
]);
$this -> assertEquals ( 201 , $account [ 'headers' ][ 'status-code' ]);
$session = $this -> client -> call ( Client :: METHOD_POST , '/account/sessions/email' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
]), [
'email' => $email ,
'password' => 'password' ,
]);
$this -> assertEquals ( 201 , $session [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $session [ 'body' ][ '$id' ]);
$sessionId = $session [ 'body' ][ '$id' ];
$cookie = 'a_session_' . $this -> getProject ()[ '$id' ] . '=' . $session [ 'cookies' ][ 'a_session_' . $this -> getProject ()[ '$id' ]];
$session = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => $cookie ,
]));
$this -> assertEquals ( 200 , $session [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $session [ 'body' ][ 'expire' ]);
$expiryBefore = $session [ 'body' ][ 'expire' ];
\sleep ( 3 ); // Small delay to ensure expiry an expand
$session = $this -> client -> call ( Client :: METHOD_PATCH , '/account/sessions/' . $sessionId , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => $cookie ,
]));
$this -> assertEquals ( 200 , $session [ 'headers' ][ 'status-code' ]);
$this -> assertNotEmpty ( $session [ 'body' ][ 'expire' ]);
$expiryAfter = $session [ 'body' ][ 'expire' ];
2026-05-06 14:21:50 +00:00
$this -> assertGreaterThan ( \strtotime ( $expiryBefore ), \strtotime ( $expiryAfter ));
2026-05-06 13:50:18 +00:00
$session = $this -> client -> call ( Client :: METHOD_GET , '/account/sessions/current' , array_merge ([
'origin' => 'http://localhost' ,
'content-type' => 'application/json' ,
'x-appwrite-project' => $this -> getProject ()[ '$id' ],
'cookie' => $cookie ,
]));
$this -> assertEquals ( 200 , $session [ 'headers' ][ 'status-code' ]);
2026-05-06 13:57:25 +00:00
$this -> assertEquals ( \strtotime ( $expiryAfter ), \strtotime ( $session [ 'body' ][ 'expire' ]));
2026-05-06 13:50:18 +00:00
}
2022-04-26 10:07:33 +00:00
}