You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by al...@apache.org on 2018/06/29 10:42:05 UTC

[2/3] brooklyn-server git commit: address code review comments -

address code review comments -

- entitlements check
- test for details endpoint including depth
- better comments on depth
- include 'self' link for entity details
- fix bug where config wasn't returned


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/475c4399
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/475c4399
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/475c4399

Branch: refs/heads/master
Commit: 475c439995e4ac4611fdf2a36f5fb397fafca0f2
Parents: db7f06d
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Tue Jun 12 10:53:32 2018 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Tue Jun 12 10:53:32 2018 +0100

----------------------------------------------------------------------
 .../brooklyn/rest/api/ApplicationApi.java       |   4 +-
 .../brooklyn/rest/domain/EntityDetail.java      |  38 ++++--
 .../rest/resources/ApplicationResource.java     |  45 ++++---
 .../rest/resources/ApplicationResourceTest.java | 135 +++++++++++++++----
 4 files changed, 172 insertions(+), 50 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/475c4399/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
index ac8b27d..f5aab9b 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/api/ApplicationApi.java
@@ -79,7 +79,7 @@ public interface ApplicationApi {
             @QueryParam("config") String config,
             @ApiParam(value="Tree depth to traverse in children for returning detail; "
                 + "default 1 means to have detail for just applications and additional entity IDs explicitly requested, "
-                + "with references to children but not their details", required=false)
+                + "with references to children but not their details; 0 is no detail even for applications; negative is full depth", required=false)
             @DefaultValue("1")
             @QueryParam("depth") int depth);
 
@@ -92,7 +92,7 @@ public interface ApplicationApi {
                 + "(This returns the complete tree which is wasteful and not usually wanted.)"
     )
     @Deprecated
