[Web] API: complete the OpenAPI docs, add route tests for json_api.php

This commit is contained in:
FreddleSpl0it committed 2026-11-10 20:04:05 +01:00
1 parent 9825c8d290
commit 2418da031f
47 files changed
+18848 -7491

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Routing;
use Mailcow\Tests\Api\ApiTestCase;
/** add/edit/delete/get alias */
class AliasRoutesTest extends ApiTestCase
{
private const SALES = ['id' => 12, 'address' => 'sales@example.org', 'goto' => 'anna@example.org,ben@example.org', 'active' => 1];
private const INFO = ['id' => 13, 'address' => 'info@example.org', 'goto' => 'anna@example.org', 'active' => 1];
private const SHOP = ['id' => 20, 'address' => 'shop@example.net', 'goto' => 'ben@example.org', 'active' => 0];
public function testAddPassesTheBody(): void
{
$attr = [
'address' => "sales@example.org, info@example.org",
'goto' => 'anna@example.org,ben@example.org',
'active' => '1',
'sogo_visible' => 1,
'internal' => '0',
'sender_allowed' => true,
'public_comment' => 'Sales team',
];
$result = $this->post('add/alias', $attr + ['csrf_token' => 'x']);
$this->assertSame([['add', 'alias', $attr]], $result->callsTo('mailbox'));
}
public function testEditMergesItemsAndAttributes(): void
{
$list = $this->post('edit/alias', ['items' => [12, 'info@example.org'], 'attr' => ['goto_null' => '1', 'active' => '0']]);
$this->assertCalled($list, 'mailbox', ['edit', 'alias', ['id' => [12, 'info@example.org'], 'goto_null' => '1', 'active' => '0']]);
$single = $this->post('edit/alias', ['items' => 12, 'attr' => ['expand_alias' => true]]);
$this->assertCalled($single, 'mailbox', ['edit', 'alias', ['id' => [12], 'expand_alias' => true]]);
}
public function testDeleteTakesIdsOrAddresses(): void
{
$result = $this->post('delete/alias', [12, 'info@example.org']);
$this->assertSame([['delete', 'alias', ['id' => [12, 'info@example.org']]]], $result->callsTo('mailbox'));
}
public function testGetOneAliasByIdOrAddress(): void
{
$byId = $this->get('get/alias/12', ['script' => [self::answer('mailbox', ['get', 'alias_details', '12'], self::SALES)]]);
$this->assertSame(self::SALES, $byId->json());
$byAddress = $this->get('get/alias/sales@example.org');
$this->assertSame([['get', 'alias_details', 'sales@example.org']], $byAddress->callsTo('mailbox'));
$this->assertSame('{}', $byAddress->body);
}
public function testGetAllWalksTheDomainsAndAliasDomains(): void
{
$result = $this->get('get/alias/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], ['example.org', 'example.com']),
self::answer('mailbox', ['get', 'alias_domains'], ['example.net']),
self::answer('mailbox', ['get', 'aliases', 'example.org'], [12, 13, 14]),
self::answer('mailbox', ['get', 'aliases', 'example.com'], []),
self::answer('mailbox', ['get', 'aliases', 'example.net'], [20]),
self::answer('mailbox', ['get', 'alias_details', 12], self::SALES),
self::answer('mailbox', ['get', 'alias_details', 13], self::INFO),
self::answer('mailbox', ['get', 'alias_details', 14], false),
self::answer('mailbox', ['get', 'alias_details', 20], self::SHOP),
]]);
$this->assertSame([self::SALES, self::INFO, self::SHOP], $result->json());
$this->assertSame([
['get', 'domains'],
['get', 'alias_domains'],
['get', 'aliases', 'example.org'],
['get', 'alias_details', 12],
['get', 'alias_details', 13],
['get', 'alias_details', 14],
['get', 'aliases', 'example.com'],
['get', 'aliases', 'example.net'],
['get', 'alias_details', 20],
], $result->callsTo('mailbox'));
}
public function testGetAllOfTheNamedDomains(): void
{
$result = $this->get('get/alias/all/example.org,example.net', ['script' => [
self::answer('mailbox', ['get', 'aliases', 'example.org'], [12]),
self::answer('mailbox', ['get', 'aliases', 'example.net'], [20]),
self::answer('mailbox', ['get', 'alias_details', 12], self::SALES),
self::answer('mailbox', ['get', 'alias_details', 20], self::SHOP),
]]);
$this->assertSame([self::SALES, self::SHOP], $result->json());
$this->assertSame([
['get', 'aliases', 'example.org'],
['get', 'alias_details', 12],
['get', 'aliases', 'example.net'],
['get', 'alias_details', 20],
], $result->callsTo('mailbox'));
}
public function testGetAllWithoutAliases(): void
{
$noDomains = $this->get('get/alias/all', ['script' => [
self::answer('mailbox', ['get', 'domains'], []),
self::answer('mailbox', ['get', 'alias_domains'], []),
]]);
$this->assertSame('{}', $noDomains->body);
$noAliases = $this->get('get/alias/all/example.org', ['script' => [self::answer('mailbox', ['get', 'aliases'], false)]]);
$this->assertSame('{}', $noAliases->body);
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Routing;
use Mailcow\Tests\Api\ApiTestCase;
/** add/edit/delete/get fwdhost */
class ForwardingHostRoutesTest extends ApiTestCase
{
private const HOSTS = [
['host' => '192.0.2.25', 'source' => 'relay.example.com', 'keep_spam' => 'yes'],
['host' => '2001:db8::25', 'source' => 'relay.example.com', 'keep_spam' => 'yes'],
];
public function testAddPassesTheBody(): void
{
$result = $this->post('add/fwdhost', ['hostname' => 'relay.example.com', 'filter_spam' => '1']);
$this->assertSame([['add', ['hostname' => 'relay.example.com', 'filter_spam' => '1']]], $result->callsTo('fwdhost'));
}
public function testEditNamesTheHostsFwdhost(): void
{
$result = $this->post('edit/fwdhost', ['items' => ['192.0.2.25', '198.51.100.0/24'], 'attr' => ['keep_spam' => '0']]);
$this->assertSame([['edit', ['fwdhost' => ['192.0.2.25', '198.51.100.0/24'], 'keep_spam' => '0']]], $result->callsTo('fwdhost'));
}
public function testDeleteNamesTheHostsForwardinghost(): void
{
$result = $this->post('delete/fwdhost', ['192.0.2.25', '2001:db8::25']);
$this->assertSame([['delete', ['forwardinghost' => ['192.0.2.25', '2001:db8::25']]]], $result->callsTo('fwdhost'));
}
public function testGetAllPassesTheList(): void
{
$result = $this->get('get/fwdhost/all', ['script' => [self::answer('fwdhost', ['get'], self::HOSTS)]]);
$this->assertSame(self::HOSTS, $result->json());
$this->assertSame([['get']], $result->callsTo('fwdhost'));
$this->assertSame('{}', $this->get('get/fwdhost/all', ['script' => [self::answer('fwdhost', ['get'], [])]])->body);
}
public function testGetOneHost(): void
{
$found = $this->get('get/fwdhost/192.0.2.25', ['script' => [self::answer('fwdhost', ['details', '192.0.2.25'], self::HOSTS[0])]]);
$this->assertSame(self::HOSTS[0], $found->json());
$this->assertSame('{}', $this->get('get/fwdhost/192.0.2.99', ['script' => [self::answer('fwdhost', ['details'], [])]])->body);
}
public function testGetOneNetworkIsCutAtTheSlash(): void
{
$result = $this->get('get/fwdhost/198.51.100.0/24');
$this->assertSame([['details', '198.51.100.0']], $result->callsTo('fwdhost'));
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Routing;
use Mailcow\Tests\Api\ApiTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
/** bcc, recipient_map and tls-policy-map: json_api.php handles the three alike */
class MapRoutesTest extends ApiTestCase
{
public static function maps(): array
{
return [
'bcc' => ['bcc', 'bcc',
['local_dest' => 'example.org', 'bcc_dest' => 'archive@example.com', 'type' => 'rcpt', 'active' => '1'],
['id' => 3, 'local_dest' => '@example.org', 'bcc_dest' => 'archive@example.com', 'active' => 1, 'type' => 'rcpt', 'domain' => 'example.org']],
'recipient_map' => ['recipient_map', 'recipient_map',
['recipient_map_old' => 'old@example.org', 'recipient_map_new' => 'new@example.org', 'active' => 1],
['id' => 3, 'recipient_map_old' => 'old@example.org', 'recipient_map_new' => 'new@example.org', 'active' => 1]],
'tls-policy-map' => ['tls-policy-map', 'tls_policy_maps',
['dest' => 'example.com', 'policy' => 'encrypt', 'parameters' => 'protocols=>=TLSv1.2', 'active' => '1'],
['id' => 3, 'dest' => 'example.com', 'policy' => 'encrypt', 'parameters' => '', 'active' => 1]],
];
}
#[DataProvider('maps')]
public function testAddPassesTheBody(string $category, string $function, array $attr): void
{
$result = $this->post("add/$category", $attr);
$this->assertSame([['add', $attr]], $result->callsTo($function));
}
#[DataProvider('maps')]
public function testEditMergesItemsAndAttributes(string $category, string $function): void
{
$list = $this->post("edit/$category", ['items' => [3, '4'], 'attr' => ['active' => '0']]);
$this->assertSame([['edit', ['id' => [3, '4'], 'active' => '0']]], $list->callsTo($function));
$single = $this->post("edit/$category", ['items' => 3, 'attr' => ['active' => '1']]);
$this->assertSame([['edit', ['id' => [3], 'active' => '1']]], $single->callsTo($function));
}
#[DataProvider('maps')]
public function testDeleteTakesIds(string $category, string $function): void
{
$result = $this->post("delete/$category", [3, 4]);
$this->assertSame([['delete', ['id' => [3, 4]]]], $result->callsTo($function));
}
#[DataProvider('maps')]
public function testGetAllCollectsTheDetailsOfEveryId(string $category, string $function, array $attr, array $details): void
{
$result = $this->get("get/$category/all", ['script' => [
self::answer($function, ['get'], [3, 4, 5]),
self::answer($function, ['details', 3], $details),
self::answer($function, ['details', 4], false),
self::answer($function, ['details', 5], ['id' => 5] + $details),
]]);
$this->assertSame([$details, ['id' => 5] + $details], $result->json());
$this->assertSame([['get'], ['details', 3], ['details', 4], ['details', 5]], $result->callsTo($function));
}
#[DataProvider('maps')]
public function testGetAllWithoutEntries(string $category, string $function): void
{
$result = $this->get("get/$category/all", ['script' => [self::answer($function, ['get'], [])]]);
$this->assertSame('{}', $result->body);
}
#[DataProvider('maps')]
public function testGetOneAddsANullMember(string $category, string $function, array $attr, array $details): void
{
// json_api.php appends the undefined $details to the answer
$found = $this->get("get/$category/3", ['script' => [self::answer($function, ['details', '3'], $details)]]);
$this->assertSame($details + [0 => null], $found->json());
$missing = $this->get("get/$category/9", ['script' => [self::answer($function, ['details', '9'], false)]]);
$this->assertSame('{}', $missing->body);
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Mailcow\Tests\Api\Routing;
use Mailcow\Tests\Api\ApiTestCase;
use PHPUnit\Framework\Attributes\DataProvider;
/** relayhost (sender-dependent transports) and transport (transport maps): json_api.php handles both alike */
class TransportRoutesTest extends ApiTestCase
{
public static function transports(): array
{
return [
'relayhost' => ['relayhost',
['hostname' => '[smtp.example.com]:587', 'username' => 'relay-user', 'password' => 'secret'],
['id' => 1, 'hostname' => '[smtp.example.com]:587', 'username' => 'relay-user', 'active' => 1, 'used_by_domains' => 'example.org']],
'transport' => ['transport',
['destination' => 'example.com, .example.com', 'nexthop' => '[mx.example.com]:25', 'is_mx_based' => '0', 'active' => '1'],
['id' => 1, 'is_mx_based' => 0, 'destination' => 'example.com', 'nexthop' => '[mx.example.com]:25', 'active' => 1]],
];
}
#[DataProvider('transports')]
public function testAddPassesTheBody(string $category, array $attr): void
{
$result = $this->post("add/$category", $attr);
$this->assertSame([['add', $attr]], $result->callsTo($category));
}
#[DataProvider('transports')]
public function testEditMergesItemsAndAttributes(string $category): void
{
$result = $this->post("edit/$category", ['items' => [1, 2], 'attr' => ['active' => '0', 'password' => '']]);
$this->assertSame([['edit', ['id' => [1, 2], 'active' => '0', 'password' => '']]], $result->callsTo($category));
}
#[DataProvider('transports')]
public function testDeleteTakesIds(string $category): void
{
$result = $this->post("delete/$category", ['1', 2]);
$this->assertSame([['delete', ['id' => ['1', 2]]]], $result->callsTo($category));
}
#[DataProvider('transports')]
public function testGetAllCollectsTheDetailsOfEveryRow(string $category, array $attr, array $details): void
{
$result = $this->get("get/$category/all", ['script' => [
self::answer($category, ['get'], [['id' => 1, 'active' => 1], ['id' => 2, 'active' => 1], ['id' => 3, 'active' => 0]]),
self::answer($category, ['details', 1], $details),
self::answer($category, ['details', 2], false),
self::answer($category, ['details', 3], ['id' => 3] + $details),
]]);
$this->assertSame([$details, ['id' => 3] + $details], $result->json());
$this->assertSame([['get'], ['details', 1], ['details', 2], ['details', 3]], $result->callsTo($category));
}
#[DataProvider('transports')]
public function testGetAllWithoutEntries(string $category): void
{
$none = $this->get("get/$category/all", ['script' => [self::answer($category, ['get'], [])]]);
$this->assertSame('{}', $none->body);
$noDetails = $this->get("get/$category/all", ['script' => [
self::answer($category, ['get'], [['id' => 1]]),
self::answer($category, ['details'], false),
]]);
$this->assertSame('{}', $noDetails->body);
}
#[DataProvider('transports')]
public function testGetOne(string $category, array $attr, array $details): void
{
$found = $this->get("get/$category/1", ['script' => [self::answer($category, ['details', '1'], $details)]]);
$this->assertSame($details, $found->json());
$missing = $this->get("get/$category/9", ['script' => [self::answer($category, ['details', '9'], false)]]);
$this->assertSame('{}', $missing->body);
}
}