@Test
    void testRouteExclusionIsEvaluatedAtLast() {
        final Route route = Route.builder()
                                 .pathPrefix(PATH)
                                 .methods(HttpMethod.GET, HttpMethod.POST)
                                 .consumes(MediaType.JSON)
                                 .produces(MediaType.JSON)
                                 .matchesHeaders("x-custom-header", Strings::isNotNullAndNotEmpty)
                                 .matchesParams("custom-param", Strings::isNotNullAndNotEmpty)
                                 .exclude(Route.builder().exact(PATH + "/excluded").build())
                                 .build();

        // Make sure that RoutingResult.empty() and RoutingResult.excluded() are different.
        assertThat(RoutingResult.empty()).isNotEqualTo(RoutingResult.excluded());

        RoutingContext ctx = withRequestHeaders(RequestHeaders.of(HttpMethod.PUT, PATH + "/bar"));
        assertThat(route.apply(ctx, false)).isEqualTo(RoutingResult.empty());
        assertThat(ctx.deferredStatusException().httpStatus()).isEqualTo(HttpStatus.METHOD_NOT_ALLOWED);

        ctx = withRequestHeaders(RequestHeaders.builder()
                                               .method(HttpMethod.POST)
                                               .path(PATH + "/bar")
                                               .contentType(MediaType.APPLICATION_XML_UTF_8)
                                               .build());
        assertThat(route.apply(ctx, false)).isEqualTo(RoutingResult.empty());
        assertThat(ctx.deferredStatusException().httpStatus()).isEqualTo(HttpStatus.UNSUPPORTED_MEDIA_TYPE);

        ctx = withRequestHeaders(
                RequestHeaders.builder()
                              .method(HttpMethod.POST)
                              .path(PATH + "/bar")
                              .add(HttpHeaderNames.ACCEPT, MediaType.APPLICATION_XML_UTF_8.toString())
                              .build());
        assertThat(route.apply(ctx, false)).isEqualTo(RoutingResult.empty());
        assertThat(ctx.deferredStatusException().httpStatus()).isEqualTo(HttpStatus.NOT_ACCEPTABLE);

        // No x-custom-header exists.
        ctx = withRequestHeaders(RequestHeaders.builder()
                                               .method(HttpMethod.POST)
                                               .path(PATH + "/bar")
                                               .contentType(MediaType.JSON)
                                               .add(HttpHeaderNames.ACCEPT, "*/*")
                                               .build());
        assertThat(route.apply(ctx, false)).isEqualTo(RoutingResult.empty());
        assertThat(ctx.deferredStatusException()).isNull();

        // No custom-param exists.
        ctx = withRequestHeaders(RequestHeaders.builder()
                                               .method(HttpMethod.POST)
                                               .path(PATH + "/bar")
                                               .contentType(MediaType.JSON)
                                               .add(HttpHeaderNames.ACCEPT, "*/*")
                                               .add("x-custom-header", "yes")
                                               .build());
        assertThat(route.apply(ctx, false)).isEqualTo(RoutingResult.empty());
        assertThat(ctx.deferredStatusException()).isNull();

        // All conditions are met.
        ctx = withRequestHeaders(RequestHeaders.builder()
                                               .method(HttpMethod.POST)
                                               .path(PATH + "/bar?custom-param=yes")
                                               .contentType(MediaType.JSON)
                                               .add(HttpHeaderNames.ACCEPT, "*/*")
                                               .add("x-custom-header", "yes")
                                               .build());
        assertThat(route.apply(ctx, false).isPresent()).isTrue();

        // All conditions are met but the request is matched with excludedRoute.
        ctx = withRequestHeaders(RequestHeaders.builder()
                                               .method(HttpMethod.POST)
                                               .path(PATH + "/excluded?custom-param=yes")
                                               .contentType(MediaType.JSON)
                                               .add(HttpHeaderNames.ACCEPT, "*/*")
                                               .add("x-custom-header", "yes")
                                               .build());
        // Note that DefaultRoute returns RoutingResult.EXCLUDED when the request is failed to route due to
        // 'excludedRoutes' configuration.
        assertThat(route.apply(ctx, false)).isEqualTo(RoutingResult.excluded());
        assertThat(ctx.deferredStatusException()).isNull();
    }
/bar");

        route = Route.builder().path("/foo", "regex:^/files/(?<filePath>.*)$").build();
        assertThat(route.pathType()).isSameAs(RoutePathType.REGEX_WITH_PREFIX);
        assertThat(route.paths()).containsExactly("^/files/(?<filePath>.*)$", "/foo/");
        assertThat(route.patternString()).isEqualTo("/foo/^/files/(?<filePath>.*)$");
    }

    @Test
    void invalidRoutePath() {
        assertThatThrownBy(() -> Route.builder().path("foo")).isInstanceOf(IllegalArgumentException.class);
        assertThatThrownBy(() -> Route.builder().path("foo:/bar")).isInstanceOf(IllegalArgumentException.class);
        assertThatThrownBy(() -> Route.builder().path("/foo/{bar}:verb1:verb2")).isInstanceOf(
                IllegalArgumentException.class);
    }

    @Test
    void notAllowSemicolon() {
        assertThatThrownBy(() -> Route.builder().path("/foo;")).isInstanceOf(
                IllegalArgumentException.class);
        assertThatThrownBy(() -> Route.builder().path("/foo/{bar};")).isInstanceOf(
                IllegalArgumentException.class);
        assertThatThrownBy(() -> Route.builder().path("/bar/:baz;")).isInstanceOf(
                IllegalArgumentException.class);
        assertThatThrownBy(() -> Route.builder().path("exact:/:foo/bar;")).isInstanceOf(
                IllegalArgumentException.class);
        assertThatThrownBy(() -> Route.builder().path("prefix:/bar/baz;")).isInstanceOf(
                IllegalArgumentException.class);
    }

    @Test
    void testHeader() {
        final Route route = Route.builder()
                                 .path(PATH)
                                 .methods(HttpMethod.GET, HttpMethod.POST)
                                 .build();

        final RoutingResult getResult = route.apply(withMethod(HttpMethod.GET), false);
        final RoutingResult postResult = route.apply(withMethod(HttpMethod.POST), false);
        assertThat(getResult.isPresent()).isTrue();
        assertThat(postResult.isPresent()).isTrue();

        assertThat(route.apply(withMethod(HttpMethod.PUT), false).isPresent()).isFalse();
        assertThat(route.apply(withMethod(HttpMethod.DELETE), false).isPresent()).isFalse();
    }

    @Test
    void testConsumeType() {
        final Route route = Route.builder()
                                 .path(PATH)
                                 .methods(HttpMethod.POST)
                                 .consumes(JSON_UTF_8)
                                 .build();

        assertThat(route.apply(withConsumeType(HttpMethod.POST, JSON_UTF_8), false).isPresent()).isTrue();
        assertThat(route.apply(withConsumeType(HttpMethod.POST, MediaType.create("application", "json")), false)
                        .isPresent()).isFalse();
    }

    @Test
    void testAcceptType() {
        final Route route = Route.builder()
                                 .path(PATH)
                                 .methods(HttpMethod.GET)
                                 .produces(JSON_UTF_8)
                                 .build();

        assertThat(route.apply(withAcceptHeader(HttpMethod.GET, "
core/src/test/java/com/linecorp/armeria/server/RouteTest.java