/*
* This file is part of the Intuitive DSL project.
* Copyright (c) 2026 DBA Labs - Switzerland. All rights reserved.
*
* This program is dual-licensed under a commercial license and the AGPLv3.
* For commercial licensing, contact us at [email protected] or visit https://www.dbalabs.ch.
*
* AGPLv3 licensing:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 (19 November 2007).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.
*/
package ch.dbalabs.intuitivedsl.parser;
import ch.dbalabs.intuitivedsl.exception.DslSyntaxException;
import ch.dbalabs.intuitivedsl.grammar.GrammarCompiler;
import ch.dbalabs.intuitivedsl.grammar.GroupNode;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class ParserRobustnessFuzzLikeTest {
private final Lexer lexer = new Lexer();
private final AstNavigator navigator = new AstNavigator();
private final GrammarCompiler compiler = new GrammarCompiler();
@Test
void shouldParseManyValidVariantsOfNestedGrammarWithoutThrowing() {
GroupNode ast = compiler.compile("ROUTE { HTTP { GET | POST } | MQ { PUBLISH | SUBSCRIBE } } target [ WITH payload ] ;");
List<String> validInputs = List.of(
"ROUTE HTTP GET users ;",
"ROUTE HTTP POST jobs WITH 'priority=high' ;",
"ROUTE MQ PUBLISH invoices ;",
"ROUTE MQ SUBSCRIBE billing WITH 'region=eu' ;"
);
assertThatCode(() -> {
for (String input : validInputs) {
ParseResult result = navigator.parse(input, lexer.tokenize(input), ast, name -> List.of());
assertThat(result.getKeywords()).isNotEmpty();
}
}).doesNotThrowAnyException();
}
@Test
void shouldRejectManyNearMissVariantsAtThePreciseFailurePoint() {
GroupNode ast = compiler.compile("ROUTE { HTTP { GET | POST } | MQ { PUBLISH | SUBSCRIBE } } target [ WITH payload ] ;");
List<String> invalidInputs = List.of(
"ROUTE FTP GET users ;",
"ROUTE HTTP PUT users ;",
"ROUTE MQ EMIT jobs ;",
"ROUTE HTTP GET users WITH ;",
"ROUTE MQ SUBSCRIBE ;"
);
for (String input : invalidInputs) {
assertThatThrownBy(() -> navigator.parse(input, lexer.tokenize(input), ast, name -> List.of()))
.isInstanceOf(DslSyntaxException.class)
.extracting(ex -> ((DslSyntaxException) ex).getRichMessage())
.asString()
.contains("Syntax Error at col")
.contains("Input: " + input);
}
}
@Test
void shouldRemainStableAcrossManyRepeatableVariants() {
GroupNode ast = compiler.compile("GRANT role_name TO user_name ... [ IN scope_name ] ... ;");
List<String> validInputs = List.of(
"GRANT admin TO alice ;",
"GRANT admin TO alice bob charlie ;",
"GRANT reader TO alice IN eu ;",
"GRANT reader TO alice bob IN eu IN finance IN archive ;"
);
assertThatCode(() -> {
for (String input : validInputs) {
navigator.parse(input, lexer.tokenize(input), ast, name -> List.of());
}
}).doesNotThrowAnyException();
}
@Test
void shouldNotLoopOrCorruptStateWhenOptionalRepeatableGroupIsAbsent() {
GroupNode ast = compiler.compile("GENERATE REPORT [ WITH metric_name ] ... ;");
String input = "GENERATE REPORT ;";
ParseResult result = navigator.parse(input, lexer.tokenize(input), ast, name -> List.of());
assertThat(result.getParameters()).isEmpty();
// Adjacent keywords are intentionally merged into a single keyword node/value.
assertThat(result.getKeywords()).contains("GENERATE REPORT");
}
}