sourceUrl() ); self::assertSame( 'https://staging.example.test', $mapping->destinationUrl() ); } public function test_it_rejects_empty_mapping_urls(): void { $this->expectException( \InvalidArgumentException::class ); $this->expectExceptionMessage( 'Source and destination URLs are required.' ); new UrlMapping( '', 'https://staging.example.test' ); } public function test_it_rejects_mapping_urls_without_scheme_and_host(): void { $this->expectException( \InvalidArgumentException::class ); $this->expectExceptionMessage( 'Source and destination URLs must include a scheme and host.' ); new UrlMapping( 'https://', 'https://staging.example.test' ); } public function test_it_preserves_query_and_fragment_trailing_slashes(): void { $mapping = new UrlMapping( 'https://example.test/?redirect=/', 'https://staging.example.test/#/' ); self::assertSame( 'https://example.test?redirect=/', $mapping->sourceUrl() ); self::assertSame( 'https://staging.example.test#/', $mapping->destinationUrl() ); } public function test_it_returns_mappings_by_longest_source_url_first(): void { $short = new UrlMapping( 'https://example.test', 'https://staging.example.test' ); $long = new UrlMapping( 'https://example.test/uploads', 'https://staging.example.test/uploads' ); $collection = new UrlMappingCollection( array( $short, $long ) ); self::assertSame( array( $long, $short ), $collection->all() ); } public function test_it_rejects_non_mapping_items(): void { $this->expectException( \InvalidArgumentException::class ); $this->expectExceptionMessage( 'URL mapping collections only accept UrlMapping instances.' ); new UrlMappingCollection( array( new \stdClass() ) ); } }