Fix schedule Create endpoint and tests

This commit is contained in:
Prem Palanisamy
2026-02-18 06:23:19 +00:00
committed by premtsd-code
parent 9dbd225118
commit d8987ad975
3 changed files with 51 additions and 36 deletions
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Schedules\Http\Schedules;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -17,6 +18,7 @@ use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\JSON;
use Utopia\Validator\WhiteList;
class Create extends Action
@@ -65,6 +67,7 @@ class Create extends Action
->desc('Create schedule')
->groups(['api', 'projects'])
->label('scope', 'schedules.write')
->label('event', 'schedules.[scheduleId].create')
->label('audits.event', 'schedule.create')
->label('audits.resource', 'schedule/{response.$id}')
->label('sdk', new Method(
@@ -77,18 +80,20 @@ class Create extends Action
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_SCHEDULE,
)
),
],
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('resourceType', '', new WhiteList($resourceTypes, true), 'The resource type for the schedule. Possible values: ' . implode(', ', $resourceTypes) . '.')
->param('resourceId', '', new UID(), 'The resource ID to associate with this schedule.')
->param('schedule', '', new Cron(), 'Schedule CRON expression.')
->param('active', false, new Boolean(), 'Whether the schedule is active.', true)
->param('projectId', '', new UID, 'Project unique ID.')
->param('resourceType', '', new WhiteList($resourceTypes, true), 'The resource type for the schedule. Possible values: '.implode(', ', $resourceTypes).'.')
->param('resourceId', '', new UID, 'The resource ID to associate with this schedule.')
->param('schedule', '', new Cron, 'Schedule CRON expression.')
->param('active', false, new Boolean, 'Whether the schedule is active.', true)
->param('data', null, new JSON, 'Schedule data as a JSON string. Used to store resource-specific context needed for execution.', true)
->inject('response')
->inject('dbForPlatform')
->inject('getProjectDB')
->inject('authorization')
->inject('queueForEvents')
->callback($this->action(...));
}
@@ -98,10 +103,12 @@ class Create extends Action
string $resourceId,
string $schedule,
bool $active,
?string $data,
Response $response,
Database $dbForPlatform,
callable $getProjectDB,
Authorization $authorization,
Event $queueForEvents,
): void {
$project = $dbForPlatform->getDocument('projects', $projectId);
@@ -112,7 +119,7 @@ class Create extends Action
$dbForProject = $getProjectDB($project);
$collectionMap = $this->getCollectionMap();
$collection = $collectionMap[$resourceType] ?? throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource type: ' . $resourceType);
$collection = $collectionMap[$resourceType] ?? throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid resource type: '.$resourceType);
$resource = $dbForProject->getDocument($collection, $resourceId);
@@ -121,23 +128,31 @@ class Create extends Action
throw new Exception($notFoundMap[$resourceType] ?? Exception::GENERAL_ARGUMENT_INVALID, 'Resource not found');
}
$attributes = [
'region' => $project->getAttribute('region'),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => $resource->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $schedule,
'active' => $active,
];
if ($data !== null) {
$attributes['data'] = \json_decode($data, true);
}
try {
$doc = $authorization->skip(
fn () => $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => $resource->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $schedule,
'active' => $active,
]))
fn () => $dbForPlatform->createDocument('schedules', new Document($attributes))
);
} catch (DuplicateException) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to create schedule. Please try again.');
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
}
$queueForEvents->setParam('scheduleId', $doc->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($doc, Response::MODEL_SCHEDULE);
@@ -34,7 +34,7 @@ trait SchedulesBase
$projectId = $project['body']['$id'];
$key = $this->client->call(Client::METHOD_POST, '/projects/' . $projectId . '/keys', array_merge([
$key = $this->client->call(Client::METHOD_POST, '/projects/'.$projectId.'/keys', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -12,8 +12,8 @@ use Utopia\System\System;
class SchedulesConsoleClientTest extends Scope
{
use SchedulesBase;
use ProjectConsole;
use SchedulesBase;
use SideClient;
/**
@@ -42,7 +42,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -74,7 +74,7 @@ class SchedulesConsoleClientTest extends Scope
*/
// Resource not found
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -86,7 +86,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(404, $response['headers']['status-code']);
// Invalid resource type
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -98,7 +98,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
// Invalid cron
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -110,7 +110,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
// Missing resourceType
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -121,7 +121,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
// Missing resourceId
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -132,7 +132,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
// Missing schedule
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -156,7 +156,7 @@ class SchedulesConsoleClientTest extends Scope
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules/' . $scheduleId, array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules/'.$scheduleId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
@@ -171,7 +171,7 @@ class SchedulesConsoleClientTest extends Scope
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules/error', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules/'.ID::unique(), array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
@@ -191,7 +191,7 @@ class SchedulesConsoleClientTest extends Scope
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
@@ -214,7 +214,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertArrayHasKey('region', $schedule);
/** Filter by resourceType */
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -229,7 +229,7 @@ class SchedulesConsoleClientTest extends Scope
}
/** Filter by active status */
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -243,7 +243,7 @@ class SchedulesConsoleClientTest extends Scope
}
/** List with total disabled */
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -257,7 +257,7 @@ class SchedulesConsoleClientTest extends Scope
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$id.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
@@ -301,7 +301,7 @@ class SchedulesConsoleClientTest extends Scope
$otherProjectId = $otherProject['body']['$id'];
// Try to get the schedule from the other project
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $otherProjectId . '/schedules/' . $scheduleId, array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$otherProjectId.'/schedules/'.$scheduleId, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);
@@ -309,7 +309,7 @@ class SchedulesConsoleClientTest extends Scope
$this->assertEquals(404, $response['headers']['status-code']);
// List should not include schedules from other projects
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $otherProjectId . '/schedules', array_merge([
$response = $this->client->call(Client::METHOD_GET, '/projects/'.$otherProjectId.'/schedules', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), []);