[ 
https://issues.apache.org/jira/browse/CLI-346?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Ruiqi Dong updated CLI-346:
---------------------------
    Affects Version/s: 1.10.0

> Options.addOptionGroup() silently overwrites existing options while 
> addOptions() throws exception for duplicates
> ----------------------------------------------------------------------------------------------------------------
>
>                 Key: CLI-346
>                 URL: https://issues.apache.org/jira/browse/CLI-346
>             Project: Commons CLI
>          Issue Type: Bug
>          Components: Options definition
>    Affects Versions: 1.10.0
>            Reporter: Ruiqi Dong
>            Priority: Major
>   Original Estimate: 0.5h
>  Remaining Estimate: 0.5h
>
> The {{Options.addOptionGroup()}} method silently overwrites existing options 
> when adding an {{OptionGroup}} that contains options with keys already 
> present in the {{Options}} instance. This behavior is inconsistent with 
> {{{}Options.addOptions(){}}}, which throws an {{IllegalArgumentException}} 
> when attempting to add duplicate options. This inconsistency violates the 
> principle of least surprise and can lead to subtle bugs where previously 
> configured options are unintentionally overwritten without any warning.
>  
> {*}Impact{*}:
>  * Can cause unexpected behavior in applications using Commons CLI
>  * Previously required options may become optional without warning
>  * Configuration may be silently lost
>  * Debugging such issues is difficult due to lack of error messages
>  
> *Test Case:*
>  
> {code:java}
> @Test
> void testAddOptionGroupShouldThrowExceptionForDuplicateOption() {
>     // Arrange
>     Options options = new Options();
>     
>     // Add an existing option
>     Option existingOption = new Option("f", "file", true, "Input file");
>     existingOption.setRequired(true);
>     options.addOption(existingOption);
>     
>     // Create an OptionGroup with the same option key
>     OptionGroup group = new OptionGroup();
>     Option duplicateOption = new Option("f", "file", true, "Config file");
>     group.addOption(duplicateOption);
>     
>     // Act & Assert
>     // This test expects addOptionGroup to throw exception like addOptions 
> does
>     // BUT IT FAILS - demonstrating the bug
>     IllegalArgumentException exception = assertThrows(
>         IllegalArgumentException.class, 
>         () -> options.addOptionGroup(group),
>         "addOptionGroup should throw IllegalArgumentException for duplicate 
> option key, " +
>         "consistent with addOptions() behavior"
>     );
>     
>     // This assertion would verify the exception message (if exception was 
> thrown)
>     assertTrue(exception.getMessage().contains("f") || 
>               exception.getMessage().contains("duplicate") ||
>               exception.getMessage().contains("Duplicate"),
>               "Exception message should mention the duplicate key");
> }{code}
> {*}Test Failure Output{*}{*}:{*}
> {code:java}
> [ERROR] 
> org.apache.commons.cli.OptionsTest.testAddOptionGroupShouldThrowExceptionForDuplicateOption
>  -- Time elapsed: 0.001 s <<< FAILURE!
> org.opentest4j.AssertionFailedError: addOptionGroup should throw 
> IllegalArgumentException for duplicate option key, consistent with 
> addOptions() behavior ==> Expected java.lang.IllegalArgumentException to be 
> thrown, but nothing was thrown.
>         at 
> org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
>         at 
> org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:73)
>         at 
> org.junit.jupiter.api.AssertThrows.assertThrows(AssertThrows.java:39)
>         at org.junit.jupiter.api.Assertions.assertThrows(Assertions.java:3153)
>         at 
> org.apache.commons.cli.OptionsTest.testAddOptionGroupShouldThrowExceptionForDuplicateOption(OptionsTest.java:46)
>         at java.lang.reflect.Method.invoke(Method.java:498)
>         at java.util.ArrayList.forEach(ArrayList.java:1259)
>         at java.util.ArrayList.forEach(ArrayList.java:1259) {code}
>  
> {*}Suggested Fix{*}:
> Modify {{addOptionGroup()}} to check for existing options before adding, 
> making it consistent with {{{}addOptions(){}}}:
> {code:java}
> public Options addOptionGroup(final OptionGroup group) {
>     // Check for duplicates first - consistent with addOptions() behavior
>     for (final Option option : group.getOptions()) {
>         if (hasOption(option.getKey())) {
>             throw new IllegalArgumentException("Option '" + option.getKey() + 
>                 "' already exists");
>         }
>     }
>     
>     // Existing logic
>     if (group.isRequired()) {
>         requiredOpts.add(group);
>     }
>     for (final Option option : group.getOptions()) {
>         option.setRequired(false);
>         addOption(option);
>         optionGroups.put(option.getKey(), group);
>     }
>     return this;
> } {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to