Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F174783
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
73 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/tests/Infrastructure/ActivesyncTest.php b/src/tests/Infrastructure/ActivesyncTest.php
index b2c56865..8e1af571 100644
--- a/src/tests/Infrastructure/ActivesyncTest.php
+++ b/src/tests/Infrastructure/ActivesyncTest.php
@@ -1,1598 +1,1667 @@
<?php
namespace Tests\Infrastructure;
use App\Backends\Roundcube;
use Tests\TestCase;
use Illuminate\Support\Str;
class ActivesyncTest extends TestCase
{
private static ?\GuzzleHttp\Client $client = null;
private static ?\App\User $user = null;
private static ?string $deviceId = null;
private static ?string $deviceId2 = null;
private static function toWbxml($xml)
{
$outputStream = fopen("php://temp", 'r+');
$encoder = new \Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
$dom = new \DOMDocument();
$dom->loadXML($xml);
$encoder->encode($dom);
rewind($outputStream);
return stream_get_contents($outputStream);
}
private static function fromWbxml($binary)
{
$stream = fopen('php://memory', 'r+');
fwrite($stream, $binary);
rewind($stream);
$decoder = new \Syncroton_Wbxml_Decoder($stream);
return $decoder->decode();
}
private function request($request, $cmd, $deviceId = null)
{
$user = self::$user;
if (!$deviceId) {
$deviceId = self::$deviceId;
}
$body = self::toWbxml($request);
return self::$client->request(
'POST',
"?Cmd={$cmd}&User={$user->email}&DeviceId={$deviceId}&DeviceType=WindowsOutlook15",
[
'headers' => [
"Content-Type" => "application/vnd.ms-sync.wbxml",
'MS-ASProtocolVersion' => "14.0"
],
'body' => $body
]
);
}
private function xpath($dom)
{
$xpath = new \DOMXpath($dom);
$xpath->registerNamespace("ns", $dom->documentElement->namespaceURI);
$xpath->registerNamespace("Tasks", "uri:Tasks");
$xpath->registerNamespace("Calendar", "uri:Calendar");
$xpath->registerNamespace("Email", "uri:Email");
$xpath->registerNamespace("Email2", "uri:Email2");
return $xpath;
}
/**
* {@inheritDoc}
*/
public function setUp(): void
{
parent::setUp();
if (!self::$deviceId) {
// By always creating a new device we force syncroton to initialize.
// Otherwise we work against uninitialized metadata (subscription states),
// because the account has been removed, but syncroton doesn't reinitalize the metadata for known devices.
self::$deviceId = (string) Str::uuid();
self::$deviceId2 = (string) Str::uuid();
}
$deviceId = self::$deviceId;
\config(['imap.default_folders' => [
'Drafts' => [
'metadata' => [
'/private/vendor/kolab/folder-type' => 'mail.drafts',
'/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
],
],
'Calendar' => [
'metadata' => [
'/private/vendor/kolab/folder-type' => 'event.default',
'/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
],
],
'Tasks' => [
'metadata' => [
'/private/vendor/kolab/folder-type' => 'task.default',
'/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
],
],
'Contacts' => [
'metadata' => [
'/private/vendor/kolab/folder-type' => 'contact.default',
'/private/vendor/kolab/activesync' => "{\"FOLDER\":{\"{$deviceId}\":{\"S\":1}}}"
],
],
]]);
if (!self::$user) {
self::$user = $this->getTestUser('activesynctest@kolab.org', ['password' => 'simple123'], true);
//FIXME this shouldn't be required, but it seems to be.
Roundcube::dbh()->table('kolab_cache_task')->truncate();
Roundcube::dbh()->table('syncroton_folder')->truncate();
// Roundcube::dbh()->table('syncroton_content')->truncate();
// Roundcube::dbh()->table('syncroton_device')->truncate();
}
if (!self::$client) {
self::$client = new \GuzzleHttp\Client([
'http_errors' => false, // No exceptions
'base_uri' => \config("services.activesync.uri"),
'verify' => false,
'auth' => [self::$user->email, 'simple123'],
'connect_timeout' => 10,
'timeout' => 10,
'headers' => [
"Content-Type" => "application/xml; charset=utf-8",
"Depth" => "1",
]
]);
}
}
public function testOptions()
{
$response = self::$client->request('OPTIONS', '');
$this->assertEquals(200, $response->getStatusCode());
$this->assertStringContainsString('14', $response->getHeader('MS-Server-ActiveSync')[0]);
$this->assertStringContainsString('14.1', $response->getHeader('MS-ASProtocolVersions')[0]);
$this->assertStringContainsString('FolderSync', $response->getHeader('MS-ASProtocolCommands')[0]);
}
public function testPartialCommand()
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<FolderSync xmlns="uri:FolderHierarchy">
<SyncKey>0</SyncKey>
</FolderSync>
EOF;
$body = self::toWbxml($request);
$deviceId = self::$deviceId;
$user = self::$user;
$response = self::$client->request(
'POST',
"?Cmd=FolderSync&User={$user->email}&DeviceId={$deviceId}&DeviceType=WindowsOutlook15",
[
'headers' => [
"Content-Type" => "application/vnd.ms-sync.wbxml",
'MS-ASProtocolVersion' => "14.0"
],
//Truncated body
'body' => substr($body, 0, strlen($body) / 2)
]
);
$this->assertEquals(500, $response->getStatusCode());
}
public function testList()
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<FolderSync xmlns="uri:FolderHierarchy">
<SyncKey>0</SyncKey>
</FolderSync>
EOF;
$response = $this->request($request, 'FolderSync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$xml = $dom->saveXML();
$this->assertStringContainsString('INBOX', $xml);
// The hash is based on the name, so it's always the same
$inboxId = '38b950ebd62cd9a66929c89615d0fc04';
$this->assertStringContainsString($inboxId, $xml);
$this->assertStringContainsString('Drafts', $xml);
$this->assertStringContainsString('Calendar', $xml);
$this->assertStringContainsString('Tasks', $xml);
$this->assertStringContainsString('Contacts', $xml);
// Find the inbox for the next step
// $collectionIds = $dom->getElementsByTagName('ServerId');
// $inboxId = $collectionIds[0]->nodeValue;
return $inboxId;
}
/**
* @depends testList
*/
public function testInitialSync($inboxId)
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$inboxId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Email", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
return $inboxId;
}
/**
* @depends testInitialSync
*/
public function testAdd($inboxId)
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$inboxId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
// We expect an empty response without a change
$this->assertEquals(0, $response->getBody()->getSize());
}
/**
* @depends testList
*/
public function testSyncTasks()
{
$tasksId = "90335880f65deff6e521acea2b71a773";
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
return $tasksId;
}
/**
* @depends testSyncTasks
*/
public function testAddTask($tasksId)
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId1</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task1</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Tasks", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("2", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(1, $add->length);
$this->assertEquals("clientId1", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(0)->nodeValue);
$this->assertEquals(0, $xpath->query("//ns:Commands")->length);
return [
'collectionId' => $tasksId,
'serverId1' => $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(0)->nodeValue
];
}
/**
* Re-issuing the same command should not result in the sync key being invalidated.
*
* @depends testAddTask
*/
public function testReAddTask($result)
{
$tasksId = $result['collectionId'];
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId1</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task1</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Tasks", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("2", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(1, $add->length);
$this->assertEquals("clientId1", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(0)->nodeValue);
$this->assertEquals(0, $xpath->query("//ns:Commands")->length);
return [
'collectionId' => $tasksId,
'serverId1' => $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(0)->nodeValue
];
}
/**
* Make sure we can continue with the sync after the previous hickup, also include a modification.
*
* @depends testAddTask
*/
public function testAddTaskContinued($result)
{
$tasksId = $result['collectionId'];
$serverId = $result['serverId1'];
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId2</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task2</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
<Add>
<ClientId>clientId3</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task3</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
<Change>
<ServerId>{$serverId}</ServerId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task4</Subject>
</ApplicationData>
</Change>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Tasks", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("3", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(2, $add->length);
$this->assertEquals("clientId2", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(0)->nodeValue);
$this->assertEquals("clientId3", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(1)->nodeValue);
$this->assertEquals(0, $xpath->query("//ns:Commands")->length);
// The server does not have to inform about a successful change
$change = $xpath->query("//ns:Responses/ns:Change");
$this->assertEquals(0, $change->length);
return [
'collectionId' => $tasksId,
'serverId1' => $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(0)->nodeValue,
'serverId2' => $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(1)->nodeValue
];
}
/**
* Perform another duplicate request.
*
* @depends testAddTaskContinued
*/
public function testAddTaskContinuedAgain($result)
{
$tasksId = $result['collectionId'];
$serverId = $result['serverId1'];
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId2</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task2</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
<Add>
<ClientId>clientId3</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task3</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
<Change>
<ServerId>{$serverId}</ServerId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task4</Subject>
</ApplicationData>
</Change>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Tasks", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("3", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
$xpath = $this->xpath($dom);
print($dom->saveXML());
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(2, $add->length);
$this->assertEquals("clientId2", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(0)->nodeValue);
$this->assertEquals(
$result['serverId1'],
$xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(0)->nodeValue
);
$this->assertEquals("clientId3", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(1)->nodeValue);
$this->assertEquals(
$result['serverId2'],
$xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(1)->nodeValue
);
// The server does not have to inform about a successful change
$change = $xpath->query("//ns:Responses/ns:Change");
$this->assertEquals(0, $change->length);
$this->assertEquals(0, $xpath->query("//ns:Commands")->length);
return [
'collectionId' => $tasksId,
'serverId2' => $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(1)->nodeValue
];
}
/**
* Test a sync key that shouldn't exist yet.
* @depends testSyncTasks
*/
public function testInvalidSyncKey($tasksId)
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>4</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId999</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task1</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("3", $status[0]->nodeValue);
//After this we have to start from scratch
}
/**
* Test fetching changes with a second device
* @depends testAddTaskContinuedAgain
*/
public function testFetchTasks($result)
{
$tasksId = $result['collectionId'];
$serverId = $result['serverId2'];
// Initialize the second device
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<FolderSync xmlns="uri:FolderHierarchy">
<SyncKey>0</SyncKey>
</FolderSync>
EOF;
$response = $this->request($request, 'FolderSync', self::$deviceId2);
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<MIMESupport>2</MIMESupport>
<MIMETruncation>8</MIMETruncation>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>4</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync', self::$deviceId2);
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
// Fetch the content
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync', self::$deviceId2);
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Commands/ns:Add");
$this->assertEquals(3, $add->length);
//Resend the same command
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync', self::$deviceId2);
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Commands/ns:Add");
$this->assertEquals(3, $add->length);
// Add another entry, delete an entry, with the original device (we have to init first again)
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<MIMESupport>2</MIMESupport>
<MIMETruncation>8</MIMETruncation>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>4</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId4</ClientId>
<ApplicationData>
<Subject xmlns="uri:Tasks">task4</Subject>
<Complete xmlns="uri:Tasks">0</Complete>
<DueDate xmlns="uri:Tasks">2020-11-04T00:00:00.000Z</DueDate>
<UtcDueDate xmlns="uri:Tasks">2020-11-03T23:00:00.000Z</UtcDueDate>
</ApplicationData>
</Add>
<Delete>
<ServerId>{$serverId}</ServerId>
</Delete>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(1, $add->length);
// Delete does not have to be confirmed according to spec
$delete = $xpath->query("//ns:Responses/ns:Delete");
$this->assertEquals(0, $delete->length);
// And fetch the changes
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync', self::$deviceId2);
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
// print("=====\n");
// print($dom->saveXML());
// print("=====\n");
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Commands/ns:Add");
$this->assertEquals(1, $add->length);
$delete = $xpath->query("//ns:Commands/ns:Delete");
$this->assertEquals(1, $delete->length);
// and finally refetch the same changes
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync', self::$deviceId2);
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
// print("=====\n");
// print($dom->saveXML());
// print("=====\n");
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Commands/ns:Add");
$this->assertEquals(1, $add->length);
//FIXME we currently miss deletions.
$delete = $xpath->query("//ns:Commands/ns:Delete");
$this->assertEquals(0, $delete->length);
}
/**
* @depends testList
*/
public function testSyncCalendar()
{
$tasksId = "cca1b81c734abbcd669bea90d23e08ae";
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
return $tasksId;
}
/**
* @depends testSyncCalendar
*/
public function testAddEvent($tasksId)
{
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId1</ClientId>
<ApplicationData>
<StartTime xmlns="uri:Calendar">20230719T200032Z</StartTime>
<BusyStatus xmlns="uri:Calendar">2</BusyStatus>
<DtStamp xmlns="uri:Calendar">20230719T194232Z</DtStamp>
<EndTime xmlns="uri:Calendar">20230719T203032Z</EndTime>
<UID xmlns="uri:Calendar">046f2e01-e8d0-47c6-a607-ba360251761d</UID>
<OrganizerEmail xmlns="uri:Calendar">activesynctest@kolab.org</OrganizerEmail>
<MeetingStatus xmlns="uri:Calendar">0</MeetingStatus>
<ResponseRequested xmlns="uri:Calendar">0</ResponseRequested>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Calendar", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("2", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(1, $add->length);
$this->assertEquals("clientId1", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(0)->nodeValue);
$this->assertEquals("1", $xpath->query("//ns:Responses/ns:Add/ns:Status")->item(0)->nodeValue);
// $this->assertEquals(0, $xpath->query("//ns:Commands")->length);
return [
'collectionId' => $tasksId,
'serverId1' => $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(0)->nodeValue
];
}
/**
* @depends testAddEvent
*/
public function testReaddEvent($result)
{
$tasksId = $result['collectionId'];
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Add>
<ClientId>clientId1</ClientId>
<ApplicationData>
<StartTime xmlns="uri:Calendar">20230719T200032Z</StartTime>
<BusyStatus xmlns="uri:Calendar">2</BusyStatus>
<DtStamp xmlns="uri:Calendar">20230719T194232Z</DtStamp>
<EndTime xmlns="uri:Calendar">20230719T203032Z</EndTime>
<UID xmlns="uri:Calendar">046f2e01-e8d0-47c6-a607-ba360251761d</UID>
<OrganizerEmail xmlns="uri:Calendar">activesynctest@kolab.org</OrganizerEmail>
<MeetingStatus xmlns="uri:Calendar">0</MeetingStatus>
<ResponseRequested xmlns="uri:Calendar">0</ResponseRequested>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$collections = $dom->getElementsByTagName('Collection');
$this->assertEquals(1, $collections->length);
$collection = $collections->item(0);
$this->assertEquals("Class", $collection->childNodes->item(0)->nodeName);
$this->assertEquals("Calendar", $collection->childNodes->item(0)->nodeValue);
$this->assertEquals("SyncKey", $collection->childNodes->item(1)->nodeName);
$this->assertEquals("3", $collection->childNodes->item(1)->nodeValue);
$this->assertEquals("Status", $collection->childNodes->item(3)->nodeName);
$this->assertEquals("1", $collection->childNodes->item(3)->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(1, $add->length);
$this->assertEquals("clientId1", $xpath->query("//ns:Responses/ns:Add/ns:ClientId")->item(0)->nodeValue);
$this->assertEquals("5", $xpath->query("//ns:Responses/ns:Add/ns:Status")->item(0)->nodeValue);
$this->assertEquals(0, $xpath->query("//ns:Commands")->length);
return $result;
}
/**
* @depends testReaddEvent
*/
public function testDeleteEvent($result)
{
$tasksId = $result['collectionId'];
$serverId = $result['serverId1'];
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Tasks="uri:Tasks">
<Collections>
<Collection>
<SyncKey>3</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<Commands>
<Delete>
<ServerId>{$serverId}</ServerId>
</Delete>
</Commands>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
return $tasksId;
}
/**
* @depends testDeleteEvent
*/
public function testMeetingResponse($tasksId)
{
$inboxId = '38b950ebd62cd9a66929c89615d0fc04';
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$inboxId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
//Add the invitation to inbox
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:Syncroton="uri:Syncroton" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Email="uri:Email" xmlns:Email2="uri:Email2" xmlns:Tasks="uri:Tasks" xmlns:Calendar="uri:Calendar">
<Collections>
<Collection xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase">
<SyncKey>1</SyncKey>
<CollectionId>{$inboxId}</CollectionId>
<Commands xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase">
<Add xmlns:default="uri:Email" xmlns:default1="uri:AirSyncBase">
<ClientId>clientid1</ClientId>
<ApplicationData>
<Email:DateReceived xmlns="uri:Email">2023-07-24T06:53:57.000Z</Email:DateReceived>
<Email:From xmlns="uri:Email">"Doe, John" <doe@kolab1.mkpf.ch></Email:From>
<Email:InternetCPID xmlns="uri:Email">65001</Email:InternetCPID>
<Email:Subject xmlns="uri:Email">You've been invited to "event1"</Email:Subject>
<Email:To xmlns="uri:Email">admin@kolab1.mkpf.ch</Email:To>
<Email:Read xmlns="uri:Email">1</Email:Read>
<Email:Flag xmlns="uri:Email"/>
<AirSyncBase:Body xmlns="uri:AirSyncBase">
<AirSyncBase:Type>4</AirSyncBase:Type>
<AirSyncBase:Data>MIME-Version: 1.0
From: "Doe, John" <doe@kolab1.mkpf.ch>
Date: Mon, 24 Jul 2023 08:53:55 +0200
Message-ID: <9cd8885d1339b976c7cb15db086e7bbc@kolab1.mkpf.ch>
To: admin@kolab1.mkpf.ch
Subject: You've been invited to "event1"
Content-Type: multipart/alternative;
boundary="=_a32392e5fc9266e3eeba97347cbfc147"
--=_a32392e5fc9266e3eeba97347cbfc147
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=UTF-8;
format=flowed
*event1*
When: 2023-07-24 11:00 - 11:30 (Europe/Vaduz)
Invitees: Doe, John <doe@kolab1.mkpf.ch>,
admin@kolab1.mkpf.ch
Please find attached an iCalendar file with all the event details which you=
=20
can import to your calendar application.
--=_a32392e5fc9266e3eeba97347cbfc147
Content-Transfer-Encoding: 8bit
Content-Type: text/calendar; charset=UTF-8; method=REQUEST;
name=event.ics
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Roundcube 1.5.3//Sabre VObject 4.5.3//EN
CALSCALE:GREGORIAN
METHOD:REQUEST
BEGIN:VTIMEZONE
TZID:Europe/Vaduz
BEGIN:STANDARD
DTSTART:20221030T010000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
END:STANDARD
BEGIN:STANDARD
DTSTART:20231029T010000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:20230326T010000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
UID:CC54191F656DFBB294BE0AC18E709315-529CBBDD47ACDDC2
DTSTAMP:20230724T065355Z
CREATED:20230724T065354Z
LAST-MODIFIED:20230724T065354Z
DTSTART;TZID=Europe/Vaduz:20230724T110000
DTEND;TZID=Europe/Vaduz:20230724T113000
SUMMARY:event1
SEQUENCE:0
TRANSP:OPAQUE
ATTENDEE;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT;CUTYPE=INDIVIDUAL;RSVP=
TRUE:mailto:admin@kolab1.mkpf.ch
ORGANIZER;CN="Doe, John":mailto:doe@kolab1.mkpf.ch
END:VEVENT
END:VCALENDAR
--=_a32392e5fc9266e3eeba97347cbfc147--
</AirSyncBase:Data>
</AirSyncBase:Body>
<AirSyncBase:NativeBodyType xmlns="uri:AirSyncBase">1</AirSyncBase:NativeBodyType>
<Email:MessageClass xmlns="uri:Email">IPM.Note</Email:MessageClass>
<Email:ContentClass xmlns="uri:Email">urn:content-classes:message</Email:ContentClass>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$status = $dom->getElementsByTagName('Status');
$this->assertEquals("1", $status[0]->nodeValue);
$xpath = $this->xpath($dom);
$add = $xpath->query("//ns:Responses/ns:Add");
$this->assertEquals(1, $add->length);
$this->assertEquals("1", $xpath->query("//ns:Responses/ns:Add/ns:Status")->item(0)->nodeValue);
$serverId = $xpath->query("//ns:Responses/ns:Add/ns:ServerId")->item(0)->nodeValue;
//List the MeetingRequest
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$inboxId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$inboxId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>1</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$xpath = $this->xpath($dom);
$this->assertEquals('IPM.Schedule.Meeting.Request', $xpath->query("//ns:Add/ns:ApplicationData/Email:MessageClass")->item(0)->nodeValue);
$this->assertEquals('urn:content-classes:calendarmessage', $xpath->query("//ns:Add/ns:ApplicationData/Email:ContentClass")->item(0)->nodeValue);
$this->assertEquals('BAAAAIIA4AB0xbcQGoLgCAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAHZDYWwtVWlkAQAAAENDNTQxOTFGNjU2REZCQjI5NEJFMEFDMThFNzA5MzE1LTUyOUNCQkRENDdBQ0REQzIA', $xpath->query("//ns:Add/ns:ApplicationData/Email:MeetingRequest/Email:GlobalObjId")->item(0)->nodeValue);
$this->assertEquals('1', $xpath->query("//ns:Add/ns:ApplicationData/Email:MeetingRequest/Email2:MeetingMessageType")->item(0)->nodeValue);
//Send a meeting response to accept
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<MeetingResponse xmlns="uri:MeetingResponse">
<Request>
<CollectionId>{$inboxId}</CollectionId>
<UserResponse>1</UserResponse>
<RequestId>$serverId</RequestId>
</Request>
</MeetingResponse>
EOF;
$response = $this->request($request, 'MeetingResponse');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$xpath = $this->xpath($dom);
$this->assertEquals("1", $xpath->query("//ns:MeetingResponse/ns:Result/ns:Status")->item(0)->nodeValue);
$this->assertStringContainsString("CC54191F656DFBB294BE0AC18E709315-529CBBDD47ACDDC2", $xpath->query("//ns:MeetingResponse/ns:Result/ns:CalendarId")->item(0)->nodeValue);
//Fetch the event and validate
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>1</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$xpath = $this->xpath($dom);
$this->assertEquals("CC54191F656DFBB294BE0AC18E709315-529CBBDD47ACDDC2", $xpath->query("//ns:Add/ns:ApplicationData/Calendar:UID")->item(0)->nodeValue);
$this->assertEquals('activesynctest@kolab.org', $xpath->query("//ns:Add/ns:ApplicationData/Calendar:Attendees/Calendar:Attendee/Calendar:Email")->item(0)->nodeValue);
$this->assertEquals('3', $xpath->query("//ns:Add/ns:ApplicationData/Calendar:Attendees/Calendar:Attendee/Calendar:AttendeeStatus")->item(0)->nodeValue);
+ $serverId = $xpath->query("//ns:Add/ns:ServerId")->item(0)->nodeValue;
-
- //FIXME there is a second "event here, why?
$add = $xpath->query("//ns:Add");
$this->assertEquals(1, $add->length);
//Send a dummy event with an invalid attendeestatus (just like outlook does)
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar">
<Collections>
<Collection>
<SyncKey>2</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
<Commands>
<Add>
<Class>Calendar</Class>
<ClientId>{B94F1272-ED5F-4613-90D6-731491596147}</ClientId>
<ApplicationData>
<Timezone xmlns="uri:Calendar">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</Timezone>
<DtStamp xmlns="uri:Calendar">20230724T173929Z</DtStamp>
<StartTime xmlns="uri:Calendar">20230724T090000Z</StartTime>
<Subject xmlns="uri:Calendar">event1</Subject>
<UID xmlns="uri:Calendar">CC54191F656DFBB294BE0AC18E709315-529CBBDD47ACDDC2</UID>
<OrganizerName xmlns="uri:Calendar">doe@kolab1.mkpf.ch</OrganizerName>
<OrganizerEmail xmlns="uri:Calendar">doe@kolab1.mkpf.ch</OrganizerEmail>
<Attendees xmlns="uri:Calendar">
<Attendee>
<Email>activesynctest@kolab.org</Email>
<Name>activesynctest@kolab.org</Name>
<AttendeeStatus>0</AttendeeStatus>
<AttendeeType>1</AttendeeType>
</Attendee>
</Attendees>
<EndTime xmlns="uri:Calendar">20230724T093000Z</EndTime>
<Sensitivity xmlns="uri:Calendar">0</Sensitivity>
<BusyStatus xmlns="uri:Calendar">2</BusyStatus>
<AllDayEvent xmlns="uri:Calendar">0</AllDayEvent>
<Reminder xmlns="uri:Calendar">15</Reminder>
<MeetingStatus xmlns="uri:Calendar">3</MeetingStatus>
<ResponseRequested xmlns="uri:Calendar">1</ResponseRequested>
<DisallowNewTimeProposal xmlns="uri:Calendar">0</DisallowNewTimeProposal>
</ApplicationData>
</Add>
</Commands>
</Collection>
</Collections>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
+ $dom = self::fromWbxml($response->getBody());
+ print($dom->saveXML());
+ $xpath = $this->xpath($dom);
+
+ $this->assertEquals("5", $xpath->query("//ns:Add/ns:Status")->item(0)->nodeValue);
+
//Fetch the event and validate again
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>0</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>0</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
<Collections>
<Collection>
<SyncKey>1</SyncKey>
<CollectionId>{$tasksId}</CollectionId>
<DeletesAsMoves>0</DeletesAsMoves>
<GetChanges>1</GetChanges>
<WindowSize>512</WindowSize>
<Options>
<FilterType>0</FilterType>
<BodyPreference xmlns="uri:AirSyncBase">
<Type>1</Type>
<AllOrNone>1</AllOrNone>
</BodyPreference>
</Options>
</Collection>
</Collections>
<WindowSize>16</WindowSize>
</Sync>
EOF;
$response = $this->request($request, 'Sync');
$this->assertEquals(200, $response->getStatusCode());
$dom = self::fromWbxml($response->getBody());
print($dom->saveXML());
$xpath = $this->xpath($dom);
$this->assertEquals("CC54191F656DFBB294BE0AC18E709315-529CBBDD47ACDDC2", $xpath->query("//ns:Add/ns:ApplicationData/Calendar:UID")->item(0)->nodeValue);
$this->assertEquals('activesynctest@kolab.org', $xpath->query("//ns:Add/ns:ApplicationData/Calendar:Attendees/Calendar:Attendee/Calendar:Email")->item(0)->nodeValue);
$this->assertEquals('3', $xpath->query("//ns:Add/ns:ApplicationData/Calendar:Attendees/Calendar:Attendee/Calendar:AttendeeStatus")->item(0)->nodeValue);
+ //Send a dummy event to change to tentative (just like outlook does
+ $request = <<<EOF
+ <?xml version="1.0" encoding="utf-8"?>
+ <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
+ <Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase" xmlns:Calendar="uri:Calendar">
+ <Collections>
+ <Collection>
+ <SyncKey>2</SyncKey>
+ <CollectionId>{$tasksId}</CollectionId>
+ <DeletesAsMoves>0</DeletesAsMoves>
+ <GetChanges>0</GetChanges>
+ <WindowSize>512</WindowSize>
+ <Options>
+ <FilterType>0</FilterType>
+ <BodyPreference xmlns="uri:AirSyncBase">
+ <Type>1</Type>
+ <AllOrNone>1</AllOrNone>
+ </BodyPreference>
+ </Options>
+ <Commands>
+ <Change>
+ <Class>Calendar</Class>
+ <ServerId>{$serverId}</ServerId>
+ <ApplicationData>
+ <Timezone xmlns="uri:Calendar">AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==</Timezone>
+ <DtStamp xmlns="uri:Calendar">20230724T173929Z</DtStamp>
+ <StartTime xmlns="uri:Calendar">20230724T090000Z</StartTime>
+ <Subject xmlns="uri:Calendar">event1</Subject>
+ <UID xmlns="uri:Calendar">CC54191F656DFBB294BE0AC18E709315-529CBBDD47ACDDC2</UID>
+ <OrganizerName xmlns="uri:Calendar">doe@kolab1.mkpf.ch</OrganizerName>
+ <OrganizerEmail xmlns="uri:Calendar">doe@kolab1.mkpf.ch</OrganizerEmail>
+ <Attendees xmlns="uri:Calendar">
+ <Attendee>
+ <Email>activesynctest@kolab.org</Email>
+ <Name>activesynctest@kolab.org</Name>
+ <AttendeeStatus>0</AttendeeStatus>
+ <AttendeeType>1</AttendeeType>
+ </Attendee>
+ </Attendees>
+ <EndTime xmlns="uri:Calendar">20230724T093000Z</EndTime>
+ <Sensitivity xmlns="uri:Calendar">0</Sensitivity>
+ <BusyStatus xmlns="uri:Calendar">1</BusyStatus>
+ <AllDayEvent xmlns="uri:Calendar">0</AllDayEvent>
+ <Reminder xmlns="uri:Calendar">15</Reminder>
+ <MeetingStatus xmlns="uri:Calendar">3</MeetingStatus>
+ <ResponseRequested xmlns="uri:Calendar">1</ResponseRequested>
+ <DisallowNewTimeProposal xmlns="uri:Calendar">0</DisallowNewTimeProposal>
+ </ApplicationData>
+ </Change>
+ </Commands>
+ </Collection>
+ </Collections>
+ </Sync>
+ EOF;
+ $response = $this->request($request, 'Sync');
+ $this->assertEquals(200, $response->getStatusCode());
+
+ $dom = self::fromWbxml($response->getBody());
+ print($dom->saveXML());
+ $xpath = $this->xpath($dom);
+
+ $this->assertEquals("1", $xpath->query("//ns:Collection/ns:Status")->item(0)->nodeValue);
+
+
}
/**
* @doesNotPerformAssertions
*/
public function testCleanup(): void
{
$this->deleteTestUser(self::$user->email);
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Jan 20, 7:13 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
120123
Default Alt Text
(73 KB)
Attached To
Mode
R2 kolab
Attached
Detach File
Event Timeline
Log In to Comment