-    /** @deprecated since 1.0.0 use {@link #details(String, String, int)} */
+    /** @deprecated since 1.0.0 use {@link #details(String, boolean, String, String, int)} */
     public List<EntityDetail> fetch(
             @ApiParam(value="Any additional entity ID's to include, as JSON or comma-separated list", required=false)
             @DefaultValue("")

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/475c4399/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
----------------------------------------------------------------------
diff --git a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
index f49bb81..83c9dd1 100644
--- a/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
+++ b/rest/rest-api/src/main/java/org/apache/brooklyn/rest/domain/EntityDetail.java
@@ -44,6 +44,8 @@ public class EntityDetail extends EntitySummary {
     private final Lifecycle serviceState;
     private final String iconUrl;
 
+    /** @deprecated since 1.0.0, supply links */
+    @Deprecated
     public EntityDetail(
             @JsonProperty("applicationId") String applicationId,
             @JsonProperty("id") String id,
@@ -57,16 +59,34 @@ public class EntityDetail extends EntitySummary {
             @JsonProperty("children") List<EntitySummary> children,
             @JsonProperty("groupIds") List<String> groupIds,
             @JsonProperty("members") List<Map<String, String>> members) {
-        super(id, name, type, catalogItemId, Collections.<String, URI>emptyMap());
-        this.applicationId = applicationId;
-        this.iconUrl = iconUrl;
-        this.parentId = parentId;
-        this.children = (children == null) ? ImmutableList.<EntitySummary>of() : ImmutableList.copyOf(children);
-        this.groupIds = (groupIds == null) ? ImmutableList.<String>of() : ImmutableList.copyOf(groupIds);
-        this.members = (members == null) ? ImmutableList.<Map<String, String>>of() : ImmutableList.copyOf(members);
-        this.serviceState = serviceState;
-        this.serviceUp = serviceUp;
+        this(applicationId, id, parentId, name, type, serviceUp, serviceState, iconUrl, catalogItemId, children, groupIds, members, 
+            Collections.<String, URI>emptyMap());
     }
+    
+    public EntityDetail(
+        @JsonProperty("applicationId") String applicationId,
+        @JsonProperty("id") String id,
+        @JsonProperty("parentId") String parentId,
+        @JsonProperty("name") String name,
+        @JsonProperty("type") String type,
+        @JsonProperty("serviceUp") Boolean serviceUp,
+        @JsonProperty("serviceState") Lifecycle serviceState,
+        @JsonProperty("iconUrl") String iconUrl,
+        @JsonProperty("catalogItemId") String catalogItemId,
+        @JsonProperty("children") List<EntitySummary> children,
+        @JsonProperty("groupIds") List<String> groupIds,
+        @JsonProperty("members") List<Map<String, String>> members,
+        @JsonProperty("links") Map<String, URI> links) {
+    super(id, name, type, catalogItemId, links);
+    this.applicationId = applicationId;
+    this.iconUrl = iconUrl;
+    this.parentId = parentId;
+    this.children = (children == null) ? ImmutableList.<EntitySummary>of() : ImmutableList.copyOf(children);
+    this.groupIds = (groupIds == null) ? ImmutableList.<String>of() : ImmutableList.copyOf(groupIds);
+    this.members = (members == null) ? ImmutableList.<Map<String, String>>of() : ImmutableList.copyOf(members);
+    this.serviceState = serviceState;
+    this.serviceUp = serviceUp;
+}
 
     public static long getSerialVersionUID() {
         return serialVersionUID;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/475c4399/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
index 0902e36..1b7fe35 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ApplicationResource.java
@@ -112,6 +112,8 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
     @Context
     private UriInfo uriInfo;
 
+    /** depth 0 means no detail even at root; negative means infinite; positive means include details for that many levels 
+     * (ie 1 means this entity but no details of descendants) */
     private EntitySummary fromEntity(Entity entity, boolean includeTags, int detailDepth, List<String> extraSensorGlobs, List<String> extraConfigGlobs) {
         if (detailDepth==0) {
             return new EntitySummary(
@@ -136,7 +138,9 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
         List<EntitySummary> children = Lists.newArrayList();
         if (!entity.getChildren().isEmpty()) {
             for (Entity child : entity.getChildren()) {
-                children.add(fromEntity(child, includeTags, detailDepth-1, extraSensorGlobs, extraConfigGlobs));
+                if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, child)) {
+                    children.add(fromEntity(child, includeTags, detailDepth-1, extraSensorGlobs, extraConfigGlobs));
+                }
             }
         }
 
@@ -170,7 +174,8 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
                 entity.getCatalogItemId(),
                 children,
                 groupIds,
-                members);
+                members,
+                MutableMap.of("self", EntityTransformer.entityUri(entity, ui.getBaseUriBuilder())) );
         
         if (includeTags) {
             result.setExtraField("tags", resolving(MutableList.copyOf(entity.tags().getTags())).preferJson(true).resolve() );
@@ -230,11 +235,15 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
     @Override
     public List<EntitySummary> details(String entityIds, boolean includeAllApps, String extraSensorsGlobsS, String extraConfigGlobsS, int depth) {
         List<String> extraSensorGlobs = JavaStringEscapes.unwrapOptionallyQuotedJavaStringList(extraSensorsGlobsS);
+        List<String> extraConfigGlobs = JavaStringEscapes.unwrapOptionallyQuotedJavaStringList(extraConfigGlobsS);
 
         Map<String, EntitySummary> entitySummaries = MutableMap.of();
+
         if (includeAllApps) {
             for (Entity application : mgmt().getApplications()) {
-                entitySummaries.put(application.getId(), fromEntity(application, true, depth, extraSensorGlobs, extraSensorGlobs));
+                if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, application)) {
+                    entitySummaries.put(application.getId(), fromEntity(application, true, depth, extraSensorGlobs, extraConfigGlobs));
+                }
             }
         }
 
@@ -244,7 +253,7 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
                 Entity entity = mgmt().getEntityManager().getEntity(entityId.trim());
                 while (entity != null && !entitySummaries.containsKey(entity.getId())) {
                     if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) {
-                        entitySummaries.put(entity.getId(), fromEntity(entity, true, depth, extraSensorGlobs, extraSensorGlobs));
+                        entitySummaries.put(entity.getId(), fromEntity(entity, true, depth, extraSensorGlobs, extraConfigGlobs));
                     }
                     entity = entity.getParent();
                 }
@@ -267,10 +276,12 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
             Map<String,Object> sensors = (Map<String, Object>) sensorsO;
             
             for (AttributeSensor<?> s: extraSensors) {
-                Object sv = entity.sensors().get(s);
-                if (sv!=null) {
-                    sv = resolving(sv).preferJson(true).asJerseyOutermostReturnValue(false).raw(true).context(entity).immediately(true).timeout(Duration.ZERO).resolve();
-                    sensors.put(s.getName(), sv);
+                if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, s.getName()))) {
+                    Object sv = entity.sensors().get(s);
+                    if (sv!=null) {
+                        sv = resolving(sv).preferJson(true).asJerseyOutermostReturnValue(false).raw(true).context(entity).immediately(true).timeout(Duration.ZERO).resolve();
+                        sensors.put(s.getName(), sv);
+                    }
                 }
             }
         }
