<?php

namespace Illuminate\Tests\Bus;

use Illuminate\Bus\Queueable;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class QueueableTest extends TestCase
{
    public static function connectionDataProvider(): array
    {
        return [
            'uses string' => ['redis', 'redis'],
            'uses BackedEnum #1' => [ConnectionEnum::SQS, 'sqs'],
            'uses BackedEnum #2' => [ConnectionEnum::REDIS, 'redis'],
            'uses null' => [null, null],
        ];
    }

    #[DataProvider('connectionDataProvider')]
    public function testOnConnection(mixed $connection, ?string $expected): void
    {
        $job = new FakeJob();
        $job->onConnection($connection);

        $this->assertSame($job->connection, $expected);
    }

    #[DataProvider('connectionDataProvider')]
    public function testAllOnConnection(mixed $connection, ?string $expected): void
    {
        $job = new FakeJob();
        $job->allOnConnection($connection);

        $this->assertSame($job->connection, $expected);
        $this->assertSame($job->chainConnection, $expected);
    }

    public static function queuesDataProvider(): array
    {
        return [
            'uses string' => ['high', 'high'],
            'uses BackedEnum #1' => [QueueEnum::DEFAULT, 'default'],
            'uses BackedEnum #2' => [QueueEnum::HIGH, 'high'],
            'uses null' => [null, null],
        ];
    }

    #[DataProvider('queuesDataProvider')]
    public function testOnQueue(mixed $queue, ?string $expected): void
    {
        $job = new FakeJob();
        $job->onQueue($queue);

        $this->assertSame($job->queue, $expected);
    }

    #[DataProvider('queuesDataProvider')]
    public function testAllOnQueue(mixed $queue, ?string $expected): void
    {
        $job = new FakeJob();
        $job->allOnQueue($queue);

        $this->assertSame($job->queue, $expected);
        $this->assertSame($job->chainQueue, $expected);
    }
}

class FakeJob
{
    use Queueable;
}

enum ConnectionEnum: string
{
    case SQS = 'sqs';
    case REDIS = 'redis';
}

enum QueueEnum: string
{
    case HIGH = 'high';
    case DEFAULT = 'default';
}


class FilesystemAdapterTest extends TestCase
{
    private $tempDir;
    private $filesystem;
    private $adapter;

    protected function setUp(): void
    {
        $this->tempDir = __DIR__.'/tmp';
        $this->filesystem = new Filesystem(
            $this->adapter = new LocalFilesystemAdapter($this->tempDir)
        );
    }

    protected function tearDown(): void
    {
        $filesystem = new Filesystem(
            $this->adapter = new LocalFilesystemAdapter(dirname($this->tempDir))
        );
        $filesystem->deleteDirectory(basename($this->tempDir));
        m::close();

        unset($this->tempDir, $this->filesystem, $this->adapter);
    }

    public function testResponse()
    {
        $this->filesystem->write('file.txt', 'Hello World');
        $files = new FilesystemAdapter($this->filesystem, $this->adapter);
        $response = $files->response('file.txt');

        ob_start();
        $response->sendContent();
        $content = ob_get_clean();

        $this->assertInstanceOf(StreamedResponse::class, $response);
        $this->assertSame('Hello World', $content);
        $this->assertSame('inline; filename=file.txt', $response->headers->get('content-disposition'));
    }

    public function testMimeTypeIsNotCalledAlreadyProvidedToResponse()
    {
        $this->filesystem->write('file.txt', 'Hello World');

        $files = m::mock(FilesystemAdapter::class, [$this->filesystem, $this->adapter])->makePartial();
        $files->shouldReceive('mimeType')->never();

        $files->response('file.txt', null, [
            'Content-Type' => 'text/x-custom',
        ]);
    }

    public function testSizeIsNotCalledAlreadyProvidedToResponse()
    {
        $this->filesystem->write('file.txt', 'Hello World');

        $files = m::mock(FilesystemAdapter::class, [$this->filesystem, $this->adapter])->makePartial();
        $files->shouldReceive('size')->never();

        $files->response('file.txt', null, [
            'Content-Length' => 11,
        ]);
    }

    public function testFallbackNameCalledAlreadyProvidedToResponse()
    {
        $this->filesystem->write('file.txt', 'Hello World');

        $files = m::mock(FilesystemAdapter::class, [$this->filesystem, $this->adapter])
            ->shouldAllowMockingProtectedMethods()
            ->makePartial();
        $files->shouldReceive('fallbackName')->never();

        $files->response('file.txt', null, [
            'Content-Disposition' => 'attachment',
        ]);
    }

    public function testDownload()
    {
        $this->filesystem->write('file.txt', 'Hello World');
        $files = new FilesystemAdapter($this->filesystem, $this->adapter);
        $response = $files->download('file.txt', 'hello.txt');
        $this->assertInstanceOf(StreamedResponse::class, $response);
        $this->assertSame('attachment; filename=hello.txt', $response->headers->get('content-disposition'));
    }

    public function testDownloadNonAsciiFilename()
    {
        $this->filesystem->write('file.txt', 'Hello World');
        $files = new FilesystemAdapter($this->filesystem, $this->adapter);
        $response = $files->download('file.txt', 'привет.txt');
        $this->assertInstanceOf(StreamedResponse::class, $response);
        $this->assertSame("attachment; filename=privet.txt; filename*=utf-8''%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82.txt", $response->headers->get('content-disposition'));
    }

    public function testDownloadNonAsciiEmptyFilename()
    {
        $this->filesystem->write('привет.txt', 'Hello World');
        $files = new FilesystemAdapter($this->filesystem, $this->adapter);
        $response = $files->download('привет.txt');
        $this->assertInstanceOf(StreamedResponse::class, $response);
        $this->assertSame('attachment; filename=privet.txt; filename*=utf-8\'\'%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82.txt', $response->headers->get('content-disposition'));
    }

    public function testDownloadPercentInFilename()
    {
        $this->filesystem->write('Hello%World.txt', 'Hello World');
        $files = new FilesystemAdapter($this->filesystem, $this->adapter);
        $response = $files->download('Hello%World.txt', 'Hello%World.txt');
        $this->assertInstanceOf(StreamedResponse::class, $response);
        $this->assertSame('attachment; filename=HelloWorld.txt; filename*=utf-8\'\'Hello%25World.txt', $response->headers->get('content-disposition'));
    }

    public function testExists()
    {
        $this->filesystem->write('file.txt', 'Hello World');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertTrue($filesystemAdapter->exists('file.txt'));
        $this->assertTrue($filesystemAdapter->fileExists('file.txt'));
    }

    public function testMissing()
    {
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertTrue($filesystemAdapter->missing('file.txt'));
        $this->assertTrue($filesystemAdapter->fileMissing('file.txt'));
    }

    public function testDirectoryExists()
    {
        $this->filesystem->write('/foo/bar/file.txt', 'Hello World');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertTrue($filesystemAdapter->directoryExists('/foo/bar'));
    }

    public function testDirectoryMissing()
    {
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertTrue($filesystemAdapter->directoryMissing('/foo/bar'));
    }

    public function testPath()
    {
        $this->filesystem->write('file.txt', 'Hello World');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter, [
            'root' => $this->tempDir.DIRECTORY_SEPARATOR,
        ]);
        $this->assertEquals($this->tempDir.DIRECTORY_SEPARATOR.'file.txt', $filesystemAdapter->path('file.txt'));
    }

    public function testGet()
    {
        $this->filesystem->write('file.txt', 'Hello World');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertSame('Hello World', $filesystemAdapter->get('file.txt'));
    }

    public function testGetFileNotFound()
    {
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertNull($filesystemAdapter->get('file.txt'));
    }

    public function testJsonReturnsDecodedJsonData()
    {
        $this->filesystem->write('file.json', '{"foo": "bar"}');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertSame(['foo' => 'bar'], $filesystemAdapter->json('file.json'));
    }

    public function testJsonReturnsNullIfJsonDataIsInvalid()
    {
        $this->filesystem->write('file.json', '{"foo":');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertNull($filesystemAdapter->json('file.json'));
    }

    public function testMimeTypeNotDetected()
    {
        $this->filesystem->write('unknown.mime-type', '');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $this->assertFalse($filesystemAdapter->mimeType('unknown.mime-type'));
    }

    public function testPut()
    {
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $filesystemAdapter->put('file.txt', 'Something inside');
        $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'Something inside');
    }

    public function testPrepend()
    {
        file_put_contents($this->tempDir.'/file.txt', 'World');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $filesystemAdapter->prepend('file.txt', 'Hello ');
        $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'Hello '.PHP_EOL.'World');
    }

    public function testAppend()
    {
        file_put_contents($this->tempDir.'/file.txt', 'Hello ');
        $filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
        $filesystemAdapter->append('file.txt', 'Moon');
        $this->assertStringEqualsFile($this->tempDir.'/file.txt', 'Hello '.PHP_EOL.'Moon');
    }

    public funct


