Repository: kestra-io/kestra (java)

Issue:
Provide more precise error messages when service is unavailable (503 status code)
### Describe the issue

See the errors in the UI which popped up when EE preview env had a downtime  https://jam.dev/c/02a72a96-1dfb-4b0c-9450-678fd4a1923f 

Instead of just "Error", we should show a precise message: `503 Service Unavailable` which will be clearer for the user

### Environment

- Kestra Version: develop


Pull request description:
feat(core):  503 Service Unavailable status in error messages.
Closes #9107 

Reproduced the 503 response using `https://app.requestly.io/rules`.
When we will have `503` from server we will see something like this in UI

![image](https://github.com/user-attachments/assets/2ad5109a-731a-4d03-9e1f-dfbf1d85e022)


Patch:
diff --git a/ui/src/components/ErrorToast.vue b/ui/src/components/ErrorToast.vue
index 3167f5ae0..9f45726db 100644
--- a/ui/src/components/ErrorToast.vue
+++ b/ui/src/components/ErrorToast.vue
@@ -28,6 +28,10 @@
                     return this.message.title;
                 }
 
+                if (this.message.response.status === 503) {
+                    return "503 Service Unavailable";
+                }
+
                 if (this.message.content && this.message.content.message && this.message.content.message.indexOf(":") > 0) {
                     return this.message.content.message.substring(0, this.message.content.message.indexOf(":"));
                 }
@@ -53,7 +57,7 @@
                 const error =  {
                     type: "ERROR",
                     error: {
-                        message: this.text,
+                        message: this.title,
                         errors: this.items,
                     },
                     page: pageFromRoute(this.$route)
diff --git a/ui/src/components/ErrorToastContainer.vue b/ui/src/components/ErrorToastContainer.vue
index 0b3f2f3e3..e419d429c 100644
--- a/ui/src/components/ErrorToastContainer.vue
+++ b/ui/src/components/ErrorToastContainer.vue
@@ -49,6 +49,9 @@
         components: {Slack},
         methods: {
             async renderMarkdown() {
+                if (this.message.response && this.message.response.status === 503) {
+                    return await Markdown.render("Server is temporarily unavailable. Please try again later.", {html: true});
+                }
                 return await Markdown.render(this.message.message || this.message.content.message, {html: true});
             },
         },
diff --git a/ui/src/components/executions/Executions.vue b/ui/src/components/executions/Executions.vue
index 2f30d84a7..1e058f5e2 100644
--- a/ui/src/components/executions/Executions.vue
+++ b/ui/src/components/executions/Executions.vue
@@ -671,7 +671,7 @@
             let queryHasChanged = false;
 
             const queryKeys = Object.keys(query);
-            if (defaultNamespace && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
+            if (this.namespace === undefined && defaultNamespace && !queryKeys.some(key => key.startsWith("filters[namespace]"))) {
                 query["filters[namespace][EQUALS]"] = defaultNamespace;
                 queryHasChanged = true;
             }
@@ -740,24 +740,15 @@
             onStatusChange() {
                 this.load(this.onDataLoaded);
             },
-            loadQuery(base, stats) {
+            loadQuery(base) {
                 let queryFilter = this.queryWithFilter();
 
-                if (stats) {
-                    delete queryFilter["timeRange"];
-                    delete queryFilter["startDate"];
-                    delete queryFilter["endDate"];
-                } else if (queryFilter.timeRange) {
-                    delete queryFilter["startDate"];
-                    delete queryFilter["endDate"];
-                }
-
                 if (this.namespace) {
-                    queryFilter["namespace"] = this.namespace;
+                    queryFilter["filters[namespace][EQUALS]"] = this.namespace;
                 }
 
                 if (this.flowId) {
-                    queryFilter["flowId"] = this.flowId;
+                    queryFilter["filters[flowId][EQUALS]"] = this.flowId;
                 }
 
                 return _merge(base, queryFilter)
@@ -770,7 +761,7 @@
                     page: parseInt(this.$route.query.page || this.internalPageNumber),
                     sort: this.$route.query.sort || "state.startDate:desc",
                     state: this.$route.query.state ? [this.$route.query.state] : this.statuses
-                }, false)).finally(callback);
+                })).finally(callback);
             },
             durationFrom(item) {
                 return (+new Date() - new Date(item.state.startDate).getTime()) / 1000
@@ -787,7 +778,7 @@
                     const query = this.loadQuery({
                         sort: this.$route.query.sort || "state.startDate:desc",
                         state: this.$route.query.state ? [this.$route.query.state] : this.statuses,
-                    }, false);
+                    });
                     let options = {...query, ...this.actionOptions};
                     if (params) {
                         options = {...options, ...params}
@@ -971,7 +962,7 @@
                                     params: this.loadQuery({
                                         sort: this.$route.query.sort || "state.startDate:desc",
                                         state: this.$route.query.state ? [this.$route.query.state] : this.statuses
-                                    }, false),
+                                    }),
                                     data: filtered.labels
                                 })
                                 .then(r => {
@@ -1013,7 +1004,7 @@
                     page: parseInt(this.$route.query.page || this.internalPageNumber),
                     sort: this.$route.query.sort || "state.startDate:desc",
                     state: states
-                }, false)).then(() => {
+                })).then(() => {
                     this.$emit("state-count", this.total);
                 });
             }
