From 44f5ab30e95336377e82dd5e971b14586724258e Mon Sep 17 00:00:00 2001 From: Lord of Abyss <103809695+Abyss-lord@users.noreply.github.com> Date: Wed, 1 Jan 2025 07:18:36 +0800 Subject: [PATCH 1/2] [#6027] improvement(CLI): fix Gravitino CLI get wrong catalogName (#6048) ### What changes were proposed in this pull request? Fix Gravitino CLI get wrong catalogName when set metalake name by --name option. A hint is given if the -metalake option is not set. ### Why are the changes needed? Fix: #6027 ### Does this PR introduce _any_ user-facing change? No ### How was this patch tested? local test ```bash gcli table list --name Hive_catalog.default Missing --metalake option. ``` --- .../apache/gravitino/cli/ErrorMessages.java | 1 + .../org/apache/gravitino/cli/FullName.java | 5 +--- .../apache/gravitino/cli/TestFulllName.java | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java index a5253664501..757e7c2cb30 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java @@ -30,6 +30,7 @@ public class ErrorMessages { public static final String UNKNOWN_TABLE = "Unknown table name."; public static final String MALFORMED_NAME = "Malformed entity name."; public static final String MISSING_NAME = "Missing --name option."; + public static final String MISSING_METALAKE = "Missing --metalake option."; public static final String MISSING_GROUP = "Missing --group option."; public static final String MISSING_USER = "Missing --user option."; public static final String MISSING_ROLE = "Missing --role option."; diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java index f2eef2a5a2d..8af7322dc29 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java @@ -73,10 +73,7 @@ public String getMetalakeName() { } } - // Extract the metalake name from the full name option - if (line.hasOption(GravitinoOptions.NAME)) { - return line.getOptionValue(GravitinoOptions.NAME).split("\\.")[0]; - } + System.err.println(ErrorMessages.MISSING_METALAKE); return null; } diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java index e5ec92e1063..48ee79cfcc5 100644 --- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java @@ -212,4 +212,28 @@ public void testMalformedName() throws ParseException { String output = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); assertEquals(output, ErrorMessages.MALFORMED_NAME); } + + @Test + @SuppressWarnings("DefaultCharset") + public void testGetMetalake() throws ParseException { + String[] args = { + "table", "list", "-i", "-m", "demo_metalake", "--name", "Hive_catalog.default" + }; + CommandLine commandLine = new DefaultParser().parse(options, args); + FullName fullName = new FullName(commandLine); + String metalakeName = fullName.getMetalakeName(); + assertEquals(metalakeName, "demo_metalake"); + } + + @Test + @SuppressWarnings("DefaultCharset") + public void testGetMetalakeWithoutMetalakeOption() throws ParseException { + String[] args = {"table", "list", "-i", "--name", "Hive_catalog.default"}; + CommandLine commandLine = new DefaultParser().parse(options, args); + FullName fullName = new FullName(commandLine); + String metalakeName = fullName.getMetalakeName(); + assertNull(metalakeName); + String errOutput = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); + assertEquals(errOutput, ErrorMessages.MISSING_METALAKE); + } } From 539ac3634bd142d63e8878408dd858ca7ccb5216 Mon Sep 17 00:00:00 2001 From: Lord of Abyss <103809695+Abyss-lord@users.noreply.github.com> Date: Thu, 2 Jan 2025 07:02:10 +0800 Subject: [PATCH 2/2] [#5961] feat(CLI): Add details and list command to CLI for model. (#6053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What changes were proposed in this pull request? add get and list command to model. - get: `gcli model get -m demo_metalake --name catalog.schema`, Displays the name of the Model along with all versions. - list:`gcli model list -m demo_metalake --name catalog.schema`, Display all models in this schema. ### Why are the changes needed? Fix: #5961 ### Does this PR introduce _any_ user-facing change? NO ### How was this patch tested? ut --- .../apache/gravitino/cli/CommandEntities.java | 2 + .../apache/gravitino/cli/ErrorMessages.java | 1 + .../org/apache/gravitino/cli/FullName.java | 9 + .../gravitino/cli/GravitinoCommandLine.java | 39 +++ .../gravitino/cli/TestableCommandLine.java | 12 + .../gravitino/cli/commands/ListModel.java | 80 ++++++ .../gravitino/cli/commands/ModelDetails.java | 92 ++++++ .../gravitino/cli/TestModelCommand.java | 270 ++++++++++++++++++ 8 files changed, 505 insertions(+) create mode 100644 clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java create mode 100644 clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java create mode 100644 clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java index dc033202956..2dd50974ea9 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java @@ -37,6 +37,7 @@ public class CommandEntities { public static final String TOPIC = "topic"; public static final String FILESET = "fileset"; public static final String ROLE = "role"; + public static final String MODEL = "model"; private static final HashSet VALID_ENTITIES = new HashSet<>(); @@ -52,6 +53,7 @@ public class CommandEntities { VALID_ENTITIES.add(TOPIC); VALID_ENTITIES.add(FILESET); VALID_ENTITIES.add(ROLE); + VALID_ENTITIES.add(MODEL); } /** diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java index 757e7c2cb30..4bd523ec280 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java @@ -28,6 +28,7 @@ public class ErrorMessages { public static final String UNKNOWN_CATALOG = "Unknown catalog name."; public static final String UNKNOWN_SCHEMA = "Unknown schema name."; public static final String UNKNOWN_TABLE = "Unknown table name."; + public static final String UNKNOWN_MODEL = "Unknown model name."; public static final String MALFORMED_NAME = "Malformed entity name."; public static final String MISSING_NAME = "Missing --name option."; public static final String MISSING_METALAKE = "Missing --metalake option."; diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java index 8af7322dc29..c21d21af483 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java @@ -96,6 +96,15 @@ public String getSchemaName() { return getNamePart(1); } + /** + * Retrieves the model name from the second part of the full name option. + * + * @return The model name, or null if not found + */ + public String getModelName() { + return getNamePart(2); + } + /** * Retrieves the table name from the third part of the full name option. * diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java index 7869dd97b62..8cd335bebbe 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java @@ -152,6 +152,8 @@ private void executeCommand() { handleTagCommand(); } else if (entity.equals(CommandEntities.ROLE)) { handleRoleCommand(); + } else if (entity.equals(CommandEntities.MODEL)) { + handleModelCommand(); } } @@ -1150,6 +1152,43 @@ private void handleFilesetCommand() { } } + private void handleModelCommand() { + String url = getUrl(); + String auth = getAuth(); + String userName = line.getOptionValue(GravitinoOptions.LOGIN); + FullName name = new FullName(line); + String metalake = name.getMetalakeName(); + String catalog = name.getCatalogName(); + String schema = name.getSchemaName(); + + Command.setAuthenticationMode(auth, userName); + + List missingEntities = Lists.newArrayList(); + if (catalog == null) missingEntities.add(CommandEntities.CATALOG); + if (schema == null) missingEntities.add(CommandEntities.SCHEMA); + + // Handle CommandActions.LIST action separately as it doesn't require the `model` + if (CommandActions.LIST.equals(command)) { + checkEntities(missingEntities); + newListModel(url, ignore, metalake, catalog, schema).handle(); + return; + } + + String model = name.getModelName(); + if (model == null) missingEntities.add(CommandEntities.MODEL); + checkEntities(missingEntities); + + switch (command) { + case CommandActions.DETAILS: + newModelDetails(url, ignore, metalake, catalog, schema, model).handle(); + break; + + default: + System.err.println(ErrorMessages.UNSUPPORTED_ACTION); + break; + } + } + /** * Retrieves the Gravitinno URL from the command line options or the GRAVITINO_URL environment * variable or the Gravitio config file. diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java index f07244c0053..3cfd84ad83c 100644 --- a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java @@ -66,6 +66,7 @@ import org.apache.gravitino.cli.commands.ListIndexes; import org.apache.gravitino.cli.commands.ListMetalakeProperties; import org.apache.gravitino.cli.commands.ListMetalakes; +import org.apache.gravitino.cli.commands.ListModel; import org.apache.gravitino.cli.commands.ListRoles; import org.apache.gravitino.cli.commands.ListSchema; import org.apache.gravitino.cli.commands.ListSchemaProperties; @@ -79,6 +80,7 @@ import org.apache.gravitino.cli.commands.MetalakeDetails; import org.apache.gravitino.cli.commands.MetalakeDisable; import org.apache.gravitino.cli.commands.MetalakeEnable; +import org.apache.gravitino.cli.commands.ModelDetails; import org.apache.gravitino.cli.commands.OwnerDetails; import org.apache.gravitino.cli.commands.RemoveAllTags; import org.apache.gravitino.cli.commands.RemoveCatalogProperty; @@ -907,4 +909,14 @@ protected CatalogDisable newCatalogDisable( String url, boolean ignore, String metalake, String catalog) { return new CatalogDisable(url, ignore, metalake, catalog); } + + protected ListModel newListModel( + String url, boolean ignore, String metalake, String catalog, String schema) { + return new ListModel(url, ignore, metalake, catalog, schema); + } + + protected ModelDetails newModelDetails( + String url, boolean ignore, String metalake, String catalog, String schema, String model) { + return new ModelDetails(url, ignore, metalake, catalog, schema, model); + } } diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java new file mode 100644 index 00000000000..1528e954b1c --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListModel.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.gravitino.cli.commands; + +import com.google.common.base.Joiner; +import java.util.Arrays; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; +import org.apache.gravitino.cli.ErrorMessages; +import org.apache.gravitino.client.GravitinoClient; +import org.apache.gravitino.exceptions.NoSuchCatalogException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchSchemaException; + +/** List the names of all models in a schema. */ +public class ListModel extends Command { + protected final String metalake; + protected final String catalog; + protected final String schema; + + /** + * List the names of all models in a schema. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + * @param metalake The name of the metalake. + * @param catalog The name of the catalog. + * @param schema The name of schema. + */ + public ListModel( + String url, boolean ignoreVersions, String metalake, String catalog, String schema) { + super(url, ignoreVersions); + this.metalake = metalake; + this.catalog = catalog; + this.schema = schema; + } + + /** List the names of all models in a schema. */ + @Override + public void handle() { + NameIdentifier[] models = new NameIdentifier[0]; + Namespace name = Namespace.of(schema); + + try { + GravitinoClient client = buildClient(metalake); + models = client.loadCatalog(catalog).asModelCatalog().listModels(name); + } catch (NoSuchMetalakeException noSuchMetalakeException) { + exitWithError(ErrorMessages.UNKNOWN_METALAKE); + } catch (NoSuchCatalogException noSuchCatalogException) { + exitWithError(ErrorMessages.UNKNOWN_CATALOG); + } catch (NoSuchSchemaException noSuchSchemaException) { + exitWithError(ErrorMessages.UNKNOWN_SCHEMA); + } catch (Exception err) { + exitWithError(err.getMessage()); + } + + String output = + models.length == 0 + ? "No models exist." + : Joiner.on(",").join(Arrays.stream(models).map(model -> model.name()).iterator()); + + System.out.println(output); + } +} diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java new file mode 100644 index 00000000000..6c3aec08fa5 --- /dev/null +++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ModelDetails.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.gravitino.cli.commands; + +import java.util.Arrays; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.cli.ErrorMessages; +import org.apache.gravitino.client.GravitinoClient; +import org.apache.gravitino.exceptions.NoSuchCatalogException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchModelException; +import org.apache.gravitino.exceptions.NoSuchSchemaException; +import org.apache.gravitino.model.Model; +import org.apache.gravitino.model.ModelCatalog; + +/** Displays the details of a model. */ +public class ModelDetails extends Command { + protected final String metalake; + protected final String catalog; + protected final String schema; + protected final String model; + + /** + * Displays the details of a model. + * + * @param url The URL of the Gravitino server. + * @param ignoreVersions If true don't check the client/server versions match. + * @param metalake The name of the metalake. + * @param catalog The name of the catalog. + * @param schema The name of schema. + * @param model The name of model. + */ + public ModelDetails( + String url, + boolean ignoreVersions, + String metalake, + String catalog, + String schema, + String model) { + super(url, ignoreVersions); + this.metalake = metalake; + this.catalog = catalog; + this.schema = schema; + this.model = model; + } + + /** Displays the details of a model. */ + @Override + public void handle() { + NameIdentifier name = NameIdentifier.of(schema, model); + Model gModel = null; + int[] versions = new int[0]; + + try { + GravitinoClient client = buildClient(metalake); + ModelCatalog modelCatalog = client.loadCatalog(catalog).asModelCatalog(); + gModel = modelCatalog.getModel(name); + versions = modelCatalog.listModelVersions(name); + } catch (NoSuchMetalakeException noSuchMetalakeException) { + exitWithError(ErrorMessages.UNKNOWN_METALAKE); + } catch (NoSuchCatalogException noSuchCatalogException) { + exitWithError(ErrorMessages.UNKNOWN_CATALOG); + } catch (NoSuchSchemaException noSuchSchemaException) { + exitWithError(ErrorMessages.UNKNOWN_SCHEMA); + } catch (NoSuchModelException noSuchModelException) { + exitWithError(ErrorMessages.UNKNOWN_MODEL); + } catch (Exception err) { + exitWithError(err.getMessage()); + } + String basicInfo = + String.format("Model name %s, latest version: %s%n", gModel.name(), gModel.latestVersion()); + String versionInfo = Arrays.toString(versions); + System.out.printf(basicInfo + "versions: " + versionInfo); + } +} diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java b/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java new file mode 100644 index 00000000000..d222655b641 --- /dev/null +++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestModelCommand.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.gravitino.cli; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.apache.gravitino.cli.commands.ListModel; +import org.apache.gravitino.cli.commands.ModelDetails; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.testcontainers.shaded.com.google.common.base.Joiner; + +public class TestModelCommand { + private final Joiner joiner = Joiner.on(", ").skipNulls(); + private CommandLine mockCommandLine; + private Options mockOptions; + + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + + @BeforeEach + void setUp() { + mockCommandLine = mock(CommandLine.class); + mockOptions = mock(Options.class); + System.setOut(new PrintStream(outContent)); + System.setErr(new PrintStream(errContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } + + @Test + void testListModelCommand() { + ListModel mockList = mock(ListModel.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.LIST)); + + doReturn(mockList) + .when(commandLine) + .newListModel( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("catalog"), + eq("schema")); + commandLine.handleCommandLine(); + verify(mockList).handle(); + } + + @Test + void testListModelCommandWithoutCatalog() { + Main.useExit = false; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(false); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.LIST)); + + assertThrows(RuntimeException.class, commandLine::handleCommandLine); + verify(commandLine, never()) + .newListModel( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + isNull(), + isNull()); + String output = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); + assertEquals( + ErrorMessages.MISSING_NAME + + "\n" + + "Missing required argument(s): " + + joiner.join(Arrays.asList(CommandEntities.CATALOG, CommandEntities.SCHEMA)), + output); + } + + @Test + void testListModelCommandWithoutSchema() { + Main.useExit = false; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.LIST)); + + assertThrows(RuntimeException.class, commandLine::handleCommandLine); + verify(commandLine, never()) + .newListModel( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("catalog"), + isNull()); + String output = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); + assertEquals( + ErrorMessages.MALFORMED_NAME + + "\n" + + "Missing required argument(s): " + + joiner.join(Collections.singletonList(CommandEntities.SCHEMA)), + output); + } + + @Test + void testModelDetailsCommand() { + ModelDetails mockList = mock(ModelDetails.class); + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema.model"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.DETAILS)); + + doReturn(mockList) + .when(commandLine) + .newModelDetails( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("catalog"), + eq("schema"), + eq("model")); + commandLine.handleCommandLine(); + verify(mockList).handle(); + } + + @Test + void testModelDetailsCommandWithoutCatalog() { + Main.useExit = false; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(false); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.DETAILS)); + + assertThrows(RuntimeException.class, commandLine::handleCommandLine); + + verify(commandLine, never()) + .newModelDetails( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + isNull(), + isNull(), + isNull()); + String output = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); + assertEquals( + ErrorMessages.MISSING_NAME + + "\n" + + "Missing required argument(s): " + + joiner.join( + Arrays.asList( + CommandEntities.CATALOG, CommandEntities.SCHEMA, CommandEntities.MODEL)), + output); + } + + @Test + void testModelDetailsCommandWithoutSchema() { + Main.useExit = false; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.DETAILS)); + + assertThrows(RuntimeException.class, commandLine::handleCommandLine); + + verify(commandLine, never()) + .newModelDetails( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("catalog"), + isNull(), + isNull()); + String output = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); + assertEquals( + ErrorMessages.MALFORMED_NAME + + "\n" + + "Missing required argument(s): " + + joiner.join(Arrays.asList(CommandEntities.SCHEMA, CommandEntities.MODEL)), + output); + } + + @Test + void testModelDetailsCommandWithoutModel() { + Main.useExit = false; + when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo"); + when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true); + when(mockCommandLine.getOptionValue(GravitinoOptions.NAME)).thenReturn("catalog.schema"); + GravitinoCommandLine commandLine = + spy( + new GravitinoCommandLine( + mockCommandLine, mockOptions, CommandEntities.MODEL, CommandActions.DETAILS)); + + assertThrows(RuntimeException.class, commandLine::handleCommandLine); + + verify(commandLine, never()) + .newModelDetails( + eq(GravitinoCommandLine.DEFAULT_URL), + eq(false), + eq("metalake_demo"), + eq("catalog"), + eq("schema"), + isNull()); + String output = new String(errContent.toByteArray(), StandardCharsets.UTF_8).trim(); + assertEquals( + ErrorMessages.MALFORMED_NAME + + "\n" + + "Missing required argument(s): " + + joiner.join(Collections.singletonList(CommandEntities.MODEL)), + output); + } +}