[DataProvider('collectionClassProvider')]
    public function testPluckWithArrayAccessValues($collection)
    {
        $data = new $collection([
            new TestArrayAccessImplementation(['name' => 'taylor', 'email' => 'foo']),
            new TestArrayAccessImplementation(['name' => 'dayle', 'email' => 'bar']),
        ]);

        $this->assertEquals(['taylor' => 'foo', 'dayle' => 'bar'], $data->pluck('email', 'name')->all());
        $this->assertEquals(['foo', 'bar'], $data->pluck('email')->all());
    }

    #[DataProvider('collectionClassProvider')]
    public function testPluckWithDotNotation($collection)
    {
        $data = new $collection([
            [
                'name' => 'amir',
                'skill' => [
                    'backend' => ['php', 'python'],
                ],
            ],
            [
                'name' => 'taylor',
                'skill' => [
                    'backend' => ['php', 'asp', 'java'],
                ],
            ],
        ]);

        $this->assertEquals([['php', 'python'], ['php', 'asp', 'java']], $data->pluck('skill.backend')->all());
    }

    #[DataProvider('collectionClassProvider')]
    public function testPluckDuplicateKeysExist($collection)
    {
        $data = new $collection([
            ['brand' => 'Tesla', 'color' => 'red'],
            ['brand' => 'Pagani', 'color' => 'white'],
            ['brand' => 'Tesla', 'color' => 'black'],
            ['brand' => 'Pagani', 'color' => 'orange'],
        ]);

        $this->assertEquals(['Tesla' => 'black', 'Pagani' => 'orange'], $data->pluck('color', 'brand')->all());
    }

    #[DataProvider('collectionClassProvider')]
    public function testHas($collection)
    {
        $data = new $collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']);
        $this->assertTrue($data->has('first'));
        $this->assertFalse($data->has('third'));
        $this->assertTrue($data->has(['first', 'second']));
        $this->assertFalse($data->has(['third', 'first']));
        $this->assertTrue($data->has('first', 'second'));
    }

    #[DataProvider('collectionClassProvider')]
    public function testHasAny($collection)
    {
        $data = new $collection(['id' => 1, 'first' => 'Hello', 'second' => 'World']);

        $this->assertTrue($data->hasAny('first'));
        $this->assertFalse($data->hasAny('third'));
        $this->assertTrue($data->hasAny(['first', 'second']));
        $this->assertTrue($data->hasAny(['first', 'fourth']));
        $this->assertFalse($data->hasAny(['third', 'fourth']));
        $this->assertFalse($data->hasAny('third', 'fourth'));
        $this->assertFalse($data->hasAny([]));
    }

    #[DataProvider('collectionClassProvider')]
    public function testImplode($collection)
    {
        $data = new $collection([['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]);
        $this->assertSame('foobar', $data->implode('email'));
        $this->assertSame('foo,bar', $data->implode('email', ','));

        $data = new $collection(['taylor', 'dayle']);
        $this->assertSame('taylordayle', $data->implode(''));
        $this->assertSame('taylor,dayle', $data->implode(','));

        $data = new $collection([
            ['name' => Str::of('taylor'), 'email' => Str::of('foo')],
            ['name' => Str::of('dayle'), 'email' => Str::of('bar')],
        ]);
        $this->assertSame('foobar', $data->implode('email'));
        $this->assertSame('foo,bar', $data->implode('email', ','));

        $data = new $collection([Str::of('taylor'), Str::of('dayle')]);
        $this->assertSame('taylordayle', $data->implode(''));
        $this->assertSame('taylor,dayle', $data->implode(','));
        $this->assertSame('taylor_dayle', $data->implode('_'));

        $data = new $collection([['name' => 'taylor', 'email' => 'foo'], ['name' => 'dayle', 'email' => 'bar']]);
        $this->assertSame('taylor-foodayle-bar', $data->implode(fn ($user) => $user['name'].'-'.$user['email']));
        $this->assertSame('taylor-foo,dayle-bar', $data->implode(fn ($user) => $user['name'].'-'.$user['email'], ','));
    }

    #[DataProvider('collectionClassProvider')]
    public function testTake($collection)
    {
        $data = new $collection(['taylor', 'dayle', 'shawn']);
        $data = $data->take(2);
        $this->assertEquals(['taylor', 'dayle'], $data->all());
    }

    public function testGetOrPut()
    {
        $data = new Collection(['name' => 'taylor', 'email' => 'foo']);

        $this->assertSame('taylor', $data->getOrPut('name', null));
        $this->assertSame('foo', $data->getOrPut('email', null));
        $this->assertSame('male', $data->getOrPut('gender', 'male'));

        $this->assertSame('taylor', $data->get('name'));
        $this->assertSame('foo', $data->get('email'));
        $this->assertSame('male', $data->get('gender'));

        $data = new Collection(['name' => 'taylor', 'email' => 'foo']);

        $this->assertSame('taylor', $data->getOrPut('name', function () {
            return null;
        }));

        $this->assertSame('foo', $data->getOrPut('email', function () {
            return null;
        }));

        $this->assertSame('male', $data->getOrPut('gender', function () {
            return 'male';
        }));

        $this->assertSame('taylor', $data->get('name'));
        $this->assertSame('foo', $data->get('email'));
        $this->assertSame('male', $data->get('gender'));
    }

    public function testPut()
    {
        $data = new Collection(['name' => 'taylor', 'email' => 'foo']);
        $data = $data->put('name', 'dayle');
        $this->assertEquals(['name' => 'dayle', 'email' => 'foo'], $data->all());
    }

    public function testPutWithNoKey()
    {
        $data = new Collection(['taylor', 'shawn']);
        $data = $data->put(null, 'dayle');
        $this->assertEquals(['taylor', 'shawn', 'dayle'], $data->all());
    }


public function testGetWithArrayQueryParamEncodes()
    {
        $this->factory->fake();

        $this->factory->get('http://foo.com/get', ['foo;bar; space test' => 'laravel']);

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/get?foo%3Bbar%3B%20space%20test=laravel'
                && $request['foo;bar; space test'] === 'laravel';
        });
    }

    public function testWithBaseUrl()
    {
        $this->factory->fake();

        $this->factory->baseUrl('http://foo.com/')->get('get');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/get';
        });

        $this->factory->fake();

        $this->factory->baseUrl('http://foo.com/')->get('http://bar.com/get');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://bar.com/get';
        });
    }

    public function testCanConfirmManyHeaders()
    {
        $this->factory->fake();

        $this->factory->withHeaders([
            'X-Test-Header' => 'foo',
            'X-Test-ArrayHeader' => ['bar', 'baz'],
        ])->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                   $request->hasHeaders([
                       'X-Test-Header' => 'foo',
                       'X-Test-ArrayHeader' => ['bar', 'baz'],
                   ]);
        });
    }

    public function testCanConfirmManyHeadersUsingAString()
    {
        $this->factory->fake();

        $this->factory->withHeaders([
            'X-Test-Header' => 'foo',
            'X-Test-ArrayHeader' => ['bar', 'baz'],
        ])->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                   $request->hasHeaders('X-Test-Header');
        });
    }

    public function testItMergesMultipleHeaders()
    {
        $this->factory->fake();

        $this->factory->withHeaders([
            'X-Test-Header' => 'foo',
        ])->withHeaders([
            'X-Test-Header' => 'bar',
        ])->withHeaders([
            'X-Test-Header' => ['baz', 'qux'],
        ])->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                   $request->hasHeaders(['X-Test-Header' => ['foo', 'bar', 'baz', 'qux']]);
        });
    }

    public function testItCanReplaceHeaders()
    {
        $this->factory->fake();

        $this->factory->withHeaders([
            'X-Test-Header' => 'foo',
        ])->replaceHeaders([
            'X-Test-Header' => 'baz',
        ])->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                   $request->hasHeaders(['X-Test-Header' => ['baz']]);
        });
    }

    public function testItCanReplaceHeadersWhenNoHeadersYetSet()
    {
        $this->factory->fake();

        $this->factory->replaceHeaders([
            'X-Test-Header' => 'baz',
        ])->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                   $request->hasHeaders(['X-Test-Header' => ['baz']]);
        });
    }

    public function testCanConfirmSingleStringHeader()
    {
        $this->factory->fake();

        $this->factory->withHeader('X-Test-Header', 'foo')->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                $request->hasHeaders([
                    'X-Test-Header' => 'foo',
                ]);
        });
    }

    public function testCanConfirmSingleArrayHeader()
    {
        $this->factory->fake();

        $this->factory->withHeader('X-Test-ArrayHeader', ['bar', 'baz'])->post('http://foo.com/json');

        $this->factory->assertSent(function (Request $request) {
            return $request->url() === 'http://foo.com/json' &&
                $request->hasHeaders([
                    'X-Test-ArrayHeader' => ['bar', 'baz'],
                ]);
        });
    }

    public function testExceptionAccessorOnSuccess()
    {
        $resp = new Response(new Psr7Response());

        $this->assertNull($resp->toException());
    }

    public function testExceptionAccessorOnFailure()
    {
        $error = [
            'error' => [
                'code' => 403,
                'message' => 'The Request can not be completed',
            ],
        ];
        $response = new Psr7Response(403, [], json_encode($error));
        $resp = new Response($response);

        $this->assertInstanceOf(RequestException::class, $resp->toException());
    }

    public function testRequestExceptionSummary()
    {
        $this->expectException(RequestException::class);
        $this->expectExceptionMessage('{"error":{"code":403,"message":"The Request can not be completed"}}');

        $error = [
            'error' => [
                'code' => 403,
                'message' => 'The Request can not be completed',
            ],
        ];
        $response = new Psr7Response(403, [], json_encode($error));

        throw new RequestException(new Response($response));
    }

    public function testRequestExceptionTruncatedSummary()
    {
        $this->expectException(RequestException::class);
        $this->expectExceptionMessage('{"error":{"code":403,"message":"The Request can not be completed because quota limit was exceeded. Please, check our sup (truncated...)');

        $error = [
            'error' => [
                'code' => 403,
                'message' => 'The Request can not be completed because quota limit was exceeded. Please, check our support team to increase your limit',
            ],
        ];
        $response = new Psr7Response(403, [], json_encode($error));

        throw new RequestException(new Response($response));
    }

    public function testRequestExceptionEmptyBody()
    {
        $this->expectException(RequestException::class);
        $this->expectExceptionMessageMatches('/HTTP request returned status code 403$/');

        $response = new Psr7Response(403);

        throw new RequestException(new Response($response));
    }

    public function testOnErrorDoesntCallClosureOnInformational()
    {
        $status = 0;
        $client = $this->factory->fake([
            'laravel.com' => $this->factory::response('', 101),
        ]);

        $response = $client->get('laravel.com')
            ->onError(function ($response) use (&$status) {
                $status = $response->status();
            });

        $this->assertSame(0, $status);
        $this->assertSame(101, $response->status());
    }

    public function testOnErrorDoesntCallClosureOnSuccess()
    {
        $status = 0;
        $client = $this->factory->fake([
            'laravel.com' => $this->factory::response('', 201),
        ]);

        $response = $client->get('laravel.com')
            ->onError(function ($response) use (&$status) {
                $status = $response->status();
            });

        $this->assertSame(0, $status);
        $this->assertSame(201, $response->status());
    }

    public function testOnErrorDoesntCallClosureOnRedirection()
    {
        $status = 0;
        $client = $this->factory->fake([
            'laravel.com' => $this->factory::response('', 301),
        ]);

        $response = $client->get('laravel.com')
            ->onError(function ($response) use (&$status) {
                $status = $response->status();
            });

        $this->assertSame(0, $status);
        $this->assertSame(301, $response->status());
    }


class HttpResponseTest extends TestCase
{
    protected function tearDown(): void
    {
        m::close();
    }