@@ -295,9 +306,11 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
                 Object sv = kv.getValue();
                 if (sv!=null) {
                     String name = kv.getKey().getName();
-                    if (extraSensorGlobs.stream().anyMatch(sn -> WildcardGlobs.isGlobMatched(sn, name))) {
-                        sv = resolving(sv).preferJson(true).asJerseyOutermostReturnValue(false).raw(true).context(entity).immediately(true).timeout(Duration.ZERO).resolve();
-                        sensors.put(name, sv);
+                    if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, name))) {
+                        if (extraSensorGlobs.stream().anyMatch(sn -> WildcardGlobs.isGlobMatched(sn, name))) {
+                            sv = resolving(sv).preferJson(true).asJerseyOutermostReturnValue(false).raw(true).context(entity).immediately(true).timeout(Duration.ZERO).resolve();
+                            sensors.put(name, sv);
+                        }
                     }
                 }
             });
@@ -321,10 +334,12 @@ public class ApplicationResource extends AbstractBrooklynRestResource implements
             List<Predicate<ConfigKey<?>>> extraConfigPreds = MutableList.of();
             extraConfigGlobs.stream().forEach(g -> { extraConfigPreds.add( ConfigPredicates.nameMatchesGlob(g) ); });
             entity.config().findKeysDeclared(Predicates.or(extraConfigPreds)).forEach(key -> {
-                Object v = entity.config().get(key);
-                if (v!=null) {
-                    v = resolving(v).preferJson(true).asJerseyOutermostReturnValue(false).raw(true).context(entity).immediately(true).timeout(Duration.ZERO).resolve();
-                    configs.put(key.getName(), v);
+                if (Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_CONFIG, new EntityAndItem<String>(entity, key.getName()))) {
+                    Object v = entity.config().get(key);
+                    if (v!=null) {
+                        v = resolving(v).preferJson(true).asJerseyOutermostReturnValue(false).raw(true).context(entity).immediately(true).timeout(Duration.ZERO).resolve();
+                        configs.put(key.getName(), v);
+                    }
                 }
             });
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/475c4399/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
index b3b5260..c38c2de 100644
--- a/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
+++ b/rest/rest-resources/src/test/java/org/apache/brooklyn/rest/resources/ApplicationResourceTest.java
@@ -33,7 +33,9 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
 
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.client.Entity;
@@ -77,12 +79,12 @@ import org.apache.brooklyn.rest.testing.mocks.RestMockApp;
 import org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity;
 import org.apache.brooklyn.test.Asserts;
 import org.apache.brooklyn.util.collections.CollectionFunctionals;
+import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.http.HttpAsserts;
 import org.apache.brooklyn.util.stream.Streams;
 import org.apache.brooklyn.util.text.StringPredicates;
-import org.apache.brooklyn.util.text.Strings;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.http.HttpHeaders;
 import org.apache.http.entity.ContentType;