diff --git a/ui/src/components/filter/KestraFilter.vue b/ui/src/components/filter/KestraFilter.vue
index 164ffd223..492dc7e08 100644
--- a/ui/src/components/filter/KestraFilter.vue
+++ b/ui/src/components/filter/KestraFilter.vue
@@ -190,11 +190,6 @@
             );
         }
 
-        query = Object.fromEntries(
-            Object.entries(query)
-                .map(([key, value]) => ([queryRemapper?.[key] ?? key, value]))
-        );
-
         if (props.legacyQuery) {
             /*
             TODO Known issue: the autocompletion for legacy queries is filling the first comparator found while the query retrieval is using EQUALS
@@ -206,7 +201,7 @@
                         values = [values];
                     }
 
-                    return values.map(value => key + Comparators.EQUALS + value);
+                    return values.map(value => (queryRemapper?.[key] ?? key) + Comparators.EQUALS + value);
                 }).join(" ");
         } else {
             filter.value = Object.entries(query)
@@ -224,12 +219,12 @@
                         values = [values];
                     }
 
-                    return values.map(value => filterKey + maybeSubKeyString + getComparator(comparator as Parameters<typeof getComparator>[0]) + value);
+                    return values.map(value => (queryRemapper?.[filterKey] ?? filterKey) + maybeSubKeyString + getComparator(comparator as Parameters<typeof getComparator>[0]) + value);
                 })
                 .join(" ");
         }
 
-        filter.value = filter.value + " "; // Add a trailing space to allow for autocompletion to work properly
+        filter.value = filter.value.length > 0 ? (filter.value + " ") : filter.value; // Add a trailing space to allow for autocompletion to work properly
     }, {immediate: true, deep: true});
 
     const COMPARATOR_LABEL_BY_VALUE: Record<Comparators, keyof typeof Comparators> = Object.fromEntries(
@@ -247,17 +242,28 @@
             return {};
         }
 
-        const KEY_MATCHER = "(\\S+?)";
+        const KEY_MATCHER = "((?:(?<!" + COMPARATORS_REGEX + ")\\S)+?)";
         const COMPARATOR_MATCHER = "(" + COMPARATORS_REGEX + ")";
         const MAYBE_PREVIOUS_VALUE = "(?:(?<=\\S),)?";
         const VALUE_MATCHER = "((?:" + MAYBE_PREVIOUS_VALUE + "(?:(?:\"[^\\n,]+\")|(?:[^\\s,]+)))+)";
-        const filterMatcher = new RegExp("\\s*" + KEY_MATCHER + COMPARATOR_MATCHER + VALUE_MATCHER + "\\s*", "g");
+        const filterMatcher = new RegExp("\\s*(?<!\\S)((?:" + KEY_MATCHER + COMPARATOR_MATCHER + VALUE_MATCHER + ")|(?:(?!" + COMPARATORS_REGEX + ")\\S(?!" + COMPARATORS_REGEX + "))+)(?!\\S)\\s*", "g");
         let matches: RegExpExecArray | null;
         const filters: Filter[] = [];
         while ((matches = filterMatcher.exec(filter.value)) !== null) {
-            const key = matches[1];
-            const comparator = matches[2] as Comparators;
-            const values = [...new Set(matches[3].split(",").filter(value => value !== "").map(value => value.replaceAll("\"", "")))];
+            const key = matches[2];
+
+            // If we're not in a {key}{comparator}{value} format, we assume it's a text search
+            if (key === undefined) {
+                filters.push({
+                    key: "text",
+                    comparator: "EQUALS",
+                    value: matches[1]
+                });
+                continue;
+            }
+
+            const comparator = matches[3] as Comparators;
+            const values = [...new Set(matches[4].split(",").filter(value => value !== "").map(value => value.replaceAll("\"", "")))];
 
             let comparatorLabel: keyof typeof Comparators | "IN" | "NOT_IN" = COMPARATOR_LABEL_BY_VALUE[comparator];
             if (values.length > 1) {
diff --git a/ui/src/components/flows/Flows.vue b/ui/src/components/flows/Flows.vue
index 6823d8018..85ad3d782 100644
--- a/ui/src/components/flows/Flows.vue
+++ b/ui/src/components/flows/Flows.vue
@@ -548,17 +548,7 @@
                             return this.$store
                                 .dispatch(
                                     "flow/exportFlowByQuery",
-                                    this.loadQuery(
-                                        {
-                                            namespace: this.$route.query.namespace
-                                                ? [this.$route.query.namespace]
-                                                : undefined,
-                                            q: this.$route.query.q
-                                                ? [this.$route.query.q]
-                                                : undefined,
-                                        },
-                                        false,
-                                    ),
+                                    this.loadQuery(),
                                 )
                                 .then((_) => {
                                     this.$toast().success(
@@ -592,17 +582,7 @@
                             return this.$store
                                 .dispatch(
                                     "flow/disableFlowByQuery",
-                                    this.loadQuery(
-                                        {
-                                            namespace: this.$route.query.namespace
-                                                ? [this.$route.query.namespace]
-                                                : undefined,
-                                            q: this.$route.query.q
-                                                ? [this.$route.query.q]
-                                                : undefined,
-                                        },
-                                        false,
-                                    ),
+                                    this.loadQuery(),
                                 )
                                 .then((r) => {
                                     this.$toast().success(
@@ -648,17 +628,7 @@
                             return this.$store
                                 .dispatch(
                                     "flow/enableFlowByQuery",
-                                    this.loadQuery(
-                                        {
-                                            namespace: this.$route.query.namespace
-                                                ? [this.$route.query.namespace]
-                                                : undefined,
-                                            q: this.$route.query.q
-                                                ? [this.$route.query.q]
-                                                : undefined,
-                                        },
-                                        false,
-                                    ),
+                                    this.loadQuery(),
                                 )
                                 .then((r) => {
                                     this.$toast().success(
@@ -698,17 +668,7 @@
                             return this.$store
                                 .dispatch(
                                     "flow/deleteFlowByQuery",
-                                    this.loadQuery(
-                                        {
-                                            namespace: this.$route.query.namespace
-                                                ? [this.$route.query.namespace]
-                                                : undefined,
-                                            q: this.$route.query.q
-                                                ? [this.$route.query.q]
-                                                : undefined,
-                                        },
-                                        false,
-                                    ),
+                                    this.loadQuery(),
                                 )
                                 .then((r) => {
                                     this.$toast().success(
@@ -772,11 +732,14 @@
                     return noState;
                 }
             },
-            loadQuery(base) {
-                let queryFilter = this.queryWithFilter();
+            loadQuery(base, ignoreDateFilters = true) {
+                let queryFilter = this.queryWithFilter(
+                    undefined,
+                    ignoreDateFilters ? ["startDate", "endDate", "timeRange"] : []
+                );
 
                 if (this.namespace) {
-                    queryFilter.namespace = this.namespace;
+                    queryFilter["filters[namespace][EQUALS]"] = this.namespace;
                 }
 
                 return _merge(base, queryFilter);
diff --git a/ui/src/composables/monaco/languages/filters/filterLanguageConfigurator.ts b/ui/src/composables/monaco/languages/filters/filterLanguageConfigurator.ts
index 491692243..5a066669c 100644
--- a/ui/src/composables/monaco/languages/filters/filterLanguageConfigurator.ts
+++ b/ui/src/composables/monaco/languages/filters/filterLanguageConfigurator.ts
@@ -61,10 +61,14 @@ export default class FilterLanguageConfigurator extends AbstractLanguageConfigur
                     root: [
                         [/[\w\\.]+/, {
                             cases: {
-                                [this.keyCompletionsRegex!.source]: {token: "variable.name", next: "@comparator"},
-                                "@default": "invalid"
+                                [this.keyCompletionsRegex!.source]: {
+                                    token: "variable.name",
+                                    next: "@comparator"
+                                },
+                                "@default": {token: "@rematch", next: "@rawText"}
                             }
-                        }]
+                        }],
+                        [/[^\w\\.]/, {token: "invalid", next: "@whitespace"}]
                     ],
                     comparator: [
                         [/@symbols/, {
@@ -73,6 +77,14 @@ export default class FilterLanguageConfigurator extends AbstractLanguageConfigur
                             }
                         }]
                     ],
+                    rawText: [
+                        [/\S+/, {
+                            cases: {
+                                [`(?:(?!${COMPARATORS_REGEX})\\S(?!${COMPARATORS_REGEX}))*`]: {token: "variable.value", next: "@whitespace"},
+                                "@default": {token: "invalid", next: "@whitespace"}
+                            }
+                        }]
+                    ],
                     value: [
                         [/"[^"]+(?![^"]*")/, "invalid"],
                         [new RegExp("\"[^\\n,\"]*\""), {
@@ -84,9 +96,14 @@ export default class FilterLanguageConfigurator extends AbstractLanguageConfigur
                             next: "@separator"
                         }]
                     ],
-                    separator: [
+                    whitespace: [
                         [/\s+/, {token: "space", next: "@popall"}],
-                        [",", {token: "comma", next: "@value"}]
+                        [/\S+/, {token: "invalid"}]
+                    ],
+                    separator: [
+                        {include: "@whitespace"},
+                        [",", {token: "comma", next: "@value"}],
+                        [/\S+/, {token: "invalid"}]
                     ]
                 }
             });
@@ -204,7 +221,11 @@ export default class FilterLanguageConfigurator extends AbstractLanguageConfigur
                         if (previousChar === ".") {
                             return TO_SUGGESTIONS(
                                 position,
-                                {word: "", startColumn: wordAtPosition.endColumn, endColumn: wordAtPosition.endColumn},
+                                {
+                                    word: "",
+                                    startColumn: wordAtPosition.endColumn,
+                                    endColumn: wordAtPosition.endColumn
+                                },
                                 []
                             );
                         }
diff --git a/ui/src/composables/monaco/languages/filters/impl/flowFilterLanguage.ts b/ui/src/composables/monaco/languages/filters/impl/flowFilterLanguage.ts
index 354f2d9d0..b5406611c 100644
--- a/ui/src/composables/monaco/languages/filters/impl/flowFilterLanguage.ts
+++ b/ui/src/composables/monaco/languages/filters/impl/flowFilterLanguage.ts
@@ -1,4 +1,4 @@
-import {Comparators, Completion, FilterKeyCompletions} from "../filterCompletion.ts";
+import {Comparators, Completion, FilterKeyCompletions, PICK_DATE_VALUE} from "../filterCompletion.ts";
 import {FilterLanguage} from "../filterLanguage.ts";
 import {Me} from "../../../../../stores/auth.ts";
 import permission from "../../../../../models/permission.ts";
@@ -32,6 +32,18 @@ const flowFilterKeys: Record<string, FilterKeyCompletions> = {
         undefined,
         true
     ),
+    timeRange: new FilterKeyCompletions(
+        [Comparators.EQUALS],
+        async (_, hardcodedValues) => hardcodedValues.RELATIVE_DATE
+    ),
+    startDate: new FilterKeyCompletions(
+        [Comparators.GREATER_THAN_OR_EQUAL_TO, Comparators.GREATER_THAN, Comparators.LESS_THAN_OR_EQUAL_TO, Comparators.LESS_THAN, Comparators.EQUALS, Comparators.NOT_EQUALS],
+        async () => PICK_DATE_VALUE
+    ),
+    endDate: new FilterKeyCompletions(
+        [Comparators.LESS_THAN_OR_EQUAL_TO, Comparators.LESS_THAN, Comparators.GREATER_THAN_OR_EQUAL_TO, Comparators.GREATER_THAN, Comparators.EQUALS, Comparators.NOT_EQUALS],
+        async () => PICK_DATE_VALUE
+    ),
 };
 
 class FlowFilterLanguage extends FilterLanguage {
diff --git a/ui/src/mixins/dataTableActions.js b/ui/src/mixins/dataTableActions.js
index 7ede83ac3..6914c5111 100644
--- a/ui/src/mixins/dataTableActions.js
+++ b/ui/src/mixins/dataTableActions.js
@@ -104,15 +104,26 @@ export default {
                 this.load(this.onDataLoaded);
             }
         },
-        queryWithFilter(namespace) {
+        queryWithFilter(namespace, excludedKeys = []) {
+            let query = this.$route.query;
+
+            if (namespace !== undefined) {
+                query = Object.fromEntries(
+                    Object.entries(query)
+                        .filter(([key]) => key.startsWith(`${namespace}[`))
+                        .map(([key, value]) => [key.substring(namespace.length + 2, key.length - 1), value])
+                );
+            }
+
+            if (excludedKeys.length > 0) {
+                const filterKeyMatcher = new RegExp(`^(?:filters\\[)?(?:${excludedKeys.join(")|(?:")})`);
+                query = Object.fromEntries(
+                    Object.entries(query).filter(([key]) => filterKeyMatcher.exec(key) === null)
+                );
+            }
+
             return _merge(_cloneDeep(
-                namespace === undefined
-                    ? this.$route.query
-                    : Object.fromEntries(
-                        Object.entries(this.$route.query)
-                            .filter(([key]) => key.startsWith(`${namespace}[`))
-                            .map(([key, value]) => [key.substring(namespace.length + 2, key.length - 1), value])
-                    ),
+                query,
                 this.filters || {}
             ));
         },


Test patch:
diff --git a/tests/src/main/java/io/kestra/core/storage/StorageTestSuite.java b/tests/src/main/java/io/kestra/core/storage/StorageTestSuite.java
index 16077a3a5..7f49f9312 100644
--- a/tests/src/main/java/io/kestra/core/storage/StorageTestSuite.java
+++ b/tests/src/main/java/io/kestra/core/storage/StorageTestSuite.java
@@ -202,31 +202,32 @@ public abstract class StorageTestSuite {
     @Test
     void listNoCrossTenant() throws Exception {
         String prefix = IdUtils.create();
-        String tenantId = IdUtils.create();
+        String tenantId1 = IdUtils.create();
+        String tenantId2 = IdUtils.create();
 
-        List<String> withTenant = Arrays.asList(
+        List<String> firstTenant = Arrays.asList(
             "/" + prefix + "/with/1.yml",
             "/" + prefix + "/with/2.yml",
             "/" + prefix + "/with/3.yml"
         );
-        withTenant.forEach(throwConsumer(s -> putFile(tenantId, s)));
-        List<String> nullTenant = Arrays.asList(
+        firstTenant.forEach(throwConsumer(s -> putFile(tenantId1, s)));
+        List<String> secondTenant = Arrays.asList(
             "/" + prefix + "/notenant/1.yml",
             "/" + prefix + "/notenant/2.yml",
             "/" + prefix + "/notenant/3.yml"
         );
-        nullTenant.forEach(throwConsumer(s -> putFile(null, s)));
+        secondTenant.forEach(throwConsumer(s -> putFile(tenantId2, s)));
 
-        List<FileAttributes> with = storageInterface.list(tenantId, prefix, new URI("/" + prefix + "/with"));
+        List<FileAttributes> with = storageInterface.list(tenantId1, prefix, new URI("/" + prefix + "/with"));
         assertThat(with.stream().map(FileAttributes::getFileName).toList()).containsExactlyInAnyOrder("1.yml", "2.yml", "3.yml");
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.list(tenantId, prefix, new URI("/" + prefix + "/notenant/"));
+            storageInterface.list(tenantId1, prefix, new URI("/" + prefix + "/notenant/"));
         });
 
-        List<FileAttributes> notenant = storageInterface.list(null, prefix, new URI("/" + prefix + "/notenant"));
+        List<FileAttributes> notenant = storageInterface.list(tenantId2, prefix, new URI("/" + prefix + "/notenant"));
         assertThat(notenant.stream().map(FileAttributes::getFileName).toList()).containsExactlyInAnyOrder("1.yml", "2.yml", "3.yml");
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.list(null, prefix, new URI("/" + prefix + "/with/"));
+            storageInterface.list(tenantId2, prefix, new URI("/" + prefix + "/with/"));
         });
     }
 
@@ -301,20 +302,21 @@ public abstract class StorageTestSuite {
     @Test
     void existsNoCrossTenant() throws Exception {
         String prefix = IdUtils.create();
-        String tenantId = IdUtils.create();
+        String tenantId1 = IdUtils.create();
+        String tenantId2 = IdUtils.create();
 
-        String withTenant = "/" + prefix + "/storage/withtenant.yml";
-        putFile(tenantId, withTenant);
-        String nullTenant = "/" + prefix + "/storage/nulltenant.yml";
-        putFile(null, nullTenant);
+        String firstTenant = "/" + prefix + "/storage/firstTenant.yml";
+        putFile(tenantId1, firstTenant);
+        String secondTenant = "/" + prefix + "/storage/secondTenant.yml";
+        putFile(tenantId2, secondTenant);
 
-        URI with = new URI(withTenant);
-        assertTrue(storageInterface.exists(tenantId, prefix, with));
-        assertFalse(storageInterface.exists(null, prefix, with));
+        URI with = new URI(firstTenant);
+        assertTrue(storageInterface.exists(tenantId1, prefix, with));
+        assertFalse(storageInterface.exists(tenantId2, prefix, with));
 
-        URI without = new URI(nullTenant);
-        assertFalse(storageInterface.exists(tenantId, prefix, without));
-        assertTrue(storageInterface.exists(null, prefix, without));
+        URI without = new URI(secondTenant);
+        assertFalse(storageInterface.exists(tenantId1, prefix, without));
+        assertTrue(storageInterface.exists(tenantId2, prefix, without));
 
     }
 
@@ -371,23 +373,24 @@ public abstract class StorageTestSuite {
     @Test
     void sizeNoCrossTenant() throws Exception {
         String prefix = IdUtils.create();
-        String tenantId = IdUtils.create();
+        String tenantId1 = IdUtils.create();
+        String tenantId2 = IdUtils.create();
 
-        String withTenant = "/" + prefix + "/storage/withtenant.yml";
-        putFile(tenantId, withTenant);
-        String nullTenant = "/" + prefix + "/storage/nulltenant.yml";
-        putFile(null, nullTenant);
+        String firstTenant = "/" + prefix + "/storage/firstTenant.yml";
+        putFile(tenantId1, firstTenant);
+        String secondTenant = "/" + prefix + "/storage/secondTenant.yml";
+        putFile(tenantId2, secondTenant);
 
-        URI with = new URI(withTenant);
-        assertThat(storageInterface.getAttributes(tenantId, prefix, with).getSize()).isEqualTo((long) CONTENT_STRING.length());
+        URI with = new URI(firstTenant);
+        assertThat(storageInterface.getAttributes(tenantId1, prefix, with).getSize()).isEqualTo((long) CONTENT_STRING.length());
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.getAttributes(null, prefix, with).getSize();
+            storageInterface.getAttributes(tenantId2, prefix, with).getSize();
         });
 
-        URI without = new URI(nullTenant);
-        assertThat(storageInterface.getAttributes(null, prefix, without).getSize()).isEqualTo((long) CONTENT_STRING.length());
+        URI without = new URI(secondTenant);
+        assertThat(storageInterface.getAttributes(tenantId2, prefix, without).getSize()).isEqualTo((long) CONTENT_STRING.length());
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.getAttributes(tenantId, prefix, without).getSize();
+            storageInterface.getAttributes(tenantId1, prefix, without).getSize();
         });
 
     }
@@ -445,23 +448,24 @@ public abstract class StorageTestSuite {
     @Test
     void lastModifiedTimeNoCrossTenant() throws Exception {
         String prefix = IdUtils.create();
-        String tenantId = IdUtils.create();
+        String tenantId1 = IdUtils.create();
+        String tenantId2 = IdUtils.create();
 
-        String withTenant = "/" + prefix + "/storage/withtenant.yml";
-        putFile(tenantId, withTenant);
-        String nullTenant = "/" + prefix + "/storage/nulltenant.yml";
-        putFile(null, nullTenant);
+        String firstTenant = "/" + prefix + "/storage/firstTenant.yml";
+        putFile(tenantId1, firstTenant);
+        String secondTenant = "/" + prefix + "/storage/secondTenant.yml";
+        putFile(tenantId2, secondTenant);
 
-        URI with = new URI(withTenant);
-        assertThat(storageInterface.getAttributes(tenantId, prefix, with).getLastModifiedTime()).isNotNull();
+        URI with = new URI(firstTenant);
+        assertThat(storageInterface.getAttributes(tenantId1, prefix, with).getLastModifiedTime()).isNotNull();
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.getAttributes(null, prefix, with).getLastModifiedTime();
+            storageInterface.getAttributes(tenantId2, prefix, with).getLastModifiedTime();
         });
 
-        URI without = new URI(nullTenant);
-        assertThat(storageInterface.getAttributes(null, prefix, without).getLastModifiedTime()).isNotNull();
+        URI without = new URI(secondTenant);
+        assertThat(storageInterface.getAttributes(tenantId2, prefix, without).getLastModifiedTime()).isNotNull();
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.getAttributes(tenantId, prefix, without).getLastModifiedTime();
+            storageInterface.getAttributes(tenantId1, prefix, without).getLastModifiedTime();
         });
 
     }
@@ -543,25 +547,26 @@ public abstract class StorageTestSuite {
     @Test
     void getAttributesNoCrossTenant() throws Exception {
         String prefix = IdUtils.create();
-        String tenantId = IdUtils.create();
+        String tenantId1 = IdUtils.create();
+        String tenantId2 = IdUtils.create();
 
-        String withTenant = "/" + prefix + "/storage/withtenant.yml";
-        putFile(tenantId, withTenant);
-        String nullTenant = "/" + prefix + "/storage/nulltenant.yml";
-        putFile(null, nullTenant);
+        String firstTenant = "/" + prefix + "/storage/firstTenant.yml";
+        putFile(tenantId1, firstTenant);
+        String secondTenant = "/" + prefix + "/storage/secondTenant.yml";
+        putFile(tenantId2, secondTenant);
 
-        URI with = new URI(withTenant);
-        FileAttributes attr = storageInterface.getAttributes(tenantId, prefix, with);
-        assertThat(attr.getFileName()).isEqualTo("withtenant.yml");
+        URI with = new URI(firstTenant);
+        FileAttributes attr = storageInterface.getAttributes(tenantId1, prefix, with);
+        assertThat(attr.getFileName()).isEqualTo("firstTenant.yml");
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.getAttributes(null, prefix, with);
+            storageInterface.getAttributes(tenantId2, prefix, with);
         });
 
-        URI without = new URI(nullTenant);
-        attr = storageInterface.getAttributes(null, prefix, without);
-        assertThat(attr.getFileName()).isEqualTo("nulltenant.yml");
+        URI without = new URI(secondTenant);
+        attr = storageInterface.getAttributes(tenantId2, prefix, without);
+        assertThat(attr.getFileName()).isEqualTo("secondTenant.yml");
         assertThrows(FileNotFoundException.class, () -> {
-            storageInterface.getAttributes(tenantId, prefix, without);
+            storageInterface.getAttributes(tenantId1, prefix, without);
         });
     }