    public function testJsonResponsesAreConvertedAndHeadersAreSet()
    {
        $response = new Response(new ArrayableStub);
        $this->assertSame('{"foo":"bar"}', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));

        $response = new Response(new JsonableStub);
        $this->assertSame('foo', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));

        $response = new Response(new ArrayableAndJsonableStub);
        $this->assertSame('{"foo":"bar"}', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));

        $response = new Response;
        $response->setContent(['foo' => 'bar']);
        $this->assertSame('{"foo":"bar"}', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));

        $response = new Response(new JsonSerializableStub);
        $this->assertSame('{"foo":"bar"}', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));

        $response = new Response(new ArrayableStub);
        $this->assertSame('{"foo":"bar"}', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));

        $response->setContent('{"foo": "bar"}');
        $this->assertSame('{"foo": "bar"}', $response->getContent());
        $this->assertSame('application/json', $response->headers->get('Content-Type'));
    }

    public function testRenderablesAreRendered()
    {
        $mock = m::mock(Renderable::class);
        $mock->shouldReceive('render')->once()->andReturn('foo');
        $response = new Response($mock);
        $this->assertSame('foo', $response->getContent());
    }

    public function testHeader()
    {
        $response = new Response;
        $this->assertNull($response->headers->get('foo'));
        $response->header('foo', 'bar');
        $this->assertSame('bar', $response->headers->get('foo'));
        $response->header('foo', 'baz', false);
        $this->assertSame('bar', $response->headers->get('foo'));
        $response->header('foo', 'baz');
        $this->assertSame('baz', $response->headers->get('foo'));
    }

    public function testWithCookie()
    {
        $response = new Response;
        $this->assertCount(0, $response->headers->getCookies());
        $this->assertEquals($response, $response->withCookie(new Cookie('foo', 'bar')));
        $cookies = $response->headers->getCookies();
        $this->assertCount(1, $cookies);
        $this->assertSame('foo', $cookies[0]->getName());
        $this->assertSame('bar', $cookies[0]->getValue());
    }

    public function testResponseCookiesInheritRequestSecureState()
    {
        $cookie = Cookie::create('foo', 'bar');

        $response = new Response('foo');
        $response->headers->setCookie($cookie);

        $request = Request::create('/', 'GET');
        $response->prepare($request);

        $this->assertFalse($cookie->isSecure());

        $request = Request::create('https://localhost/', 'GET');
        $response->prepare($request);

        $this->assertTrue($cookie->isSecure());
    }

    public function testGetOriginalContent()
    {
        $arr = ['foo' => 'bar'];
        $response = new Response;
        $response->setContent($arr);
        $this->assertSame($arr, $response->getOriginalContent());
    }

    public function testGetOriginalContentRetrievesTheFirstOriginalContent()
    {
        $previousResponse = new Response(['foo' => 'bar']);
        $response = new Response($previousResponse);

        $this->assertSame(['foo' => 'bar'], $response->getOriginalContent());
    }

    public function testSetAndRetrieveStatusCode()
    {
        $response = new Response('foo');
        $response->setStatusCode(404);
        $this->assertSame(404, $response->getStatusCode());
    }

    public function testSetStatusCodeAndRetrieveStatusText()
    {
        $response = new Response('foo');
        $response->setStatusCode(404);
        $this->assertSame('Not Found', $response->statusText());
    }

    public function testOnlyInputOnRedirect()
    {
        $response = new RedirectResponse('foo.bar');
        $response->setRequest(Request::create('/', 'GET', ['name' => 'Taylor', 'age' => 26]));
        $response->setSession($session = m::mock(Store::class));
        $session->shouldReceive('flashInput')->once()->with(['name' => 'Taylor']);
        $response->onlyInput('name');
    }

    public function testExceptInputOnRedirect()
    {
        $response = new RedirectResponse('foo.bar');
        $response->setRequest(Request::create('/', 'GET', ['name' => 'Taylor', 'age' => 26]));
        $response->setSession($session = m::mock(Store::class));
        $session->shouldReceive('flashInput')->once()->with(['name' => 'Taylor']);
        $response->exceptInput('age');
    }

    public function testFlashingErrorsOnRedirect()
    {
        $response = new RedirectResponse('foo.bar');
        $response->setRequest(Request::create('/', 'GET', ['name' => 'Taylor', 'age' => 26]));
        $response->setSession($session = m::mock(Store::class));
        $session->shouldReceive('get')->with('errors', m::type(ViewErrorBag::class))->andReturn(new ViewErrorBag);
        $session->shouldReceive('flash')->once()->with('errors', m::type(ViewErrorBag::class));
        $provider = m::mock(MessageProvider::class);
        $provider->shouldReceive('getMessageBag')->once()->andReturn(new MessageBag);
        $response->withErrors($provider);
    }

    public function testSettersGettersOnRequest()
    {
        $response = new RedirectResponse('foo.bar');
        $this->assertNull($response->getRequest());
        $this->assertNull($response->getSession());

        $request = Request::create('/', 'GET');
        $session = m::mock(Store::class);
        $response->setRequest($request);
        $response->setSession($session);
        $this->assertSame($request, $response->getRequest());
        $this->assertSame($session, $response->getSession());
    }

    public function testRedirectWithErrorsArrayConvertsToMessageBag()
    {
        $response = new RedirectResponse('foo.bar');
        $response->setRequest(Request::create('/', 'GET', ['name' => 'Taylor', 'age' => 26]));
        $response->setSession($session = m::mock(Store::class));
        $session->shouldReceive('get')->with('errors', m::type(ViewErrorBag::class))->andReturn(new ViewErrorBag);
        $session->shouldReceive('flash')->once()->with('errors', m::type(ViewErrorBag::class));
        $provider = ['foo' => 'bar'];
        $response->withErrors($provider);
    }

    public function testWithHeaders()
    {
        $response = new Response(null, 200, ['foo' => 'bar']);
        $this->assertSame('bar', $response->headers->get('foo'));

        $response->withHeaders(['foo' => 'BAR', 'bar' => 'baz']);
        $this->assertSame('BAR', $response->headers->get('foo'));
        $this->assertSame('baz', $response->headers->get('bar'));

        $responseMessageBag = new ResponseHeaderBag(['bar' => 'BAZ', 'titi' => 'toto']);
        $response->withHeaders($responseMessageBag);
        $this->assertSame('BAZ', $response->headers->get('bar'));
        $this->assertSame('toto', $response->headers->get('titi'));

        $headerBag = new HeaderBag(['bar' => 'BAAA', 'titi' => 'TATA']);
        $response->withHeaders($headerBag);
        $this->assertSame('BAAA', $response->headers->get('bar'));
        $this->assertSame('TATA', $response->headers->get('titi'));
    }

    public function testMagicCall()
    {
        $response = new RedirectResponse('foo.bar');
        $response->setRequest(Request::create('/', 'GET', ['name' => 'Taylor', 'age' => 26]));
        $response->setSession($session = m::mock(Store::class));
        $session->shouldReceive('flash')->once()->with('foo', 'bar');
        $response->withFoo('bar');
    }

    public function testMagicCallException()
    {
        $this->expectException(BadMethodCallException::class);
        $this->expectExceptionMessage('Call to undefined method Illuminate\Http\RedirectResponse::doesNotExist()');

        $response = new RedirectResponse('foo.bar');
        $response->doesNotExist('bar');
    }
}


<?php

namespace Illuminate\Tests\View\Blade;

use Illuminate\View\ComponentAttributeBag;