@@ -340,32 +342,26 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
     public void testFetchApplicationsAndEntity() {
         Collection apps = client().path("/applications/fetch").get(Collection.class);
         log.info("Applications fetched are: " + apps);
-
-        Map app = null;
-        for (Object appI : apps) {
-            Object name = ((Map) appI).get("name");
-            if ("simple-app".equals(name)) {
-                app = (Map) appI;
-            }
-            if (ImmutableSet.of("simple-ent", "simple-group").contains(name))
-                Assert.fail(name + " should not be listed at high level: " + apps);
-        }
-
-        Assert.assertNotNull(app);
-        Assert.assertFalse(Strings.isBlank((String) app.get("applicationId")),
-                "expected value for applicationId, was: " + app.get("applicationId"));
-        Assert.assertFalse(Strings.isBlank((String) app.get("serviceState")),
-                "expected value for serviceState, was: " + app.get("serviceState"));
-        Assert.assertNotNull(app.get("serviceUp"), "expected non-null value for serviceUp");
+        
+        Map app = ((Collection<Map>)apps).stream().filter(m -> "simple-app".equals(m.get("name"))).findFirst().orElse(null);
+        Assert.assertNotNull(app, "did not find 'simple-app'");
+        Asserts.assertThat((String) app.get("applicationId"), StringPredicates.isNonBlank(), "expected value for applicationId");
+        Asserts.assertThat((String) app.get("serviceState"), StringPredicates.isNonBlank(), "expected value for serviceState");
+        Asserts.assertThat(app.get("serviceUp"), Predicates.not(Predicates.isNull()), "expected non-null for serviceUp");
 
         Collection children = (Collection) app.get("children");
-        Assert.assertEquals(children.size(), 2);
+        Asserts.assertSize(children, 2);
 
         Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
         Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
         Assert.assertNotNull(entitySummary);
         Assert.assertNotNull(groupSummary);
 
+        Asserts.assertThat(
+            ((Collection<Map>)apps).stream().filter(m -> ImmutableSet.of("simple-ent", "simple-group").contains(m.get("name"))).collect(Collectors.toList()),
+            CollectionFunctionals.empty(),
+            "Some entities should not be listed at high level");
+        
         String itemIds = app.get("id") + "," + entitySummary.get("id") + "," + groupSummary.get("id");
         Collection entities = client().path("/applications/fetch")
                 .query("items", itemIds)
@@ -373,10 +369,6 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
                 .get(Collection.class);
         log.info("Applications+Entities fetched are: " + entities);
 
-        // on jenkins/windows a while ago (early 2016) this would sometimes fail with expected 4 but found 3,
-        // as per BROOKLYN-272 and PR 156; re-enabling now (2017-11) to test sensor query param; 
-        // and using assertSize to see which entity/app is missing if it does still fail
-        // (it should definitely be 4, as we got 2 before, and we've asked for 2 more entities)
         Asserts.assertSize(entities, apps.size() + 2);
         Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
         Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
@@ -415,7 +407,102 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
             MutableMap.of("state", expected.getState().name(),
                 "timestampUtc", expected.getTimestamp().getTime()));
     }
