diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java index 15d8cf577..da4235646 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java @@ -105,6 +105,15 @@ public ItemDomainLogbook completeCreateEntityInstance(ItemDomainLogbook newLogbo } public ItemDomainLogbook completeCreateEntityInstance(ItemDomainLogbook newLogbookDoc, EntityType logbookType, UserInfo userInfo, boolean attachDefaultTemplate) throws CdbException, CloneNotSupportedException { + if (logbookType == null) { + throw new InvalidObjectState("A logbook type is required to create a log document."); + } + if (logbookType.isHasChildren()) { + throw new InvalidObjectState(String.format( + "'%s' is a grouping logbook type and cannot contain log documents. Select one of its child logbook types.", + logbookType.getAvailableLongDisplayName())); + } + newLogbookDoc.setEntityTypeList(new ArrayList<>()); EntityType entityType = logbookType; newLogbookDoc.getEntityTypeList().add(entityType); diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/EntityType.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/EntityType.java index e1d5b9db8..2974c7e00 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/EntityType.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/EntityType.java @@ -142,6 +142,13 @@ public void setDescription(String description) { this.description = description; } + public String getParentDisplayName() { + if (getParentEntityType() != null) { + return getParentEntityType().getDisplayName(); + } + return null; + } + @XmlTransient public List getAllowedEntityTypeList() { return allowedEntityTypeList; @@ -240,7 +247,6 @@ public void setIsInternal(Boolean isInternal) { this.isInternal = isInternal; } - @XmlTransient public List getEntityTypeChildren() { return entityTypeChildren; } @@ -280,7 +286,7 @@ public void setDefaultOwnerUserGroup(UserGroup defaultOwnerUserGroup) { @JsonIgnore public boolean isHasChildren() { - return !entityTypeChildren.isEmpty(); + return entityTypeChildren != null && !entityTypeChildren.isEmpty(); } @JsonIgnore diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/mcp/tools/McpToolContext.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/mcp/tools/McpToolContext.java index 1828e97cf..7dc0414ef 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/mcp/tools/McpToolContext.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/mcp/tools/McpToolContext.java @@ -62,7 +62,7 @@ public UserInfo getCurrentUser() { // Delegates to the shared helper so REST and MCP filter logbook types identically. public List getLogbookTypes() { - return LogbookDomainUtility.getLogbookTypes(domainFacade); + return LogbookDomainUtility.getLogbookTypes(domainFacade, false); } // Delegates to the shared helper so REST and MCP resolve systems identically. diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java index 0964f5845..36acd50fc 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java @@ -83,8 +83,19 @@ public class LogbookRoute extends ItemBaseRoute { @Operation(responses = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) @Produces(MediaType.APPLICATION_JSON) - public List getLogbookTypes() { - return LogbookDomainUtility.getLogbookTypes(domainFacade); + public List getLogbookTypes( + @Parameter(description = "Include logbook types that have children (grouping types).") + @QueryParam("includeAll") boolean includeAll) { + return LogbookDomainUtility.getLogbookTypes(domainFacade, includeAll); + } + + @GET + @Path("/LogbookTypeHierarchy") + @Operation(responses = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @Produces(MediaType.APPLICATION_JSON) + public List getLogbookTypeHierarchy() { + return LogbookDomainUtility.getLogbookTypeHierarchy(domainFacade); } // Delegates to the shared helper; behavior is unchanged, the item type list is returned unfiltered. @@ -114,7 +125,7 @@ public List getLogbookTemplates() { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) @Produces(MediaType.APPLICATION_JSON) public List getLogDocuments(@PathParam("logbookTypeId") int logbookTypeId, @PathParam("limit") int rowLimit) throws InvalidArgument { - List logbookTypes = getLogbookTypes(); + List logbookTypes = LogbookDomainUtility.getLogbookTypes(domainFacade, false); EntityType logbookType = null; for (EntityType type : logbookTypes) { @@ -479,7 +490,7 @@ private ItemDomainLogbook getLogDocumentById(int logDocumentId) throws InvalidAr } private EntityType verifyLogbookTypeArgument(Integer logbookTypeId) throws InvalidArgument { - List logbookTypes = getLogbookTypes(); + List logbookTypes = LogbookDomainUtility.getLogbookTypes(domainFacade, false); for (EntityType logbookType : logbookTypes) { if (logbookType.getId() == logbookTypeId) { diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/SearchRoute.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/SearchRoute.java index 681a45520..685dd4a2a 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/SearchRoute.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/SearchRoute.java @@ -195,7 +195,7 @@ public SearchEntitiesResults genericSearch(@RequestBody(required = true) SearchE // Delegates to the shared helper, which copies before removing the template type instead of filtering the domain's managed list in place. private List getLogbookTypes() { - return LogbookDomainUtility.getLogbookTypes(domainFacade); + return LogbookDomainUtility.getLogbookTypes(domainFacade, false); } // Delegates to the shared helper; behavior is unchanged, the item type list is returned unfiltered. diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/utilities/LogbookDomainUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/utilities/LogbookDomainUtility.java index b502c9f88..bc7981ba8 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/utilities/LogbookDomainUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/utilities/LogbookDomainUtility.java @@ -12,6 +12,7 @@ import gov.anl.aps.logr.portal.model.db.entities.ItemType; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; // Shared logbook domain lookups for the REST and MCP layers; copies before filtering so the domain's managed collection is never modified. @@ -25,9 +26,12 @@ public static Domain getLogbookDomain(DomainFacade domainFacade) { return domainFacade == null ? null : domainFacade.find(ItemDomainName.LOGBOOK_ID); } - // Convenience overload so callers holding only the facade need no domain lookup of their own. - public static List getLogbookTypes(DomainFacade domainFacade) { - return getLogbookTypes(getLogbookDomain(domainFacade)); + public static List getLogbookTypes(DomainFacade domainFacade, boolean includeParents) { + return getLogbookTypes(getLogbookDomain(domainFacade), includeParents); + } + + public static List getLogbookTypeHierarchy(DomainFacade domainFacade) { + return getLogbookTypeHierarchy(getLogbookDomain(domainFacade)); } // Convenience overload so callers holding only the facade need no domain lookup of their own. @@ -35,28 +39,47 @@ public static List getLogbookSystems(DomainFacade domainFacade) { return getLogbookSystems(getLogbookDomain(domainFacade)); } - // Allowed logbook types minus the template type, as a new list; never returns the managed collection. - public static List getLogbookTypes(Domain domain) { - // Callers may hold no domain; return an empty, modifiable list rather than throwing. - if (domain == null) { - return new ArrayList<>(); + public static List getLogbookTypes(Domain domain, boolean includeParents) { + List logbookTypes = getAllLogbookTypes(domain); + if (!includeParents) { + logbookTypes.removeIf(type -> type != null && type.isHasChildren()); + } + return logbookTypes; + } + + public static List getLogbookTypeHierarchy(Domain domain) { + List roots = getAllLogbookTypes(domain); + roots.removeIf(type -> type == null || type.getParentEntityType() != null); + roots.sort(Comparator.comparing(EntityType::getSortOrder, + Comparator.nullsLast(Comparator.naturalOrder()))); + for (EntityType root : roots) { + initializeChildren(root); } + return roots; + } - List allowedEntityTypeList = domain.getAllowedEntityTypeList(); - // A domain with no configured allowed types is valid, not an error. - if (allowedEntityTypeList == null) { + private static List getAllLogbookTypes(Domain domain) { + if (domain == null || domain.getAllowedEntityTypeList() == null) { return new ArrayList<>(); } - // Copy before filtering; never mutate the domain's managed collection. - List logbookTypes = new ArrayList<>(allowedEntityTypeList); + List logbookTypes = new ArrayList<>(domain.getAllowedEntityTypeList()); String templateName = EntityTypeName.template.getValue(); - // Null-tolerant exact-name match, preserving the original filtering semantics. - logbookTypes.removeIf(t -> t != null && templateName.equals(t.getName())); - + logbookTypes.removeIf(type -> type != null && templateName.equals(type.getName())); return logbookTypes; } + private static void initializeChildren(EntityType entityType) { + List children = entityType.getEntityTypeChildren(); + if (children == null) { + return; + } + children.size(); + for (EntityType child : children) { + initializeChildren(child); + } + } + // Systems configured for the logbook domain; unfiltered, empty when unavailable. public static List getLogbookSystems(Domain domain) { // Nothing filters this list, so the managed collection can be returned as-is. diff --git a/src/java/LogrPortal/test/gov/anl/aps/logr/portal/controllers/utilities/LogbookTypeLockoutTest.java b/src/java/LogrPortal/test/gov/anl/aps/logr/portal/controllers/utilities/LogbookTypeLockoutTest.java new file mode 100644 index 000000000..9a91c3fdd --- /dev/null +++ b/src/java/LogrPortal/test/gov/anl/aps/logr/portal/controllers/utilities/LogbookTypeLockoutTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.portal.controllers.utilities; + +import gov.anl.aps.logr.common.exceptions.InvalidObjectState; +import gov.anl.aps.logr.portal.model.db.entities.Domain; +import gov.anl.aps.logr.portal.model.db.entities.EntityInfo; +import gov.anl.aps.logr.portal.model.db.entities.EntityType; +import gov.anl.aps.logr.portal.model.db.entities.ItemDomainLogbook; +import java.util.ArrayList; +import java.util.Arrays; + +public class LogbookTypeLockoutTest { + + public static void main(String[] args) throws Exception { + int passed = 0; + int failed = 0; + for (java.lang.reflect.Method method : LogbookTypeLockoutTest.class.getDeclaredMethods()) { + if (method.getName().startsWith("test") && method.getParameterCount() == 0) { + try { + method.setAccessible(true); + method.invoke(null); + System.out.println("PASS " + method.getName()); + passed++; + } catch (java.lang.reflect.InvocationTargetException ex) { + System.out.println("FAIL " + method.getName() + ": " + ex.getCause()); + failed++; + } + } + } + System.out.println(passed + " passed, " + failed + " failed"); + if (failed > 0) { + System.exit(1); + } + } + + static void check(boolean condition, String message) { + if (!condition) { + throw new AssertionError(message); + } + } + + static void testGroupingTypeIsRejected() throws Exception { + EntityType parent = new EntityType(1, "Operations"); + parent.setDisplayName("Operations"); + EntityType child = new EntityType(2, "Storage-Ring"); + parent.setEntityTypeChildren(new ArrayList<>(Arrays.asList(child))); + + try { + new ItemDomainLogbookControllerUtility().completeCreateEntityInstance( + new ItemDomainLogbook(), parent, null, false); + throw new AssertionError("a grouping type must be rejected"); + } catch (InvalidObjectState ex) { + check(ex.getMessage().contains("grouping logbook type"), + "the rejection must explain that the type is a grouping type"); + } + } + + static void testNullTypeIsRejected() throws Exception { + try { + new ItemDomainLogbookControllerUtility().completeCreateEntityInstance( + new ItemDomainLogbook(), null, null, false); + throw new AssertionError("a null logbook type must be rejected"); + } catch (InvalidObjectState ex) { + check(ex.getMessage().contains("required"), + "the rejection must explain that a logbook type is required"); + } + } + + static void testLeafTypeIsAccepted() throws Exception { + EntityType leaf = new EntityType(1, "Storage-Ring"); + leaf.setEntityTypeChildren(new ArrayList<>()); + Domain domain = new Domain(); + domain.setAllowedEntityTypeList(new ArrayList<>(Arrays.asList(leaf))); + ItemDomainLogbook document = new ItemDomainLogbook(); + document.init(domain, new EntityInfo()); + + ItemDomainLogbook result = new ItemDomainLogbookControllerUtility() + .completeCreateEntityInstance(document, leaf, null, false); + + check(result == document, "the utility must return the supplied document"); + check(result.getEntityTypeList().size() == 1 + && result.getEntityTypeList().get(0).equals(leaf), + "the leaf type must be assigned to the document"); + } +} diff --git a/src/java/LogrPortal/test/gov/anl/aps/logr/rest/utilities/LogbookDomainUtilityTest.java b/src/java/LogrPortal/test/gov/anl/aps/logr/rest/utilities/LogbookDomainUtilityTest.java index bb6b5b375..7e00a492a 100644 --- a/src/java/LogrPortal/test/gov/anl/aps/logr/rest/utilities/LogbookDomainUtilityTest.java +++ b/src/java/LogrPortal/test/gov/anl/aps/logr/rest/utilities/LogbookDomainUtilityTest.java @@ -71,7 +71,7 @@ static void testTemplateIsFilteredOut() { new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE), new EntityType(2, "Maintenance")); - List result = LogbookDomainUtility.getLogbookTypes(domain); + List result = LogbookDomainUtility.getLogbookTypes(domain, false); check(!containsName(result, TEMPLATE), "template must be filtered out of the logbook types"); check(result.size() == 2, "the two non-template types must survive, got " + result.size()); @@ -79,6 +79,71 @@ static void testTemplateIsFilteredOut() { "non-template types must be preserved"); } + static void testParentTypesAreFilteredWhenIncludeParentsIsFalse() { + EntityType parent = new EntityType(1, "Operations"); + EntityType child = new EntityType(2, "Storage-Ring"); + child.setParentEntityType(parent); + parent.setEntityTypeChildren(new ArrayList<>(Arrays.asList(child))); + child.setEntityTypeChildren(new ArrayList<>()); + Domain domain = domainWithAllowedTypes(parent, child); + + List result = LogbookDomainUtility.getLogbookTypes(domain, false); + + check(result.size() == 1 && result.get(0).equals(child), + "the default list must contain leaves only"); + } + + static void testIncludeParentsPreservesParentTypes() { + EntityType parent = new EntityType(1, "Operations"); + EntityType child = new EntityType(2, "Storage-Ring"); + parent.setEntityTypeChildren(new ArrayList<>(Arrays.asList(child))); + child.setEntityTypeChildren(new ArrayList<>()); + Domain domain = domainWithAllowedTypes(parent, child); + + List result = LogbookDomainUtility.getLogbookTypes(domain, true); + + check(result.size() == 2 && containsName(result, "Operations"), + "includeAll must preserve grouping types"); + } + + static void testHierarchyReturnsSortedRootsWithNestedChildren() { + EntityType secondRoot = new EntityType(1, "Second"); + secondRoot.setSortOrder(2.0f); + secondRoot.setEntityTypeChildren(new ArrayList<>()); + EntityType firstRoot = new EntityType(2, "First"); + firstRoot.setSortOrder(1.0f); + EntityType child = new EntityType(3, "Child"); + child.setParentEntityType(firstRoot); + child.setEntityTypeChildren(new ArrayList<>()); + firstRoot.setEntityTypeChildren(new ArrayList<>(Arrays.asList(child))); + EntityType template = new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE); + template.setEntityTypeChildren(new ArrayList<>()); + Domain domain = domainWithAllowedTypes(secondRoot, child, template, firstRoot); + + List result = LogbookDomainUtility.getLogbookTypeHierarchy(domain); + + check(result.size() == 2, "the hierarchy must return only non-template roots"); + check(result.get(0).equals(firstRoot) && result.get(1).equals(secondRoot), + "hierarchy roots must be ordered by sort order"); + check(result.get(0).getEntityTypeChildren().size() == 1 + && result.get(0).getEntityTypeChildren().get(0).equals(child), + "children must remain nested under their root"); + } + + static void testHierarchyDoesNotMutateSourceList() { + EntityType root = new EntityType(1, "Operations"); + root.setEntityTypeChildren(new ArrayList<>()); + EntityType template = new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE); + template.setEntityTypeChildren(new ArrayList<>()); + Domain domain = domainWithAllowedTypes(template, root); + List managedList = domain.getAllowedEntityTypeList(); + + LogbookDomainUtility.getLogbookTypeHierarchy(domain); + + check(managedList.size() == 2 && containsName(managedList, TEMPLATE), + "building the hierarchy must not mutate the managed list"); + } + // The actual regression guard for the in-place mutation bug. static void testSourceListIsNotMutated() { Domain domain = domainWithAllowedTypes( @@ -86,7 +151,7 @@ static void testSourceListIsNotMutated() { new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE)); List managedList = domain.getAllowedEntityTypeList(); - LogbookDomainUtility.getLogbookTypes(domain); + LogbookDomainUtility.getLogbookTypes(domain, false); check(managedList.size() == 2, "the domain's managed allowed-entity-type list must not be modified, size is " + managedList.size()); @@ -100,7 +165,7 @@ static void testSourceListIsNotMutated() { static void testReturnedListIsNotTheManagedList() { Domain domain = domainWithAllowedTypes(new EntityType(1, "Ops-Shift")); - List result = LogbookDomainUtility.getLogbookTypes(domain); + List result = LogbookDomainUtility.getLogbookTypes(domain, false); check(result != domain.getAllowedEntityTypeList(), "callers must receive a copy, never the managed collection itself"); @@ -115,9 +180,9 @@ static void testRepeatedCallsAreStable() { new EntityType(1, "Ops-Shift"), new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE)); - List first = LogbookDomainUtility.getLogbookTypes(domain); - List second = LogbookDomainUtility.getLogbookTypes(domain); - List third = LogbookDomainUtility.getLogbookTypes(domain); + List first = LogbookDomainUtility.getLogbookTypes(domain, false); + List second = LogbookDomainUtility.getLogbookTypes(domain, false); + List third = LogbookDomainUtility.getLogbookTypes(domain, false); check(first.size() == second.size() && second.size() == third.size(), "repeated calls on the same domain must return identically sized results"); @@ -128,7 +193,7 @@ static void testRepeatedCallsAreStable() { static void testNoTemplatePresentIsANoOp() { Domain domain = domainWithAllowedTypes(new EntityType(1, "Ops-Shift"), new EntityType(2, "Maintenance")); - List result = LogbookDomainUtility.getLogbookTypes(domain); + List result = LogbookDomainUtility.getLogbookTypes(domain, false); check(result.size() == 2, "a domain without a template entry must be returned intact"); } @@ -136,7 +201,7 @@ static void testNoTemplatePresentIsANoOp() { static void testEmptyAllowedListReturnsEmpty() { Domain domain = domainWithAllowedTypes(); - List result = LogbookDomainUtility.getLogbookTypes(domain); + List result = LogbookDomainUtility.getLogbookTypes(domain, false); check(result != null && result.isEmpty(), "an empty allowed list must yield an empty result, not null"); } @@ -145,13 +210,13 @@ static void testNullAllowedListReturnsEmpty() { Domain domain = new Domain(); domain.setAllowedEntityTypeList(null); - List result = LogbookDomainUtility.getLogbookTypes(domain); + List result = LogbookDomainUtility.getLogbookTypes(domain, false); check(result != null && result.isEmpty(), "a null allowed list must yield an empty result, not throw"); } static void testNullDomainReturnsEmptyTypes() { - List result = LogbookDomainUtility.getLogbookTypes((Domain) null); + List result = LogbookDomainUtility.getLogbookTypes((Domain) null, false); check(result != null && result.isEmpty(), "a null domain must yield an empty result, not throw"); } @@ -162,7 +227,7 @@ static void testNullEntryInAllowedListIsTolerated() { domain.setAllowedEntityTypeList(new ArrayList<>(Arrays.asList( new EntityType(1, "Ops-Shift"), null, new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE)))); - List result = LogbookDomainUtility.getLogbookTypes(domain); + List result = LogbookDomainUtility.getLogbookTypes(domain, false); check(!containsNull(result) || result.size() == 2, "a null entry must not cause a NullPointerException"); check(!containsName(stripNulls(result), TEMPLATE), "template must still be filtered alongside a null entry"); @@ -231,13 +296,29 @@ static void testFacadeOverloadResolvesLogbookDomain() { new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE)); RecordingDomainFacade facade = new RecordingDomainFacade(domain); - List result = LogbookDomainUtility.getLogbookTypes(facade); + List result = LogbookDomainUtility.getLogbookTypes(facade, false); check(facade.requestedId != null && facade.requestedId == ItemDomainName.LOGBOOK_ID, "the facade must be queried with the logbook domain id, got " + facade.requestedId); check(result.size() == 1, "the facade overload must apply the same template filtering"); } + static void testFacadeIncludeAllAndHierarchyOverloads() { + EntityType parent = new EntityType(1, "Operations"); + EntityType child = new EntityType(2, "Storage-Ring"); + child.setParentEntityType(parent); + parent.setEntityTypeChildren(new ArrayList<>(Arrays.asList(child))); + child.setEntityTypeChildren(new ArrayList<>()); + Domain domain = domainWithAllowedTypes(parent, child); + RecordingDomainFacade facade = new RecordingDomainFacade(domain); + + check(LogbookDomainUtility.getLogbookTypes(facade, true).size() == 2, + "facade includeAll overload must preserve grouping types"); + List hierarchy = LogbookDomainUtility.getLogbookTypeHierarchy(facade); + check(hierarchy.size() == 1 && hierarchy.get(0).equals(parent), + "facade hierarchy overload must return roots"); + } + // The facade overload must not mutate the domain it resolves either. static void testFacadeOverloadDoesNotMutateDomain() { Domain domain = domainWithAllowedTypes( @@ -245,7 +326,7 @@ static void testFacadeOverloadDoesNotMutateDomain() { new EntityType(EntityTypeName.TEMPLATE_ID, TEMPLATE)); List managedList = domain.getAllowedEntityTypeList(); - LogbookDomainUtility.getLogbookTypes(new RecordingDomainFacade(domain)); + LogbookDomainUtility.getLogbookTypes(new RecordingDomainFacade(domain), false); check(managedList.size() == 2, "the facade overload must leave the managed list intact, size is " + managedList.size()); @@ -265,7 +346,7 @@ static void testFacadeOverloadReturnsSystems() { static void testNullFacadeIsSafe() { check(LogbookDomainUtility.getLogbookDomain(null) == null, "a null facade must resolve to a null domain, not throw"); - check(LogbookDomainUtility.getLogbookTypes((DomainFacade) null).isEmpty(), + check(LogbookDomainUtility.getLogbookTypes((DomainFacade) null, false).isEmpty(), "a null facade must yield empty logbook types, not throw"); check(LogbookDomainUtility.getLogbookSystems((DomainFacade) null).isEmpty(), "a null facade must yield empty systems, not throw");