class BladePropsTest extends AbstractBladeTestCase
{
    public function testPropsAreCompiled()
    {
        $this->assertSame('<?php $attributes ??= new \Illuminate\View\ComponentAttributeBag;

$__newAttributes = [];
$__propNames = \Illuminate\View\ComponentAttributeBag::extractPropNames(([\'one\' => true, \'two\' => \'string\']));

foreach ($attributes->all() as $__key => $__value) {
    if (in_array($__key, $__propNames)) {
        $$__key = $$__key ?? $__value;
    } else {
        $__newAttributes[$__key] = $__value;
    }
}

$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes);

unset($__propNames);
unset($__newAttributes);

foreach (array_filter(([\'one\' => true, \'two\' => \'string\']), \'is_string\', ARRAY_FILTER_USE_KEY) as $__key => $__value) {
    $$__key = $$__key ?? $__value;
}

$__defined_vars = get_defined_vars();

foreach ($attributes->all() as $__key => $__value) {
    if (array_key_exists($__key, $__defined_vars)) unset($$__key);
}

unset($__defined_vars); ?>', $this->compiler->compileString('@props([\'one\' => true, \'two\' => \'string\'])'));
    }

    public function testPropsAreExtractedFromParentAttributesCorrectly()
    {
        $test1 = $test2 = $test4 = null;

        $attributes = new ComponentAttributeBag(['test1' => 'value1', 'test2' => 'value2', 'test3' => 'value3']);

        $template = $this->compiler->compileString('@props([\'test1\' => \'default\', \'test2\', \'test4\' => \'default\'])');

        ob_start();
        eval(" ?> $template <?php ");
        ob_get_clean();

        $this->assertSame($test1, 'value1');
        $this->assertSame($test2, 'value2');
        $this->assertFalse(isset($test3));
        $this->assertSame($test4, 'default');

        $this->assertNull($attributes->get('test1'));
        $this->assertNull($attributes->get('test2'));
        $this->assertSame($attributes->get('test3'), 'value3');
    }
}


<?php

namespace Illuminate\Tests\View\Blade;

class BladeSessionTest extends AbstractBladeTestCase
{
    public function testSessionsAreCompiled()
    {
        $string = '
@session(\'status\')
    {{ $value }}
@endsession';
        $expected = '
<?php $__sessionArgs = [\'status\'];
if (session()->has($__sessionArgs[0])) :
if (isset($value)) { $__sessionPrevious[] = $value; }
$value = session()->get($__sessionArgs[0]); ?>
    <?php echo e($value); ?>
<?php unset($value);
if (isset($__sessionPrevious) && !empty($__sessionPrevious)) { $value = array_pop($__sessionPrevious); }
if (isset($__sessionPrevious) && empty($__sessionPrevious)) { unset($__sessionPrevious); }
endif;
unset($__sessionArgs); ?>';

        $this->assertEquals($expected, $this->compiler->compileString($string));
    }
}


public function testItUtilisesTheNullDriverDuringTestsWhenNullDriverUsed()
    {
        $config = $this->app->make('config');
        $config->set('logging.default', null);
        $config->set('logging.channels.null', [
            'driver' => 'monolog',
            'handler' => NullHandler::class,
        ]);
        $manager = new class($this->app) extends LogManager
        {
            protected function createEmergencyLogger()
            {
                throw new RuntimeException('Emergency logger was created.');
            }
        };

        // In tests, this should not need to create the emergency logger...
        $manager->info('message');

        // we should also be able to forget the null channel...
        $this->assertCount(1, $manager->getChannels());
        $manager->forgetChannel();
        $this->assertCount(0, $manager->getChannels());

        // However in production we want it to fallback to the emergency logger...
        $this->app['env'] = 'production';
        try {
            $manager->info('message');

            $this->fail('Emergency logger was not created as expected.');
        } catch (RuntimeException $exception) {
            $this->assertSame('Emergency logger was created.', $exception->getMessage());
        }
    }

    public function testLogManagerCreateSingleDriverWithConfiguredFormatter()
    {
        $config = $this->app['config'];
        $config->set('logging.channels.defaultsingle', [
            'driver' => 'single',
            'name' => 'ds',
            'path' => storage_path('logs/laravel.log'),
            'replace_placeholders' => true,
        ]);

        $manager = new LogManager($this->app);

        // create logger with handler specified from configuration
        $logger = $manager->channel('defaultsingle');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(StreamHandler::class, $handler);
        $this->assertInstanceOf(LineFormatter::class, $formatter);
        $this->assertInstanceOf(PsrLogMessageProcessor::class, $logger->getLogger()->getProcessors()[1]);

        $config->set('logging.channels.formattedsingle', [
            'driver' => 'single',
            'name' => 'fs',
            'path' => storage_path('logs/laravel.log'),
            'formatter' => HtmlFormatter::class,
            'formatter_with' => [
                'dateFormat' => 'Y/m/d--test',
            ],
            'replace_placeholders' => false,
        ]);

        $logger = $manager->channel('formattedsingle');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(StreamHandler::class, $handler);
        $this->assertInstanceOf(HtmlFormatter::class, $formatter);
        $this->assertCount(1, $logger->getLogger()->getProcessors());

        $dateFormat = new ReflectionProperty(get_class($formatter), 'dateFormat');

        $this->assertSame('Y/m/d--test', $dateFormat->getValue($formatter));
    }

    public function testLogManagerCreateDailyDriverWithConfiguredFormatter()
    {
        $config = $this->app['config'];
        $config->set('logging.channels.defaultdaily', [
            'driver' => 'daily',
            'name' => 'dd',
            'path' => storage_path('logs/laravel.log'),
            'replace_placeholders' => true,
        ]);

        $manager = new LogManager($this->app);

        // create logger with handler specified from configuration
        $logger = $manager->channel('defaultdaily');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(StreamHandler::class, $handler);
        $this->assertInstanceOf(LineFormatter::class, $formatter);
        $this->assertInstanceOf(PsrLogMessageProcessor::class, $logger->getLogger()->getProcessors()[1]);

        $config->set('logging.channels.formatteddaily', [
            'driver' => 'daily',
            'name' => 'fd',
            'path' => storage_path('logs/laravel.log'),
            'formatter' => HtmlFormatter::class,
            'formatter_with' => [
                'dateFormat' => 'Y/m/d--test',
            ],
            'replace_placeholders' => false,
        ]);

        $logger = $manager->channel('formatteddaily');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(StreamHandler::class, $handler);
        $this->assertInstanceOf(HtmlFormatter::class, $formatter);
        $this->assertCount(1, $logger->getLogger()->getProcessors());

        $dateFormat = new ReflectionProperty(get_class($formatter), 'dateFormat');

        $this->assertSame('Y/m/d--test', $dateFormat->getValue($formatter));
    }

    public function testLogManagerCreateSyslogDriverWithConfiguredFormatter()
    {
        $config = $this->app['config'];
        $config->set('logging.channels.defaultsyslog', [
            'driver' => 'syslog',
            'name' => 'ds',
            'replace_placeholders' => true,
        ]);

        $manager = new LogManager($this->app);

        // create logger with handler specified from configuration
        $logger = $manager->channel('defaultsyslog');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(SyslogHandler::class, $handler);
        $this->assertInstanceOf(LineFormatter::class, $formatter);
        $this->assertInstanceOf(PsrLogMessageProcessor::class, $logger->getLogger()->getProcessors()[1]);

        $config->set('logging.channels.formattedsyslog', [
            'driver' => 'syslog',
            'name' => 'fs',
            'formatter' => HtmlFormatter::class,
            'formatter_with' => [
                'dateFormat' => 'Y/m/d--test',
            ],
            'replace_placeholders' => false,
        ]);

        $logger = $manager->channel('formattedsyslog');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(SyslogHandler::class, $handler);
        $this->assertInstanceOf(HtmlFormatter::class, $formatter);
        $this->assertCount(1, $logger->getLogger()->getProcessors());

        $dateFormat = new ReflectionProperty(get_class($formatter), 'dateFormat');

        $this->assertSame('Y/m/d--test', $dateFormat->getValue($formatter));
    }

    public function testLogManagerPurgeResolvedChannels()
    {
        $manager = new LogManager($this->app);

        $this->assertEmpty($manager->getChannels());

        $manager->channel('single')->getLogger();

        $this->assertCount(1, $manager->getChannels());

        $manager->forgetChannel('single');

        $this->assertEmpty($manager->getChannels());
    }

    public function testLogManagerCanBuildOnDemandChannel()
    {
        $manager = new LogManager($this->app);

        $logger = $manager->build([
            'driver' => 'single',
            'path' => storage_path('logs/on-demand.log'),
        ]);
        $handler = $logger->getLogger()->getHandlers()[0];

        $this->assertInstanceOf(StreamHandler::class, $handler);

        $url = new ReflectionProperty(get_class($handler), 'url');

        $this->assertSame(storage_path('logs/on-demand.log'), $url->getValue($handler));
    }

    public function testLogManagerCanUseOnDemandChannelInOnDemandStack()
    {
        $manager = new LogManager($this->app);
        $this->app['config']->set('logging.channels.test', [
            'driver' => 'single',
        ]);

        $factory = new class()
        {
            public function __invoke()
            {
                return new Monolog(
                    'uuid',
                    [new StreamHandler(storage_path('logs/custom.log'))],
                    [new UidProcessor()]
                );
            }
        };
        $channel = $manager->build([
            'driver' => 'custom',
            'via' => get_class($factory),
        ]);
        $logger = $manager->stack(['test', $channel]);

        $handler = $logger->getLogger()->getHandlers()[1];
        $processor = $logger->getLogger()->getProcessors()[1];

        $this->assertInstanceOf(StreamHandler::class, $handler);
        $this->assertInstanceOf(UidProcessor::class, $processor);

        $url = new ReflectionProperty(get_class($handler), 'url');

        $this->assertSame(storage_path('logs/custom.log'), $url->getValue($handler));
    }


public function testWrappingHandlerInFingersCrossedWhenActionLevelIsUsed()
    {
        $config = $this->app['config'];

        $config->set('logging.channels.fingerscrossed', [
            'driver' => 'monolog',
            'handler' => StreamHandler::class,
            'level' => 'debug',
            'action_level' => 'critical',
            'with' => [
                'stream' => 'php://stderr',
                'bubble' => false,
            ],
        ]);

        $manager = new LogManager($this->app);

        // create logger with handler specified from configuration
        $logger = $manager->channel('fingerscrossed');
        $handlers = $logger->getLogger()->getHandlers();

        $this->assertInstanceOf(Logger::class, $logger);
        $this->assertCount(1, $handlers);

        $expectedFingersCrossedHandler = $handlers[0];
        $this->assertInstanceOf(FingersCrossedHandler::class, $expectedFingersCrossedHandler);

        $activationStrategyProp = new ReflectionProperty(get_class($expectedFingersCrossedHandler), 'activationStrategy');
        $activationStrategyValue = $activationStrategyProp->getValue($expectedFingersCrossedHandler);

        $actionLevelProp = new ReflectionProperty(get_class($activationStrategyValue), 'actionLevel');
        $actionLevelValue = $actionLevelProp->getValue($activationStrategyValue);

        $this->assertEquals(Level::Critical, $actionLevelValue);

        if (method_exists($expectedFingersCrossedHandler, 'getHandler')) {
            $expectedStreamHandler = $expectedFingersCrossedHandler->getHandler();
        } else {
            $handlerProp = new ReflectionProperty(get_class($expectedFingersCrossedHandler), 'handler');
            $expectedStreamHandler = $handlerProp->getValue($expectedFingersCrossedHandler);
        }
        $this->assertInstanceOf(StreamHandler::class, $expectedStreamHandler);
        $this->assertEquals(Level::Debug, $expectedStreamHandler->getLevel());
    }

    public function testFingersCrossedHandlerStopsRecordBufferingAfterFirstFlushByDefault()
    {
        $config = $this->app['config'];

        $config->set('logging.channels.fingerscrossed', [
            'driver' => 'monolog',
            'handler' => StreamHandler::class,
            'level' => 'debug',
            'action_level' => 'critical',
            'with' => [
                'stream' => 'php://stderr',
                'bubble' => false,
            ],
        ]);

        $manager = new LogManager($this->app);

        // create logger with handler specified from configuration
        $logger = $manager->channel('fingerscrossed');
        $handlers = $logger->getLogger()->getHandlers();

        $expectedFingersCrossedHandler = $handlers[0];

        $stopBufferingProp = new ReflectionProperty(get_class($expectedFingersCrossedHandler), 'stopBuffering');
        $stopBufferingValue = $stopBufferingProp->getValue($expectedFingersCrossedHandler);

        $this->assertTrue($stopBufferingValue);
    }

    public function testFingersCrossedHandlerCanBeConfiguredToResumeBufferingAfterFlushing()
    {
        $config = $this->app['config'];

        $config->set('logging.channels.fingerscrossed', [
            'driver' => 'monolog',
            'handler' => StreamHandler::class,
            'level' => 'debug',
            'action_level' => 'critical',
            'stop_buffering' => false,
            'with' => [
                'stream' => 'php://stderr',
                'bubble' => false,
            ],
        ]);

        $manager = new LogManager($this->app);

        // create logger with handler specified from configuration
        $logger = $manager->channel('fingerscrossed');
        $handlers = $logger->getLogger()->getHandlers();

        $expectedFingersCrossedHandler = $handlers[0];

        $stopBufferingProp = new ReflectionProperty(get_class($expectedFingersCrossedHandler), 'stopBuffering');
        $stopBufferingValue = $stopBufferingProp->getValue($expectedFingersCrossedHandler);

        $this->assertFalse($stopBufferingValue);
    }

    public function testItSharesContextWithAlreadyResolvedChannels()
    {
        $manager = new LogManager($this->app);
        $channel = $manager->channel('single');
        $context = null;

        $channel->listen(function ($message) use (&$context) {
            $context = $message->context;
        });
        $manager->shareContext([
            'invocation-id' => 'expected-id',
        ]);
        $channel->info('xxxx');

        $this->assertSame(['invocation-id' => 'expected-id'], $context);
    }

    public function testItSharesContextWithFreshlyResolvedChannels()
    {
        $manager = new LogManager($this->app);
        $context = null;

        $manager->shareContext([
            'invocation-id' => 'expected-id',
        ]);
        $manager->channel('single')->listen(function ($message) use (&$context) {
            $context = $message->context;
        });
        $manager->channel('single')->info('xxxx');

        $this->assertSame(['invocation-id' => 'expected-id'], $context);
    }

    public function testContextCanBePubliclyAccessedByOtherLoggingSystems()
    {
        $manager = new LogManager($this->app);
        $context = null;

        $manager->shareContext([
            'invocation-id' => 'expected-id',
        ]);

        $this->assertSame($manager->sharedContext(), ['invocation-id' => 'expected-id']);
    }

    public function testItSharesContextWithStacksWhenTheyAreResolved()
    {
        $manager = new LogManager($this->app);
        $context = null;

        $manager->shareContext([
            'invocation-id' => 'expected-id',
        ]);
        $stack = $manager->stack(['single']);
        $stack->listen(function ($message) use (&$context) {
            $context = $message->context;
        });
        $stack->info('xxxx');

        $this->assertSame(['invocation-id' => 'expected-id'], $context);
    }

    public function testItMergesSharedContextRatherThanReplacing()
    {
        $manager = new LogManager($this->app);
        $context = null;

        $manager->shareContext([
            'invocation-id' => 'expected-id',
        ]);
        $manager->shareContext([
            'invocation-start' => 1651800456,
        ]);
        $manager->channel('single')->listen(function ($message) use (&$context) {
            $context = $message->context;
        });
        $manager->channel('single')->info('xxxx', [
            'logged' => 'context',
        ]);

        $this->assertSame([
            'invocation-id' => 'expected-id',
            'invocation-start' => 1651800456,
            'logged' => 'context',
        ], $context);
        $this->assertSame([
            'invocation-id' => 'expected-id',
            'invocation-start' => 1651800456,
        ], $manager->sharedContext());
    }

    public function testFlushSharedContext()
    {
        $manager = new LogManager($this->app);

        $manager->shareContext($context = ['foo' => 'bar']);

        $this->assertSame($context, $manager->sharedContext());

        $manager->flushSharedContext();

        $this->assertEmpty($manager->sharedContext());
    }

    public function testLogManagerCreateCustomFormatterWithTap()
    {
        $config = $this->app['config'];
        $config->set('logging.channels.custom', [
            'driver' => 'single',
            'tap' => [CustomizeFormatter::class],
        ]);

        $manager = new LogManager($this->app);

        $logger = $manager->channel('custom');
        $handler = $logger->getLogger()->getHandlers()[0];
        $formatter = $handler->getFormatter();

        $this->assertInstanceOf(LineFormatter::class, $formatter);

        $format = new ReflectionProperty(get_class($formatter), 'format');

        $this->assertEquals(
            '[%datetime%] %channel%.%level_name%: %message% %context% %extra%',
            rtrim($format->getValue($formatter)));
    }

    public function testDriverUsersPsrLoggerManagerReturnsLogger()
    {
        // Given
        $config = $this->app['config'];
        $config->set('logging.channels.spy', [
            'driver' => 'spy',
        ]);

        $manager = new LogManager($this->app);

        $loggerSpy = new LoggerSpy();
        $manager->extend('spy', fn () => $loggerSpy);

        // When
        $logger = $manager->channel('spy');
        $logger->alert('some alert');

        // Then
        $this->assertCount(1, $loggerSpy->logs);
        $this->assertEquals('some alert', $loggerSpy->logs[0]['message']);
    }
}


lic function testRouteMacro()
    {
        $router = $this->getRouter();

        Route::macro('breadcrumb', function ($breadcrumb) {
            $this->action['breadcrumb'] = $breadcrumb;

            return $this;
        });

        $router->get('foo', function () {
            return 'bar';
        })->breadcrumb('fooBreadcrumb')->name('foo');

        $router->getRoutes()->refreshNameLookups();

        $this->assertSame('fooBreadcrumb', $router->getRoutes()->getByName('foo')->getAction()['breadcrumb']);
    }

    public function testClassesCanBeInjectedIntoRoutes()
    {
        unset($_SERVER['__test.route_inject']);
        $router = $this->getRouter();
        $router->get('foo/{var}', function (stdClass $foo, $var) {
            $_SERVER['__test.route_inject'] = func_get_args();

            return 'hello';
        });

        $this->assertSame('hello', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent());
        $this->assertInstanceOf(stdClass::class, $_SERVER['__test.route_inject'][0]);
        $this->assertSame('bar', $_SERVER['__test.route_inject'][1]);

        unset($_SERVER['__test.route_inject']);
    }

    public function testNullValuesCanBeInjectedIntoRoutes()
    {
        $container = new Container;
        $router = new Router(new Dispatcher, $container);
        $container->instance(Registrar::class, $router);

        $container->bind(RoutingTestUserModel::class, function () {
        });
        $container->bind(CallableDispatcherContract::class, fn ($app) => new CallableDispatcher($app));

        $router->get('foo/{team}/{post}', [
            'middleware' => SubstituteBindings::class,
            'uses' => function (?RoutingTestUserModel $userFromContainer, RoutingTestTeamModel $team, $postId) {
                $this->assertNull($userFromContainer);
                $this->assertInstanceOf(RoutingTestTeamModel::class, $team);
                $this->assertSame('bar', $team->value);
                $this->assertSame('baz', $postId);
            },
        ]);
        $router->dispatch(Request::create('foo/bar/baz', 'GET'))->getContent();
    }

    public function testOptionsResponsesAreGeneratedByDefault()
    {
        $router = $this->getRouter();
        $router->get('foo/bar', function () {
            return 'hello';
        });
        $router->post('foo/bar', function () {
            return 'hello';
        });
        $response = $router->dispatch(Request::create('foo/bar', 'OPTIONS'));

        $this->assertEquals(200, $response->getStatusCode());
        $this->assertSame('GET,HEAD,POST', $response->headers->get('Allow'));
    }

    public function testHeadDispatcher()
    {
        $router = $this->getRouter();
        $router->match(['GET', 'POST'], 'foo', function () {
            return 'bar';
        });

        $response = $router->dispatch(Request::create('foo', 'OPTIONS'));
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertSame('GET,HEAD,POST', $response->headers->get('Allow'));

        $response = $router->dispatch(Request::create('foo', 'HEAD'));
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertEmpty($response->getContent());

        $router = $this->getRouter();
        $router->match(['GET'], 'foo', function () {
            return 'bar';
        });

        $response = $router->dispatch(Request::create('foo', 'OPTIONS'));
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertSame('GET,HEAD', $response->headers->get('Allow'));

        $router = $this->getRouter();
        $router->match(['POST'], 'foo', function () {
            return 'bar';
        });

        $response = $router->dispatch(Request::create('foo', 'OPTIONS'));
        $this->assertEquals(200, $response->getStatusCode());
        $this->assertSame('POST', $response->headers->get('Allow'));
    }

    public function testNonGreedyMatches()
    {
        $route = new Route('GET', 'images/{id}.{ext}', function () {
            //
        });

        $request1 = Request::create('images/1.png', 'GET');
        $this->assertTrue($route->matches($request1));
        $route->bind($request1);
        $this->assertTrue($route->hasParameter('id'));
        $this->assertFalse($route->hasParameter('foo'));
        $this->assertSame('1', (string) $route->parameter('id'));
        $this->assertSame('png', $route->parameter('ext'));

        $request2 = Request::create('images/12.png', 'GET');
        $this->assertTrue($route->matches($request2));
        $route->bind($request2);
        $this->assertSame('12', $route->parameter('id'));
        $this->assertSame('png', $route->parameter('ext'));

        // Test parameter() default value
        $route = new Route('GET', 'foo/{foo?}', function () {
            //
        });

        $request3 = Request::create('foo', 'GET');
        $this->assertTrue($route->matches($request3));
        $route->bind($request3);
        $this->assertSame('bar', $route->parameter('foo', 'bar'));
    }

    public function testHasParameters()
    {
        $route = new Route('GET', 'images/{id}.{ext}', function () {
            //
        });
        $request1 = Request::create('images/1.png', 'GET');
        $this->assertFalse($route->hasParameters());
        $this->assertTrue($route->matches($request1));
        $route->bind($request1);
        $this->assertTrue($route->hasParameters());
    }

    public function testForgetParameter()
    {
        $route = new Route('GET', 'images/{id}.{ext}', function () {
            //
        });
        $request1 = Request::create('images/1.png', 'GET');
        $route->bind($request1);
        $this->assertTrue($route->hasParameter('id'));
        $this->assertTrue($route->hasParameter('ext'));
        $route->forgetParameter('id');
        $this->assertFalse($route->hasParameter('id'));
        $this->assertTrue($route->hasParameter('ext'));
    }

    public function testParameterNames()
    {
        $route = new Route('GET', 'images/{id}.{ext}', function () {
            //
        });
        $this->assertSame(['id', 'ext'], $route->parameterNames());

        $route = new Route('GET', 'foo/{bar?}', function () {
            //
        });
        $this->assertSame(['bar'], $route->parameterNames());

        $route = new Route('GET', '/', function () {
            //
        });
        $this->assertSame([], $route->parameterNames());
    }

    public function testParametersWithoutNulls()
    {
        $route = new Route('GET', 'users/{id?}/{name?}/', function () {
            //
        });
        $request1 = Request::create('users/12/amir', 'GET');
        $route->bind($request1);
        $this->assertSame(['id' => '12', 'name' => 'amir'], $route->parametersWithoutNulls());

        $route = new Route('GET', 'users/{id?}/{name?}/', function () {
            //
        });
        $request1 = Request::create('users/12', 'GET');
        $route->bind($request1);
        $this->assertSame(['id' => '12'], $route->parametersWithoutNulls());

        $route = new Route('GET', 'users/{id?}/{name?}/', function () {
            //
        });
        $request1 = Request::create('users/', 'GET');
        $route->bind($request1);
        $this->assertSame([], $route->parametersWithoutNulls());
    }

    public function testRouteParametersDefaultValue()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar?}', ['uses' => RouteTestControllerWithParameterStub::class.'@returnParameter'])->defaults('bar', 'foo');
        $this->assertSame('foo', $router->dispatch(Request::create('foo', 'GET'))->getContent());

        $router->get('foo/{bar?}', ['uses' => RouteTestControllerWithParameterStub::class.'@returnParameter'])->defaults('bar', 'foo');
        $this->assertSame('bar', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent());

        $router->get('foo/{bar?}', function ($bar = '') {
            return $bar;
        })->defaults('bar', 'foo');
        $this->assertSame('foo', $router->dispatch(Request::create('foo', 'GET'))->getContent());
    }


lic function testControllerCallActionMethodParameters()
    {
        $router = $this->getRouter();

        // Has one argument but receives two
        unset($_SERVER['__test.controller_callAction_parameters']);
        $router->get(($str = Str::random()).'/{one}/{two}', RouteTestAnotherControllerWithParameterStub::class.'@oneArgument');
        $router->dispatch(Request::create($str.'/one/two', 'GET'));
        $this->assertEquals(['one' => 'one', 'two' => 'two'], $_SERVER['__test.controller_callAction_parameters']);

        // Has two arguments and receives two
        unset($_SERVER['__test.controller_callAction_parameters']);
        $router->get(($str = Str::random()).'/{one}/{two}', RouteTestAnotherControllerWithParameterStub::class.'@twoArguments');
        $router->dispatch(Request::create($str.'/one/two', 'GET'));
        $this->assertEquals(['one' => 'one', 'two' => 'two'], $_SERVER['__test.controller_callAction_parameters']);

        // Has two arguments but with different names from the ones passed from the route
        unset($_SERVER['__test.controller_callAction_parameters']);
        $router->get(($str = Str::random()).'/{one}/{two}', RouteTestAnotherControllerWithParameterStub::class.'@differentArgumentNames');
        $router->dispatch(Request::create($str.'/one/two', 'GET'));
        $this->assertEquals(['one' => 'one', 'two' => 'two'], $_SERVER['__test.controller_callAction_parameters']);

        // Has two arguments with same name but argument order is reversed
        unset($_SERVER['__test.controller_callAction_parameters']);
        $router->get(($str = Str::random()).'/{one}/{two}', RouteTestAnotherControllerWithParameterStub::class.'@reversedArguments');
        $router->dispatch(Request::create($str.'/one/two', 'GET'));
        $this->assertEquals(['one' => 'one', 'two' => 'two'], $_SERVER['__test.controller_callAction_parameters']);

        // No route parameters while method has parameters
        unset($_SERVER['__test.controller_callAction_parameters']);
        $router->get(($str = Str::random()).'', RouteTestAnotherControllerWithParameterStub::class.'@oneArgument');
        $router->dispatch(Request::create($str, 'GET'));
        $this->assertEquals([], $_SERVER['__test.controller_callAction_parameters']);

        // With model bindings
        unset($_SERVER['__test.controller_callAction_parameters']);
        $router->get(($str = Str::random()).'/{user}/{defaultNull?}/{team?}', [
            'middleware' => SubstituteBindings::class,
            'uses' => RouteTestAnotherControllerWithParameterStub::class.'@withModels',
        ]);
        $router->dispatch(Request::create($str.'/1', 'GET'));

        $values = array_values($_SERVER['__test.controller_callAction_parameters']);

        $this->assertInstanceOf(Request::class, $values[0]);
        $this->assertEquals(1, $values[1]->value);
        $this->assertNull($values[2]);
        $this->assertNull($values[3]);
    }

    public function testLeadingParamDoesntReceiveForwardSlashOnEmptyPath()
    {
        $router = $this->getRouter();
        $outer_one = 'abc1234'; // a string that is not one we're testing
        $router->get('{one?}', [
            'uses' => function ($one = null) use (&$outer_one) {
                $outer_one = $one;

                return $one;
            },
            'where' => ['one' => '(.+)'],
        ]);

        $this->assertSame('', $router->dispatch(Request::create(''))->getContent());
        $this->assertNull($outer_one);
        // Expects: '' ($one === null)
        // Actual: '/' ($one === '/')

        $this->assertSame('foo', $router->dispatch(Request::create('/foo', 'GET'))->getContent());
        $this->assertSame('foo/bar/baz', $router->dispatch(Request::create('/foo/bar/baz', 'GET'))->getContent());
    }

    public function testRoutesDontMatchNonMatchingPathsWithLeadingOptionals()
    {
        $this->expectException(NotFoundHttpException::class);

        $router = $this->getRouter();
        $router->get('{baz?}', function ($age = 25) {
            return $age;
        });
        $this->assertSame('25', $router->dispatch(Request::create('foo/bar', 'GET'))->getContent());
    }

    public function testRoutesDontMatchNonMatchingDomain()
    {
        $this->expectException(NotFoundHttpException::class);

        $router = $this->getRouter();
        $router->get('foo/bar', ['domain' => 'api.foo.bar', function () {
            return 'hello';
        }]);
        $this->assertSame('hello', $router->dispatch(Request::create('http://api.baz.boom/foo/bar', 'GET'))->getContent());
    }

    public function testRouteDomainRegistration()
    {
        $router = $this->getRouter();
        $router->get('/foo/bar')->domain('api.foo.bar')->uses(function () {
            return 'hello';
        });
        $this->assertSame('hello', $router->dispatch(Request::create('http://api.foo.bar/foo/bar', 'GET'))->getContent());
    }

    public function testMatchesMethodAgainstRequests()
    {
        /*
         * Basic
         */
        $request = Request::create('foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', function () {
            //
        });
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/bar', 'GET');
        $route = new Route('GET', 'foo', function () {
            //
        });
        $this->assertFalse($route->matches($request));

        /*
         * Method checks
         */
        $request = Request::create('foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', function () {
            //
        });
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/bar', 'POST');
        $route = new Route('GET', 'foo', function () {
            //
        });
        $this->assertFalse($route->matches($request));

        /*
         * Domain checks
         */
        $request = Request::create('http://something.foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['domain' => '{foo}.foo.com', function () {
            //
        }]);
        $this->assertTrue($route->matches($request));

        $request = Request::create('http://something.bar.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['domain' => '{foo}.foo.com', function () {
            //
        }]);
        $this->assertFalse($route->matches($request));

        /*
         * HTTPS checks
         */
        $request = Request::create('https://foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['https', function () {
            //
        }]);
        $this->assertTrue($route->matches($request));

        $request = Request::create('https://foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['https', 'baz' => true, function () {
            //
        }]);
        $this->assertTrue($route->matches($request));

        $request = Request::create('http://foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['https', function () {
            //
        }]);
        $this->assertFalse($route->matches($request));

        /*
         * HTTP checks
         */
        $request = Request::create('https://foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['http', function () {
            //
        }]);
        $this->assertFalse($route->matches($request));

        $request = Request::create('http://foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['http', function () {
            //
        }]);
        $this->assertTrue($route->matches($request));

        $request = Request::create('http://foo.com/foo/bar', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['baz' => true, function () {
            //
        }]);
        $this->assertTrue($route->matches($request));
    }


