diff --git a/dotCMS/src/main/java/com/dotcms/browser/BrowserAPIImpl.java b/dotCMS/src/main/java/com/dotcms/browser/BrowserAPIImpl.java
index df6d58202b3b..ae8e8c83e83d 100644
--- a/dotCMS/src/main/java/com/dotcms/browser/BrowserAPIImpl.java
+++ b/dotCMS/src/main/java/com/dotcms/browser/BrowserAPIImpl.java
@@ -121,6 +121,13 @@ public class BrowserAPIImpl implements BrowserAPI {
(ContentletJsonAPI.CONTENTLET_AS_JSON).append(", '$.fields.").append("fileName.").append("value')" +
" ");
+ /**
+ * Synthetic MIME Type that dotCMS assigns to HTML Pages at display time. It is never persisted to the
+ * {@code contentlet_as_json} column, so it can only be resolved through the HTMLPAGE base type. Legacy display
+ * code declares its own copies of this value; they are intentionally left alone.
+ */
+ private static final String DOTPAGE_MIME_TYPE = "application/dotpage";
+
private static final StringBuilder ASSET_NAME_LIKE = new StringBuilder().append("LOWER(%s) LIKE ? ");
private static final StringBuilder ASSET_NAME_EQ = new StringBuilder().append("LOWER(%s) = ? ");
@@ -2433,14 +2440,32 @@ private void appendOrderByQuery(StringBuilder sqlQuery, boolean orderByDesc) {
}
/**
- * Appends the specified MIME Types to the main SQL query.
+ * Appends the specified MIME Types to the main SQL query. Every requested MIME Type is routed to the only
+ * condition that can actually match it, and the resulting conditions are OR'ed together:
+ *
+ * - {@link #DOTPAGE_MIME_TYPE} resolves to the {@link BaseContentType#HTMLPAGE} base type. That value is
+ * synthetic: it is stamped onto a Page's view map at display time and is never written to
+ * {@code contentlet_as_json}, so the asset metadata check below can never match a Page.
+ * - Any other MIME Type keeps the asset metadata {@code contentType} check. Only File Assets and
+ * dotAssets carry asset metadata, which is precisely the "MIME type(s) (for file assets)" scoping that
+ * ADR-0018 assigns to this predicate.
+ *
+ * The match on the synthetic value is exact on purpose. A MIME Type that merely starts with it -- say,
+ * {@code application/dotpage-foo} -- must still go through the metadata check.
+ * The {@code struc} table is already joined by {@link #buildSelectBaseQuery(BrowserQuery, String)}, so the
+ * base type condition needs no extra join and no bound parameter.
*
* @param sqlQuery The main SQL query.
* @param mimeTypes The list of MIME Types specified by the client.
*/
private void appendMIMETypeQuery(final StringBuilder sqlQuery, final List mimeTypes) {
final String mimeTypesFilter = String.format(" AND (%s)", mimeTypes.stream()
- .map(mimeType -> String.format("jsonb_path_exists(c.contentlet_as_json,'$.fields.**.metadata ? (@.contentType like_regex \".*%s.*\")')", mimeType))
+ .map(mimeType -> {
+ if (DOTPAGE_MIME_TYPE.equals(mimeType)) {
+ return String.format("struc.structuretype = %d", BaseContentType.HTMLPAGE.getType());
+ }
+ return String.format("jsonb_path_exists(c.contentlet_as_json,'$.fields.**.metadata ? (@.contentType like_regex \".*%s.*\")')", mimeType);
+ })
.collect(Collectors.joining(" OR ")));
sqlQuery.append(mimeTypesFilter);
}
diff --git a/dotcms-integration/src/test/java/com/dotcms/browser/BrowserAPITest.java b/dotcms-integration/src/test/java/com/dotcms/browser/BrowserAPITest.java
index 3519877b9c0f..fa4286a91be8 100644
--- a/dotcms-integration/src/test/java/com/dotcms/browser/BrowserAPITest.java
+++ b/dotcms-integration/src/test/java/com/dotcms/browser/BrowserAPITest.java
@@ -22,6 +22,7 @@
import com.dotcms.rest.api.v1.content.search.strategies.GlobalSearchAttributeStrategy;
import com.dotcms.datagen.RoleDataGen;
import com.dotcms.datagen.SiteDataGen;
+import com.dotcms.datagen.TemplateDataGen;
import com.dotcms.datagen.TestDataUtils;
import com.dotcms.datagen.TestUserUtils;
import com.dotcms.datagen.UserDataGen;
@@ -95,6 +96,14 @@ public class BrowserAPITest extends IntegrationTestBase {
static Link testlink;
+ // Fixture for the MIME-type routing tests -- https://github.com/dotCMS/core/issues/36916
+ static final String DOTPAGE_MIME_TYPE = "application/dotpage";
+ static Host mimeHost;
+ static Folder mimeFolder, mimeSubFolder;
+ static HTMLPageAsset mimePage, mimePageAltLanguage, mimeSubFolderPage;
+ static FileAsset mimeJpgFile, mimePdfFile, mimeTxtFile, mimeSubFolderJpgFile;
+ static Link mimeLink;
+
@BeforeClass
public static void prepare() throws Exception {
//Setting web app environment
@@ -138,6 +147,92 @@ public static void prepare() throws Exception {
testPage = APILocator.getHTMLPageAssetAPI().fromContentlet(HTMLPageDataGen.checkin(page, IndexPolicy.FORCE));
testlink = new LinkDataGen().hostId(testHost.getIdentifier()).title("testLink").parent(testFolder).target("https://google.com").linkType("EXTERNAL").nextPersisted();
+
+ seedMimeTypeFixture();
+ }
+
+ /**
+ * Seeds a dedicated Site and folder tree for the MIME-type routing tests of
+ * issue #36916. It is kept apart from the
+ * fixture above so the assertions can be exact about which items a MIME-filtered browse returns.
+ *
+ * mimeFolder
+ * |_ mimePage HTMLPAGE, default language
+ * |_ mimePageAltLanguage HTMLPAGE, testLanguage
+ * |_ mimeJpgFile FILEASSET, image/jpeg
+ * |_ mimePdfFile FILEASSET, application/pdf
+ * |_ mimeTxtFile FILEASSET, text/plain
+ * |_ mimeLink LINK
+ * |_ mimeSubFolder
+ * |_ mimeSubFolderPage HTMLPAGE, default language
+ * |_ mimeSubFolderJpgFile FILEASSET, image/jpeg
+ *
+ */
+ private static void seedMimeTypeFixture() throws Exception {
+ mimeHost = new SiteDataGen().nextPersisted();
+ mimeFolder = new FolderDataGen().name("mimeFolder").site(mimeHost).nextPersisted();
+ mimeSubFolder = new FolderDataGen().name("mimeSubFolder").parent(mimeFolder).nextPersisted();
+
+ final Template template = new TemplateDataGen().host(mimeHost).nextPersisted();
+
+ mimePage = new HTMLPageDataGen(mimeFolder, template).title("mimePage").pageURL("mime-page")
+ .nextPersisted();
+ mimePageAltLanguage = new HTMLPageDataGen(mimeFolder, template).title("mimePageAltLanguage")
+ .pageURL("mime-page-alt-language").languageId(testLanguage.getId()).nextPersisted();
+ mimeSubFolderPage = new HTMLPageDataGen(mimeSubFolder, template).title("mimeSubFolderPage")
+ .pageURL("mime-sub-folder-page").nextPersisted();
+
+ mimeJpgFile = persistFileAsset(mimeFolder, copyTestResource("/images/test.jpg", "mimeJpgFile", ".jpg"));
+ mimePdfFile = persistFileAsset(mimeFolder, copyTestResource(
+ "/com/dotmarketing/portlets/contentlet/business/test_files/test.pdf", "mimePdfFile", ".pdf"));
+ mimeTxtFile = persistFileAsset(mimeFolder,
+ FileUtil.createTemporaryFile("mimeTxtFile", ".txt", "this is a test!"));
+ mimeSubFolderJpgFile = persistFileAsset(mimeSubFolder,
+ copyTestResource("/images/test.jpg", "mimeSubFolderJpgFile", ".jpg"));
+
+ mimeLink = new LinkDataGen().hostId(mimeHost.getIdentifier()).title("mimeLink").parent(mimeFolder)
+ .target("https://google.com").linkType("EXTERNAL").nextPersisted();
+ }
+
+ /**
+ * Copies a classpath test resource into a temporary file so that a File Asset can be generated from it. The
+ * file extension matters here: the asset metadata -- and therefore the {@code contentType} the SQL MIME
+ * predicate looks at -- is derived from the actual file contents on check-in.
+ */
+ private static File copyTestResource(final String resourcePath, final String prefix, final String suffix)
+ throws IOException {
+ final URL url = BrowserAPITest.class.getResource(resourcePath);
+ assertNotNull("Test resource must exist in the classpath: " + resourcePath, url);
+ final File tempFile = File.createTempFile(prefix, suffix);
+ FileUtils.copyFile(new File(url.getFile()), tempFile);
+ return tempFile;
+ }
+
+ private static FileAsset persistFileAsset(final Folder folder, final File file)
+ throws DotDataException, DotSecurityException {
+ return APILocator.getFileAssetAPI().fromContentlet(new FileAssetDataGen(file).folder(folder)
+ .setPolicy(IndexPolicy.WAIT_FOR).nextPersisted());
+ }
+
+ /**
+ * Runs a browse against the MIME-type fixture folder through {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * and returns the Identifiers that came back. This entry point deliberately has no in-memory MIME filter
+ * (see research R2), so what it returns is what the SQL predicate itself selected.
+ */
+ private Set browseIdentifiers(final Folder folder, final List mimeTypes)
+ throws DotSecurityException, DotDataException {
+ return browserAPI.getFolderContentList(BrowserQuery.builder()
+ .withUser(APILocator.systemUser())
+ .withHostOrFolderId(folder.getIdentifier())
+ .showPages(true)
+ .showFiles(true)
+ .showDotAssets(true)
+ .showFolders(false)
+ .showWorking(true)
+ .showMimeTypes(mimeTypes)
+ .build()).stream()
+ .map(Treeable::getIdentifier)
+ .collect(Collectors.toSet());
}
/**
@@ -2125,4 +2220,227 @@ public void test_getPaginatedContents_scanLimitStopsLoop() throws Exception {
BrowserAPIImpl.BROWSER_DB_MAX_SCAN_ROWS_DEFAULT);
}
}
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: A folder holding Pages, File Assets and a Link is browsed filtering by the
+ * synthetic {@code application/dotpage} MIME type -- the value the legacy redirect target picker sends.
+ * - Expected Result: The Pages under the folder are returned and no File Asset is. The synthetic
+ * MIME type is never persisted to {@code contentlet_as_json}, so it can only resolve by HTMLPAGE base
+ * type.
+ *
+ * Contract case C1 -- AC-001 and AC-006 of #36916.
+ */
+ @Test
+ public void test_getFolderContentList_dotPageMimeType_returnsPages() throws Exception {
+ final Set identifiers = browseIdentifiers(mimeFolder, List.of(DOTPAGE_MIME_TYPE));
+
+ assertTrue("Pages must be returned for an '" + DOTPAGE_MIME_TYPE + "' browse, but got: " + identifiers,
+ identifiers.containsAll(
+ Set.of(mimePage.getIdentifier(), mimePageAltLanguage.getIdentifier())));
+ assertFalse("The JPG File Asset must not be returned for an '" + DOTPAGE_MIME_TYPE + "' browse",
+ identifiers.contains(mimeJpgFile.getIdentifier()));
+ assertFalse("The PDF File Asset must not be returned for an '" + DOTPAGE_MIME_TYPE + "' browse",
+ identifiers.contains(mimePdfFile.getIdentifier()));
+ assertFalse("The TXT File Asset must not be returned for an '" + DOTPAGE_MIME_TYPE + "' browse",
+ identifiers.contains(mimeTxtFile.getIdentifier()));
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: The same folder is browsed filtering by a real MIME type, {@code image/jpeg},
+ * with Pages enabled.
+ * - Expected Result: Only the matching File Asset comes back. Pages must be dropped by the SQL
+ * predicate itself, since two of the three consumers of the query have no in-memory MIME filter.
+ *
+ * Contract case C2, invariant I2 -- AC-005 and AC-007 of
+ * #36916.
+ */
+ @Test
+ public void test_getFolderContentList_realMimeType_returnsMatchingFilesAndNoPages() throws Exception {
+ final Set identifiers = browseIdentifiers(mimeFolder, List.of("image/jpeg"));
+
+ assertTrue("The JPG File Asset must be returned for an 'image/jpeg' browse, but got: " + identifiers,
+ identifiers.contains(mimeJpgFile.getIdentifier()));
+ assertFalse("The PDF File Asset must not match 'image/jpeg'",
+ identifiers.contains(mimePdfFile.getIdentifier()));
+ assertFalse("The TXT File Asset must not match 'image/jpeg'",
+ identifiers.contains(mimeTxtFile.getIdentifier()));
+ assertFalse("Pages must never leak into a real MIME type browse",
+ identifiers.contains(mimePage.getIdentifier()));
+ assertFalse("Pages must never leak into a real MIME type browse",
+ identifiers.contains(mimePageAltLanguage.getIdentifier()));
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: The folder is browsed filtering by two real MIME types at once.
+ * - Expected Result: Both matching File Assets come back and nothing else. This is the pure
+ * regression guard for the MIME filtering added by PR #34217, and it must behave identically before and
+ * after the fix.
+ *
+ * Contract case C4, invariant I1 -- AC-005 of
+ * #36916.
+ */
+ @Test
+ public void test_getFolderContentList_multipleRealMimeTypes_areUnchanged() throws Exception {
+ final Set identifiers = browseIdentifiers(mimeFolder, List.of("image/jpeg", "application/pdf"));
+
+ assertTrue("The JPG File Asset must be returned, but got: " + identifiers,
+ identifiers.contains(mimeJpgFile.getIdentifier()));
+ assertTrue("The PDF File Asset must be returned, but got: " + identifiers,
+ identifiers.contains(mimePdfFile.getIdentifier()));
+ assertFalse("The TXT File Asset matches neither MIME type",
+ identifiers.contains(mimeTxtFile.getIdentifier()));
+ assertFalse("Pages must never leak into a real MIME type browse",
+ identifiers.contains(mimePage.getIdentifier()));
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: The folder is browsed with a MIME type that merely starts with the synthetic
+ * one, {@code application/dotpage-foo}.
+ * - Expected Result: No Page comes back. Only the exact string {@code application/dotpage} may be
+ * routed to the base type branch; anything else goes through the asset metadata check.
+ *
+ * Invariant I3 of #36916.
+ */
+ @Test
+ public void test_getFolderContentList_dotPageMimeTypePrefix_isNotRoutedToBaseType() throws Exception {
+ final Set identifiers = browseIdentifiers(mimeFolder, List.of(DOTPAGE_MIME_TYPE + "-foo"));
+
+ assertFalse("Only the exact '" + DOTPAGE_MIME_TYPE + "' may resolve Pages by base type",
+ identifiers.contains(mimePage.getIdentifier()));
+ assertFalse("Only the exact '" + DOTPAGE_MIME_TYPE + "' may resolve Pages by base type",
+ identifiers.contains(mimePageAltLanguage.getIdentifier()));
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: The folder is browsed asking for the synthetic Page MIME type and a real one
+ * at the same time -- a folder holding a mix of Pages, File Assets and a Link.
+ * - Expected Result: Both the Pages and the matching File Asset come back; the File Assets that
+ * match neither requested MIME type do not.
+ *
+ * Contract case C3 -- AC-004 of #36916.
+ */
+ @Test
+ public void test_getFolderContentList_mixedMimeTypes_returnsPagesAndMatchingFiles() throws Exception {
+ final Set identifiers =
+ browseIdentifiers(mimeFolder, List.of(DOTPAGE_MIME_TYPE, "image/jpeg"));
+
+ assertTrue("Pages must be returned for a mixed browse, but got: " + identifiers,
+ identifiers.containsAll(
+ Set.of(mimePage.getIdentifier(), mimePageAltLanguage.getIdentifier())));
+ assertTrue("The JPG File Asset must be returned for a mixed browse, but got: " + identifiers,
+ identifiers.contains(mimeJpgFile.getIdentifier()));
+ assertFalse("The PDF File Asset matches neither requested MIME type",
+ identifiers.contains(mimePdfFile.getIdentifier()));
+ assertFalse("The TXT File Asset matches neither requested MIME type",
+ identifiers.contains(mimeTxtFile.getIdentifier()));
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: The same mixed browse is requested twice with the MIME types in opposite
+ * order.
+ * - Expected Result: Both requests select exactly the same rows -- the requested MIME types are
+ * an unordered set as far as the generated predicate is concerned.
+ *
+ * Invariant I5 of #36916.
+ */
+ @Test
+ public void test_getFolderContentList_mimeTypeOrder_doesNotChangeResults() throws Exception {
+ final Set pageFirst =
+ browseIdentifiers(mimeFolder, List.of(DOTPAGE_MIME_TYPE, "image/jpeg"));
+ final Set imageFirst =
+ browseIdentifiers(mimeFolder, List.of("image/jpeg", DOTPAGE_MIME_TYPE));
+
+ assertEquals("The order of the requested MIME types must not change the result set", pageFirst,
+ imageFirst);
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: A sub-folder holding a Page and a JPG File Asset is browsed, first by the
+ * synthetic Page MIME type and then by a real one.
+ * - Expected Result: Each browse returns only its own kind, and neither returns items from the
+ * parent folder. Folders themselves are listed separately by the Browser API and are out of scope here.
+ *
+ * AC-004 of #36916.
+ */
+ @Test
+ public void test_getFolderContentList_subFolder_filtersEachTypeAsExpected() throws Exception {
+ final Set pages = browseIdentifiers(mimeSubFolder, List.of(DOTPAGE_MIME_TYPE));
+
+ assertTrue("The sub-folder Page must be returned, but got: " + pages,
+ pages.contains(mimeSubFolderPage.getIdentifier()));
+ assertFalse("The sub-folder JPG File Asset must not match '" + DOTPAGE_MIME_TYPE + "'",
+ pages.contains(mimeSubFolderJpgFile.getIdentifier()));
+ assertFalse("A sub-folder browse must not return items from its parent folder",
+ pages.contains(mimePage.getIdentifier()));
+
+ final Set images = browseIdentifiers(mimeSubFolder, List.of("image/jpeg"));
+
+ assertTrue("The sub-folder JPG File Asset must be returned, but got: " + images,
+ images.contains(mimeSubFolderJpgFile.getIdentifier()));
+ assertFalse("Pages must never leak into a real MIME type browse",
+ images.contains(mimeSubFolderPage.getIdentifier()));
+ }
+
+ /**
+ *
+ * - Method to test: {@link BrowserAPI#getFolderContentList(BrowserQuery)}
+ * - Given Scenario: Pages are browsed by the synthetic MIME type under a specific language, with
+ * and without the default language fallback the legacy dialog turns on.
+ * - Expected Result: With the fallback on, both the Page in the requested language and the one in
+ * the default language come back; with it off, only the Page in the requested language does. Language
+ * resolution is unchanged by the MIME routing.
+ *
+ * AC-008 of #36916.
+ */
+ @Test
+ public void test_getFolderContentList_dotPageMimeType_honorsLanguageFallback() throws Exception {
+ final Set withFallback = browseIdentifiersByLanguage(testLanguage.getId(), true);
+
+ assertTrue("The Page in the requested language must be returned, but got: " + withFallback,
+ withFallback.contains(mimePageAltLanguage.getIdentifier()));
+ assertTrue("The default language Page must be returned when the fallback is on, but got: " + withFallback,
+ withFallback.contains(mimePage.getIdentifier()));
+
+ final Set withoutFallback = browseIdentifiersByLanguage(testLanguage.getId(), false);
+
+ assertTrue("The Page in the requested language must be returned, but got: " + withoutFallback,
+ withoutFallback.contains(mimePageAltLanguage.getIdentifier()));
+ assertFalse("The default language Page must not be returned when the fallback is off",
+ withoutFallback.contains(mimePage.getIdentifier()));
+ }
+
+ /**
+ * Same browse as {@link #browseIdentifiers(Folder, List)} but scoped to a language, mirroring how the legacy
+ * dialog resolves content -- see {@code BrowserAjax.getFolderContentWithDotAssets}.
+ */
+ private Set browseIdentifiersByLanguage(final long languageId, final boolean showDefaultLangItems)
+ throws DotSecurityException, DotDataException {
+ return browserAPI.getFolderContentList(BrowserQuery.builder()
+ .withUser(APILocator.systemUser())
+ .withHostOrFolderId(mimeFolder.getIdentifier())
+ .showPages(true)
+ .showFiles(true)
+ .showFolders(false)
+ .showWorking(true)
+ .withLanguageId(languageId)
+ .showDefaultLangItems(showDefaultLangItems)
+ .showMimeTypes(List.of(DOTPAGE_MIME_TYPE))
+ .build()).stream()
+ .map(Treeable::getIdentifier)
+ .collect(Collectors.toSet());
+ }
}
diff --git a/dotcms-integration/src/test/java/com/dotmarketing/portlets/browser/ajax/BrowserAjaxTest.java b/dotcms-integration/src/test/java/com/dotmarketing/portlets/browser/ajax/BrowserAjaxTest.java
index 31e9d8abb194..1f94703ac969 100644
--- a/dotcms-integration/src/test/java/com/dotmarketing/portlets/browser/ajax/BrowserAjaxTest.java
+++ b/dotcms-integration/src/test/java/com/dotmarketing/portlets/browser/ajax/BrowserAjaxTest.java
@@ -49,6 +49,9 @@
*/
public class BrowserAjaxTest {
+ /** Synthetic MIME type the legacy browser dialogs send for Pages. See {@code PageViewStrategy}. */
+ private static final String DOTPAGE_MIME_TYPE = "application/dotpage";
+
private static Host testSite = null;
private static Folder parentFolderOne = null;
private static Folder parentFolderTwo = null;
@@ -361,4 +364,38 @@ public void test_getHosts_ShoudlRetrieveSystemHosts() throws DotDataException, D
assertTrue(hosts.stream().anyMatch(host -> host.get("identifier").equals(APILocator.systemHost().getIdentifier())) && loggedInUser.getFirstName().equals(user.getFirstName()) );
setUpDwrContext(APILocator.getUserAPI().getSystemUser());
}
+
+ /**
+ *
+ * - Method to test: {@link BrowserAjax#getFolderContentWithDotAssets(String, int, int, String,
+ * List, List, boolean, boolean, boolean, String, boolean, boolean, boolean)}
+ * - Given Scenario: The legacy "Select a file" / "Select link" dialog browses a folder that
+ * contains a Page, passing the synthetic {@code application/dotpage} MIME type. This is the exact call the
+ * redirect target picker makes from a Page's Properties.
+ * - Expected Result: The Page is listed and {@code total} is greater than zero -- instead of the
+ * {@code {total: 0, list: []}} the dialog reports today.
+ *
+ * AC-001 and AC-003 of #36916.
+ */
+ @Test
+ public void test_getFolderContentWithDotAssets_dotPageMimeType_returnsPages() throws Exception {
+ setUpDwrContext(APILocator.getUserAPI().getSystemUser());
+
+ final Host host = new SiteDataGen().nextPersisted();
+ final Folder folder = new FolderDataGen().site(host).nextPersisted();
+ final Template template = new TemplateDataGen().host(host).nextPersisted();
+ final HTMLPageAsset page = new HTMLPageDataGen(folder, template).nextPersisted();
+
+ final BrowserAjax browserAjax = new BrowserAjax();
+ final Map results = browserAjax.getFolderContentWithDotAssets(folder.getIdentifier(), 0,
+ 100, "", List.of(DOTPAGE_MIME_TYPE), List.of(), false, true, false, "moddate", true, true, false);
+
+ assertNotNull("The dialog must always get a result map back", results);
+ final List