Files
global-autoresponder/tests/directadmin_vacation_sync_service_test.php
T
2026-07-04 15:16:50 +02:00

248 lines
9.4 KiB
PHP

<?php
declare(strict_types=1);
require_once PLUGIN_ROOT . '/exec/lib/Settings.php';
require_once PLUGIN_ROOT . '/exec/lib/RuleRepository.php';
require_once PLUGIN_ROOT . '/exec/lib/DirectAdminSyncRepository.php';
require_once PLUGIN_ROOT . '/exec/lib/MailboxDirectory.php';
require_once PLUGIN_ROOT . '/exec/lib/DirectAdminVacationApi.php';
require_once PLUGIN_ROOT . '/exec/lib/DirectAdminVacationSyncService.php';
test('directadmin vacation sync creates tracked entries for enabled rule mailboxes', function (): void {
$virtualRoot = TEST_TMP . '/da-sync-virtual';
test_write($virtualRoot . '/domainowners', "example.com: alice\n");
test_write($virtualRoot . '/example.com/passwd', "info:x:1000:12::/home/alice/imap/example.com/info:/bin/false\nsales:x:1000:12::/home/alice/imap/example.com/sales:/bin/false\n");
$repo = new RuleRepository(TEST_TMP . '/da-sync-data');
$rule = $repo->create('alice', [
'subject_prefix' => 'Urlop',
'repeat_minutes' => 1440,
'body' => 'Body',
'start_value' => '2026-06-10|morning',
'end_value' => '2026-06-24|evening',
'start_ts' => 1,
'end_ts' => 2,
'enabled' => true,
'dynamic_scope' => true,
]);
$calls = [];
$api = new class($calls) extends DirectAdminVacationApi {
public function __construct(private array &$calls) {}
public function vacationExists(string $owner, string $email): bool
{
return false;
}
public function saveVacation(string $owner, string $email, array $rule, bool $exists): void
{
$this->calls[] = ['save', $owner, $email, $exists, $rule['subject_prefix']];
}
public function deleteVacation(string $owner, string $email): void
{
$this->calls[] = ['delete', $owner, $email];
}
};
$sync = new DirectAdminVacationSyncService(
$repo,
new DirectAdminSyncRepository(TEST_TMP . '/da-sync-data'),
new MailboxDirectory($virtualRoot),
$api
);
$sync->syncUser('alice');
assert_same([
['save', 'alice', 'info@example.com', false, 'Urlop'],
['save', 'alice', 'sales@example.com', false, 'Urlop'],
], $calls);
$tracked = (new DirectAdminSyncRepository(TEST_TMP . '/da-sync-data'))->entriesForRule('alice', (string)$rule['id']);
assert_same(['info@example.com', 'sales@example.com'], array_column($tracked, 'email'));
});
test('directadmin vacation sync deletes stale tracked entries after rule removal', function (): void {
$virtualRoot = TEST_TMP . '/da-sync-delete-virtual';
test_write($virtualRoot . '/domainowners', "example.com: alice\n");
test_write($virtualRoot . '/example.com/passwd', "info:x:1000:12::/home/alice/imap/example.com/info:/bin/false\n");
$dataDir = TEST_TMP . '/da-sync-delete-data';
$repo = new RuleRepository($dataDir);
$syncRepo = new DirectAdminSyncRepository($dataDir);
$rule = $repo->create('alice', [
'subject_prefix' => 'Urlop',
'repeat_minutes' => 1440,
'body' => 'Body',
'start_value' => '2026-06-10|morning',
'end_value' => '2026-06-24|evening',
'start_ts' => 1,
'end_ts' => 2,
'enabled' => true,
'dynamic_scope' => true,
]);
$syncRepo->replaceRuleEntries('alice', (string)$rule['id'], [
['email' => 'info@example.com', 'domain' => 'example.com', 'mailbox' => 'info', 'api_key' => 'info@example.com'],
]);
$repo->delete('alice', (string)$rule['id']);
$calls = [];
$api = new class($calls) extends DirectAdminVacationApi {
public function __construct(private array &$calls) {}
public function saveVacation(string $owner, string $email, array $rule, bool $exists): void {}
public function deleteVacation(string $owner, string $email): void
{
$this->calls[] = ['delete', $owner, $email];
}
};
$sync = new DirectAdminVacationSyncService($repo, $syncRepo, new MailboxDirectory($virtualRoot), $api);
$sync->syncUser('alice');
assert_same([['delete', 'alice', 'info@example.com']], $calls);
assert_same([], $syncRepo->entriesForRule('alice', (string)$rule['id']));
});
test('directadmin vacation sync refuses to overwrite untracked native vacation', function (): void {
$virtualRoot = TEST_TMP . '/da-sync-conflict-virtual';
test_write($virtualRoot . '/domainowners', "example.com: alice\n");
test_write($virtualRoot . '/example.com/passwd', "info:x:1000:12::/home/alice/imap/example.com/info:/bin/false\n");
$dataDir = TEST_TMP . '/da-sync-conflict-data';
$repo = new RuleRepository($dataDir);
$repo->create('alice', [
'subject_prefix' => 'Urlop',
'repeat_minutes' => 1440,
'body' => 'Body',
'start_value' => '2026-06-10|morning',
'end_value' => '2026-06-24|evening',
'start_ts' => 1,
'end_ts' => 2,
'enabled' => true,
'dynamic_scope' => true,
]);
$calls = [];
$api = new class($calls) extends DirectAdminVacationApi {
public function __construct(private array &$calls) {}
public function vacationExists(string $owner, string $email): bool
{
return $email === 'info@example.com';
}
public function saveVacation(string $owner, string $email, array $rule, bool $exists): void
{
$this->calls[] = ['save', $owner, $email];
}
};
$sync = new DirectAdminVacationSyncService(
$repo,
new DirectAdminSyncRepository($dataDir),
new MailboxDirectory($virtualRoot),
$api
);
$thrown = false;
try {
$sync->syncUser('alice');
} catch (RuntimeException $e) {
$thrown = true;
assert_contains('Untracked DirectAdmin vacation already exists', $e->getMessage());
}
assert_true($thrown);
assert_same([], $calls);
});
test('directadmin vacation sync preflights all target mailboxes before saving', function (): void {
$virtualRoot = TEST_TMP . '/da-sync-preflight-virtual';
test_write($virtualRoot . '/domainowners', "example.com: alice\n");
test_write($virtualRoot . '/example.com/passwd', "info:x:1000:12::/home/alice/imap/example.com/info:/bin/false\nsales:x:1000:12::/home/alice/imap/example.com/sales:/bin/false\n");
$dataDir = TEST_TMP . '/da-sync-preflight-data';
$repo = new RuleRepository($dataDir);
$repo->create('alice', [
'subject_prefix' => 'Urlop',
'repeat_minutes' => 1440,
'body' => 'Body',
'start_value' => '2026-06-10|morning',
'end_value' => '2026-06-24|evening',
'start_ts' => 1,
'end_ts' => 2,
'enabled' => true,
'dynamic_scope' => true,
]);
$calls = [];
$api = new class($calls) extends DirectAdminVacationApi {
public function __construct(private array &$calls) {}
public function vacationExists(string $owner, string $email): bool
{
return $email === 'sales@example.com';
}
public function saveVacation(string $owner, string $email, array $rule, bool $exists): void
{
$this->calls[] = ['save', $owner, $email];
}
};
$sync = new DirectAdminVacationSyncService(
$repo,
new DirectAdminSyncRepository($dataDir),
new MailboxDirectory($virtualRoot),
$api
);
$thrown = false;
try {
$sync->syncUser('alice');
} catch (RuntimeException $e) {
$thrown = true;
assert_contains('sales@example.com', $e->getMessage());
}
assert_true($thrown);
assert_same([], $calls, 'No vacation should be saved before all conflicts are known');
});
test('directadmin vacation sync lists and disables untracked native vacations for user domains', function (): void {
$virtualRoot = TEST_TMP . '/da-native-list-virtual';
test_write($virtualRoot . '/domainowners', "example.com: alice\nother.com: bob\n");
test_write($virtualRoot . '/example.com/passwd', "info:x:1000:12::/home/alice/imap/example.com/info:/bin/false\n");
$dataDir = TEST_TMP . '/da-native-list-data';
$repo = new RuleRepository($dataDir);
$syncRepo = new DirectAdminSyncRepository($dataDir);
$rule = $repo->create('alice', [
'subject_prefix' => 'Urlop',
'body' => 'Body',
'start_ts' => 1,
'end_ts' => 2,
'enabled' => true,
]);
$syncRepo->replaceRuleEntries('alice', (string)$rule['id'], [
['email' => 'tracked@example.com', 'domain' => 'example.com', 'mailbox' => 'tracked', 'api_key' => 'tracked@example.com'],
]);
$calls = [];
$api = new class($calls) extends DirectAdminVacationApi {
public function __construct(private array &$calls) {}
public function listVacations(string $owner, string $domain): array
{
return $domain === 'example.com' ? ['manual', 'tracked'] : ['foreign'];
}
public function deleteVacation(string $owner, string $email): void
{
$this->calls[] = ['delete', $owner, $email];
}
};
$sync = new DirectAdminVacationSyncService($repo, $syncRepo, new MailboxDirectory($virtualRoot), $api);
$native = $sync->untrackedNativeVacations('alice');
assert_same([[
'domain' => 'example.com',
'mailbox' => 'manual',
'email' => 'manual@example.com',
]], $native);
$sync->deleteNativeVacations('alice', $native);
assert_same([['delete', 'alice', 'manual@example.com']], $calls);
});