lic function testWherePatternsProperlyFilter()
    {
        $request = Request::create('foo/123', 'GET');
        $route = new Route('GET', 'foo/{bar}', function () {
            //
        });
        $route->where('bar', '[0-9]+');
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/123abc', 'GET');
        $route = new Route('GET', 'foo/{bar}', function () {
            //
        });
        $route->where('bar', '[0-9]+');
        $this->assertFalse($route->matches($request));

        $request = Request::create('foo/123abc', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['where' => ['bar' => '[0-9]+'], function () {
            //
        }]);
        $route->where('bar', '[0-9]+');
        $this->assertFalse($route->matches($request));

        $request = Request::create('foo/123', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['where' => ['bar' => '123|456'], function () {
            //
        }]);
        $route->where('bar', '123|456');
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/123abc', 'GET');
        $route = new Route('GET', 'foo/{bar}', ['where' => ['bar' => '123|456'], function () {
            //
        }]);
        $route->where('bar', '123|456');
        $this->assertFalse($route->matches($request));

        /*
         * Optional
         */
        $request = Request::create('foo/123', 'GET');
        $route = new Route('GET', 'foo/{bar?}', function () {
            //
        });
        $route->where('bar', '[0-9]+');
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/123', 'GET');
        $route = new Route('GET', 'foo/{bar?}', ['where' => ['bar' => '[0-9]+'], function () {
            //
        }]);
        $route->where('bar', '[0-9]+');
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/123', 'GET');
        $route = new Route('GET', 'foo/{bar?}/{baz?}', function () {
            //
        });
        $route->where('bar', '[0-9]+');
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/123/foo', 'GET');
        $route = new Route('GET', 'foo/{bar?}/{baz?}', function () {
            //
        });
        $route->where('bar', '[0-9]+');
        $this->assertTrue($route->matches($request));

        $request = Request::create('foo/123abc', 'GET');
        $route = new Route('GET', 'foo/{bar?}', function () {
            //
        });
        $route->where('bar', '[0-9]+');
        $this->assertFalse($route->matches($request));
    }

    public function testRoutePrefixParameterParsing()
    {
        $route = new Route('GET', '/foo', ['prefix' => 'profiles/{user:username}/portfolios', 'uses' => function () {
            //
        }]);

        $this->assertSame('profiles/{user}/portfolios/foo', $route->uri());
    }

    public function testDotDoesNotMatchEverything()
    {
        $route = new Route('GET', 'images/{id}.{ext}', function () {
            //
        });

        $request1 = Request::create('images/1.png', 'GET');
        $this->assertTrue($route->matches($request1));
        $route->bind($request1);
        $this->assertSame('1', (string) $route->parameter('id'));
        $this->assertSame('png', $route->parameter('ext'));

        $request2 = Request::create('images/12.png', 'GET');
        $this->assertTrue($route->matches($request2));
        $route->bind($request2);
        $this->assertSame('12', $route->parameter('id'));
        $this->assertSame('png', $route->parameter('ext'));
    }

    public function testRouteBinding()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->bind('bar', function ($value) {
            return strtoupper($value);
        });
        $this->assertSame('TAYLOR', $router->dispatch(Request::create('foo/taylor', 'GET'))->getContent());
    }

    public function testRouteClassBinding()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->bind('bar', RouteBindingStub::class);
        $this->assertSame('TAYLOR', $router->dispatch(Request::create('foo/taylor', 'GET'))->getContent());
    }

    public function testRouteClassMethodBinding()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->bind('bar', RouteBindingStub::class.'@find');
        $this->assertSame('dragon', $router->dispatch(Request::create('foo/Dragon', 'GET'))->getContent());
    }

    public function testMiddlewarePrioritySorting()
    {
        $middleware = [
            Placeholder1::class,
            SubstituteBindings::class,
            Placeholder2::class,
            Authenticate::class,
            ExampleMiddleware::class,
            Placeholder3::class,
        ];

        $router = $this->getRouter();

        $router->middlewarePriority = [ExampleMiddlewareContract::class, Authenticate::class, SubstituteBindings::class, Authorize::class];

        $route = $router->get('foo', ['middleware' => $middleware, 'uses' => function ($name) {
            return $name;
        }]);

        $this->assertEquals([
            Placeholder1::class,
            ExampleMiddleware::class,
            Authenticate::class,
            SubstituteBindings::class,
            Placeholder2::class,
            Placeholder3::class,
        ], $router->gatherRouteMiddleware($route));
    }

    public function testModelBinding()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->model('bar', RouteModelBindingStub::class);
        $this->assertSame('TAYLOR', $router->dispatch(Request::create('foo/taylor', 'GET'))->getContent());
    }

    public function testModelBindingWithNullReturn()
    {
        $this->expectException(ModelNotFoundException::class);
        $this->expectExceptionMessage('No query results for model [Illuminate\Tests\Routing\RouteModelBindingNullStub].');

        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->model('bar', RouteModelBindingNullStub::class);
        $router->dispatch(Request::create('foo/taylor', 'GET'))->getContent();
    }

    public function testModelBindingWithCustomNullReturn()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->model('bar', RouteModelBindingNullStub::class, function () {
            return 'missing';
        });
        $this->assertSame('missing', $router->dispatch(Request::create('foo/taylor', 'GET'))->getContent());
    }

    public function testModelBindingWithBindingClosure()
    {
        $router = $this->getRouter();
        $router->get('foo/{bar}', ['middleware' => SubstituteBindings::class, 'uses' => function ($name) {
            return $name;
        }]);
        $router->model('bar', RouteModelBindingNullStub::class, function ($value) {
            return (new RouteModelBindingClosureStub)->findAlternate($value);
        });
        $this->assertSame('tayloralt', $router->dispatch(Request::create('foo/TAYLOR', 'GET'))->getContent());
    }


