mirror of
https://github.com/appwrite/appwrite
synced 2026-05-22 16:38:32 +00:00
Merge branch '1.6.x' of https://github.com/appwrite/appwrite into feat-schedule-execution-improvements
This commit is contained in:
commit
e2ed0b2693
28 changed files with 157 additions and 74 deletions
Binary file not shown.
BIN
app/assets/dbip/dbip-country-lite-2024-08.mmdb
Normal file
BIN
app/assets/dbip/dbip-country-lite-2024-08.mmdb
Normal file
Binary file not shown.
|
|
@ -6,7 +6,8 @@ return [
|
|||
'magicSession',
|
||||
'recovery',
|
||||
'invitation',
|
||||
'mfaChallenge'
|
||||
'mfaChallenge',
|
||||
'sessionAlert'
|
||||
],
|
||||
'sms' => [
|
||||
'verification',
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@
|
|||
<p>{{footer}}</p>
|
||||
|
||||
<p style="margin-bottom: 0px;">{{thanks}}</p>
|
||||
<p style="margin-top: 0px;">{{signature}}</p>
|
||||
<p style="margin-top: 0px;">{{signature}}</p>
|
||||
|
|
@ -18,13 +18,13 @@
|
|||
"emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.",
|
||||
"emails.magicSession.thanks": "Thanks,",
|
||||
"emails.magicSession.signature": "{{project}} team",
|
||||
"emails.sessionAlert.subject": "New session alert for {{project}}",
|
||||
"emails.sessionAlert.subject": "Security alert: new session on your {{project}} account",
|
||||
"emails.sessionAlert.hello":"Hello {{user}}",
|
||||
"emails.sessionAlert.body": "We're writing to inform you that a new session has been initiated on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}. \nHere are the details of the new session: ",
|
||||
"emails.sessionAlert.body": "A new session has been created on your {{b}}{{project}}{{/b}} account, on {{b}}{{dateTime}}{{/b}}.\nHere are the details of the new session: ",
|
||||
"emails.sessionAlert.listDevice": "Device: {{b}}{{device}}{{/b}}",
|
||||
"emails.sessionAlert.listIpAddress": "IP Address: {{b}}{{ipAddress}}{{/b}}",
|
||||
"emails.sessionAlert.listCountry": "Country: {{b}}{{country}}{{/b}}",
|
||||
"emails.sessionAlert.footer": "If you didn't request the sign in, you can safely ignore this email. If you suspect unauthorized activity, please secure your account immediately.",
|
||||
"emails.sessionAlert.footer": "If this was you, there's nothing more you need to do.\nIf you didn't initiate this session or suspect any unauthorized activity, please secure your account.",
|
||||
"emails.sessionAlert.thanks": "Thanks,",
|
||||
"emails.sessionAlert.signature": "{{project}} team",
|
||||
"emails.otpSession.subject": "OTP for {{project}} Login",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -124,7 +124,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
|
|||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'dateTime' => DateTime::format(new \DateTime(), 'Y-m-d H:i:s'),
|
||||
'dateTime' => DateTime::format(new \DateTime(), 'h:ia MMMM dS'),
|
||||
'user' => $user->getAttribute('name'),
|
||||
'project' => $project->getAttribute('name'),
|
||||
'device' => $session->getAttribute('clientName'),
|
||||
|
|
@ -224,7 +224,11 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
|||
}
|
||||
|
||||
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
|
||||
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
|
||||
if ($dbForProject->count('sessions', [
|
||||
Query::equal('userId', [$user->getId()]),
|
||||
]) !== 1) {
|
||||
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
|
|
@ -904,7 +908,11 @@ App::post('/v1/account/sessions/email')
|
|||
;
|
||||
|
||||
if ($project->getAttribute('auths', [])['sessionAlerts'] ?? false) {
|
||||
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
|
||||
if ($dbForProject->count('sessions', [
|
||||
Query::equal('userId', [$user->getId()]),
|
||||
]) !== 1) {
|
||||
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
|
||||
}
|
||||
}
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
|
|
|
|||
|
|
@ -1459,7 +1459,8 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
|||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds) {
|
||||
->inject('deviceForFunctions')
|
||||
->action(function (string $functionId, string $deploymentId, string $buildId, Request $request, Response $response, Database $dbForProject, Document $project, Event $queueForEvents, Build $queueForBuilds, Device $deviceForFunctions) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
|
|
@ -1471,13 +1472,23 @@ App::post('/v1/functions/:functionId/deployments/:deploymentId/build')
|
|||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $deployment->getAttribute('path');
|
||||
if(empty($path) || !$deviceForFunctions->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
|
||||
$destination = $deviceForFunctions->getPath($deploymentId . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
|
||||
$deviceForFunctions->transfer($path, $destination, $deviceForFunctions);
|
||||
|
||||
$deployment->removeAttribute('$internalId');
|
||||
$deployment = $dbForProject->createDocument('deployments', $deployment->setAttributes([
|
||||
'$internalId' => '',
|
||||
'$id' => $deploymentId,
|
||||
'buildId' => '',
|
||||
'buildInternalId' => '',
|
||||
'path' => $destination,
|
||||
'entrypoint' => $function->getAttribute('entrypoint'),
|
||||
'commands' => $function->getAttribute('commands', ''),
|
||||
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
|
||||
|
|
|
|||
|
|
@ -876,6 +876,14 @@ App::patch('/v1/projects/:projectId/auth/mock-numbers')
|
|||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, array $numbers, Response $response, Database $dbForConsole) {
|
||||
|
||||
$uniqueNumbers = [];
|
||||
foreach ($numbers as $number) {
|
||||
if (isset($uniqueNumbers[$number['phone']])) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Duplicate phone numbers are not allowed.');
|
||||
}
|
||||
$uniqueNumbers[$number['phone']] = $number['otp'];
|
||||
}
|
||||
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -999,7 +999,7 @@ $register->set('smtp', function () {
|
|||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-02.mmdb');
|
||||
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2024-08.mmdb');
|
||||
});
|
||||
$register->set('passwordsDictionary', function () {
|
||||
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
|
||||
|
|
|
|||
|
|
@ -787,7 +787,7 @@ $image = $this->getParam('image', '');
|
|||
<<: *x-logging
|
||||
restart: unless-stopped
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.5.5
|
||||
image: openruntimes/executor:0.6.5
|
||||
networks:
|
||||
- appwrite
|
||||
- runtimes
|
||||
|
|
|
|||
22
composer.lock
generated
22
composer.lock
generated
|
|
@ -1923,16 +1923,16 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.33.6",
|
||||
"version": "0.33.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6"
|
||||
"reference": "78d293d99a262bd63ece750bbf989c7e0643b825"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/8fe57da0cecd57e3b17cd395b4a666a24f4c07a6",
|
||||
"reference": "8fe57da0cecd57e3b17cd395b4a666a24f4c07a6",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/78d293d99a262bd63ece750bbf989c7e0643b825",
|
||||
"reference": "78d293d99a262bd63ece750bbf989c7e0643b825",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1962,9 +1962,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.6"
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.7"
|
||||
},
|
||||
"time": "2024-03-21T18:10:57+00:00"
|
||||
"time": "2024-08-01T14:01:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
|
|
@ -3158,16 +3158,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/pint",
|
||||
"version": "v1.17.0",
|
||||
"version": "v1.17.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/pint.git",
|
||||
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5"
|
||||
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
|
||||
"reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
|
||||
"url": "https://api.github.com/repos/laravel/pint/zipball/b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
|
||||
"reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3220,7 +3220,7 @@
|
|||
"issues": "https://github.com/laravel/pint/issues",
|
||||
"source": "https://github.com/laravel/pint"
|
||||
},
|
||||
"time": "2024-07-23T16:40:20+00:00"
|
||||
"time": "2024-08-01T09:06:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
|
|
|
|||
|
|
@ -873,7 +873,7 @@ services:
|
|||
hostname: exc1
|
||||
<<: *x-logging
|
||||
stop_signal: SIGINT
|
||||
image: openruntimes/executor:0.6.1
|
||||
image: openruntimes/executor:0.6.5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ class MockNumber extends Validator
|
|||
return false;
|
||||
}
|
||||
|
||||
$otp = new Text(6, 6);
|
||||
$otp = new Text(6, 6, Text::NUMBERS);
|
||||
if (!$otp->isValid($value['otp'])) {
|
||||
$this->message = 'OTP must be a valid string and exactly 6 characters.';
|
||||
$this->message = 'Invalid OTP. Please make sure the OTP is a 6 digit number';
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -235,7 +235,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
->setTwitter(APP_SOCIAL_TWITTER_HANDLE)
|
||||
->setDiscord(APP_SOCIAL_DISCORD_CHANNEL, APP_SOCIAL_DISCORD)
|
||||
->setDefaultHeaders([
|
||||
'X-Appwrite-Response-Format' => '1.5.0',
|
||||
'X-Appwrite-Response-Format' => '1.6.0',
|
||||
]);
|
||||
|
||||
// Make sure we have a clean slate.
|
||||
|
|
|
|||
|
|
@ -333,6 +333,7 @@ class Builds extends Action
|
|||
$source = $path;
|
||||
|
||||
$build = $dbForProject->updateDocument('builds', $build->getId(), $build->setAttribute('source', $source));
|
||||
$deployment = $dbForProject->updateDocument('deployments', $deployment->getId(), $deployment->setAttribute('path', $source));
|
||||
|
||||
$this->runGitAction('processing', $github, $providerCommitHash, $owner, $repositoryName, $project, $function, $deployment->getId(), $dbForProject, $dbForConsole);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,11 @@ class BodyMultipart
|
|||
}
|
||||
|
||||
$query .= $eol . $eol;
|
||||
$query .= $value . $eol;
|
||||
if ($value === false) {
|
||||
$query .= 0 . $eol;
|
||||
} else {
|
||||
$query .= $value . $eol;
|
||||
}
|
||||
$query .= '--' . $this->boundary;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,8 +110,7 @@ class TemplateFunction extends Model
|
|||
'default' => [],
|
||||
'example' => [],
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ class TemplateRuntime extends Model
|
|||
'description' => 'Path to function in VCS (Version Control System) repository',
|
||||
'default' => '',
|
||||
'example' => 'node/starter',
|
||||
])
|
||||
;
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,37 +10,42 @@ class TemplateVariable extends Model
|
|||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Name.',
|
||||
'default' => '',
|
||||
'example' => 'APPWRITE_DATABASE_ID',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Description.',
|
||||
'default' => '',
|
||||
'example' => 'The ID of the Appwrite database that contains the collection to sync.',
|
||||
])
|
||||
->addRule('placeholder', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Placeholder.',
|
||||
'default' => '',
|
||||
'example' => '64a55...7b912',
|
||||
])
|
||||
->addRule('required', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Is the variable required?',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Type.',
|
||||
'default' => '',
|
||||
'example' => 'password',
|
||||
])
|
||||
;
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Name.',
|
||||
'default' => '',
|
||||
'example' => 'APPWRITE_DATABASE_ID',
|
||||
])
|
||||
->addRule('description', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Description.',
|
||||
'default' => '',
|
||||
'example' => 'The ID of the Appwrite database that contains the collection to sync.',
|
||||
])
|
||||
->addRule('value', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Value.',
|
||||
'default' => '',
|
||||
'example' => '512',
|
||||
])
|
||||
->addRule('placeholder', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Placeholder.',
|
||||
'default' => '',
|
||||
'example' => '64a55...7b912',
|
||||
])
|
||||
->addRule('required', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Is the variable required?',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Variable Type.',
|
||||
'default' => '',
|
||||
'example' => 'password',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ class Executor
|
|||
* @param string $source
|
||||
* @param string $entrypoint
|
||||
* @param string $runtimeEntrypoint
|
||||
* @param bool $logging
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
|
@ -193,8 +194,7 @@ class Executor
|
|||
}
|
||||
|
||||
$runtimeId = "$projectId-$deploymentId";
|
||||
$route = '/runtimes/' . $runtimeId . '/execution';
|
||||
|
||||
$route = '/runtimes/' . $runtimeId . '/executions';
|
||||
|
||||
// Remove after migration
|
||||
if ($version == 'v3') {
|
||||
|
|
@ -216,6 +216,7 @@ class Executor
|
|||
'version' => $version,
|
||||
'runtimeEntrypoint' => $runtimeEntrypoint,
|
||||
'logging' => $logging,
|
||||
'restartPolicy' => 'always' // Once utopia/orchestration has it, use DockerAPI::ALWAYS (0.13+)
|
||||
];
|
||||
|
||||
if(!empty($body)) {
|
||||
|
|
|
|||
|
|
@ -1225,7 +1225,7 @@ class AccountCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Create a session for the new account
|
||||
// Create first session for the new account
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
|
|
@ -1238,11 +1238,23 @@ class AccountCustomClientTest extends Scope
|
|||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// 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,
|
||||
]);
|
||||
|
||||
|
||||
// Check the alert email
|
||||
$lastEmail = $this->getLastEmail();
|
||||
|
||||
$this->assertEquals($email, $lastEmail['to'][0]['address']);
|
||||
$this->assertStringContainsString('New session alert', $lastEmail['subject']);
|
||||
$this->assertStringContainsString('Security alert: new session', $lastEmail['subject']);
|
||||
$this->assertStringContainsString($response['body']['ip'], $lastEmail['text']); // IP Address
|
||||
$this->assertStringContainsString('Unknown', $lastEmail['text']); // Country
|
||||
$this->assertStringContainsString($response['body']['clientName'], $lastEmail['text']); // Client name
|
||||
|
|
|
|||
|
|
@ -1592,7 +1592,7 @@ class ProjectsConsoleClientTest extends Scope
|
|||
]
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and OTP must be a valid string and exactly 6 characters.', $response['body']['message']);
|
||||
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Invalid OTP. Please make sure the OTP is a 6 digit number', $response['body']['message']);
|
||||
|
||||
/** Trying to pass an OTP shorter than 6 characters*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
|
||||
|
|
@ -1607,7 +1607,22 @@ class ProjectsConsoleClientTest extends Scope
|
|||
]
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and OTP must be a valid string and exactly 6 characters.', $response['body']['message']);
|
||||
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Invalid OTP. Please make sure the OTP is a 6 digit number', $response['body']['message']);
|
||||
|
||||
/** Trying to pass an OTP with non numeric characters */
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'numbers' => [
|
||||
[
|
||||
'phone' => '+1655513432',
|
||||
'otp' => '123re2'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Invalid OTP. Please make sure the OTP is a 6 digit number', $response['body']['message']);
|
||||
|
||||
/** Trying to pass an invalid phone number */
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
|
||||
|
|
@ -1639,6 +1654,25 @@ class ProjectsConsoleClientTest extends Scope
|
|||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Invalid `numbers` param: Value must a valid array no longer than 10 items and Phone number must start with a \'+\' can have a maximum of fifteen digits.', $response['body']['message']);
|
||||
|
||||
/** Trying to pass duplicate numbers */
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/mock-numbers', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'numbers' => [
|
||||
[
|
||||
'phone' => '+1655513432',
|
||||
'otp' => '123456'
|
||||
],
|
||||
[
|
||||
'phone' => '+1655513432',
|
||||
'otp' => '123456'
|
||||
]
|
||||
]
|
||||
]);
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
$this->assertEquals('Duplicate phone numbers are not allowed.', $response['body']['message']);
|
||||
|
||||
$numbers = [];
|
||||
for ($i = 0; $i < 11; $i++) {
|
||||
$numbers[] = [
|
||||
|
|
|
|||
Loading…
Reference in a new issue