+    
+    // [{applicationId=zqxybvny1p, id=zqxybvny1p, parentId=null, name=simple-app, type=org.apache.brooklyn.entity.stock.BasicApplication, 
+    // serviceUp=true, serviceState=RUNNING, iconUrl=null, 
+    // children=[{id=pttwaekjhu, name=simple-ent, type=org.apache.brooklyn.rest.testing.mocks.RestMockSimpleEntity, links={self=/applications/zqxybvny1p/entities/pttwaekjhu}}, {id=dtqviudvsg, name=simple-group, type=org.apache.brooklyn.rest.testing.mocks.NameMatcherGroup, links={self=/applications/zqxybvny1p/entities/dtqviudvsg}}], groupIds=[], members=[], links={self=/applications/zqxybvny1p/entities/zqxybvny1p}, tags=[]}]
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test(dependsOnMethods = "testDeployApplication")
+    public void testApplicationDetailsAndEntity() {
+        Collection apps = client().path("/applications/details").get(Collection.class);
+        log.info("Application details are: " + apps);
+        
+        Map app = ((Collection<Map>)apps).stream().filter(m -> "simple-app".equals(m.get("name"))).findFirst().orElse(null);
+        Assert.assertNotNull(app, "did not find 'simple-app'");
+        Asserts.assertThat((String) app.get("applicationId"), StringPredicates.isNonBlank(), "expected value for applicationId");
+        Asserts.assertThat((String) app.get("serviceState"), StringPredicates.isNonBlank(), "expected value for serviceState");
+        Asserts.assertThat(app.get("serviceUp"), Predicates.not(Predicates.isNull()), "expected non-null for serviceUp");
+
+        Collection children = (Collection) app.get("children");
+        Asserts.assertSize(children, 2);
+
+        Map entitySummary = (Map) Iterables.find(children, withValueForKey("name", "simple-ent"), null);
+        Map groupSummary = (Map) Iterables.find(children, withValueForKey("name", "simple-group"), null);
+        Assert.assertNotNull(entitySummary);
+        Assert.assertNotNull(groupSummary);
 
+        Asserts.assertThat(
+            ((Collection<Map>)apps).stream().filter(m -> ImmutableSet.of("simple-ent", "simple-group").contains(m.get("name"))).collect(Collectors.toList()),
+            CollectionFunctionals.empty(),
+            "Some entities should not be listed at high level");
+        
+        String itemIds = entitySummary.get("id") + "," + groupSummary.get("id");
+        Collection entities1 = client().path("/applications/details")
+                .query("items", itemIds)
+                .query("includeAllApps", false)
+                .query("sensors", "[ service.state.expected, \"host.address\" ]")
+                .query("config", "*")
+                .get(Collection.class);
+        log.info("Applications+Entities details are: " + entities1);
+
+        Predicate<Collection> check = (entities) -> {
+            Asserts.assertSize(entities, 3);
+            Map entityDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-ent"), null);
+            Map groupDetails = (Map) Iterables.find(entities, withValueForKey("name", "simple-group"), null);
+            Assert.assertNotNull(entityDetails);
+            Assert.assertNotNull(groupDetails);
+    
+            Assert.assertEquals(entityDetails.get("parentId"), app.get("id"));
+            Assert.assertNull(entityDetails.get("children"));
+            Assert.assertEquals(groupDetails.get("parentId"), app.get("id"));
+            Assert.assertNull(groupDetails.get("children"));
+    
+            Collection entityGroupIds = (Collection) entityDetails.get("groupIds");
+            Assert.assertNotNull(entityGroupIds);
+            Assert.assertEquals(entityGroupIds.size(), 1);
+            Assert.assertEquals(entityGroupIds.iterator().next(), groupDetails.get("id"));
+    
+            Collection groupMembers = (Collection) groupDetails.get("members");
+            Assert.assertNotNull(groupMembers);
+            log.info("MEMBERS: " + groupMembers);
+    
+            Assert.assertEquals(groupMembers.size(), 1);
+            Map entityMemberDetails = (Map) Iterables.find(groupMembers, withValueForKey("name", "simple-ent"), null);
+            Assert.assertNotNull(entityMemberDetails);
+            Assert.assertEquals(entityMemberDetails.get("id"), entityDetails.get("id"));
+            
+            Map<String,Object> simpleEntSensors = Preconditions.checkNotNull(Map.class.cast(entityDetails.get("sensors")), "sensors");
+            org.apache.brooklyn.api.entity.Entity simpleEnt = Preconditions.checkNotNull(getManagementContext().<org.apache.brooklyn.api.entity.Entity>lookup(
+                EntityPredicates.displayNameEqualTo("simple-ent")));
+            Assert.assertEquals(simpleEntSensors.get("host.address"), simpleEnt.sensors().get(Attributes.ADDRESS));
+            Transition expected = simpleEnt.sensors().get(Attributes.SERVICE_STATE_EXPECTED);
+            Assert.assertEquals(simpleEntSensors.get("service.state.expected"), 
+                MutableMap.of("state", expected.getState().name(),
+                    "timestampUtc", expected.getTimestamp().getTime()));
+    
+            // and config also present
+            Assert.assertEquals( ((Map)groupDetails.get("config")).get("namematchergroup.regex"), "simple-ent" );
+            Assert.assertEquals( ((Map)entityDetails.get("config")).get("namematchergroup.regex"), null );
+            
+            return true;
+        };
+        
+        check.apply(entities1);
+        
+        Collection entities2 = client().path("/applications/details")
+            .query("items", app.get("id"))
+            .query("includeAllApps", false)
+            .query("sensors", "[ service.state.expected, \"host.address\" ]")
+            .query("config", "n*")
+            .query("depth", 2)
+            .get(Collection.class);
+        log.info("Applications+Entities details 2 are: " + entities2);
+        // in order to reuse the check, copy the children from the single app to the list
+        MutableList entities2b = MutableList.copyOf(entities2);
+        entities2b.addAll((Iterable) ((Map)Iterables.getOnlyElement(entities2)).get("children"));
+        check.apply(entities2b);
+    }
+    
     @Test(dependsOnMethods = "testDeployApplication")
     public void testListSensors() {
         Set<SensorSummary> sensors = client().path("/applications/simple-app/entities/simple-ent/sensors")
@@ -585,7 +672,7 @@ public class ApplicationResourceTest extends BrooklynRestResourceTest {
     }
 
     
-    @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
+    @Test(dependsOnMethods = {"testListEffectors", "testFetchApplicationsAndEntity", "testApplicationDetailsAndEntity", "testTriggerSampleEffector", "testListApplications","testReadEachSensor","testPolicyWhichCapitalizes","testLocatedLocation"})
     public void testDeleteApplication() throws TimeoutException, InterruptedException {
         waitForPageFoundResponse("/applications/simple-app", ApplicationSummary.class);
         Collection<Application> apps = getManagementContext().getApplications();