lic function testResourceRouting()
    {
        $router = $this->getRouter();
        $router->resource('foo', 'FooController');
        $routes = $router->getRoutes();
        $this->assertCount(7, $routes);

        $router = $this->getRouter();
        $router->resource('foo', 'FooController', ['only' => ['update']]);
        $routes = $router->getRoutes();

        $this->assertCount(1, $routes);

        $router = $this->getRouter();
        $router->resource('foo', 'FooController', ['only' => ['show', 'destroy']]);
        $routes = $router->getRoutes();

        $this->assertCount(2, $routes);

        $router = $this->getRouter();
        $router->resource('foo', 'FooController', ['except' => ['show', 'destroy']]);
        $routes = $router->getRoutes();

        $this->assertCount(5, $routes);

        $router = $this->getRouter();
        $router->resource('foo-bars', 'FooController', ['only' => ['show']]);
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foo-bars/{foo_bar}', $routes[0]->uri());

        $router = $this->getRouter();
        $router->resource('foo-bar.foo-baz', 'FooController', ['only' => ['show']]);
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foo-bar/{foo_bar}/foo-baz/{foo_baz}', $routes[0]->uri());

        $router = $this->getRouter();
        $router->resource('foo-bars', 'FooController', ['only' => ['show'], 'as' => 'prefix']);
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foo-bars/{foo_bar}', $routes[0]->uri());
        $this->assertSame('prefix.foo-bars.show', $routes[0]->getName());

        ResourceRegistrar::verbs([
            'create' => 'ajouter',
            'edit' => 'modifier',
        ]);
        $router = $this->getRouter();
        $router->resource('foo', 'FooController');
        $routes = $router->getRoutes();

        $this->assertSame('foo/ajouter', $routes->getByName('foo.create')->uri());
        $this->assertSame('foo/{foo}/modifier', $routes->getByName('foo.edit')->uri());
    }

    public function testResourceRoutingParameters()
    {
        ResourceRegistrar::singularParameters();

        $router = $this->getRouter();
        $router->resource('foos', 'FooController');
        $router->resource('foos.bars', 'FooController');
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foos/{foo}', $routes[3]->uri());
        $this->assertSame('foos/{foo}/bars/{bar}', $routes[10]->uri());

        ResourceRegistrar::setParameters(['foos' => 'oof', 'bazs' => 'b']);

        $router = $this->getRouter();
        $router->resource('bars.foos.bazs', 'FooController');
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('bars/{bar}/foos/{oof}/bazs/{b}', $routes[3]->uri());

        ResourceRegistrar::setParameters();
        ResourceRegistrar::singularParameters(false);

        $router = $this->getRouter();
        $router->resource('foos', 'FooController', ['parameters' => 'singular']);
        $router->resource('foos.bars', 'FooController')->parameters('singular');
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foos/{foo}', $routes[3]->uri());
        $this->assertSame('foos/{foo}/bars/{bar}', $routes[10]->uri());

        $router = $this->getRouter();
        $router->resource('foos.bars', 'FooController', ['parameters' => ['foos' => 'foo', 'bars' => 'bar']]);
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foos/{foo}/bars/{bar}', $routes[3]->uri());

        $router = $this->getRouter();
        $router->resource('foos.bars', 'FooController')->parameter('foos', 'foo')->parameter('bars', 'bar');
        $routes = $router->getRoutes();
        $routes = $routes->getRoutes();

        $this->assertSame('foos/{foo}/bars/{bar}', $routes[3]->uri());
    }

    public function testResourceRouteNaming()
    {
        $router = $this->getRouter();
        $router->resource('foo', 'FooController');

        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.index'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.show'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.create'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.store'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.edit'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.update'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.destroy'));

        $router = $this->getRouter();
        $router->resource('foo.bar', 'FooController');

        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.index'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.show'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.create'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.store'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.edit'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.update'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.destroy'));

        $router = $this->getRouter();
        $router->resource('prefix/foo.bar', 'FooController');

        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.index'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.show'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.create'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.store'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.edit'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.update'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo.bar.destroy'));

        $router = $this->getRouter();
        $router->resource('foo', 'FooController', ['names' => [
            'index' => 'foo',
            'show' => 'bar',
        ]]);

        $this->assertTrue($router->getRoutes()->hasNamedRoute('foo'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar'));

        $router = $this->getRouter();
        $router->resource('foo', 'FooController', ['names' => 'bar']);

        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.index'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.show'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.create'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.store'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.edit'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.update'));
        $this->assertTrue($router->getRoutes()->hasNamedRoute('bar.destroy'));
    }


