Add resource validation, admin scopes, and events for schedules

This commit is contained in:
Prem Palanisamy
2026-02-13 18:37:48 +00:00
committed by premtsd-code
parent 9b72b78338
commit 3fde05e024
5 changed files with 76 additions and 15 deletions
+14
View File
@@ -405,6 +405,20 @@ return [
'$description' => 'This event triggers when a provider is deleted.'
],
],
'schedules' => [
'$model' => Response::MODEL_SCHEDULE,
'$resource' => true,
'$description' => 'This event triggers on any schedule event.',
'create' => [
'$description' => 'This event triggers when a schedule is created.',
],
'update' => [
'$description' => 'This event triggers when a schedule is updated.',
],
'delete' => [
'$description' => 'This event triggers when a schedule is deleted.',
],
],
'rules' => [
'$model' => Response::MODEL_PROXY_RULE,
'$resource' => true,
+2
View File
@@ -91,6 +91,8 @@ $admins = [
'subscribers.read',
'tokens.read',
'tokens.write',
'schedules.read',
'schedules.write',
];
return [
@@ -57,6 +57,7 @@ class Create extends Action
->param('active', false, new Boolean(), 'Whether the schedule is active.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->callback($this->action(...));
@@ -69,16 +70,33 @@ class Create extends Action
bool $active,
Response $response,
Document $project,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
): void {
$collection = match ($resourceType) {
SCHEDULE_RESOURCE_TYPE_FUNCTION => 'functions',
SCHEDULE_RESOURCE_TYPE_EXECUTION => 'executions',
SCHEDULE_RESOURCE_TYPE_MESSAGE => 'messages',
};
$resource = $dbForProject->getDocument($collection, $resourceId);
if ($resource->isEmpty()) {
throw new Exception(match ($resourceType) {
SCHEDULE_RESOURCE_TYPE_FUNCTION => Exception::FUNCTION_NOT_FOUND,
SCHEDULE_RESOURCE_TYPE_EXECUTION => Exception::EXECUTION_NOT_FOUND,
SCHEDULE_RESOURCE_TYPE_MESSAGE => Exception::MESSAGE_NOT_FOUND,
});
}
try {
$doc = $authorization->skip(
fn () => $dbForPlatform->createDocument('schedules', new Document([
'region' => $project->getAttribute('region'),
'resourceType' => $resourceType,
'resourceId' => $resourceId,
'resourceInternalId' => '',
'resourceInternalId' => $resource->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'schedule' => $schedule,
@@ -3,6 +3,7 @@
namespace Tests\E2E\Services\Schedules;
use Tests\E2E\Client;
use Utopia\Database\Helpers\ID;
trait SchedulesBase
{
@@ -29,4 +30,18 @@ trait SchedulesBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), $params);
}
protected function createFunction(array $params = []): array
{
return $this->client->call(Client::METHOD_POST, '/functions', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), array_merge([
'functionId' => ID::unique(),
'name' => 'Test Schedule Function',
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'execute' => ['any'],
], $params));
}
}
@@ -19,9 +19,13 @@ class SchedulesCustomServerTest extends Scope
/**
* Test for SUCCESS
*/
$function = $this->createFunction();
$this->assertEquals(201, $function['headers']['status-code']);
$functionId = $function['body']['$id'];
$response = $this->createSchedule([
'resourceType' => 'function',
'resourceId' => ID::unique(),
'resourceId' => $functionId,
'schedule' => '0 0 * * *',
'active' => true,
]);
@@ -31,39 +35,44 @@ class SchedulesCustomServerTest extends Scope
$this->assertNotEmpty($response['body']['$createdAt']);
$this->assertNotEmpty($response['body']['$updatedAt']);
$this->assertEquals('function', $response['body']['resourceType']);
$this->assertNotEmpty($response['body']['resourceId']);
$this->assertEquals($functionId, $response['body']['resourceId']);
$this->assertNotEmpty($response['body']['resourceUpdatedAt']);
$this->assertNotEmpty($response['body']['projectId']);
$this->assertEquals('0 0 * * *', $response['body']['schedule']);
$this->assertTrue($response['body']['active']);
$this->assertNotEmpty($response['body']['region']);
return ['scheduleId' => $response['body']['$id']];
return ['scheduleId' => $response['body']['$id'], 'functionId' => $functionId];
}
public function testCreateScheduleExecutionType(): void
public function testCreateScheduleResourceNotFound(): void
{
// Function not found
$response = $this->createSchedule([
'resourceType' => 'function',
'resourceId' => ID::unique(),
'schedule' => '0 0 * * *',
]);
$this->assertEquals(404, $response['headers']['status-code']);
// Execution not found
$response = $this->createSchedule([
'resourceType' => 'execution',
'resourceId' => ID::unique(),
'schedule' => '*/10 * * * *',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals('execution', $response['body']['resourceType']);
$this->assertFalse($response['body']['active']);
}
$this->assertEquals(404, $response['headers']['status-code']);
public function testCreateScheduleMessageType(): void
{
// Message not found
$response = $this->createSchedule([
'resourceType' => 'message',
'resourceId' => ID::unique(),
'schedule' => '0 9 * * 1',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertEquals('message', $response['body']['resourceType']);
$this->assertEquals(404, $response['headers']['status-code']);
}
public function testCreateScheduleInvalidResourceType(): void
@@ -220,10 +229,13 @@ class SchedulesCustomServerTest extends Scope
public function testScheduleProjectIsolation(): void
{
// Create a schedule in the current project
// Create a function and schedule in the current project
$function = $this->createFunction();
$this->assertEquals(201, $function['headers']['status-code']);
$response = $this->createSchedule([
'resourceType' => 'function',
'resourceId' => ID::unique(),
'resourceId' => $function['body']['$id'],
'schedule' => '0 12 * * *',
]);