Files
appwrite/tests/unit/Utopia/RequestTest.php
T
2025-11-28 16:46:49 +01:00

326 lines
9.9 KiB
PHP

<?php
namespace Tests\Unit\Utopia;
use Appwrite\SDK\Method;
use Appwrite\SDK\Parameter;
use Appwrite\Utopia\Request;
use PHPUnit\Framework\TestCase;
use Swoole\Http\Request as SwooleRequest;
use Tests\Unit\Utopia\Request\Filters\First;
use Tests\Unit\Utopia\Request\Filters\Second;
use Utopia\Route;
class RequestTest extends TestCase
{
protected ?Request $request = null;
public function setUp(): void
{
$this->request = new Request(new SwooleRequest());
}
public function testFilters(): void
{
$this->assertFalse($this->request->hasFilters());
$this->assertIsArray($this->request->getFilters());
$this->assertEmpty($this->request->getFilters());
$this->request->addFilter(new First());
$this->request->addFilter(new Second());
$this->assertTrue($this->request->hasFilters());
$this->assertCount(2, $this->request->getFilters());
$route = new Route(Request::METHOD_GET, '/test');
$route->label('sdk', new Method(
namespace: 'namespace',
group: 'group',
name: 'method',
description: 'description',
auth: [],
responses: [],
));
// set test header to prevent header populaten inside the request class
$this->request->addHeader('EXAMPLE', 'VALUE');
$this->request->setRoute($route);
$this->request->setQueryString([
'initial' => true,
'first' => false
]);
$output = $this->request->getParams();
$this->assertArrayHasKey('initial', $output);
$this->assertTrue($output['initial']);
$this->assertArrayHasKey('first', $output);
$this->assertTrue($output['first']);
$this->assertArrayHasKey('second', $output);
$this->assertTrue($output['second']);
$this->assertArrayNotHasKey('deleted', $output);
}
public function testGetParamsWithMultipleMethods(): void
{
$this->setupMultiMethodRoute();
// Pass only "foo", should match Method A
$this->request->setQueryString([
'foo' => 'valueFoo',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('foo', $params);
$this->assertSame('valueFoo', $params['foo']);
$this->assertArrayNotHasKey('baz', $params);
}
public function testGetParamsWithAllRequired(): void
{
$this->setupMultiMethodRoute();
// Pass "foo" and "bar", should match Method A
$this->request->setQueryString([
'foo' => 'valueFoo',
'bar' => 'valueBar',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('foo', $params);
$this->assertSame('valueFoo', $params['foo']);
$this->assertArrayHasKey('bar', $params);
$this->assertSame('valueBar', $params['bar']);
$this->assertArrayNotHasKey('baz', $params);
}
public function testGetParamsWithAllOptional(): void
{
$this->setupMultiMethodRoute();
// Pass only "bar", should match Method A
$this->request->setQueryString([
'bar' => 'valueBar',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('bar', $params);
$this->assertSame('valueBar', $params['bar']);
$this->assertArrayNotHasKey('foo', $params);
$this->assertArrayNotHasKey('baz', $params);
}
public function testGetParamsMatchesMethodB(): void
{
$this->setupMultiMethodRoute();
// Pass only "baz", should match Method B
$this->request->setQueryString([
'baz' => 'valueBaz',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('baz', $params);
$this->assertSame('valueBaz', $params['baz']);
$this->assertArrayNotHasKey('foo', $params);
}
public function testGetParamsFallbackForMixedAndUnknown(): void
{
$this->setupMultiMethodRoute();
// Mixed and unknown should fallback to raw params
$this->request->setQueryString([
'foo' => 'valueFoo',
'baz' => 'valueBaz',
'extra' => 'unexpected',
]);
$params = $this->request->getParams();
$this->assertArrayHasKey('foo', $params);
$this->assertSame('valueFoo', $params['foo']);
$this->assertArrayHasKey('baz', $params);
$this->assertSame('valueBaz', $params['baz']);
$this->assertArrayHasKey('extra', $params);
$this->assertSame('unexpected', $params['extra']);
}
/**
* Helper to attach a route with multiple SDK methods to the request.
*/
private function setupMultiMethodRoute(): void
{
$route = new Route(Request::METHOD_GET, '/multi');
$methodA = new Method(
namespace: 'namespace',
group: 'group',
name: 'methodA',
description: 'desc',
auth: [],
responses: [],
parameters: [
new Parameter('foo'),
new Parameter('bar', optional: true),
],
);
$methodB = new Method(
namespace: 'namespace',
group: 'group',
name: 'methodB',
description: 'desc',
auth: [],
responses: [],
parameters: [
new Parameter('baz'),
],
);
$route->label('sdk', [$methodA, $methodB]);
$this->request->addFilter(new First());
$this->request->addFilter(new Second());
$this->request->setRoute($route);
}
public function testGetIPWithDefaultFallback(): void
{
// No headers set, should return remote_addr
$ip = $this->request->getIP();
$this->assertIsString($ip);
// Default fallback when nothing is set
$this->assertSame('0.0.0.0', $ip);
}
public function testGetIPWithRemoteAddr(): void
{
// Set remote_addr in server variables
$this->request->setServer('remote_addr', '192.168.1.100');
$ip = $this->request->getIP();
$this->assertSame('192.168.1.100', $ip);
}
public function testGetIPWithXForwardedFor(): void
{
// Set X-Forwarded-For header with single IP
$this->request->addHeader('x-forwarded-for', '203.0.113.195');
$ip = $this->request->getIP();
$this->assertSame('203.0.113.195', $ip);
}
public function testGetIPWithMultipleProxies(): void
{
// Set X-Forwarded-For with multiple IPs (leftmost is client)
$this->request->addHeader('x-forwarded-for', '203.0.113.195, 70.41.3.18, 150.172.238.178');
$ip = $this->request->getIP();
// Should return the leftmost (original client) IP
$this->assertSame('203.0.113.195', $ip);
}
public function testGetIPWithWhitespaceInHeader(): void
{
// Test that whitespace is properly trimmed
$this->request->addHeader('x-forwarded-for', ' 203.0.113.195 , 70.41.3.18 ');
$ip = $this->request->getIP();
$this->assertSame('203.0.113.195', $ip);
}
public function testGetIPWithInvalidIP(): void
{
// Set invalid IP in X-Forwarded-For
// When the leftmost IP is invalid, the entire header is skipped
// and we fallback to remote_addr (intentional security behavior)
$this->request->addHeader('x-forwarded-for', 'not-an-ip, 203.0.113.195');
$this->request->setServer('remote_addr', '192.168.1.100');
$ip = $this->request->getIP();
// Should fallback to remote_addr since leftmost IP is invalid
$this->assertSame('192.168.1.100', $ip);
}
public function testGetIPWithCustomTrustedHeader(): void
{
// Assuming you can set environment variable in test
putenv('_APP_TRUSTED_HEADERS=cf-connecting-ip');
$this->request->addHeader('cf-connecting-ip', '203.0.113.195');
$this->request->addHeader('x-forwarded-for', '198.51.100.178');
$ip = $this->request->getIP();
// Should use cf-connecting-ip since it's the trusted header
$this->assertSame('203.0.113.195', $ip);
}
public function testGetIPWithMultipleTrustedHeaders(): void
{
putenv('_APP_TRUSTED_HEADERS=cf-connecting-ip, x-real-ip, x-forwarded-for');
// Only set the second header
$this->request->addHeader('x-real-ip', '203.0.113.195');
$this->request->addHeader('x-forwarded-for', '203.0.113.192');
$ip = $this->request->getIP();
$this->assertSame('203.0.113.195', $ip);
}
public function testGetIPHeaderPriority(): void
{
putenv('_APP_TRUSTED_HEADERS=cf-connecting-ip, x-forwarded-for');
// Set both headers, cf-connecting-ip should take priority
$this->request->addHeader('cf-connecting-ip', '203.0.113.195');
$this->request->addHeader('x-forwarded-for', '198.51.100.178');
$ip = $this->request->getIP();
// Should return the first trusted header's value
$this->assertSame('203.0.113.195', $ip);
}
public function testGetIPWithIPv6(): void
{
$this->request->addHeader('x-forwarded-for', '2001:db8:85a3:8d3:1319:8a2e:370:7348');
$ip = $this->request->getIP();
$this->assertSame('2001:db8:85a3:8d3:1319:8a2e:370:7348', $ip);
}
public function testGetIPWithEmptyHeader(): void
{
$this->request->addHeader('x-forwarded-for', '');
$this->request->setServer('remote_addr', '192.168.1.100');
$ip = $this->request->getIP();
// Should fallback to remote_addr when header is empty
$this->assertSame('192.168.1.100', $ip);
}
public function testGetIPWithEmptyTrustedHeadersConfig(): void
{
putenv('_APP_TRUSTED_HEADERS= , , ');
$this->request->setServer('remote_addr', '192.168.1.100');
$ip = $this->request->getIP();
// Should fallback to remote_addr when config is effectively empty
$this->assertSame('192.168.1.100', $ip);
}
}