nction testControllerRoutesWithADefaultNamespace()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $url->setRootControllerNamespace('namespace');

        /*
         * Controller Route Route
         */
        $route = new Route(['GET'], 'foo/bar', ['controller' => 'namespace\foo@bar']);
        $routes->add($route);

        $route = new Route(['GET'], 'something/else', ['controller' => 'something\foo@bar']);
        $routes->add($route);

        $route = new Route(['GET'], 'foo/invoke', ['controller' => 'namespace\InvokableActionStub']);
        $routes->add($route);

        $this->assertSame('http://www.foo.com/foo/bar', $url->action('foo@bar'));
        $this->assertSame('http://www.foo.com/something/else', $url->action('\something\foo@bar'));
        $this->assertSame('http://www.foo.com/foo/invoke', $url->action('InvokableActionStub'));
    }

    public function testControllerRoutesOutsideOfDefaultNamespace()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $url->setRootControllerNamespace('namespace');

        $route = new Route(['GET'], 'root/namespace', ['controller' => '\root\namespace@foo']);
        $routes->add($route);

        $route = new Route(['GET'], 'invokable/namespace', ['controller' => '\root\namespace\InvokableActionStub']);
        $routes->add($route);

        $this->assertSame('http://www.foo.com/root/namespace', $url->action('\root\namespace@foo'));
        $this->assertSame('http://www.foo.com/invokable/namespace', $url->action('\root\namespace\InvokableActionStub'));
    }

    public function testRoutableInterfaceRouting()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $route = new Route(['GET'], 'foo/{bar}', ['as' => 'routable']);
        $routes->add($route);

        $model = new RoutableInterfaceStub;
        $model->key = 'routable';

        $this->assertSame('/foo/routable', $url->route('routable', [$model], false));
    }

    public function testRoutableInterfaceRoutingWithCustomBindingField()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $route = new Route(['GET'], 'foo/{bar:slug}', ['as' => 'routable']);
        $routes->add($route);

        $model = new RoutableInterfaceStub;
        $model->key = 'routable';

        $this->assertSame('/foo/test-slug', $url->route('routable', ['bar' => $model], false));
        $this->assertSame('/foo/test-slug', $url->route('routable', [$model], false));
    }

    public function testRoutableInterfaceRoutingAsQueryString()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $route = new Route(['GET'], 'foo', ['as' => 'query-string']);
        $routes->add($route);

        $model = new RoutableInterfaceStub;
        $model->key = 'routable';

        $this->assertSame('/foo?routable', $url->route('query-string', $model, false));
        $this->assertSame('/foo?routable', $url->route('query-string', [$model], false));
        $this->assertSame('/foo?foo=routable', $url->route('query-string', ['foo' => $model], false));
    }

    /**
     * @todo Fix bug related to route keys
     *
     * @link https://github.com/laravel/framework/pull/42425
     */
    public function testRoutableInterfaceRoutingWithSeparateBindingFieldOnlyForSecondParameter()
    {
        $this->markTestSkipped('See https://github.com/laravel/framework/pull/43255');

        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $route = new Route(['GET'], 'foo/{bar}/{baz:slug}', ['as' => 'routable']);
        $routes->add($route);

        $model1 = new RoutableInterfaceStub;
        $model1->key = 'routable-1';

        $model2 = new RoutableInterfaceStub;
        $model2->key = 'routable-2';

        $this->assertSame('/foo/routable-1/test-slug', $url->route('routable', ['bar' => $model1, 'baz' => $model2], false));
        $this->assertSame('/foo/routable-1/test-slug', $url->route('routable', [$model1, $model2], false));
    }

    public function testRoutableInterfaceRoutingWithSingleParameter()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $route = new Route(['GET'], 'foo/{bar}', ['as' => 'routable']);
        $routes->add($route);

        $model = new RoutableInterfaceStub;
        $model->key = 'routable';

        $this->assertSame('/foo/routable', $url->route('routable', $model, false));
    }

    public function testRoutesMaintainRequestScheme()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('https://www.foo.com/')
        );

        /*
         * Named Routes
         */
        $route = new Route(['GET'], 'foo/bar', ['as' => 'foo']);
        $routes->add($route);

        $this->assertSame('https://www.foo.com/foo/bar', $url->route('foo'));
    }

    public function testHttpOnlyRoutes()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('https://www.foo.com/')
        );

        /*
         * Named Routes
         */
        $route = new Route(['GET'], 'foo/bar', ['as' => 'foo', 'http']);
        $routes->add($route);

        $this->assertSame('http://www.foo.com/foo/bar', $url->route('foo'));
    }

    public function testRoutesWithDomains()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com/')
        );

        $route = new Route(['GET'], 'foo/bar', ['as' => 'foo', 'domain' => 'sub.foo.com']);
        $routes->add($route);

        /*
         * Wildcards & Domains...
         */
        $route = new Route(['GET'], 'foo/bar/{baz}', ['as' => 'bar', 'domain' => 'sub.{foo}.com']);
        $routes->add($route);

        $this->assertSame('http://sub.foo.com/foo/bar', $url->route('foo'));
        $this->assertSame('http://sub.taylor.com/foo/bar/otwell', $url->route('bar', ['taylor', 'otwell']));
        $this->assertSame('/foo/bar/otwell', $url->route('bar', ['taylor', 'otwell'], false));
    }

    public function testRoutesWithDomainsAndPorts()
    {
        $url = new UrlGenerator(
            $routes = new RouteCollection,
            Request::create('http://www.foo.com:8080/')
        );

        $route = new Route(['GET'], 'foo/bar', ['as' => 'foo', 'domain' => 'sub.foo.com']);
        $routes->add($route);

        /*
         * Wildcards & Domains...
         */
        $route = new Route(['GET'], 'foo/bar/{baz}', ['as' => 'bar', 'domain' => 'sub.{foo}.com']);
        $routes->add($route);

        $this->assertSame('http://sub.foo.com:8080/foo/bar', $url->route('foo'));
        $this->assertSame('http://sub.taylor.com:8080/foo/bar/otwell', $url->route('bar', ['taylor', 'otwell']));
    }

    pub


