content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
  package org.apache.causeway.testdomain.rest;
  
  import java.util.function.UnaryOperator;
  
  import org.junit.jupiter.api.Assertions;
  import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
  import org.junit.jupiter.api.Order;
  import org.junit.jupiter.api.Test;
  import org.junit.jupiter.api.TestMethodOrder;
  import org.springframework.boot.test.context.SpringBootTest;
  import org.springframework.context.annotation.Import;
  import org.springframework.test.context.TestPropertySource;
  
  import static org.junit.jupiter.api.Assertions.assertEquals;
  import static org.junit.jupiter.api.Assertions.assertTrue;
  
  import org.apache.causeway.commons.collections.Can;
  import org.apache.causeway.commons.functional.Try;
  import org.apache.causeway.core.config.presets.CausewayPresets;
  import org.apache.causeway.extensions.cors.impl.CausewayModuleExtCors;
  import org.apache.causeway.testdomain.conf.Configuration_headless;
  import org.apache.causeway.testdomain.rospec.Configuration_usingRoSpec;
  import org.apache.causeway.testdomain.rospec.RoSpecSampler;
  import org.apache.causeway.testdomain.util.rest.RestEndpointService;
  import org.apache.causeway.viewer.restfulobjects.client.log.ClientConversationFilter;
  import org.apache.causeway.viewer.restfulobjects.jaxrsresteasy.CausewayModuleViewerRestfulObjectsJaxrsResteasy;
  
  import jakarta.inject.Inject;
  import lombok.val;
  
  @SpringBootTest(
          classes = {
                  RestEndpointService.class
          },
          properties = {
                  //preparing the CORS filter with specific settings for testing
                  "causeway.extensions.cors.allowedMethods=POST",
                  "causeway.extensions.cors.allowedOrigins=http://www.google.com",
          },
          webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  @TestPropertySource(CausewayPresets.UseLog4j2Test)
  @Import({
      Configuration_headless.class,
      Configuration_usingRoSpec.class,
      CausewayModuleViewerRestfulObjectsJaxrsResteasy.class,
      CausewayModuleExtCors.class
  })
  @TestMethodOrder(OrderAnnotation.class) // run tests in sequence, to ease debugging
  class CorsFilterTest {
  
      @Inject RestEndpointService restService;
  
      private final RoSpecSampler refSampler = new RoSpecSampler();
      private final Can<ClientConversationFilter> conversationFilters = Can.empty();
  
      // -- STRING
  
      @Test @Order(1)
      void requestWithValidOriginAndMethod_shouldSucceed() {
          val digest = digestUsingPost("string", String.class, builder->builder
                  .header("Origin", validOrigin()));
          assertHttpResponse200(digest);
      }
  
      @Test @Order(2)
      void requestWithInvalidOrigin_shouldFail() {
          val digest = digestUsingPost("string", String.class, builder->builder
                  .header("Origin", invalidOrigin()));
          assertHttpResponse403(digest);
      }
  
      @Test @Order(3)
      void requestWithMissingOrigin_shouldSucceed() {
          val digest = digestUsingPost("string", String.class, builder->builder);
          assertHttpResponse200(digest);
      }
  
      @Test @Order(4)
      void requestWithInvalidMethod_shouldFail() {
          val digest = digestUsingGet("stringSafe", String.class, builder->builder
                  .header("Origin", validOrigin()));
          assertHttpResponse403(digest);
      }
  
      // -- HELPER
  
      <T> Try<T> digestUsingPost(
              final String actionName,
              final Class<T> entityType,
              final UnaryOperator<jakarta.ws.rs.client.Invocation.Builder> onRequestBuilder) {
  
          assertTrue(restService.getPort()>0);
  
          val useRequestDebugLogging = false;
          val client = restService.newClient(useRequestDebugLogging, conversationFilters);
  
          val request = onRequestBuilder.apply(
                  restService.newInvocationBuilder(client,
                          String.format("services/testdomain.RoSpecSampler/actions/%s/invoke", actionName)));
  
          val args = client.arguments()
                  .build();
  
          val response = request.post(args);
          val digest = client.digest(response, entityType);
  
          return digest;
  
      }
  
      <T> Try<T> digestUsingGet(
              final String actionName,
              final Class<T> entityType,
              final UnaryOperator<jakarta.ws.rs.client.Invocation.Builder> onRequestBuilder) {
  
          assertTrue(restService.getPort()>0);
  
          val useRequestDebugLogging = false;
          val client = restService.newClient(useRequestDebugLogging, conversationFilters);
  
          val request = onRequestBuilder.apply(
                  restService.newInvocationBuilder(client,
                          String.format("services/testdomain.RoSpecSampler/actions/%s/invoke", actionName)));
  
          val response = request.get();
          val digest = client.digest(response, entityType);
  
          return digest;
      }
  
      private String validOrigin() {
          return "http://www.google.com";
      }
  
      private String invalidOrigin() {
          return "http://localhost";
      }
  
      private void assertHttpResponse200(final Try<String> digest) {
          digest.ifFailure(Assertions::fail);
          val returnValue = digest.getValue().orElseThrow();
          assertEquals(refSampler.string(), returnValue);
      }
  
      private void assertHttpResponse403(final Try<String> digest) {
          assertTrue(digest.getFailure().isPresent(), "request was expected to fail, but succeeded");
          assertTrue(digest.getFailure().get().getMessage().contains("403"));
      }
  
  }
  