Files
deskhub/DeskHub-Webseite/tests/api_mail_actions.php
T
2026-11-04 07:55:36 +00:00

136 lines
8.5 KiB
PHP

<?php
declare(strict_types=1);
/**
* Prüft die Schnittstellen der Apps zum E-Mail-Abruf von Hand und zum Nachladen von Mail-Anhängen: Ohne Postfach eine klare Meldung,
* nur für E-Mail-Tickets, Rechte, und dass eingebettete Bilder nicht in der Anhangliste der Ticket-Schnittstelle stehen.
*/
date_default_timezone_set('Europe/Berlin'); // wie in der Test-Installation
$root = dirname(__DIR__);
$work = sys_get_temp_dir() . '/deskhub-apimail-' . bin2hex(random_bytes(4));
$tmp = $work . '/inst';
mkdir($tmp, 0755, true);
$webPort = random_int(36500, 36999);
$skip = '#^(config/config\.php|database/[^/]+\.sqlite[^/]*|backups|uploads|tests|plugins/[^/]+|\.git)(/|$)#';
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST) as $f) {
$rel = str_replace(chr(92), '/', substr($f->getPathname(), strlen($root) + 1));
if (preg_match($skip, $rel) && !preg_match('#^(backups|uploads|plugins)/(\.htaccess|README\.md)$#', $rel)) continue;
if ($f->isDir()) { @mkdir($tmp . '/' . $rel, 0755, true); continue; }
@mkdir(dirname($tmp . '/' . $rel), 0755, true);
copy($f->getPathname(), $tmp . '/' . $rel);
}
@mkdir($tmp . '/uploads');
@mkdir($tmp . '/config');
file_put_contents($tmp . '/config/config.php', '<?php return ' . var_export([
'app_key' => bin2hex(random_bytes(32)),
'db' => ['driver' => 'sqlite', 'sqlite_path' => $tmp . '/database/deskhub.sqlite'],
'site' => ['name' => 'Test', 'url' => 'http://127.0.0.1', 'admin_email' => 'a@test.de', 'timezone' => 'Europe/Berlin'],
'uploads' => ['max_size_bytes' => 10485760, 'allowed_extensions' => ['png'], 'path' => $tmp . '/uploads'],
], true) . ';');
putenv('DESKHUB_CONFIG_JSON');
putenv('DESKHUB_SQLITE_PATH');
putenv('DESKHUB_TESTING');
putenv('DESKHUB_API_ALLOW_INSECURE=1');
require_once $root . '/database/sqlite_schema.php';
require_once $root . '/database/migrate.php';
$db = new PDO('sqlite:' . $tmp . '/database/deskhub.sqlite');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec('PRAGMA foreign_keys = ON');
$db->exec(convert_schema_to_sqlite((string) file_get_contents($root . '/database/schema.sql')));
$mig = run_migrations($db, true);
$now = date('Y-m-d H:i:s');
$dept = (int) $db->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn();
$db->exec('DELETE FROM staff');
$ins = $db->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)');
$ids = [];
foreach ([['Chef', 'chef@test.de', 'admin'], ['Agent', 'agent@test.de', 'agent']] as [$n, $e, $r]) {
$ins->execute([$n, $e, password_hash('Sehr-Sicher-1234', PASSWORD_DEFAULT), $r, $dept, $now]);
$ids[$n] = (int) $db->lastInsertId();
}
$db = null;
$failures = [];
$total = 0;
$check = function (bool $ok, string $what) use (&$failures, &$total): void { $total++; if (!$ok) $failures[] = $what; };
$webProc = proc_open([PHP_BINARY, '-S', "127.0.0.1:$webPort", '-t', $tmp], [['pipe', 'r'], ['file', $work . '/web.out', 'w'], ['file', $work . '/web.out', 'a']], $wp, $tmp);
try {
for ($i = 0; $i < 50; $i++) { if (@fsockopen('127.0.0.1', $webPort)) break; usleep(100000); }
$pdo = function () use ($tmp): PDO {
$p = new PDO('sqlite:' . $tmp . '/database/deskhub.sqlite');
$p->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$p->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $p;
};
$api = function (string $method, string $path, ?array $json = null, ?string $bearer = null) use ($webPort): array {
$ch = curl_init("http://127.0.0.1:$webPort$path");
$h = ['Content-Type: application/json'];
if ($bearer) $h[] = 'Authorization: Bearer ' . $bearer;
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60, CURLOPT_HTTPHEADER => $h, CURLOPT_CUSTOMREQUEST => $method]);
if ($json !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json));
$b = (string) curl_exec($ch);
$s = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
return [$s, json_decode($b, true) ?? $b];
};
$p = $pdo();
$nowS = date('Y-m-d H:i:s');
$p->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)')
->execute(['T9001', 'API-Test', 'Kunde', 'kunde@test.de', $dept, 2, 'open', 'web', $nowS, $nowS]);
$tid = (int) $p->lastInsertId();
$login = function (string $email) use ($api): string {
[, $lg] = $api('POST', '/api/staff/v1/login.php', ['email' => $email, 'password' => 'Sehr-Sicher-1234', 'deviceName' => 'Test']);
return (string) ($lg['token'] ?? '');
};
$admin = $login('chef@test.de');
$agent = $login('agent@test.de');
$check($admin !== '' && $agent !== '', 'Anmeldung von Administrator und Mitarbeiter');
// E-Mail-Abruf von Hand: ohne Postfach eine verständliche Meldung (kein Absturz)
[$s, $r] = $api('POST', '/api/staff/v1/mail_fetch.php', [], $agent);
$check($s === 200 && $r['ok'] === false && $r['available'] === false && str_contains((string) $r['message'], 'kein aktives Postfach'), 'Abruf ohne Postfach: Meldung: ' . json_encode($r));
[$s, ] = $api('POST', '/api/staff/v1/mail_fetch.php', []);
$check($s === 401, 'Abruf ohne Anmeldung: ' . $s);
[$s, ] = $api('GET', '/api/staff/v1/mail_fetch.php', null, $agent);
$check($s === 405, 'Abruf nur per POST: ' . $s);
// Nachladen: nur für E-Mail-Tickets
[$s, $r] = $api('POST', '/api/staff/v1/ticket_mail_reload.php', ['number' => 'T9001'], $agent);
$check($s === 422 && ($r['error']['code'] ?? '') === 'EMAIL_TICKET_REQUIRED', 'Web-Ticket hat keine Original-E-Mail: ' . json_encode($r));
[$s, $r] = $api('POST', '/api/staff/v1/ticket_mail_reload.php', [], $agent);
$check($s === 422 && ($r['error']['code'] ?? '') === 'TICKET_NUMBER_REQUIRED', 'Ticketnummer ist Pflicht');
$p->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?)')
->execute(['T9002', 'Mail-Test', 'Kunde', 'kunde@test.de', $dept, 2, 'open', 'email', $nowS, $nowS]);
$mailTid = (int) $p->lastInsertId();
$p->exec("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES ({$mailTid},'customer','Hallo',0,'{$nowS}')");
$thId = (int) $p->lastInsertId();
[$s, $r] = $api('POST', '/api/staff/v1/ticket_mail_reload.php', ['number' => 'T9002'], $agent);
$check($s === 422 && ($r['error']['code'] ?? '') === 'MAIL_RELOAD_FAILED' && str_contains((string) $r['error']['message'], 'keine Original-E-Mail'), 'E-Mail-Ticket ohne gespeicherte Mail: verständliche Meldung: ' . json_encode($r));
// Eingebettete Bilder stehen nicht in der Anhangliste der Ticket-Schnittstelle
$p->exec("INSERT INTO ticket_attachments (thread_id, original_name, stored_name, filesize, created_at, content_id, is_inline) VALUES ({$thId},'Bericht.docx','a.docx',100,'{$nowS}',NULL,0)");
$p->exec("INSERT INTO ticket_attachments (thread_id, original_name, stored_name, filesize, created_at, content_id, is_inline) VALUES ({$thId},'logo.png','b.png',100,'{$nowS}','logo@x',1)");
[$s, $r] = $api('GET', '/api/staff/v1/ticket.php?number=T9002', null, $agent);
$names = [];
foreach ($r['ticket']['messages'] ?? $r['ticket']['threads'] ?? [] as $m) foreach ($m['attachments'] ?? [] as $a) $names[] = $a['name'] ?? $a['originalName'] ?? '';
$check($s === 200 && $names === ['Bericht.docx'], 'nur der echte Anhang steht in der Liste, nicht das eingebettete Bild: ' . json_encode($names) . ' ' . substr(json_encode($r), 0, 300));
} finally {
if (isset($webProc) && is_resource($webProc)) { proc_terminate($webProc); proc_close($webProc); }
}
usleep(300000);
$rm = function (string $d) use (&$rm) {
foreach (scandir($d) ?: [] as $e) { if ($e === '.' || $e === '..') continue; is_dir("$d/$e") && !is_link("$d/$e") ? $rm("$d/$e") : @unlink("$d/$e"); }
@rmdir($d);
};
if (str_starts_with($work, sys_get_temp_dir() . '/deskhub-apicss-')) $rm($work);
if ($failures) {
fwrite(STDERR, "Fehlgeschlagen:\n - " . implode("\n - ", $failures) . "\n");
exit(1);
}
echo "API Mail actions checks passed ({$total} Prüfungen).\n";