content: package io.agrest.jaxrs2.junit;
  
  import io.agrest.jaxrs2.junit.pojo.P1;
  import io.agrest.jaxrs2.junit.pojo.P10;
  import io.agrest.jaxrs2.junit.pojo.P2;
  import io.agrest.jaxrs2.junit.pojo.P3;
  import io.agrest.jaxrs2.junit.pojo.P4;
  import io.agrest.jaxrs2.junit.pojo.P6;
  import io.agrest.jaxrs2.junit.pojo.P7;
  import io.agrest.jaxrs2.junit.pojo.P8;
  import io.agrest.jaxrs2.junit.pojo.P9;
  import io.agrest.meta.AgEntityOverlay;
  import io.agrest.runtime.AgRuntime;
  import io.agrest.runtime.AgRuntimeBuilder;
  import io.bootique.BQRuntime;
  import io.bootique.Bootique;
  import io.bootique.command.CommandOutcome;
  import io.bootique.BQModule;
  import io.bootique.di.Binder;
  import io.bootique.di.Provides;
  import io.bootique.jersey.JerseyModule;
  import io.bootique.jersey.JerseyModuleExtender;
  import io.bootique.jetty.junit5.JettyTester;
  import io.bootique.junit5.BQTestScope;
  import io.bootique.junit5.scope.BQAfterScopeCallback;
  import io.bootique.junit5.scope.BQBeforeMethodCallback;
  import io.bootique.junit5.scope.BQBeforeScopeCallback;
  import org.junit.jupiter.api.Assertions;
  import org.junit.jupiter.api.extension.ExtensionContext;
  
  import javax.inject.Singleton;
  import javax.ws.rs.client.WebTarget;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.function.Function;
  
  public class AgPojoTester implements BQBeforeScopeCallback, BQAfterScopeCallback, BQBeforeMethodCallback {
  
      private final List<Class<?>> resources;
      private Function<AgRuntimeBuilder, AgRuntimeBuilder> agCustomizer;
      private final List<BQModule> bqModules;
  
      private PojoStore pojoStoreInScope;
      private JettyTester jettyInScope;
      private BQRuntime appInScope;
  
      public static Builder builder() {
          return new Builder();
      }
  
      protected AgPojoTester() {
          this.resources = new ArrayList<>();
          this.agCustomizer = Function.identity();
          this.bqModules = new ArrayList<>();
      }
  
      public AgHttpTester target() {
          return AgHttpTester.request(internalTarget());
      }
  
      public AgHttpTester target(String path) {
          return target().path(path);
      }
  
      /**
       * Provides access to JAX-RS WebTarget. Used in special cases, as normally you should call {@link #target()} and
       * use the returned {@link AgHttpTester} to manage web request and run result assertions.
       */
      public WebTarget internalTarget() {
          return getJettyInScope().getTarget();
      }
  
      public <T> Map<Object, T> bucket(Class<T> type) {
          return getPojoStoreInScope().bucket(type);
      }
  
      public Map<Object, P1> p1() {
          return bucket(P1.class);
      }
  
      public Map<Object, P2> p2() {
          return bucket(P2.class);
      }
  
      public Map<Object, P4> p4() {
          return bucket(P4.class);
      }
  
      public Map<Object, P6> p6() {
          return bucket(P6.class);
      }
  
      public Map<Object, P8> p8() {
          return bucket(P8.class);
      }
  
      public Map<Object, P9> p9() {
          return bucket(P9.class);
      }
  
      public Map<Object, P10> p10() {
          return bucket(P10.class);
      }
  
      protected JettyTester getJettyInScope() {
          return Objects.requireNonNull(jettyInScope, "Not in test scope");
      }
  
      protected PojoStore getPojoStoreInScope() {
          return Objects.requireNonNull(pojoStoreInScope, "Not in test scope");
      }
  
      @Override
      public void beforeScope(BQTestScope scope, ExtensionContext context) {
  
          this.pojoStoreInScope = new PojoStore();
          this.jettyInScope = JettyTester.create();
          this.appInScope = createAppInScope(this.jettyInScope, this.pojoStoreInScope);
  
          CommandOutcome result = appInScope.run();
          Assertions.assertTrue(result.isSuccess(), result::toString);
          Assertions.assertTrue(result.forkedToBackground());
      }
  
      @Override
      public void afterScope(BQTestScope scope, ExtensionContext context) {
          this.appInScope.shutdown();
          this.appInScope = null;
          this.jettyInScope = null;
          this.pojoStoreInScope = null;
      }
  
      @Override
      public void beforeMethod(BQTestScope scope, ExtensionContext context) {
          getPojoStoreInScope().clear();
      }
  
      protected BQRuntime createAppInScope(JettyTester jetty, PojoStore pojoStore) {
  
          Bootique builder = Bootique.app("-s")
                  .autoLoadModules()
                  .module(jetty.moduleReplacingConnectors())
                  .module(new AgModule(agCustomizer, pojoStore, resources));
  
          bqModules.forEach(builder::module);
  
          return builder.createRuntime();
      }
  
      public static class Builder {
  
          private final AgPojoTester tester = new AgPojoTester();
  
          public Builder resources(Class<?>... resources) {
              tester.resources.addAll(Arrays.asList(resources));
              return this;
          }
  
          public Builder agCustomizer(Function<AgRuntimeBuilder, AgRuntimeBuilder> agCustomizer) {
              tester.agCustomizer = Objects.requireNonNull(agCustomizer);
              return this;
          }
  
          public AgPojoTester build() {
              return tester;
          }
      }
  
      static class AgModule implements BQModule {
  
          private final PojoStore pojoStore;
          private final Function<AgRuntimeBuilder, AgRuntimeBuilder> customizer;
          private final List<Class<?>> resources;
  
          public AgModule(Function<AgRuntimeBuilder, AgRuntimeBuilder> customizer, PojoStore pojoStore, List<Class<?>> resources) {
              this.pojoStore = pojoStore;
              this.customizer = customizer;
              this.resources = resources;
          }
  
          @Override
          public void configure(Binder binder) {
              configureJersey(JerseyModule.extend(binder));
          }
  
          private void configureJersey(JerseyModuleExtender extender) {
              extender.addFeature(AgTestJaxrsFeature.class);
              resources.forEach(extender::addResource);
          }
  
          @Provides
          @Singleton
          AgRuntime provideAgRuntime() {
              AgRuntimeBuilder agBuilder = AgRuntime.builder().module(this::configureAg);
              return customizer.apply(agBuilder).build();
          }
  
          private void configureAg(org.apache.cayenne.di.Binder agBinder) {
              agBinder.bind(PojoStore.class).toInstance(pojoStore);
              agBinder.bindMap(AgEntityOverlay.class)
                      .put(P1.class.getName(), new PojoOverlay<>(P1.class, pojoStore))
                      .put(P2.class.getName(), new PojoOverlay<>(P2.class, pojoStore))
                      .put(P3.class.getName(), new PojoOverlay<>(P3.class, pojoStore))
                      .put(P4.class.getName(), new PojoOverlay<>(P4.class, pojoStore))
                      .put(P6.class.getName(), new PojoOverlay<>(P6.class, pojoStore))
                      .put(P7.class.getName(), new PojoOverlay<>(P7.class, pojoStore))
                      .put(P8.class.getName(), new PojoOverlay<>(P8.class, pojoStore))
                      .put(P9.class.getName(), new PojoOverlay<>(P9.class, pojoStore))
                      .put(P10.class.getName(), new PojoOverlay<>(P10.class, pojoStore));
          }
      }
  }
  