mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
2232 lines
72 KiB
PHP
2232 lines
72 KiB
PHP
<?php
|
|
|
|
namespace Tests\E2E\Services\Webhooks;
|
|
|
|
use Appwrite\Tests\Async;
|
|
use Tests\E2E\Client;
|
|
use Utopia\Database\Document;
|
|
use Utopia\Database\Helpers\ID;
|
|
use Utopia\Database\Query;
|
|
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
|
|
|
trait WebhooksBase
|
|
{
|
|
use Async;
|
|
|
|
// Tests for all auth scenarios
|
|
|
|
public function testCreateWebhook(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertNotEmpty($webhook['body']['$id']);
|
|
$this->assertEquals('Test Webhook', $webhook['body']['name']);
|
|
$this->assertEquals('https://appwrite.io', $webhook['body']['url']);
|
|
$this->assertContains('users.*.create', $webhook['body']['events']);
|
|
$this->assertCount(1, $webhook['body']['events']);
|
|
$this->assertEquals(true, $webhook['body']['enabled']);
|
|
$this->assertEquals(false, $webhook['body']['tls']);
|
|
$this->assertEquals('', $webhook['body']['authUsername']);
|
|
$this->assertEquals('', $webhook['body']['authPassword']);
|
|
$this->assertNotEmpty($webhook['body']['secret']);
|
|
$this->assertEquals(128, \strlen($webhook['body']['secret']));
|
|
$this->assertEquals(0, $webhook['body']['attempts']);
|
|
$this->assertEquals('', $webhook['body']['logs']);
|
|
|
|
$dateValidator = new DatetimeValidator();
|
|
$this->assertEquals(true, $dateValidator->isValid($webhook['body']['$createdAt']));
|
|
$this->assertEquals(true, $dateValidator->isValid($webhook['body']['$updatedAt']));
|
|
|
|
// Verify via GET
|
|
$get = $this->getWebhook($webhook['body']['$id']);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals($webhook['body']['$id'], $get['body']['$id']);
|
|
$this->assertEquals('Test Webhook', $get['body']['name']);
|
|
|
|
// Verify via LIST
|
|
$list = $this->listWebhooks(null, true);
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertGreaterThanOrEqual(1, $list['body']['total']);
|
|
$this->assertGreaterThanOrEqual(1, \count($list['body']['webhooks']));
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testCreateWebhookWithTls(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Webhook With TLS',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertNotEmpty($webhook['body']['$id']);
|
|
$this->assertEquals(true, $webhook['body']['tls']);
|
|
$this->assertIsBool($webhook['body']['tls']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testCreateWebhookWithHttpAuth(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Webhook With HTTP Auth',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
'username',
|
|
'password'
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertNotEmpty($webhook['body']['$id']);
|
|
$this->assertEquals('username', $webhook['body']['authUsername']);
|
|
$this->assertEquals('password', $webhook['body']['authPassword']);
|
|
$this->assertEquals(true, $webhook['body']['tls']);
|
|
|
|
// Verify via GET
|
|
$get = $this->getWebhook($webhook['body']['$id']);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals('username', $get['body']['authUsername']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testCreateWebhookEnabled(): void
|
|
{
|
|
// Create disabled webhook
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Disabled Webhook',
|
|
['users.*.create'],
|
|
false,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals(false, $webhook['body']['enabled']);
|
|
$this->assertIsBool($webhook['body']['enabled']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
|
|
// Create enabled webhook explicitly
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Enabled Webhook',
|
|
['users.*.create'],
|
|
true,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals(true, $webhook['body']['enabled']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testCreateWebhookWithoutAuthentication(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_POST, '/webhooks', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'webhookId' => ID::unique(),
|
|
'name' => 'Test Webhook',
|
|
'events' => ['users.*.create'],
|
|
'url' => 'https://appwrite.io',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateWebhookInvalidId(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
'!invalid-id!',
|
|
'Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $webhook['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateWebhookMissingName(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'webhookId' => ID::unique(),
|
|
'events' => ['users.*.create'],
|
|
'url' => 'https://appwrite.io',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateWebhookMissingUrl(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'webhookId' => ID::unique(),
|
|
'name' => 'Test Webhook',
|
|
'events' => ['users.*.create'],
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateWebhookMissingEvents(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'webhookId' => ID::unique(),
|
|
'name' => 'Test Webhook',
|
|
'url' => 'https://appwrite.io',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testCreateWebhookDuplicateId(): void
|
|
{
|
|
$webhookId = ID::unique();
|
|
|
|
$webhook = $this->createWebhook(
|
|
$webhookId,
|
|
'Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
|
|
// Attempt to create with same ID
|
|
$duplicate = $this->createWebhook(
|
|
$webhookId,
|
|
'Duplicate Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(409, $duplicate['headers']['status-code']);
|
|
$this->assertEquals('webhook_already_exists', $duplicate['body']['type']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testCreateWebhookAudit(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Audit Webhook',
|
|
['users.*.create', 'users.*.update.email'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertNotEmpty($webhook['body']['$id']);
|
|
$this->assertContains('users.*.create', $webhook['body']['events']);
|
|
$this->assertContains('users.*.update.email', $webhook['body']['events']);
|
|
$this->assertCount(2, $webhook['body']['events']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testUpdateWebhook(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Original Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Update the webhook
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Updated Webhook',
|
|
['users.*.delete', 'users.*.sessions.*.delete'],
|
|
null,
|
|
'https://appwrite.io/new',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals($webhookId, $updated['body']['$id']);
|
|
$this->assertEquals('Updated Webhook', $updated['body']['name']);
|
|
$this->assertEquals('https://appwrite.io/new', $updated['body']['url']);
|
|
$this->assertContains('users.*.delete', $updated['body']['events']);
|
|
$this->assertContains('users.*.sessions.*.delete', $updated['body']['events']);
|
|
$this->assertCount(2, $updated['body']['events']);
|
|
|
|
// Verify update persisted via GET
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals('Updated Webhook', $get['body']['name']);
|
|
$this->assertEquals('https://appwrite.io/new', $get['body']['url']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookWithTls(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'TLS Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
false,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals(false, $webhook['body']['tls']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Update to enable security
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Security Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals(true, $updated['body']['tls']);
|
|
$this->assertIsBool($updated['body']['tls']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookWithHttpAuth(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'HTTP Auth Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals('', $webhook['body']['authUsername']);
|
|
$this->assertEquals('', $webhook['body']['authPassword']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Update with HTTP auth credentials
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'HTTP Auth Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
'newuser',
|
|
'newpass'
|
|
);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals('newuser', $updated['body']['authUsername']);
|
|
$this->assertEquals('newpass', $updated['body']['authPassword']);
|
|
|
|
// Verify via GET
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals('newuser', $get['body']['authUsername']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookEnabled(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Enabled Webhook',
|
|
['users.*.create'],
|
|
true,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals(true, $webhook['body']['enabled']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Disable the webhook
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Enabled Webhook',
|
|
['users.*.create'],
|
|
false,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals(false, $updated['body']['enabled']);
|
|
$this->assertIsBool($updated['body']['enabled']);
|
|
|
|
// Re-enable the webhook (should reset attempts to 0)
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Enabled Webhook',
|
|
['users.*.create'],
|
|
true,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals(true, $updated['body']['enabled']);
|
|
$this->assertEquals(0, $updated['body']['attempts']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookWithoutAuthentication(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Auth Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Attempt update without authentication
|
|
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], [
|
|
'name' => 'Updated Webhook',
|
|
'events' => ['users.*.create'],
|
|
'url' => 'https://appwrite.io',
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookInvalidId(): void
|
|
{
|
|
$updated = $this->updateWebhook(
|
|
'non-existent-id',
|
|
'Updated Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(404, $updated['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $updated['body']['type']);
|
|
}
|
|
|
|
public function testUpdateWebhookMissingName(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Missing Name Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'events' => ['users.*.create'],
|
|
'url' => 'https://appwrite.io',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookMissingUrl(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Missing URL Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'name' => 'Missing URL Webhook',
|
|
'events' => ['users.*.create'],
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookMissingEvents(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Missing Events Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
$response = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'name' => 'Missing Events Webhook',
|
|
'url' => 'https://appwrite.io',
|
|
]);
|
|
|
|
$this->assertEquals(400, $response['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookDuplicateId(): void
|
|
{
|
|
// Update endpoint doesn't change the ID, so this tests updating a non-existent webhook
|
|
$updated = $this->updateWebhook(
|
|
'non-existent-id',
|
|
'Duplicate Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(404, $updated['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $updated['body']['type']);
|
|
}
|
|
|
|
public function testUpdateWebhookAudit(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Audit Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Update with multiple events
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Audit Update Webhook Updated',
|
|
['users.*.delete', 'users.*.sessions.*.delete', 'buckets.*.files.*.create'],
|
|
null,
|
|
'https://appwrite.io/updated',
|
|
true,
|
|
'user',
|
|
'pass'
|
|
);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals($webhookId, $updated['body']['$id']);
|
|
$this->assertEquals('Audit Update Webhook Updated', $updated['body']['name']);
|
|
$this->assertContains('users.*.delete', $updated['body']['events']);
|
|
$this->assertContains('users.*.sessions.*.delete', $updated['body']['events']);
|
|
$this->assertContains('buckets.*.files.*.create', $updated['body']['events']);
|
|
$this->assertCount(3, $updated['body']['events']);
|
|
$this->assertEquals('https://appwrite.io/updated', $updated['body']['url']);
|
|
$this->assertEquals(true, $updated['body']['tls']);
|
|
$this->assertEquals('user', $updated['body']['authUsername']);
|
|
$this->assertEquals('pass', $updated['body']['authPassword']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookSecret(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Secret Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
$originalSecret = $webhook['body']['secret'];
|
|
|
|
$this->assertNotEmpty($originalSecret);
|
|
$this->assertEquals(128, \strlen($originalSecret));
|
|
|
|
// Update secret
|
|
$updated = $this->updateWebhookSecret($webhookId);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals($webhookId, $updated['body']['$id']);
|
|
$this->assertNotEmpty($updated['body']['secret']);
|
|
$this->assertEquals(128, \strlen($updated['body']['secret']));
|
|
$this->assertNotEquals($originalSecret, $updated['body']['secret']);
|
|
|
|
// Verify secret is not exposed via GET
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEmpty($get['body']['secret']);
|
|
|
|
// Test secret update on non-existent webhook
|
|
$notFound = $this->updateWebhookSecret('non-existent-id');
|
|
$this->assertEquals(404, $notFound['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $notFound['body']['type']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testSecretRotationZeroDowntime(): void
|
|
{
|
|
// Create webhook pointing to request-catcher so deliveries are captured
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Rotation Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'http://request-catcher-webhook:5000/',
|
|
false,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
$originalSecret = $webhook['body']['secret'];
|
|
$this->assertNotEmpty($originalSecret);
|
|
$this->assertEquals(128, \strlen($originalSecret));
|
|
|
|
// Step 1: Trigger user creation with the original auto-generated secret
|
|
$email1 = uniqid() . 'rotation1@localhost.test';
|
|
$user1 = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email1,
|
|
'password' => 'password',
|
|
'name' => 'Rotation User 1',
|
|
]);
|
|
|
|
$this->assertEquals(201, $user1['headers']['status-code']);
|
|
$userId1 = $user1['body']['$id'];
|
|
|
|
// Verify webhook delivery is signed with the original secret
|
|
$this->assertEventually(function () use ($userId1, $originalSecret) {
|
|
$delivery = $this->getLastRequest(function (array $request) use ($userId1) {
|
|
$this->assertStringContainsString(
|
|
"users.{$userId1}.create",
|
|
$request['headers']['X-Appwrite-Webhook-Events'] ?? ''
|
|
);
|
|
});
|
|
|
|
$this->assertNotEmpty($delivery);
|
|
$payload = json_encode($delivery['data']);
|
|
$url = $delivery['url'];
|
|
$signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $originalSecret, true));
|
|
$this->assertEquals($signatureExpected, $delivery['headers']['X-Appwrite-Webhook-Signature']);
|
|
}, 15000, 500);
|
|
|
|
// Step 2: Rotate the secret to a known custom value
|
|
$newSecret = 'new-key-after-rotation';
|
|
$updated = $this->updateWebhookSecret($webhookId, $newSecret);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals($newSecret, $updated['body']['secret']);
|
|
$this->assertNotEquals($originalSecret, $updated['body']['secret']);
|
|
|
|
// Step 3: Trigger another user creation — should be signed with the new secret
|
|
$email2 = uniqid() . 'rotation2@localhost.test';
|
|
$user2 = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email2,
|
|
'password' => 'password',
|
|
'name' => 'Rotation User 2',
|
|
]);
|
|
|
|
$this->assertEquals(201, $user2['headers']['status-code']);
|
|
$userId2 = $user2['body']['$id'];
|
|
|
|
// Verify webhook delivery is signed with the new rotated secret
|
|
$this->assertEventually(function () use ($userId2, $newSecret) {
|
|
$delivery = $this->getLastRequest(function (array $request) use ($userId2) {
|
|
$this->assertStringContainsString(
|
|
"users.{$userId2}.create",
|
|
$request['headers']['X-Appwrite-Webhook-Events'] ?? ''
|
|
);
|
|
});
|
|
|
|
$this->assertNotEmpty($delivery);
|
|
$payload = json_encode($delivery['data']);
|
|
$url = $delivery['url'];
|
|
$signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $newSecret, true));
|
|
$this->assertEquals($signatureExpected, $delivery['headers']['X-Appwrite-Webhook-Signature']);
|
|
}, 15000, 500);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testCreateWebhookWithCustomSecret(): void
|
|
{
|
|
$customSecret = 'custom-secret-key';
|
|
|
|
// Create webhook with a custom secret pointing to request-catcher
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Custom Secret Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'http://request-catcher-webhook:5000/',
|
|
false,
|
|
null,
|
|
null,
|
|
$customSecret
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
$this->assertEquals($customSecret, $webhook['body']['secret']);
|
|
|
|
// Trigger user creation to generate a webhook delivery
|
|
$email = uniqid() . 'customsecret@localhost.test';
|
|
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'userId' => ID::unique(),
|
|
'email' => $email,
|
|
'password' => 'password',
|
|
'name' => 'Custom Secret User',
|
|
]);
|
|
|
|
$this->assertEquals(201, $user['headers']['status-code']);
|
|
$userId = $user['body']['$id'];
|
|
|
|
// Verify webhook delivery is signed with the custom secret
|
|
$this->assertEventually(function () use ($userId, $customSecret) {
|
|
$delivery = $this->getLastRequest(function (array $request) use ($userId) {
|
|
$this->assertStringContainsString(
|
|
"users.{$userId}.create",
|
|
$request['headers']['X-Appwrite-Webhook-Events'] ?? ''
|
|
);
|
|
});
|
|
|
|
$this->assertNotEmpty($delivery);
|
|
$payload = json_encode($delivery['data']);
|
|
$url = $delivery['url'];
|
|
$signatureExpected = base64_encode(hash_hmac('sha1', $url . $payload, $customSecret, true));
|
|
$this->assertEquals($signatureExpected, $delivery['headers']['X-Appwrite-Webhook-Signature']);
|
|
}, 15000, 500);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testCreateWebhookSecretMinLength(): void
|
|
{
|
|
// 7 chars — below minimum of 8
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Short Secret Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null,
|
|
'short12'
|
|
);
|
|
|
|
$this->assertEquals(400, $webhook['headers']['status-code']);
|
|
|
|
// 8 chars — exactly at minimum
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Min Secret Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null,
|
|
'exact8ch'
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals('exact8ch', $webhook['body']['secret']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testCreateWebhookSecretMaxLength(): void
|
|
{
|
|
// 256 chars — exactly at maximum
|
|
$maxSecret = str_repeat('a', 256);
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Max Secret Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null,
|
|
$maxSecret
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals($maxSecret, $webhook['body']['secret']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
|
|
// 257 chars — above maximum
|
|
$tooLongSecret = str_repeat('a', 257);
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Too Long Secret Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null,
|
|
$tooLongSecret
|
|
);
|
|
|
|
$this->assertEquals(400, $webhook['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateWebhookSecretMinLength(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Secret Min Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// 7 chars — below minimum of 8
|
|
$updated = $this->updateWebhookSecret($webhookId, 'short12');
|
|
$this->assertEquals(400, $updated['headers']['status-code']);
|
|
|
|
// 8 chars — exactly at minimum
|
|
$updated = $this->updateWebhookSecret($webhookId, 'exact8ch');
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals('exact8ch', $updated['body']['secret']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookSecretMaxLength(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Secret Max Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// 256 chars — exactly at maximum
|
|
$maxSecret = str_repeat('a', 256);
|
|
$updated = $this->updateWebhookSecret($webhookId, $maxSecret);
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEquals($maxSecret, $updated['body']['secret']);
|
|
|
|
// 257 chars — above maximum
|
|
$tooLongSecret = str_repeat('a', 257);
|
|
$updated = $this->updateWebhookSecret($webhookId, $tooLongSecret);
|
|
$this->assertEquals(400, $updated['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testWebhookSecretNotExposedInResponses(): void
|
|
{
|
|
// Create webhook — secret IS returned on creation
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Secret Exposure Test',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null,
|
|
'my-custom-secret'
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
$this->assertEquals('my-custom-secret', $webhook['body']['secret']);
|
|
$this->assertArrayNotHasKey('signatureKey', $webhook['body']);
|
|
|
|
// Get webhook — secret must not be exposed
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEmpty($get['body']['secret']);
|
|
$this->assertArrayNotHasKey('signatureKey', $get['body']);
|
|
|
|
// List webhooks — secret must not be exposed
|
|
$list = $this->listWebhooks(null, true);
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
foreach ($list['body']['webhooks'] as $item) {
|
|
$this->assertEmpty($item['secret']);
|
|
$this->assertArrayNotHasKey('signatureKey', $item);
|
|
}
|
|
|
|
// Update webhook — secret must not be exposed
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Secret Exposure Test Updated',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
$this->assertEmpty($updated['body']['secret']);
|
|
$this->assertArrayNotHasKey('signatureKey', $updated['body']);
|
|
|
|
// Update webhook secret — secret IS returned on rotation
|
|
$rotated = $this->updateWebhookSecret($webhookId, 'rotated-secret-key');
|
|
$this->assertEquals(200, $rotated['headers']['status-code']);
|
|
$this->assertEquals('rotated-secret-key', $rotated['body']['secret']);
|
|
$this->assertArrayNotHasKey('signatureKey', $rotated['body']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
// URL validation tests
|
|
|
|
public function testCreateWebhookWithPrivateDomain(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Private Domain Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'http://localhost/webhook',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $webhook['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateWebhookWithPrivateDomain(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Private Domain Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Attempt to update URL to private domain
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Private Domain Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'http://localhost/webhook',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $updated['headers']['status-code']);
|
|
|
|
// Verify original URL unchanged
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals('https://appwrite.io', $get['body']['url']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testCreateWebhookInvalidUrlScheme(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Invalid Scheme Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'invalid://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $webhook['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateWebhookInvalidUrlScheme(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Scheme Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Attempt to update URL to invalid scheme
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Scheme Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'invalid://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $updated['headers']['status-code']);
|
|
|
|
// Verify original URL unchanged
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals('https://appwrite.io', $get['body']['url']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
// Event validation tests
|
|
|
|
public function testCreateWebhookInvalidEvents(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Invalid Events Webhook',
|
|
['account.unknown'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $webhook['headers']['status-code']);
|
|
}
|
|
|
|
public function testUpdateWebhookInvalidEvents(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Invalid Events Update Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Attempt to update with invalid event
|
|
$updated = $this->updateWebhook(
|
|
$webhookId,
|
|
'Invalid Events Update Webhook',
|
|
['account.unknown'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(400, $updated['headers']['status-code']);
|
|
|
|
// Verify original events unchanged
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertContains('users.*.create', $get['body']['events']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
// Custom ID test
|
|
|
|
public function testCreateWebhookCustomId(): void
|
|
{
|
|
$customId = 'my-custom-webhook-id';
|
|
|
|
// Clean up stale webhook from a previous run if it exists
|
|
$existing = $this->getWebhook($customId);
|
|
if ($existing['headers']['status-code'] === 200) {
|
|
$this->deleteWebhook($customId);
|
|
}
|
|
|
|
$webhook = $this->createWebhook(
|
|
$customId,
|
|
'Custom ID Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$this->assertEquals($customId, $webhook['body']['$id']);
|
|
|
|
// Verify via GET
|
|
$get = $this->getWebhook($customId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals($customId, $get['body']['$id']);
|
|
|
|
// Ensure duplicate creation fails
|
|
$duplicate = $this->createWebhook(
|
|
$customId,
|
|
'Duplicate Custom ID Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(409, $duplicate['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($customId);
|
|
}
|
|
|
|
// Get webhook tests
|
|
|
|
public function testGetWebhook(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Get Test Webhook',
|
|
['users.*.create', 'users.*.update.email'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
'myuser',
|
|
'mypass'
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
$get = $this->getWebhook($webhookId);
|
|
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
$this->assertEquals($webhookId, $get['body']['$id']);
|
|
$this->assertEquals('Get Test Webhook', $get['body']['name']);
|
|
$this->assertEquals('https://appwrite.io', $get['body']['url']);
|
|
$this->assertContains('users.*.create', $get['body']['events']);
|
|
$this->assertContains('users.*.update.email', $get['body']['events']);
|
|
$this->assertCount(2, $get['body']['events']);
|
|
$this->assertEquals(true, $get['body']['enabled']);
|
|
$this->assertEquals(true, $get['body']['tls']);
|
|
$this->assertEquals('myuser', $get['body']['authUsername']);
|
|
$this->assertEquals('mypass', $get['body']['authPassword']);
|
|
$this->assertEmpty($get['body']['secret']);
|
|
$this->assertEquals(0, $get['body']['attempts']);
|
|
$this->assertEquals('', $get['body']['logs']);
|
|
|
|
$dateValidator = new DatetimeValidator();
|
|
$this->assertEquals(true, $dateValidator->isValid($get['body']['$createdAt']));
|
|
$this->assertEquals(true, $dateValidator->isValid($get['body']['$updatedAt']));
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testGetWebhookNotFound(): void
|
|
{
|
|
$get = $this->getWebhook('non-existent-id');
|
|
|
|
$this->assertEquals(404, $get['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $get['body']['type']);
|
|
}
|
|
|
|
public function testGetWebhookWithoutAuthentication(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Auth Get Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Attempt GET without authentication
|
|
$response = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
// List webhooks tests
|
|
|
|
public function testListWebhooks(): void
|
|
{
|
|
// Create multiple webhooks
|
|
$webhook1 = $this->createWebhook(
|
|
ID::unique(),
|
|
'List Webhook Alpha',
|
|
['users.*.create'],
|
|
true,
|
|
'https://appwrite.io/alpha',
|
|
false,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook1['headers']['status-code']);
|
|
|
|
$webhook2 = $this->createWebhook(
|
|
ID::unique(),
|
|
'List Webhook Beta',
|
|
['users.*.delete'],
|
|
false,
|
|
'https://appwrite.io/beta',
|
|
true,
|
|
'user',
|
|
'pass'
|
|
);
|
|
$this->assertEquals(201, $webhook2['headers']['status-code']);
|
|
|
|
$webhook3 = $this->createWebhook(
|
|
ID::unique(),
|
|
'List Webhook Gamma',
|
|
['users.*.create', 'users.*.delete'],
|
|
true,
|
|
'https://appwrite.io/gamma',
|
|
false,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook3['headers']['status-code']);
|
|
|
|
// List all
|
|
$list = $this->listWebhooks(null, true);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertGreaterThanOrEqual(3, $list['body']['total']);
|
|
$this->assertGreaterThanOrEqual(3, \count($list['body']['webhooks']));
|
|
$this->assertIsArray($list['body']['webhooks']);
|
|
|
|
// Verify structure of returned webhooks
|
|
foreach ($list['body']['webhooks'] as $webhook) {
|
|
$this->assertArrayHasKey('$id', $webhook);
|
|
$this->assertArrayHasKey('$createdAt', $webhook);
|
|
$this->assertArrayHasKey('$updatedAt', $webhook);
|
|
$this->assertArrayHasKey('name', $webhook);
|
|
$this->assertArrayHasKey('url', $webhook);
|
|
$this->assertArrayHasKey('events', $webhook);
|
|
$this->assertArrayHasKey('tls', $webhook);
|
|
$this->assertArrayHasKey('enabled', $webhook);
|
|
$this->assertArrayHasKey('secret', $webhook);
|
|
$this->assertArrayHasKey('attempts', $webhook);
|
|
$this->assertArrayHasKey('logs', $webhook);
|
|
}
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook1['body']['$id']);
|
|
$this->deleteWebhook($webhook2['body']['$id']);
|
|
$this->deleteWebhook($webhook3['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksWithLimit(): void
|
|
{
|
|
$webhook1 = $this->createWebhook(
|
|
ID::unique(),
|
|
'Limit Webhook 1',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/one',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook1['headers']['status-code']);
|
|
|
|
$webhook2 = $this->createWebhook(
|
|
ID::unique(),
|
|
'Limit Webhook 2',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/two',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook2['headers']['status-code']);
|
|
|
|
// List with limit of 1
|
|
$list = $this->listWebhooks([
|
|
Query::limit(1)->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertCount(1, $list['body']['webhooks']);
|
|
$this->assertGreaterThanOrEqual(2, $list['body']['total']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook1['body']['$id']);
|
|
$this->deleteWebhook($webhook2['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksWithOffset(): void
|
|
{
|
|
$webhook1 = $this->createWebhook(
|
|
ID::unique(),
|
|
'Offset Webhook 1',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/one',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook1['headers']['status-code']);
|
|
|
|
$webhook2 = $this->createWebhook(
|
|
ID::unique(),
|
|
'Offset Webhook 2',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/two',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook2['headers']['status-code']);
|
|
|
|
// List all to get total
|
|
$listAll = $this->listWebhooks(null, true);
|
|
$this->assertEquals(200, $listAll['headers']['status-code']);
|
|
$totalAll = \count($listAll['body']['webhooks']);
|
|
|
|
// List with offset
|
|
$listOffset = $this->listWebhooks([
|
|
Query::offset(1)->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $listOffset['headers']['status-code']);
|
|
$this->assertCount($totalAll - 1, $listOffset['body']['webhooks']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook1['body']['$id']);
|
|
$this->deleteWebhook($webhook2['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksFilterByName(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'UniqueFilterName-XYZ',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
|
|
$list = $this->listWebhooks([
|
|
Query::equal('name', ['UniqueFilterName-XYZ'])->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertEquals(1, $list['body']['total']);
|
|
$this->assertCount(1, $list['body']['webhooks']);
|
|
$this->assertEquals('UniqueFilterName-XYZ', $list['body']['webhooks'][0]['name']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksFilterByEnabled(): void
|
|
{
|
|
$webhookEnabled = $this->createWebhook(
|
|
ID::unique(),
|
|
'Enabled Filter Webhook',
|
|
['users.*.create'],
|
|
true,
|
|
'https://appwrite.io/enabled',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhookEnabled['headers']['status-code']);
|
|
|
|
$webhookDisabled = $this->createWebhook(
|
|
ID::unique(),
|
|
'Disabled Filter Webhook',
|
|
['users.*.create'],
|
|
false,
|
|
'https://appwrite.io/disabled',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhookDisabled['headers']['status-code']);
|
|
|
|
// Filter by enabled=true
|
|
$listEnabled = $this->listWebhooks([
|
|
Query::equal('enabled', [true])->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $listEnabled['headers']['status-code']);
|
|
$this->assertGreaterThanOrEqual(1, $listEnabled['body']['total']);
|
|
foreach ($listEnabled['body']['webhooks'] as $webhook) {
|
|
$this->assertEquals(true, $webhook['enabled']);
|
|
}
|
|
|
|
// Filter by enabled=false
|
|
$listDisabled = $this->listWebhooks([
|
|
Query::equal('enabled', [false])->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $listDisabled['headers']['status-code']);
|
|
$this->assertGreaterThanOrEqual(1, $listDisabled['body']['total']);
|
|
foreach ($listDisabled['body']['webhooks'] as $webhook) {
|
|
$this->assertEquals(false, $webhook['enabled']);
|
|
}
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookEnabled['body']['$id']);
|
|
$this->deleteWebhook($webhookDisabled['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksFilterByUrl(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'URL Filter Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/unique-url-filter',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
|
|
$list = $this->listWebhooks([
|
|
Query::equal('url', ['https://appwrite.io/unique-url-filter'])->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertEquals(1, $list['body']['total']);
|
|
$this->assertCount(1, $list['body']['webhooks']);
|
|
$this->assertEquals('https://appwrite.io/unique-url-filter', $list['body']['webhooks'][0]['url']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksFilterByTls(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'TLS Filter Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/sec',
|
|
true,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
|
|
$list = $this->listWebhooks([
|
|
Query::equal('tls', [true])->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertGreaterThanOrEqual(1, $list['body']['total']);
|
|
foreach ($list['body']['webhooks'] as $w) {
|
|
$this->assertEquals(true, $w['tls']);
|
|
}
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksWithoutTotal(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'No Total Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/nototal',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
|
|
// List with total=false
|
|
$list = $this->listWebhooks(null, false);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertEquals(0, $list['body']['total']);
|
|
$this->assertGreaterThanOrEqual(1, \count($list['body']['webhooks']));
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksCursorPagination(): void
|
|
{
|
|
$webhook1 = $this->createWebhook(
|
|
ID::unique(),
|
|
'Cursor Webhook 1',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/cursor1',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook1['headers']['status-code']);
|
|
|
|
$webhook2 = $this->createWebhook(
|
|
ID::unique(),
|
|
'Cursor Webhook 2',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io/cursor2',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
$this->assertEquals(201, $webhook2['headers']['status-code']);
|
|
|
|
// Get first page with limit 1
|
|
$page1 = $this->listWebhooks([
|
|
Query::limit(1)->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $page1['headers']['status-code']);
|
|
$this->assertCount(1, $page1['body']['webhooks']);
|
|
$cursorId = $page1['body']['webhooks'][0]['$id'];
|
|
|
|
// Get next page using cursor
|
|
$page2 = $this->listWebhooks([
|
|
Query::limit(1)->toString(),
|
|
Query::cursorAfter(new Document(['$id' => $cursorId]))->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(200, $page2['headers']['status-code']);
|
|
$this->assertCount(1, $page2['body']['webhooks']);
|
|
$this->assertNotEquals($cursorId, $page2['body']['webhooks'][0]['$id']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook1['body']['$id']);
|
|
$this->deleteWebhook($webhook2['body']['$id']);
|
|
}
|
|
|
|
public function testListWebhooksWithoutAuthentication(): void
|
|
{
|
|
$response = $this->client->call(Client::METHOD_GET, '/webhooks', [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
}
|
|
|
|
public function testListWebhooksInvalidCursor(): void
|
|
{
|
|
$list = $this->listWebhooks([
|
|
Query::cursorAfter(new Document(['$id' => 'non-existent-id']))->toString(),
|
|
], true);
|
|
|
|
$this->assertEquals(400, $list['headers']['status-code']);
|
|
}
|
|
|
|
// Delete webhook tests
|
|
|
|
public function testDeleteWebhook(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Delete Test Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Verify it exists
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
|
|
// Delete
|
|
$delete = $this->deleteWebhook($webhookId);
|
|
$this->assertEquals(204, $delete['headers']['status-code']);
|
|
$this->assertEmpty($delete['body']);
|
|
|
|
// Verify it no longer exists
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(404, $get['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $get['body']['type']);
|
|
}
|
|
|
|
public function testDeleteWebhookNotFound(): void
|
|
{
|
|
$delete = $this->deleteWebhook('non-existent-id');
|
|
|
|
$this->assertEquals(404, $delete['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $delete['body']['type']);
|
|
}
|
|
|
|
public function testDeleteWebhookWithoutAuthentication(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Delete Auth Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Attempt DELETE without authentication
|
|
$response = $this->client->call(Client::METHOD_DELETE, '/webhooks/' . $webhookId, [
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
]);
|
|
|
|
$this->assertEquals(401, $response['headers']['status-code']);
|
|
|
|
// Verify it still exists
|
|
$get = $this->getWebhook($webhookId);
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testDeleteWebhookRemovedFromList(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Delete List Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Get list count before delete
|
|
$listBefore = $this->listWebhooks(null, true);
|
|
$this->assertEquals(200, $listBefore['headers']['status-code']);
|
|
$countBefore = $listBefore['body']['total'];
|
|
|
|
// Delete
|
|
$delete = $this->deleteWebhook($webhookId);
|
|
$this->assertEquals(204, $delete['headers']['status-code']);
|
|
|
|
// Get list count after delete
|
|
$listAfter = $this->listWebhooks(null, true);
|
|
$this->assertEquals(200, $listAfter['headers']['status-code']);
|
|
$this->assertEquals($countBefore - 1, $listAfter['body']['total']);
|
|
|
|
// Verify the deleted webhook is not in the list
|
|
$ids = \array_column($listAfter['body']['webhooks'], '$id');
|
|
$this->assertNotContains($webhookId, $ids);
|
|
}
|
|
|
|
public function testDeleteWebhookDoubleDelete(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'Double Delete Webhook',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// First delete succeeds
|
|
$delete = $this->deleteWebhook($webhookId);
|
|
$this->assertEquals(204, $delete['headers']['status-code']);
|
|
|
|
// Second delete returns 404
|
|
$delete = $this->deleteWebhook($webhookId);
|
|
$this->assertEquals(404, $delete['headers']['status-code']);
|
|
$this->assertEquals('webhook_not_found', $delete['body']['type']);
|
|
}
|
|
|
|
// =========================================================================
|
|
// Backward compatibility tests (1.9.0 response format)
|
|
// =========================================================================
|
|
|
|
public function testCreateWebhookV22BackwardCompatRequest(): void
|
|
{
|
|
$headers = array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-response-format' => '1.9.0',
|
|
], $this->getHeaders());
|
|
|
|
// Send old param names with 1.9.0 header
|
|
$webhook = $this->client->call(Client::METHOD_POST, '/webhooks', $headers, [
|
|
'webhookId' => ID::unique(),
|
|
'name' => 'V22 Compat Create',
|
|
'events' => ['users.*.create'],
|
|
'url' => 'https://appwrite.io',
|
|
'security' => true,
|
|
'httpUser' => 'olduser',
|
|
'httpPass' => 'oldpass',
|
|
]);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
|
|
// Response should use OLD field names
|
|
$this->assertArrayHasKey('security', $webhook['body']);
|
|
$this->assertArrayHasKey('httpUser', $webhook['body']);
|
|
$this->assertArrayHasKey('httpPass', $webhook['body']);
|
|
$this->assertArrayHasKey('signatureKey', $webhook['body']);
|
|
|
|
// New field names should NOT be present
|
|
$this->assertArrayNotHasKey('tls', $webhook['body']);
|
|
$this->assertArrayNotHasKey('authUsername', $webhook['body']);
|
|
$this->assertArrayNotHasKey('authPassword', $webhook['body']);
|
|
$this->assertArrayNotHasKey('secret', $webhook['body']);
|
|
|
|
// Values should be correct
|
|
$this->assertEquals(true, $webhook['body']['security']);
|
|
$this->assertEquals('olduser', $webhook['body']['httpUser']);
|
|
$this->assertEquals('oldpass', $webhook['body']['httpPass']);
|
|
$this->assertNotEmpty($webhook['body']['signatureKey']);
|
|
$this->assertEquals(128, \strlen($webhook['body']['signatureKey']));
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhook['body']['$id']);
|
|
}
|
|
|
|
public function testUpdateWebhookV22BackwardCompatRequest(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'V22 Compat Update',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
$headers = array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-response-format' => '1.9.0',
|
|
], $this->getHeaders());
|
|
|
|
// Update using old param names
|
|
$updated = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, $headers, [
|
|
'name' => 'V22 Compat Updated',
|
|
'events' => ['users.*.create'],
|
|
'url' => 'https://appwrite.io',
|
|
'security' => true,
|
|
'httpUser' => 'updateduser',
|
|
'httpPass' => 'updatedpass',
|
|
]);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
|
|
// Response should use OLD field names
|
|
$this->assertArrayHasKey('security', $updated['body']);
|
|
$this->assertArrayHasKey('httpUser', $updated['body']);
|
|
$this->assertArrayHasKey('httpPass', $updated['body']);
|
|
$this->assertArrayHasKey('signatureKey', $updated['body']);
|
|
|
|
$this->assertArrayNotHasKey('tls', $updated['body']);
|
|
$this->assertArrayNotHasKey('authUsername', $updated['body']);
|
|
$this->assertArrayNotHasKey('authPassword', $updated['body']);
|
|
$this->assertArrayNotHasKey('secret', $updated['body']);
|
|
|
|
$this->assertEquals(true, $updated['body']['security']);
|
|
$this->assertEquals('updateduser', $updated['body']['httpUser']);
|
|
$this->assertEquals('updatedpass', $updated['body']['httpPass']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testGetWebhookV22BackwardCompatResponse(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'V22 Compat Get',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
'getuser',
|
|
'getpass'
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// GET with 1.9.0 header
|
|
$headers = array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-response-format' => '1.9.0',
|
|
], $this->getHeaders());
|
|
|
|
$get = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, $headers);
|
|
|
|
$this->assertEquals(200, $get['headers']['status-code']);
|
|
|
|
// Should have old field names
|
|
$this->assertArrayHasKey('security', $get['body']);
|
|
$this->assertArrayHasKey('httpUser', $get['body']);
|
|
$this->assertArrayHasKey('httpPass', $get['body']);
|
|
$this->assertArrayHasKey('signatureKey', $get['body']);
|
|
|
|
$this->assertArrayNotHasKey('tls', $get['body']);
|
|
$this->assertArrayNotHasKey('authUsername', $get['body']);
|
|
$this->assertArrayNotHasKey('authPassword', $get['body']);
|
|
$this->assertArrayNotHasKey('secret', $get['body']);
|
|
|
|
$this->assertEquals(true, $get['body']['security']);
|
|
$this->assertEquals('getuser', $get['body']['httpUser']);
|
|
$this->assertEquals('getpass', $get['body']['httpPass']);
|
|
$this->assertEmpty($get['body']['signatureKey']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testListWebhooksV22BackwardCompatResponse(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'V22 Compat List',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
true,
|
|
'listuser',
|
|
'listpass'
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// LIST with 1.9.0 header
|
|
$headers = array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-response-format' => '1.9.0',
|
|
], $this->getHeaders());
|
|
|
|
$list = $this->client->call(Client::METHOD_GET, '/webhooks', $headers, [
|
|
'queries' => [
|
|
Query::equal('name', ['V22 Compat List'])->toString(),
|
|
],
|
|
'total' => true,
|
|
]);
|
|
|
|
$this->assertEquals(200, $list['headers']['status-code']);
|
|
$this->assertEquals(1, $list['body']['total']);
|
|
$this->assertCount(1, $list['body']['webhooks']);
|
|
|
|
$item = $list['body']['webhooks'][0];
|
|
|
|
// Each item should have old field names
|
|
$this->assertArrayHasKey('security', $item);
|
|
$this->assertArrayHasKey('httpUser', $item);
|
|
$this->assertArrayHasKey('httpPass', $item);
|
|
$this->assertArrayHasKey('signatureKey', $item);
|
|
|
|
$this->assertArrayNotHasKey('tls', $item);
|
|
$this->assertArrayNotHasKey('authUsername', $item);
|
|
$this->assertArrayNotHasKey('authPassword', $item);
|
|
$this->assertArrayNotHasKey('secret', $item);
|
|
|
|
$this->assertEquals(true, $item['security']);
|
|
$this->assertEquals('listuser', $item['httpUser']);
|
|
$this->assertEquals('listpass', $item['httpPass']);
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
public function testUpdateWebhookSecretV22BackwardCompatResponse(): void
|
|
{
|
|
$webhook = $this->createWebhook(
|
|
ID::unique(),
|
|
'V22 Compat Secret',
|
|
['users.*.create'],
|
|
null,
|
|
'https://appwrite.io',
|
|
null,
|
|
null,
|
|
null
|
|
);
|
|
|
|
$this->assertEquals(201, $webhook['headers']['status-code']);
|
|
$webhookId = $webhook['body']['$id'];
|
|
|
|
// Update secret with 1.9.0 header
|
|
$headers = array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
'x-appwrite-response-format' => '1.9.0',
|
|
], $this->getHeaders());
|
|
|
|
$updated = $this->client->call(Client::METHOD_PATCH, '/webhooks/' . $webhookId . '/secret', $headers);
|
|
|
|
$this->assertEquals(200, $updated['headers']['status-code']);
|
|
|
|
// Response should use old field names
|
|
$this->assertArrayHasKey('signatureKey', $updated['body']);
|
|
$this->assertArrayHasKey('security', $updated['body']);
|
|
$this->assertArrayHasKey('httpUser', $updated['body']);
|
|
$this->assertArrayHasKey('httpPass', $updated['body']);
|
|
|
|
$this->assertArrayNotHasKey('secret', $updated['body']);
|
|
$this->assertArrayNotHasKey('tls', $updated['body']);
|
|
$this->assertArrayNotHasKey('authUsername', $updated['body']);
|
|
$this->assertArrayNotHasKey('authPassword', $updated['body']);
|
|
|
|
$this->assertNotEmpty($updated['body']['signatureKey']);
|
|
$this->assertEquals(128, \strlen($updated['body']['signatureKey']));
|
|
|
|
// Cleanup
|
|
$this->deleteWebhook($webhookId);
|
|
}
|
|
|
|
// Helpers
|
|
|
|
/**
|
|
* @param array<string>|null $queries
|
|
*/
|
|
protected function listWebhooks(?array $queries, ?bool $total): mixed
|
|
{
|
|
$webhooks = $this->client->call(Client::METHOD_GET, '/webhooks', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), [
|
|
'queries' => $queries,
|
|
'total' => $total
|
|
]);
|
|
|
|
return $webhooks;
|
|
}
|
|
|
|
protected function getWebhook(string $webhookId): mixed
|
|
{
|
|
$webhook = $this->client->call(Client::METHOD_GET, '/webhooks/' . $webhookId, array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()));
|
|
|
|
return $webhook;
|
|
}
|
|
|
|
protected function createWebhook(string $webhookId, string $name, array $events, ?bool $enabled, ?string $url, ?bool $tls, ?string $authUsername, ?string $authPassword, ?string $secret = null): mixed
|
|
{
|
|
$params = [
|
|
'webhookId' => $webhookId,
|
|
'name' => $name,
|
|
'events' => $events,
|
|
'url' => $url,
|
|
];
|
|
|
|
if ($enabled !== null) {
|
|
$params['enabled'] = $enabled;
|
|
}
|
|
if ($tls !== null) {
|
|
$params['tls'] = $tls;
|
|
}
|
|
if ($authUsername !== null) {
|
|
$params['authUsername'] = $authUsername;
|
|
}
|
|
if ($authPassword !== null) {
|
|
$params['authPassword'] = $authPassword;
|
|
}
|
|
if ($secret !== null) {
|
|
$params['secret'] = $secret;
|
|
}
|
|
|
|
$webhook = $this->client->call(Client::METHOD_POST, '/webhooks', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), $params);
|
|
|
|
return $webhook;
|
|
}
|
|
|
|
protected function updateWebhook(string $webhookId, string $name, array $events, ?bool $enabled, ?string $url, ?bool $tls, ?string $authUsername, ?string $authPassword): mixed
|
|
{
|
|
$params = [
|
|
'name' => $name,
|
|
'events' => $events,
|
|
'url' => $url,
|
|
];
|
|
|
|
if ($enabled !== null) {
|
|
$params['enabled'] = $enabled;
|
|
}
|
|
if ($tls !== null) {
|
|
$params['tls'] = $tls;
|
|
}
|
|
if ($authUsername !== null) {
|
|
$params['authUsername'] = $authUsername;
|
|
}
|
|
if ($authPassword !== null) {
|
|
$params['authPassword'] = $authPassword;
|
|
}
|
|
|
|
$webhook = $this->client->call(Client::METHOD_PUT, '/webhooks/' . $webhookId, array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), $params);
|
|
|
|
return $webhook;
|
|
}
|
|
|
|
protected function updateWebhookSecret(string $webhookId, ?string $secret = null): mixed
|
|
{
|
|
$params = [];
|
|
if ($secret !== null) {
|
|
$params['secret'] = $secret;
|
|
}
|
|
|
|
$webhook = $this->client->call(Client::METHOD_PATCH, '/webhooks/' . $webhookId . '/secret', array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()), $params);
|
|
|
|
return $webhook;
|
|
}
|
|
|
|
protected function deleteWebhook(string $webhookId): mixed
|
|
{
|
|
$webhook = $this->client->call(Client::METHOD_DELETE, '/webhooks/' . $webhookId, array_merge([
|
|
'content-type' => 'application/json',
|
|
'x-appwrite-project' => $this->getProject()['$id'],
|
|
], $this->getHeaders()));
|
|
|
|
return $webhook;
|
|
}
|
|
}
|