public function testCanRegisterGroupWithMiddleware()
    {
        $this->router->middleware('group-middleware')->group(function ($router) {
            $router->get('users', function () {
                return 'all-users';
            });
        });

        $this->seeResponse('all-users', Request::create('users', 'GET'));
        $this->seeMiddleware('group-middleware');
    }

    public function testCanRegisterGroupWithoutMiddleware()
    {
        $this->router->withoutMiddleware('one')->group(function ($router) {
            $router->get('users', function () {
                return 'all-users';
            })->middleware(['one', 'two']);
        });

        $this->seeResponse('all-users', Request::create('users', 'GET'));
        $this->assertEquals(['one'], $this->getRoute()->excludedMiddleware());
    }

    public function testCanRegisterGroupWithStringableMiddleware()
    {
        $one = new class implements Stringable
        {
            public function __toString()
            {
                return 'one';
            }
        };

        $this->router->middleware($one)->group(function ($router) {
            $router->get('users', function () {
                return 'all-users';
            });
        });

        $this->seeResponse('all-users', Request::create('users', 'GET'));
        $this->seeMiddleware('one');
    }

    public function testCanRegisterGroupWithNamespace()
    {
        $this->router->namespace('App\Http\Controllers')->group(function ($router) {
            $router->get('users', 'UsersController@index');
        });

        $this->assertSame(
            'App\Http\Controllers\UsersController@index',
            $this->getRoute()->getAction()['uses']
        );
    }

    public function testCanRegisterGroupWithPrefix()
    {
        $this->router->prefix('api')->group(function ($router) {
            $router->get('users', 'UsersController@index');
        });

        $this->assertSame('api/users', $this->getRoute()->uri());
    }

    public function testCanRegisterGroupWithPrefixAndWhere()
    {
        $this->router->prefix('foo/{bar}')->where(['bar' => '[0-9]+'])->group(function ($router) {
            $router->get('here', function () {
                return 'good';
            });
        });

        $this->seeResponse('good', Request::create('foo/12345/here', 'GET'));
    }

    public function testCanRegisterGroupWithNamePrefix()
    {
        $this->router->name('api.')->group(function ($router) {
            $router->get('users', 'UsersController@index')->name('users');
        });

        $this->assertSame('api.users', $this->getRoute()->getName());
    }

    public function testCanRegisterGroupWithDomain()
    {
        $this->router->domain('{account}.myapp.com')->group(function ($router) {
            $router->get('users', 'UsersController@index');
        });

        $this->assertSame('{account}.myapp.com', $this->getRoute()->getDomain());
    }

    public function testCanRegisterGroupWithDomainAndNamePrefix()
    {
        $this->router->domain('{account}.myapp.com')->name('api.')->group(function ($router) {
            $router->get('users', 'UsersController@index')->name('users');
        });

        $this->assertSame('{account}.myapp.com', $this->getRoute()->getDomain());
        $this->assertSame('api.users', $this->getRoute()->getName());
    }

    public function testCanRegisterGroupWithController()
    {
        $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) {
            $router->get('users', 'index');
        });

        $this->assertSame(
            RouteRegistrarControllerStub::class.'@index',
            $this->getRoute()->getAction()['uses']
        );
    }

    public function testCanOverrideGroupControllerWithStringSyntax()
    {
        $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) {
            $router->get('users', 'UserController@index');
        });

        $this->assertSame(
            'UserController@index',
            $this->getRoute()->getAction()['uses']
        );
    }

    public function testCanOverrideGroupControllerWithClosureSyntax()
    {
        $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) {
            $router->get('users', function () {
                return 'hello world';
            });
        });

        $this->seeResponse('hello world', Request::create('users', 'GET'));
    }

    public function testCanOverrideGroupControllerWithInvokableControllerSyntax()
    {
        $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) {
            $router->get('users', InvokableRouteRegistrarControllerStub::class);
        });

        $this->assertSame(
            InvokableRouteRegistrarControllerStub::class.'@__invoke',
            $this->getRoute()->getAction()['uses']
        );
    }

    public function testWillUseTheLatestGroupController()
    {
        $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) {
            $router->group(['controller' => FooController::class], function ($router) {
                $router->get('users', 'index');
            });
        });

        $this->assertSame(
            FooController::class.'@index',
            $this->getRoute()->getAction()['uses']
        );
    }

    public function testCanOverrideGroupControllerWithArraySyntax()
    {
        $this->router->controller(RouteRegistrarControllerStub::class)->group(function ($router) {
            $router->get('users', [FooController::class, 'index']);
        });

        $this->assertSame(
            FooController::class.'@index',
            $this->getRoute()->getAction()['uses']
        );
    }

    public function testRouteGroupingWithoutPrefix()
    {
        $this->router->group([], function ($router) {
            $router->prefix('bar')->get('baz', ['as' => 'baz', function () {
                return 'hello';
            }]);
        });
        $this->seeResponse('hello', Request::create('bar/baz', 'GET'));
    }

    public function testRouteGroupChaining()
    {
        $this->router
            ->group([], function ($router) {
                $router->get('foo', function () {
                    return 'hello';
                });
            })
            ->group([], function ($router) {
                $router->get('bar', function () {
                    return 'goodbye';
                });
            });

        $routeCollection = $this->router->getRoutes();

        $this->assertInstanceOf(\Illuminate\Routing\Route::class, $routeCollection->match(Request::create('foo', 'GET')));
        $this->assertInstanceOf(\Illuminate\Routing\Route::class, $routeCollection->match(Request::create('bar', 'GET')));
    }

    public function testRegisteringNonApprovedAttributesThrows()
    {
        $this->expectException(BadMethodCallException::class);
        $this->expectExceptionMessage('Method Illuminate\Routing\RouteRegistrar::unsupportedMethod does not exist.');

        $this->router->domain('foo')->unsupportedMethod('bar')->group(function ($router) {
            //
        });
    }

    public function testCanRegisterResource()
    {
        $this->router->middleware('resource-middleware')
                     ->resource('users', RouteRegistrarControllerStub::class);

        $this->seeResponse('deleted', Request::create('users/1', 'DELETE'));
        $this->seeMiddleware('resource-middleware');
    }

    public function testCanRegisterResourcesWithExceptOption()
    {
        $this->router->resources([
            'resource-one' => RouteRegistrarControllerStubOne::class,
            'resource-two' => RouteRegistrarControllerStubTwo::class,
            'resource-three' => RouteRegistrarControllerStubThree::class,
        ], ['except' => ['create', 'show']]);

        $this->assertCount(15, $this->router->getRoutes());

        foreach (['one', 'two', 'three'] as $resource) {
            $this->assertTrue($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.index'));
            $this->assertTrue($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.store'));
            $this->assertTrue($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.edit'));
            $this->assertTrue($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.update'));
            $this->assertTrue($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.destroy'));

            $this->assertFalse($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.create'));
            $this->assertFalse($this->router->getRoutes()->hasNamedRoute('resource-'.$resource.'.show'));
        }
    }