@Override
 @SelectJson("extensions")
 @Consumes(MediaType.APPLICATION_JSON)
 public Set<Extension> expected() {
   return ImmutableSet.of(
       Extension.builder()
           .alias("RAX-PIE")
           .name("Public Image Extension")
           .namespace(URI.create("http://docs.rackspacecloud.com/servers/api/ext/pie/v1.0"))
           .updated(
               new SimpleDateFormatDateService()
                   .iso8601SecondsDateParse("2011-01-22T13:25:27-06:00"))
           .description("Adds the capability to share an image with other users.")
           .links(
               ImmutableSet.of(
                   Link.create(
                       Relation.DESCRIBEDBY,
                       "application/pdf",
                       URI.create(
                           "http://docs.rackspacecloud.com/servers/api/ext/cs-pie-20111111.pdf")),
                   Link.create(
                       Relation.DESCRIBEDBY,
                       "application/vnd.sun.wadl+xml",
                       URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-pie.wadl"))))
           .build(),
       Extension.builder()
           .alias("RAX-CBS")
           .name("Cloud Block Storage")
           .namespace(URI.create("http://docs.rackspacecloud.com/servers/api/ext/cbs/v1.0"))
           .updated(
               new SimpleDateFormatDateService()
                   .iso8601SecondsDateParse("2011-01-12T11:22:33-06:00"))
           .description("Allows mounting cloud block storage volumes.")
           .links(
               ImmutableSet.of(
                   Link.create(
                       Relation.DESCRIBEDBY,
                       "application/pdf",
                       URI.create(
                           "http://docs.rackspacecloud.com/servers/api/ext/cs-cbs-20111201.pdf")),
                   Link.create(
                       Relation.DESCRIBEDBY,
                       "application/vnd.sun.wadl+xml",
                       URI.create("http://docs.rackspacecloud.com/servers/api/ext/cs-cbs.wadl"))))
           .build());
 }
 public void testChangesHttpsToHttp() {
   assertEquals(
       fn.apply(
           Extension.builder()
               .alias("security_groups")
               .name("SecurityGroups")
               .namespace(URI.create("https://docs.openstack.org/ext/securitygroups/api/v1.1"))
               .updated(
                   new SimpleDateFormatDateService()
                       .iso8601SecondsDateParse("2011-07-21T00:00:00+00:00"))
               .description("Security group support")
               .build()),
       URI.create("http://docs.openstack.org/ext/securitygroups/api/v1.1"));
 }
 public void testReturnsNamespace() {
   URI ns = URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1");
   assertEquals(
       fn.apply(
           Extension.builder()
               .alias("os-keypairs")
               .name("Keypairs")
               .namespace(ns)
               .updated(
                   new SimpleDateFormatDateService()
                       .iso8601SecondsDateParse("2011-08-08T00:00:00+00:00"))
               .description("Keypair Support")
               .build()),
       ns);
 }
@Test(
    groups = "unit",
    testName = "PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSetTest")
public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSetTest {

  Extension keypairs =
      Extension.builder()
          .alias("os-keypairs")
          .name("Keypairs")
          .namespace(URI.create("http://docs.openstack.org/ext/keypairs/api/v1.1"))
          .updated(
              new SimpleDateFormatDateService()
                  .iso8601SecondsDateParse("2011-08-08T00:00:00+00:00"))
          .description("Keypair Support")
          .build();

  @org.jclouds.openstack.v2_0.services.Extension(
      of = ServiceType.COMPUTE,
      namespace = "http://docs.openstack.org/ext/keypairs/api/v1.1")
  interface KeyPairApi {}

  Extension floatingIps =
      Extension.builder()
          .alias("os-floating-ips")
          .name("Floating_ips")
          .namespace(URI.create("http://docs.openstack.org/ext/floating_ips/api/v1.1"))
          .updated(
              new SimpleDateFormatDateService()
                  .iso8601SecondsDateParse("2011-06-16T00:00:00+00:00"))
          .description("Floating IPs support")
          .build();

  @org.jclouds.openstack.v2_0.services.Extension(
      of = ServiceType.COMPUTE,
      name = "Floating_ips",
      alias = "os-floating-ips",
      namespace = "http://docs.openstack.org/ext/floating_ips/api/v1.1")
  interface FloatingIPApi {}

  interface NovaApi {

    @Delegate
    Optional<FloatingIPApi> getFloatingIPExtensionApi(String region);

    @Delegate
    Optional<KeyPairApi> getKeyPairExtensionApi(String region);
  }

  InvocationSuccess getFloatingIPExtension(List<Object> args)
      throws SecurityException, NoSuchMethodException {
    return InvocationSuccess.create(
        Invocation.create(method(NovaApi.class, "getFloatingIPExtensionApi", String.class), args),
        "foo");
  }

  InvocationSuccess getKeyPairExtension(List<Object> args)
      throws SecurityException, NoSuchMethodException {
    return InvocationSuccess.create(
        Invocation.create(method(NovaApi.class, "getKeyPairExtensionApi", String.class), args),
        "foo");
  }

  public void testPresentWhenExtensionsIncludeNamespaceFromAnnotationAbsentWhenNot()
      throws SecurityException, NoSuchMethodException {

    assertEquals(
        whenExtensionsInRegionInclude("region", keypairs, floatingIps)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));
    assertEquals(
        whenExtensionsInRegionInclude("region", keypairs, floatingIps)
            .apply(getKeyPairExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));
    assertEquals(
        whenExtensionsInRegionInclude("region", keypairs)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.absent());
    assertEquals(
        whenExtensionsInRegionInclude("region", floatingIps)
            .apply(getKeyPairExtension(ImmutableList.<Object>of("region"))),
        Optional.absent());
  }

  public void testRegionWithoutExtensionsReturnsAbsent()
      throws SecurityException, NoSuchMethodException {
    assertEquals(
        whenExtensionsInRegionInclude("region", floatingIps)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("differentregion"))),
        Optional.absent());
    assertEquals(
        whenExtensionsInRegionInclude("region", keypairs)
            .apply(getKeyPairExtension(ImmutableList.<Object>of("differentregion"))),
        Optional.absent());
  }

  /**
   * It is possible that the /extensions call returned the correct extension, but that the
   * namespaces were different, for whatever reason. One way to address this is to have a multimap
   * of the authoritative namespace to alternate ones, which could be wired up with guice
   */
  public void testPresentWhenAliasForExtensionMapsToNamespace()
      throws SecurityException, NoSuchMethodException {
    Extension keypairsWithDifferentNamespace =
        keypairs
            .toBuilder()
            .namespace(
                URI.create("http://docs.openstack.org/ext/arbitrarilydifferent/keypairs/api/v1.1"))
            .build();

    Multimap<URI, URI> aliases =
        ImmutableMultimap.of(
            keypairs.getNamespace(), keypairsWithDifferentNamespace.getNamespace());

    assertEquals(
        whenExtensionsAndAliasesInRegionInclude(
                "region", ImmutableSet.of(keypairsWithDifferentNamespace), aliases)
            .apply(getKeyPairExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));
    assertEquals(
        whenExtensionsAndAliasesInRegionInclude(
                "region", ImmutableSet.of(keypairsWithDifferentNamespace), aliases)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.absent());
  }

  /**
   * In devstack and going forward, namespaces are being deprecated. When namespace is missing (or
   * replaced with a "fake" /fake namespace), allow matching by name and alias.
   */
  public void testPresentWhenNameSpaceIsFakeAndMatchByNameOrAlias()
      throws SecurityException, NoSuchMethodException {
    // Revert to alias
    Extension floatingIpsWithFakeNamespace =
        floatingIps.toBuilder().namespace(URI.create("http://docs.openstack.org/ext/fake")).build();

    // Revert to name
    Extension floatingIpsWithFakeNamespaceAndAlias =
        floatingIps
            .toBuilder()
            .namespace(URI.create("http://docs.openstack.org/ext/fake"))
            .alias("fake")
            .build();

    Multimap<URI, URI> aliases = ImmutableMultimap.of();

    assertEquals(
        whenExtensionsAndAliasesInRegionInclude(
                "region", ImmutableSet.of(floatingIpsWithFakeNamespace), aliases)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));

    assertEquals(
        whenExtensionsAndAliasesInRegionInclude(
                "region", ImmutableSet.of(floatingIpsWithFakeNamespaceAndAlias), aliases)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));
  }

  public void testPresentWhenNameSpaceIsMissingAndMatchByNameOrAlias()
      throws SecurityException, NoSuchMethodException {
    // Revert to alias
    Extension floatingIpsWithMissingNamespace = floatingIps.toBuilder().namespace(null).build();

    // Revert to name
    Extension floatingIpsWithMissingNamespaceAndAlias =
        floatingIps.toBuilder().namespace(null).alias("fake").build();

    Multimap<URI, URI> aliases = ImmutableMultimap.of();

    assertEquals(
        whenExtensionsAndAliasesInRegionInclude(
                "region", ImmutableSet.of(floatingIpsWithMissingNamespace), aliases)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));

    assertEquals(
        whenExtensionsAndAliasesInRegionInclude(
                "region", ImmutableSet.of(floatingIpsWithMissingNamespaceAndAlias), aliases)
            .apply(getFloatingIPExtension(ImmutableList.<Object>of("region"))),
        Optional.of("foo"));
  }

  private PresentWhenExtensionAnnotationMatchesExtensionSet whenExtensionsInRegionInclude(
      String region, Extension... extensions) {
    return whenExtensionsAndAliasesInRegionInclude(
        region, ImmutableSet.copyOf(extensions), ImmutableMultimap.<URI, URI>of());
  }

  private PresentWhenExtensionAnnotationMatchesExtensionSet whenExtensionsAndAliasesInRegionInclude(
      String region, final Set<Extension> extensions, final Multimap<URI, URI> aliases) {
    final LoadingCache<String, Set<? extends Extension>> extensionsForRegion =
        CacheBuilder.newBuilder()
            .build(
                CacheLoader.from(
                    Functions.forMap(
                        ImmutableMap.<String, Set<? extends Extension>>of(
                            region, extensions, "differentregion", ImmutableSet.<Extension>of()))));

    PresentWhenExtensionAnnotationMatchesExtensionSet fn =
        Guice.createInjector(
                new AbstractModule() {
                  @Override
                  protected void configure() {
                    MapBinder<URI, URI> aliasBindings =
                        MapBinder.newMapBinder(
                                binder(), URI.class, URI.class, NamespaceAliases.class)
                            .permitDuplicates();
                    for (URI key : aliases.keySet()) {
                      for (URI value : aliases.get(key)) {
                        aliasBindings.addBinding(key).toInstance(value);
                      }
                    }
                  }

                  @Provides
                  LoadingCache<String, Set<? extends Extension>> getExtensions() {
                    return extensionsForRegion;
                  }
                })
            .getInstance(PresentWhenExtensionAnnotationMatchesExtensionSet.class);

    return fn;
  }
}