From fa6b05d37d64f20d125df1579d029a525f39ff4c Mon Sep 17 00:00:00 2001 From: Alex Gnidash Date: Wed, 1 Apr 2026 09:47:52 +0300 Subject: [PATCH 1/6] Bump pom versions Added unit tests --- common/comparator/pom.xml | 5 - common/condition/pom.xml | 2 - common/configuration/pom.xml | 6 - .../framework/util/DataSourceUtil.java | 4 +- .../framework/util/DataSourceUtilTest.java | 2 +- .../connection/ConnectionTemplateImpl.java | 2 +- .../ConnectionTemplateImplTest.java | 7 +- common/exception/pom.xml | 12 + .../exception/ComparisonExceptionTest.java | 56 + .../DefaultFrameworkExceptionTest.java | 175 ++ .../exception/FileLinkingExceptionTest.java | 95 + .../IntegrationDisabledExceptionTest.java | 70 + .../exception/StopSignalExceptionTest.java | 61 + common/log/pom.xml | 12 + .../com/knubisoft/testlum/log/ColorTest.java | 78 + .../testlum/log/ColoredTextTest.java | 109 ++ .../knubisoft/testlum/log/LogFormatTest.java | 167 ++ .../constant/ExceptionMessageTest.java | 214 +++ .../framework/constant/LogMessageTest.java | 219 +++ common/xml-parser/pom.xml | 1 - engine/pom.xml | 28 +- .../testing/framework/ConnectionManager.java | 10 - .../main/resources/META-INF/spring.factories | 10 - ...text.ApplicationContextInitializer.imports | 1 + .../assembly-plugin-configuration.xml | 6 +- .../testlum/starter/TESTLUMStarterTest.java | 159 ++ .../framework/ConnectionManagerTest.java | 19 - .../framework/TestSetCollectorTest.java | 209 ++- .../EnvironmentExecutionServiceTest.java | 144 +- .../lib/InterpreterProviderTest.java | 172 +- .../lib/RepeatCommandsRunnerImplTest.java | 104 +- .../lib/SubCommandRunnerImplTest.java | 152 +- .../lib/ui/ExecutorProviderTest.java | 135 +- .../ui/executor/AssertEqualityHelperTest.java | 160 ++ .../lib/ui/executor/AssertExecutorTest.java | 521 +++++- .../ui/executor/BrowserTabExecutorTest.java | 188 +- .../ui/executor/CompareImageExecutorTest.java | 191 +- .../ui/executor/DragAndDropExecutorTest.java | 105 ++ .../lib/ui/executor/DropDownExecutorTest.java | 309 +++- .../lib/ui/executor/HotKeyExecutorTest.java | 237 +++ .../lib/ui/executor/HoverExecutorTest.java | 54 + .../MobileCompareImageExecutorTest.java | 166 +- .../ui/executor/NativeAssertExecutorTest.java | 211 ++- .../NativeCompareImageExecutorTest.java | 248 ++- .../executor/NativeVariableExecutorTest.java | 105 +- .../lib/ui/executor/NavigateExecutorTest.java | 38 + .../executor/NavigateNativeExecutorTest.java | 125 +- .../ui/executor/ScrollWebExecutorTest.java | 51 +- .../ui/executor/SwipeNativeExecutorTest.java | 209 ++- .../lib/ui/executor/WaitExecutorTest.java | 105 ++ .../ui/executor/WebVariableExecutorTest.java | 237 ++- .../locator/LocatorCollectorTest.java | 207 ++- .../ExtentReportsGeneratorTest.java | 457 ++++- .../scenario/ScenarioCollectorTest.java | 353 +++- .../scenario/ScenarioRunnerTest.java | 201 +- .../scenario/ScenarioValidatorTest.java | 1640 ++++++++++++++++- .../framework/util/BrowserUtilTest.java | 537 ++++-- .../util/ImageComparisonUtilTest.java | 156 +- .../testing/framework/util/LogUtilTest.java | 638 +++++++ .../framework/util/MobileUtilTest.java | 294 ++- .../testing/framework/util/UiUtilTest.java | 405 +++- .../util/VariableHelperImplTest.java | 796 +++++++- .../framework/util/WebElementFinderTest.java | 512 ++++- modules/api-util/pom.xml | 7 - modules/assert/pom.xml | 31 + modules/auth-util/pom.xml | 2 - .../testing/framework/auth/JwtAuth.java | 11 +- .../testing/framework/auth/BasicAuthTest.java | 154 ++ .../testing/framework/auth/JwtAuthTest.java | 472 +++++ modules/auth/pom.xml | 2 - modules/clickhouse/pom.xml | 30 + .../OnClickhouseEnabledConditionTest.java | 91 + .../impl/AliasClickhouseAdapterTest.java | 142 ++ .../db/sql/ClickhouseExecutorTest.java | 120 ++ .../db/sql/ClickhouseOperationTest.java | 80 + .../ClickhouseInterpreterTest.java | 181 ++ modules/conditional/pom.xml | 2 - modules/db-util/pom.xml | 2 - .../context/AliasAbstractAdapterTest.java | 163 ++ .../context/AliasAdapterImplTest.java | 163 ++ .../framework/db/StorageOperationTest.java | 4 + .../db/sql/SqlOperationImplTest.java | 295 +++ .../db/sql/executor/SqlExecutorTest.java | 4 + .../lib/DatabaseInterpreterTest.java | 4 + modules/dynamo/pom.xml | 31 + .../OnDynamoEnabledConditionTest.java | 122 ++ .../context/impl/AliasDynamoAdapterTest.java | 138 ++ modules/elasticsearch/pom.xml | 35 +- .../ElasticsearchConfiguration.java | 35 +- .../elasticsearch/ElasticsearchOperation.java | 23 +- .../OnElasticEnabledConditionTest.java | 92 + .../ElasticsearchConfigurationTest.java | 282 +++ .../impl/AliasElasticsearchAdapterTest.java | 127 ++ .../ElasticsearchOperationTest.java | 133 ++ .../ElasticsearchInterpreterTest.java | 60 + modules/graphql/pom.xml | 21 + .../interpreter/GraphqlInterpreterTest.java | 153 ++ modules/http/pom.xml | 30 + .../interpreter/HttpInterpreterTest.java | 108 ++ modules/kafka/pom.xml | 2 - .../OnKafkaEnabledConditionTest.java | 89 + .../context/impl/AliasKafkaAdapterTest.java | 109 ++ .../db/kafka/KafkaOperationTest.java | 38 + .../interpreter/KafkaInterpreterTest.java | 280 +++ modules/lambda/pom.xml | 31 +- .../OnLambdaEnabledConditionTest.java | 116 ++ .../interpreter/LambdaInterpreterTest.java | 90 + modules/models/pom.xml | 5 - modules/mongo/pom.xml | 2 +- modules/mysql/pom.xml | 30 + .../OnMysqlEnabledConditionTest.java | 92 + .../context/impl/AliasMySqlAdapterTest.java | 118 ++ .../framework/db/sql/MySqlExecutorTest.java | 107 ++ .../framework/db/sql/MySqlOperationTest.java | 73 + .../interpreter/MySqlInterpreterTest.java | 183 ++ modules/oracle/pom.xml | 30 + .../OnOracleEnabledConditionTest.java | 91 + .../context/impl/AliasOracleAdapterTest.java | 137 ++ .../framework/db/sql/OracleExecutorTest.java | 149 ++ .../framework/db/sql/OracleOperationTest.java | 79 + .../interpreter/OracleInterpreterTest.java | 178 ++ modules/postgres/pom.xml | 30 + .../OnPostgresEnabledConditionTest.java | 91 + .../impl/AliasPostgresAdapterTest.java | 140 ++ .../db/sql/PostgresExecutorTest.java | 133 ++ .../db/sql/PostgresSqlOperationTest.java | 79 + .../interpreter/PostgresInterpreterTest.java | 180 ++ modules/rabbit/pom.xml | 30 + .../db/rabbitmq/RabbitMQOperation.java | 4 +- .../OnRabbitMQEnabledConditionTest.java | 116 ++ .../context/impl/AliasRabbitAdapterTest.java | 138 ++ .../db/rabbitmq/RabbitMQOperationTest.java | 135 ++ modules/s3/pom.xml | 30 + .../testing/framework/db/s3/S3Operation.java | 2 +- .../condition/OnS3EnabledConditionTest.java | 111 ++ .../context/impl/AliasS3AdapterTest.java | 138 ++ .../framework/db/s3/S3OperationTest.java | 141 ++ modules/sendgrid/pom.xml | 30 + .../OnSendgridEnabledConditionTest.java | 116 ++ .../impl/AliasSendGridAdapterTest.java | 138 ++ .../db/sendgrid/SendGridOperationTest.java | 72 + modules/ses/pom.xml | 30 + .../condition/OnSESEnabledConditionTest.java | 116 ++ .../context/impl/AliasSESAdapterTest.java | 138 ++ .../framework/db/ses/SESOperationTest.java | 72 + modules/shared-util/pom.xml | 2 - .../AbstractMessageBrokerInterpreterTest.java | 92 +- .../interpreter/lib/InterpreterTest.java | 4 + .../lib/MessageBrokerInterpreterTest.java | 4 + .../scenario/ScenarioContextTest.java | 36 + modules/smtp/pom.xml | 30 + .../condition/OnSmtpEnabledConditionTest.java | 116 ++ .../interpreter/SmtpInterpreterTest.java | 145 ++ modules/sqs/pom.xml | 30 + .../framework/db/sqs/SQSOperation.java | 4 +- .../condition/OnSQSEnabledConditionTest.java | 116 ++ .../context/impl/AliasSQSAdapterTest.java | 138 ++ .../framework/db/sqs/SQSOperationTest.java | 173 ++ pom.xml | 202 +- suppressions.xml | 1 + 160 files changed, 19811 insertions(+), 773 deletions(-) create mode 100644 common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/ComparisonExceptionTest.java create mode 100644 common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/DefaultFrameworkExceptionTest.java create mode 100644 common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/FileLinkingExceptionTest.java create mode 100644 common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/IntegrationDisabledExceptionTest.java create mode 100644 common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/StopSignalExceptionTest.java create mode 100644 common/log/src/test/java/com/knubisoft/testlum/log/ColorTest.java create mode 100644 common/log/src/test/java/com/knubisoft/testlum/log/ColoredTextTest.java create mode 100644 common/log/src/test/java/com/knubisoft/testlum/log/LogFormatTest.java create mode 100644 common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/ExceptionMessageTest.java create mode 100644 common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/LogMessageTest.java delete mode 100644 engine/src/main/resources/META-INF/spring.factories create mode 100644 engine/src/main/resources/META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports create mode 100644 engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertEqualityHelperTest.java create mode 100644 modules/auth-util/src/test/java/com/knubisoft/testlum/testing/framework/auth/BasicAuthTest.java create mode 100644 modules/auth-util/src/test/java/com/knubisoft/testlum/testing/framework/auth/JwtAuthTest.java create mode 100644 modules/clickhouse/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnClickhouseEnabledConditionTest.java create mode 100644 modules/clickhouse/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasClickhouseAdapterTest.java create mode 100644 modules/clickhouse/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/ClickhouseExecutorTest.java create mode 100644 modules/clickhouse/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/ClickhouseOperationTest.java create mode 100644 modules/clickhouse/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/ClickhouseInterpreterTest.java create mode 100644 modules/db-util/src/test/java/com/knubisoft/testlum/testing/framework/context/AliasAbstractAdapterTest.java create mode 100644 modules/db-util/src/test/java/com/knubisoft/testlum/testing/framework/context/AliasAdapterImplTest.java create mode 100644 modules/db-util/src/test/java/com/knubisoft/testlum/testing/framework/db/StorageOperationTest.java create mode 100644 modules/db-util/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/SqlOperationImplTest.java create mode 100644 modules/db-util/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/executor/SqlExecutorTest.java create mode 100644 modules/db-util/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/DatabaseInterpreterTest.java create mode 100644 modules/dynamo/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnDynamoEnabledConditionTest.java create mode 100644 modules/dynamo/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasDynamoAdapterTest.java create mode 100644 modules/elasticsearch/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnElasticEnabledConditionTest.java create mode 100644 modules/elasticsearch/src/test/java/com/knubisoft/testlum/testing/framework/configuration/elasticsearch/ElasticsearchConfigurationTest.java create mode 100644 modules/elasticsearch/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasElasticsearchAdapterTest.java create mode 100644 modules/elasticsearch/src/test/java/com/knubisoft/testlum/testing/framework/db/elasticsearch/ElasticsearchOperationTest.java create mode 100644 modules/elasticsearch/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/ElasticsearchInterpreterTest.java create mode 100644 modules/graphql/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/GraphqlInterpreterTest.java create mode 100644 modules/http/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/HttpInterpreterTest.java create mode 100644 modules/kafka/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnKafkaEnabledConditionTest.java create mode 100644 modules/kafka/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasKafkaAdapterTest.java create mode 100644 modules/kafka/src/test/java/com/knubisoft/testlum/testing/framework/db/kafka/KafkaOperationTest.java create mode 100644 modules/kafka/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/KafkaInterpreterTest.java create mode 100644 modules/lambda/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnLambdaEnabledConditionTest.java create mode 100644 modules/lambda/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/LambdaInterpreterTest.java create mode 100644 modules/mysql/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnMysqlEnabledConditionTest.java create mode 100644 modules/mysql/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasMySqlAdapterTest.java create mode 100644 modules/mysql/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/MySqlExecutorTest.java create mode 100644 modules/mysql/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/MySqlOperationTest.java create mode 100644 modules/mysql/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/MySqlInterpreterTest.java create mode 100644 modules/oracle/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnOracleEnabledConditionTest.java create mode 100644 modules/oracle/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasOracleAdapterTest.java create mode 100644 modules/oracle/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/OracleExecutorTest.java create mode 100644 modules/oracle/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/OracleOperationTest.java create mode 100644 modules/oracle/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/OracleInterpreterTest.java create mode 100644 modules/postgres/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnPostgresEnabledConditionTest.java create mode 100644 modules/postgres/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasPostgresAdapterTest.java create mode 100644 modules/postgres/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/PostgresExecutorTest.java create mode 100644 modules/postgres/src/test/java/com/knubisoft/testlum/testing/framework/db/sql/PostgresSqlOperationTest.java create mode 100644 modules/postgres/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/PostgresInterpreterTest.java create mode 100644 modules/rabbit/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnRabbitMQEnabledConditionTest.java create mode 100644 modules/rabbit/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasRabbitAdapterTest.java create mode 100644 modules/rabbit/src/test/java/com/knubisoft/testlum/testing/framework/db/rabbitmq/RabbitMQOperationTest.java create mode 100644 modules/s3/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnS3EnabledConditionTest.java create mode 100644 modules/s3/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasS3AdapterTest.java create mode 100644 modules/s3/src/test/java/com/knubisoft/testlum/testing/framework/db/s3/S3OperationTest.java create mode 100644 modules/sendgrid/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnSendgridEnabledConditionTest.java create mode 100644 modules/sendgrid/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasSendGridAdapterTest.java create mode 100644 modules/sendgrid/src/test/java/com/knubisoft/testlum/testing/framework/db/sendgrid/SendGridOperationTest.java create mode 100644 modules/ses/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnSESEnabledConditionTest.java create mode 100644 modules/ses/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasSESAdapterTest.java create mode 100644 modules/ses/src/test/java/com/knubisoft/testlum/testing/framework/db/ses/SESOperationTest.java create mode 100644 modules/shared-util/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/InterpreterTest.java create mode 100644 modules/shared-util/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/MessageBrokerInterpreterTest.java create mode 100644 modules/smtp/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnSmtpEnabledConditionTest.java create mode 100644 modules/smtp/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/SmtpInterpreterTest.java create mode 100644 modules/sqs/src/test/java/com/knubisoft/testlum/testing/framework/condition/OnSQSEnabledConditionTest.java create mode 100644 modules/sqs/src/test/java/com/knubisoft/testlum/testing/framework/context/impl/AliasSQSAdapterTest.java create mode 100644 modules/sqs/src/test/java/com/knubisoft/testlum/testing/framework/db/sqs/SQSOperationTest.java diff --git a/common/comparator/pom.xml b/common/comparator/pom.xml index 92a654408..ecfe86674 100644 --- a/common/comparator/pom.xml +++ b/common/comparator/pom.xml @@ -67,10 +67,5 @@ junit-jupiter-params test - - org.junit.jupiter - junit-jupiter-api - test - diff --git a/common/condition/pom.xml b/common/condition/pom.xml index 2c4bb3841..65011ec14 100644 --- a/common/condition/pom.xml +++ b/common/condition/pom.xml @@ -60,14 +60,12 @@ org.mockito mockito-core - 5.18.0 test org.mockito mockito-junit-jupiter - 5.18.0 test diff --git a/common/configuration/pom.xml b/common/configuration/pom.xml index acbdbedaf..1490071af 100644 --- a/common/configuration/pom.xml +++ b/common/configuration/pom.xml @@ -118,12 +118,6 @@ org.mockito mockito-core - 5.18.0 - test - - - org.junit.jupiter - junit-jupiter-api test diff --git a/common/configuration/src/main/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtil.java b/common/configuration/src/main/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtil.java index cf007e828..b23a5e73a 100755 --- a/common/configuration/src/main/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtil.java +++ b/common/configuration/src/main/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtil.java @@ -24,7 +24,9 @@ public DataSource getHikariDataSource(final DatabaseConfig dataSource) { private void setDefaultHikariSettings(final HikariDataSource hikariDataSourceOriginal, final DatabaseConfig dataSource) { - hikariDataSourceOriginal.setDriverClassName(dataSource.getJdbcDriver()); + if (StringUtils.isNotBlank(dataSource.getJdbcDriver())) { + hikariDataSourceOriginal.setDriverClassName(dataSource.getJdbcDriver()); + } hikariDataSourceOriginal.setJdbcUrl(dataSource.getConnectionUrl()); hikariDataSourceOriginal.setUsername(dataSource.getUsername()); hikariDataSourceOriginal.setPassword(dataSource.getPassword()); diff --git a/common/configuration/src/test/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtilTest.java b/common/configuration/src/test/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtilTest.java index 1a845903c..cab9a9d8c 100644 --- a/common/configuration/src/test/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtilTest.java +++ b/common/configuration/src/test/java/com/knubisoft/testlum/testing/framework/util/DataSourceUtilTest.java @@ -98,7 +98,7 @@ private DatabaseConfig createDatabaseConfig(final String schema) { hikari.setMaxLifetime(60000); DatabaseConfig config = mock(DatabaseConfig.class); - when(config.getJdbcDriver()).thenReturn("com.zaxxer.hikari.HikariDataSource"); + when(config.getJdbcDriver()).thenReturn(null); when(config.getConnectionUrl()).thenReturn("jdbc:h2:mem:test"); when(config.getUsername()).thenReturn("user"); when(config.getPassword()).thenReturn("pass"); diff --git a/common/connection-template/src/main/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImpl.java b/common/connection-template/src/main/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImpl.java index fdc4396e5..9ae2da410 100644 --- a/common/connection-template/src/main/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImpl.java +++ b/common/connection-template/src/main/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImpl.java @@ -79,7 +79,7 @@ private RuntimeException handleFinalFailure(final String integrationName, return new IntegrationFailureException(error); } - private void sleep() { + void sleep() { try { TimeUnit.MILLISECONDS.sleep(INITIAL_BACKOFF_MS); } catch (InterruptedException e) { diff --git a/common/connection-template/src/test/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImplTest.java b/common/connection-template/src/test/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImplTest.java index 7d3c95510..17f425a34 100644 --- a/common/connection-template/src/test/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImplTest.java +++ b/common/connection-template/src/test/java/com/knubisoft/testlum/testing/connection/ConnectionTemplateImplTest.java @@ -14,7 +14,12 @@ class ConnectionTemplateImplTest { @BeforeEach void setUp() { - template = new ConnectionTemplateImpl(); + template = new ConnectionTemplateImpl() { + @Override + void sleep() { + // no-op to avoid delays in tests + } + }; } @Nested diff --git a/common/exception/pom.xml b/common/exception/pom.xml index 5f2e22731..dd141681a 100644 --- a/common/exception/pom.xml +++ b/common/exception/pom.xml @@ -34,5 +34,17 @@ lombok compile + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/ComparisonExceptionTest.java b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/ComparisonExceptionTest.java new file mode 100644 index 000000000..e0a25b654 --- /dev/null +++ b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/ComparisonExceptionTest.java @@ -0,0 +1,56 @@ +package com.knubisoft.testlum.testing.framework.exception; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link ComparisonException}. */ +class ComparisonExceptionTest { + + @Nested + class Constructor { + @Test + void messageIsPreserved() { + ComparisonException ex = new ComparisonException("expected X but got Y"); + assertEquals("expected X but got Y", ex.getMessage()); + } + + @Test + void nullMessageIsAllowed() { + ComparisonException ex = new ComparisonException(null); + assertNull(ex.getMessage()); + } + + @Test + void emptyMessageIsAllowed() { + ComparisonException ex = new ComparisonException(""); + assertEquals("", ex.getMessage()); + } + } + + @Nested + class InheritanceHierarchy { + @Test + void isRuntimeException() { + ComparisonException ex = new ComparisonException("test"); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void canBeCaughtAsRuntimeException() { + assertThrows(RuntimeException.class, () -> { + throw new ComparisonException("fail"); + }); + } + } + + @Nested + class CauseChain { + @Test + void causeIsNullByDefault() { + ComparisonException ex = new ComparisonException("msg"); + assertNull(ex.getCause()); + } + } +} diff --git a/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/DefaultFrameworkExceptionTest.java b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/DefaultFrameworkExceptionTest.java new file mode 100644 index 000000000..1221bb00e --- /dev/null +++ b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/DefaultFrameworkExceptionTest.java @@ -0,0 +1,175 @@ +package com.knubisoft.testlum.testing.framework.exception; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link DefaultFrameworkException}. */ +class DefaultFrameworkExceptionTest { + + @Nested + class NoArgConstructor { + @Test + void messageIsNull() { + DefaultFrameworkException ex = new DefaultFrameworkException(); + assertNull(ex.getMessage()); + } + + @Test + void causeIsNull() { + DefaultFrameworkException ex = new DefaultFrameworkException(); + assertNull(ex.getCause()); + } + } + + @Nested + class StringMessageConstructor { + @Test + void messageIsPreserved() { + DefaultFrameworkException ex = new DefaultFrameworkException("something went wrong"); + assertEquals("something went wrong", ex.getMessage()); + } + + @Test + void nullMessageIsAllowed() { + DefaultFrameworkException ex = new DefaultFrameworkException((String) null); + assertNull(ex.getMessage()); + } + + @Test + void emptyMessageIsAllowed() { + DefaultFrameworkException ex = new DefaultFrameworkException(""); + assertEquals("", ex.getMessage()); + } + } + + @Nested + class ListMessageConstructor { + @Test + void joinsMultipleMessages() { + List messages = Arrays.asList("error 1", "error 2", "error 3"); + DefaultFrameworkException ex = new DefaultFrameworkException(messages); + String msg = ex.getMessage(); + assertNotNull(msg); + assertTrue(msg.contains("error 1")); + assertTrue(msg.contains("error 2")); + assertTrue(msg.contains("error 3")); + } + + @Test + void singleMessage() { + List messages = Collections.singletonList("only one error"); + DefaultFrameworkException ex = new DefaultFrameworkException(messages); + assertNotNull(ex.getMessage()); + assertTrue(ex.getMessage().contains("only one error")); + } + + @Test + void messageStartsWithErrors() { + List messages = List.of("err"); + DefaultFrameworkException ex = new DefaultFrameworkException(messages); + assertTrue(ex.getMessage().startsWith("Errors:")); + } + + @Test + void emptyListProducesErrorsPrefix() { + List messages = Collections.emptyList(); + DefaultFrameworkException ex = new DefaultFrameworkException(messages); + assertTrue(ex.getMessage().startsWith("Errors:")); + } + } + + @Nested + class FormattedMessageConstructor { + @Test + void formatsWithSingleArg() { + DefaultFrameworkException ex = new DefaultFrameworkException("File %s not found", "test.xml"); + assertEquals("File test.xml not found", ex.getMessage()); + } + + @Test + void formatsWithMultipleArgs() { + DefaultFrameworkException ex = new DefaultFrameworkException( + "%s failed at line %d", "Parser", 42); + assertEquals("Parser failed at line 42", ex.getMessage()); + } + + @Test + void formatsWithNoArgs() { + DefaultFrameworkException ex = new DefaultFrameworkException("no placeholders"); + assertEquals("no placeholders", ex.getMessage()); + } + } + + @Nested + class ThrowableCauseConstructor { + @Test + void causeIsPreserved() { + IOException cause = new IOException("disk full"); + DefaultFrameworkException ex = new DefaultFrameworkException(cause); + assertSame(cause, ex.getCause()); + } + + @Test + void messageContainsCauseInfo() { + IOException cause = new IOException("disk full"); + DefaultFrameworkException ex = new DefaultFrameworkException(cause); + assertNotNull(ex.getMessage()); + assertTrue(ex.getMessage().contains("disk full")); + } + + @Test + void nullCauseIsAllowed() { + DefaultFrameworkException ex = new DefaultFrameworkException((Throwable) null); + assertNull(ex.getCause()); + } + } + + @Nested + class MessageAndCauseConstructor { + @Test + void messageAndCauseArePreserved() { + RuntimeException cause = new RuntimeException("root"); + DefaultFrameworkException ex = new DefaultFrameworkException("wrapper msg", cause); + assertEquals("wrapper msg", ex.getMessage()); + assertSame(cause, ex.getCause()); + } + + @Test + void nullMessageWithValidCause() { + RuntimeException cause = new RuntimeException("root"); + DefaultFrameworkException ex = new DefaultFrameworkException(null, cause); + assertNull(ex.getMessage()); + assertSame(cause, ex.getCause()); + } + + @Test + void validMessageWithNullCause() { + DefaultFrameworkException ex = new DefaultFrameworkException("msg", (Throwable) null); + assertEquals("msg", ex.getMessage()); + assertNull(ex.getCause()); + } + } + + @Nested + class InheritanceHierarchy { + @Test + void isRuntimeException() { + DefaultFrameworkException ex = new DefaultFrameworkException(); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void canBeCaughtAsRuntimeException() { + assertThrows(RuntimeException.class, () -> { + throw new DefaultFrameworkException("fail"); + }); + } + } +} diff --git a/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/FileLinkingExceptionTest.java b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/FileLinkingExceptionTest.java new file mode 100644 index 000000000..615c0d8c6 --- /dev/null +++ b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/FileLinkingExceptionTest.java @@ -0,0 +1,95 @@ +package com.knubisoft.testlum.testing.framework.exception; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link FileLinkingException}. */ +class FileLinkingExceptionTest { + + @Nested + class ThreeArgConstructor { + @Test + void messageContainsFileKey() { + File start = new File("/project/scenarios"); + File root = new File("/project"); + FileLinkingException ex = new FileLinkingException(start, root, "data.json"); + assertTrue(ex.getMessage().contains("data.json")); + } + + @Test + void messageContainsStartFolder() { + File start = new File("/project/scenarios"); + File root = new File("/project"); + FileLinkingException ex = new FileLinkingException(start, root, "data.json"); + assertTrue(ex.getMessage().contains(start.toString())); + } + + @Test + void messageContainsRootFolder() { + File start = new File("/project/scenarios"); + File root = new File("/project"); + FileLinkingException ex = new FileLinkingException(start, root, "data.json"); + assertTrue(ex.getMessage().contains(root.toString())); + } + + @Test + void messageFollowsExpectedFormat() { + File start = new File("/a/b"); + File root = new File("/a"); + FileLinkingException ex = new FileLinkingException(start, root, "key123"); + String msg = ex.getMessage(); + assertTrue(msg.contains("Unable to find file by key")); + assertTrue(msg.contains("key123")); + assertTrue(msg.contains("Initial scan folder")); + } + } + + @Nested + class TwoArgConstructor { + @Test + void messageContainsErrorMessage() { + File start = new File("/project/config"); + FileLinkingException ex = new FileLinkingException("Config missing", start); + assertTrue(ex.getMessage().contains("Config missing")); + } + + @Test + void messageContainsAbsolutePath() { + File start = new File("/project/config"); + FileLinkingException ex = new FileLinkingException("Config missing", start); + assertTrue(ex.getMessage().contains(start.getAbsolutePath())); + } + + @Test + void messageFollowsExpectedLocationFormat() { + File start = new File("/x/y"); + FileLinkingException ex = new FileLinkingException("err", start); + assertTrue(ex.getMessage().contains("Expected location ->")); + } + } + + @Nested + class InheritanceHierarchy { + @Test + void isRuntimeException() { + File start = new File("/a"); + File root = new File("/"); + FileLinkingException ex = new FileLinkingException(start, root, "k"); + assertInstanceOf(RuntimeException.class, ex); + } + } + + @Nested + class Constants { + @Test + void folderLocationErrorMessageContainsPlaceholders() { + String template = FileLinkingException.FOLDER_LOCATION_ERROR_MESSAGE; + assertTrue(template.contains("%s")); + assertTrue(template.contains("Expected location")); + } + } +} diff --git a/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/IntegrationDisabledExceptionTest.java b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/IntegrationDisabledExceptionTest.java new file mode 100644 index 000000000..cf8bdb537 --- /dev/null +++ b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/IntegrationDisabledExceptionTest.java @@ -0,0 +1,70 @@ +package com.knubisoft.testlum.testing.framework.exception; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link IntegrationDisabledException}. */ +class IntegrationDisabledExceptionTest { + + @Nested + class FormattedMessageConstructor { + @Test + void formatsWithSingleArg() { + IntegrationDisabledException ex = new IntegrationDisabledException( + "Integration %s is disabled", "Redis"); + assertEquals("Integration Redis is disabled", ex.getMessage()); + } + + @Test + void formatsWithMultipleArgs() { + IntegrationDisabledException ex = new IntegrationDisabledException( + "%s on %s is disabled", "Elasticsearch", "env-prod"); + assertEquals("Elasticsearch on env-prod is disabled", ex.getMessage()); + } + + @Test + void noArgsUsesFormatAsIs() { + IntegrationDisabledException ex = new IntegrationDisabledException("plain text"); + assertEquals("plain text", ex.getMessage()); + } + + @Test + void formatsWithIntegerArg() { + IntegrationDisabledException ex = new IntegrationDisabledException( + "Port %d is disabled", 8080); + assertEquals("Port 8080 is disabled", ex.getMessage()); + } + } + + @Nested + class InheritanceHierarchy { + @Test + void extendsDefaultFrameworkException() { + IntegrationDisabledException ex = new IntegrationDisabledException("test"); + assertInstanceOf(DefaultFrameworkException.class, ex); + } + + @Test + void isRuntimeException() { + IntegrationDisabledException ex = new IntegrationDisabledException("test"); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void canBeCaughtAsDefaultFrameworkException() { + assertThrows(DefaultFrameworkException.class, () -> { + throw new IntegrationDisabledException("disabled %s", "mongo"); + }); + } + } + + @Nested + class ClassModifiers { + @Test + void isFinalClass() { + assertTrue(java.lang.reflect.Modifier.isFinal(IntegrationDisabledException.class.getModifiers())); + } + } +} diff --git a/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/StopSignalExceptionTest.java b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/StopSignalExceptionTest.java new file mode 100644 index 000000000..156af43b4 --- /dev/null +++ b/common/exception/src/test/java/com/knubisoft/testlum/testing/framework/exception/StopSignalExceptionTest.java @@ -0,0 +1,61 @@ +package com.knubisoft.testlum.testing.framework.exception; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link StopSignalException}. */ +class StopSignalExceptionTest { + + @Nested + class DefaultConstructor { + @Test + void messageIsNull() { + StopSignalException ex = new StopSignalException(); + assertNull(ex.getMessage()); + } + + @Test + void causeIsNull() { + StopSignalException ex = new StopSignalException(); + assertNull(ex.getCause()); + } + + @Test + void canBeInstantiated() { + assertDoesNotThrow(StopSignalException::new); + } + } + + @Nested + class InheritanceHierarchy { + @Test + void isRuntimeException() { + StopSignalException ex = new StopSignalException(); + assertInstanceOf(RuntimeException.class, ex); + } + + @Test + void canBeCaughtAsRuntimeException() { + assertThrows(RuntimeException.class, () -> { + throw new StopSignalException(); + }); + } + + @Test + void canBeThrown() { + assertThrows(StopSignalException.class, () -> { + throw new StopSignalException(); + }); + } + } + + @Nested + class ClassModifiers { + @Test + void isFinalClass() { + assertTrue(java.lang.reflect.Modifier.isFinal(StopSignalException.class.getModifiers())); + } + } +} diff --git a/common/log/pom.xml b/common/log/pom.xml index 274a137f1..66c474a05 100644 --- a/common/log/pom.xml +++ b/common/log/pom.xml @@ -29,5 +29,17 @@ lombok compile + + + org.junit.jupiter + junit-jupiter-api + test + + + + org.junit.jupiter + junit-jupiter-engine + test + diff --git a/common/log/src/test/java/com/knubisoft/testlum/log/ColorTest.java b/common/log/src/test/java/com/knubisoft/testlum/log/ColorTest.java new file mode 100644 index 000000000..254508331 --- /dev/null +++ b/common/log/src/test/java/com/knubisoft/testlum/log/ColorTest.java @@ -0,0 +1,78 @@ +package com.knubisoft.testlum.log; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link Color} enum. */ +class ColorTest { + + @Nested + class AnsiCodes { + @Test + void redHasAnsiCode() { + assertEquals("\u001B[31m", Color.RED.getCode()); + } + + @Test + void orangeHasAnsiCode() { + assertEquals("\u001b[38;5;208m", Color.ORANGE.getCode()); + } + + @Test + void greenHasAnsiCode() { + assertEquals("\u001B[32m", Color.GREEN.getCode()); + } + + @Test + void cyanHasAnsiCode() { + assertEquals("\u001b[36m", Color.CYAN.getCode()); + } + + @Test + void resetHasAnsiCode() { + assertEquals("\u001b[0m", Color.RESET.getCode()); + } + + @Test + void yellowHasAnsiCode() { + assertEquals("\u001B[33m", Color.YELLOW.getCode()); + } + + @Test + void noneHasEmptyCode() { + assertEquals("", Color.NONE.getCode()); + } + } + + @Nested + class EnumValues { + @Test + void allValuesExist() { + Color[] values = Color.values(); + assertEquals(7, values.length); + } + + @Test + void valueOfWorks() { + assertEquals(Color.RED, Color.valueOf("RED")); + assertEquals(Color.NONE, Color.valueOf("NONE")); + } + + @Test + void valueOfInvalidThrows() { + assertThrows(IllegalArgumentException.class, () -> Color.valueOf("PURPLE")); + } + } + + @Nested + class CodeNonNullity { + @Test + void allCodesAreNonNull() { + for (Color color : Color.values()) { + assertNotNull(color.getCode(), "Code should not be null for " + color); + } + } + } +} diff --git a/common/log/src/test/java/com/knubisoft/testlum/log/ColoredTextTest.java b/common/log/src/test/java/com/knubisoft/testlum/log/ColoredTextTest.java new file mode 100644 index 000000000..e257d4af7 --- /dev/null +++ b/common/log/src/test/java/com/knubisoft/testlum/log/ColoredTextTest.java @@ -0,0 +1,109 @@ +package com.knubisoft.testlum.log; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link ColoredText}. */ +class ColoredTextTest { + + @Nested + class AddMethod { + @Test + void addNonNullDoesNotThrow() { + ColoredText ct = new ColoredText(Color.GREEN); + assertDoesNotThrow(() -> ct.add("hello")); + } + + @Test + void addNullIsIgnored() { + ColoredText ct = new ColoredText(Color.GREEN); + assertDoesNotThrow(() -> ct.add(null)); + } + + @Test + void addEmptyStringIsAccepted() { + ColoredText ct = new ColoredText(Color.GREEN); + assertDoesNotThrow(() -> ct.add("")); + } + } + + @Nested + class AddAllMethod { + @Test + void addAllWithNonNullList() { + ColoredText ct = new ColoredText(Color.RED); + List data = Arrays.asList("line1", "line2"); + assertDoesNotThrow(() -> ct.addAll(data)); + } + + @Test + void addAllWithNullListDoesNotThrow() { + ColoredText ct = new ColoredText(Color.RED); + assertDoesNotThrow(() -> ct.addAll(null)); + } + + @Test + void addAllWithEmptyListDoesNotThrow() { + ColoredText ct = new ColoredText(Color.RED); + assertDoesNotThrow(() -> ct.addAll(Collections.emptyList())); + } + + @Test + void addAllSkipsNullEntries() { + ColoredText ct = new ColoredText(Color.CYAN); + List data = Arrays.asList("line1", null, "line3"); + assertDoesNotThrow(() -> ct.addAll(data)); + } + } + + @Nested + class InfoMethod { + @Test + void infoDoesNotThrowWhenEmpty() { + ColoredText ct = new ColoredText(Color.YELLOW); + assertDoesNotThrow(ct::info); + } + + @Test + void infoDoesNotThrowWithLines() { + ColoredText ct = new ColoredText(Color.RED); + ct.add("test line"); + assertDoesNotThrow(ct::info); + } + + @Test + void infoClearsLinesAfterCall() { + ColoredText ct = new ColoredText(Color.GREEN); + ct.add("line1"); + ct.info(); + // calling info again should not re-print the same lines (they were cleared) + assertDoesNotThrow(ct::info); + } + } + + @Nested + class NullColor { + @Test + void nullColorDoesNotThrowOnInfo() { + ColoredText ct = new ColoredText(null); + ct.add("uncolored line"); + assertDoesNotThrow(ct::info); + } + } + + @Nested + class NoneColor { + @Test + void noneColorWrapsWithEmptyCode() { + ColoredText ct = new ColoredText(Color.NONE); + ct.add("test"); + assertDoesNotThrow(ct::info); + } + } +} diff --git a/common/log/src/test/java/com/knubisoft/testlum/log/LogFormatTest.java b/common/log/src/test/java/com/knubisoft/testlum/log/LogFormatTest.java new file mode 100644 index 000000000..1301d9cd4 --- /dev/null +++ b/common/log/src/test/java/com/knubisoft/testlum/log/LogFormatTest.java @@ -0,0 +1,167 @@ +package com.knubisoft.testlum.log; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link LogFormat}. */ +class LogFormatTest { + + @Nested + class TableMethod { + @Test + void tableWithTextAndArgFormatsCorrectly() { + String result = LogFormat.table("Name", "TestUser"); + assertNotNull(result); + assertTrue(result.contains("Name")); + assertTrue(result.contains("TestUser")); + } + + @Test + void tableWithTextOnlyUsesPlaceholder() { + String result = LogFormat.table("Query"); + assertNotNull(result); + assertTrue(result.contains("Query")); + assertTrue(result.contains("{}")); + } + + @Test + void tableWithNullArgConvertsToString() { + String result = LogFormat.table("Field", null); + assertNotNull(result); + assertTrue(result.contains("null")); + } + + @Test + void tableWithNumericArg() { + String result = LogFormat.table("Port", 8080); + assertTrue(result.contains("8080")); + } + + @Test + void tableWithEmptyText() { + String result = LogFormat.table("", "value"); + assertNotNull(result); + assertTrue(result.contains("value")); + } + } + + @Nested + class WithColorMethods { + @Test + void withColorWrapsText() { + String result = LogFormat.with(Color.RED, "error"); + assertTrue(result.startsWith(Color.RED.getCode())); + assertTrue(result.endsWith(Color.RESET.getCode())); + assertTrue(result.contains("error")); + } + + @Test + void withRedContainsRedCode() { + String result = LogFormat.withRed("red text"); + assertTrue(result.contains(Color.RED.getCode())); + assertTrue(result.contains("red text")); + } + + @Test + void withOrangeContainsOrangeCode() { + String result = LogFormat.withOrange("orange text"); + assertTrue(result.contains(Color.ORANGE.getCode())); + assertTrue(result.contains("orange text")); + } + + @Test + void withGreenContainsGreenCode() { + String result = LogFormat.withGreen("green text"); + assertTrue(result.contains(Color.GREEN.getCode())); + assertTrue(result.contains("green text")); + } + + @Test + void withCyanContainsCyanCode() { + String result = LogFormat.withCyan("cyan text"); + assertTrue(result.contains(Color.CYAN.getCode())); + assertTrue(result.contains("cyan text")); + } + + @Test + void withYellowContainsYellowCode() { + String result = LogFormat.withYellow("yellow text"); + assertTrue(result.contains(Color.YELLOW.getCode())); + assertTrue(result.contains("yellow text")); + } + + @Test + void withNoneColorWrapsWithEmptyCodes() { + String result = LogFormat.with(Color.NONE, "plain"); + assertTrue(result.contains("plain")); + assertTrue(result.endsWith(Color.RESET.getCode())); + } + + @Test + void withEmptyText() { + String result = LogFormat.withRed(""); + assertEquals(Color.RED.getCode() + Color.RESET.getCode(), result); + } + } + + @Nested + class StaticAccessors { + @Test + void newLineReturnsPattern() { + String result = LogFormat.newLine(); + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + void contentFormatReturnsNonEmpty() { + String result = LogFormat.contentFormat(); + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + void newLogLineReturnsNonEmpty() { + String result = LogFormat.newLogLine(); + assertNotNull(result); + assertFalse(result.isEmpty()); + } + + @Test + void exceptionLogReturnsNonEmpty() { + String result = LogFormat.exceptionLog(); + assertNotNull(result); + assertTrue(result.contains("EXCEPTION")); + } + + @Test + void commandLogReturnsNonEmpty() { + String result = LogFormat.commandLog(); + assertNotNull(result); + assertTrue(result.contains("Command")); + } + + @Test + void exceptionLogContainsRedColor() { + String result = LogFormat.exceptionLog(); + assertTrue(result.contains(Color.RED.getCode())); + } + + @Test + void commandLogContainsCyanColor() { + String result = LogFormat.commandLog(); + assertTrue(result.contains(Color.CYAN.getCode())); + } + } + + @Nested + class PrivateConstructor { + @Test + void cannotBeInstantiatedDirectly() { + // LogFormat has a private constructor - verify the class exists as utility + assertNotNull(LogFormat.class); + } + } +} diff --git a/common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/ExceptionMessageTest.java b/common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/ExceptionMessageTest.java new file mode 100644 index 000000000..c5e6ea9e9 --- /dev/null +++ b/common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/ExceptionMessageTest.java @@ -0,0 +1,214 @@ +package com.knubisoft.testlum.testing.framework.constant; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link ExceptionMessage} constants. */ +class ExceptionMessageTest { + + @Nested + class ConfigFilenames { + @Test + void integrationConfigFilename() { + assertEquals("integration.xml", ExceptionMessage.INTEGRATION_CONFIG_FILENAME); + } + + @Test + void uiConfigFilename() { + assertEquals("ui.xml", ExceptionMessage.UI_CONFIG_FILENAME); + } + } + + @Nested + class InterpreterMessages { + @Test + void notDeclaredWithInterpreterForClassContainsPlaceholder() { + assertTrue(ExceptionMessage.NOT_DECLARED_WITH_INTERPRETER_FOR_CLASS.contains("%s")); + } + + @Test + void functionForCommandNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.FUNCTION_FOR_COMMAND_NOT_FOUND.contains("%s")); + } + + @Test + void interpreterNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.INTERPRETER_NOT_FOUND.contains("%s")); + } + } + + @Nested + class ExecutorMessages { + @Test + void notDeclaredWithExecutorForClassContainsPlaceholder() { + assertTrue(ExceptionMessage.NOT_DECLARED_WITH_EXECUTOR_FOR_CLASS.contains("%s")); + } + + @Test + void executorForUiCommandNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.EXECUTOR_FOR_UI_COMMAND_NOT_FOUND.contains("%s")); + } + } + + @Nested + class AliasMessages { + @Test + void aliasByStorageNameNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.ALIAS_BY_STORAGE_NAME_NOT_FOUND.contains("%s")); + } + + @Test + void aliasNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.ALIAS_NOT_FOUND.contains("%s")); + } + } + + @Nested + class EnvironmentMessages { + @Test + void noEnabledEnvironmentsFoundIsNotEmpty() { + assertNotNull(ExceptionMessage.NO_ENABLED_ENVIRONMENTS_FOUND); + assertFalse(ExceptionMessage.NO_ENABLED_ENVIRONMENTS_FOUND.isEmpty()); + } + + @Test + void noEnabledReportGeneratorsFoundIsNotEmpty() { + assertNotNull(ExceptionMessage.NO_ENABLED_REPORT_GENERATORS_FOUND); + assertFalse(ExceptionMessage.NO_ENABLED_REPORT_GENERATORS_FOUND.isEmpty()); + } + } + + @Nested + class ScenarioMessages { + @Test + void scenarioCannotBeIncludedToItself() { + assertEquals("Scenario cannot be included to itself", + ExceptionMessage.SCENARIO_CANNOT_BE_INCLUDED_TO_ITSELF); + } + + @Test + void stopIfNonParsedScenarioIsNotEmpty() { + assertNotNull(ExceptionMessage.STOP_IF_NON_PARSED_SCENARIO); + } + + @Test + void validScenariosNotFoundIsNotEmpty() { + assertNotNull(ExceptionMessage.VALID_SCENARIOS_NOT_FOUND); + } + } + + @Nested + class IntegrationMessages { + @Test + void integrationNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.INTEGRATION_NOT_FOUND.contains("%s")); + } + + @Test + void dbNotSupportedContainsPlaceholder() { + assertTrue(ExceptionMessage.DB_NOT_SUPPORTED.contains("%s")); + } + + @Test + void unsupportedMigrationFormatContainsPlaceholder() { + assertTrue(ExceptionMessage.UNSUPPORTED_MIGRATION_FORMAT.contains("%s")); + } + } + + @Nested + class RedisMessages { + @Test + void redisCommandNotFoundIsNotEmpty() { + assertNotNull(ExceptionMessage.REDIS_COMMAND_NOT_FOUND); + assertEquals("Redis command was not provided", ExceptionMessage.REDIS_COMMAND_NOT_FOUND); + } + } + + @Nested + class DriverMessages { + @Test + void driverInitializerNotFound() { + assertEquals("Driver initializer not found", ExceptionMessage.DRIVER_INITIALIZER_NOT_FOUND); + } + + @Test + void webDriverNotInitContainsCheckConfig() { + assertTrue(ExceptionMessage.WEB_DRIVER_NOT_INIT.contains("check your configuration")); + } + + @Test + void mobilebrowserDriverNotInitContainsCheckConfig() { + assertTrue(ExceptionMessage.MOBILEBROWSER_DRIVER_NOT_INIT.contains("check your configuration")); + } + + @Test + void nativeDriverNotInitContainsCheckConfig() { + assertTrue(ExceptionMessage.NATIVE_DRIVER_NOT_INIT.contains("check your configuration")); + } + } + + @Nested + class VariableMessages { + @Test + void varTypeNotSupportedContainsPlaceholder() { + assertTrue(ExceptionMessage.VAR_TYPE_NOT_SUPPORTED.contains("%s")); + } + + @Test + void varQueryResultErrorIsNotEmpty() { + assertNotNull(ExceptionMessage.VAR_QUERY_RESULT_ERROR); + assertFalse(ExceptionMessage.VAR_QUERY_RESULT_ERROR.isEmpty()); + } + + @Test + void envVariableNotFoundContainsPlaceholder() { + assertTrue(ExceptionMessage.ENV_VARIABLE_NOT_FOUND.contains("%s")); + } + } + + @Nested + class UiMessages { + @Test + void imagesSizeMismatchContainsPlaceholders() { + assertTrue(ExceptionMessage.IMAGES_SIZE_MISMATCH.contains("%s")); + } + + @Test + void imagesMismatchContainsPlaceholder() { + assertTrue(ExceptionMessage.IMAGES_MISMATCH.contains("%s")); + } + + @Test + void tabNotFoundIsNotEmpty() { + assertNotNull(ExceptionMessage.TAB_NOT_FOUND); + } + + @Test + void tabOutOfBoundsContainsPlaceholder() { + assertTrue(ExceptionMessage.TAB_OUT_OF_BOUNDS.contains("%s")); + } + } + + @Nested + class FormattingVerification { + @Test + void formatWithValidArgs() { + String result = String.format(ExceptionMessage.INTEGRATION_NOT_FOUND, "Elasticsearch"); + assertEquals("Cannot find integration configuration for ", result); + } + + @Test + void formatAliasNotFound() { + String result = String.format(ExceptionMessage.ALIAS_NOT_FOUND, "my-alias"); + assertEquals("Cannot find enabled integration with alias ", result); + } + + @Test + void formatDbNotSupported() { + String result = String.format(ExceptionMessage.DB_NOT_SUPPORTED, "CouchDB"); + assertEquals("Database by name CouchDB not supported", result); + } + } +} diff --git a/common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/LogMessageTest.java b/common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/LogMessageTest.java new file mode 100644 index 000000000..73e572416 --- /dev/null +++ b/common/log/src/test/java/com/knubisoft/testlum/testing/framework/constant/LogMessageTest.java @@ -0,0 +1,219 @@ +package com.knubisoft.testlum.testing.framework.constant; + +import com.knubisoft.testlum.log.Color; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** Unit tests for {@link LogMessage} constants. */ +class LogMessageTest { + + @Nested + class UiCommandLogs { + @Test + void uiCommandLogContainsCyan() { + assertTrue(LogMessage.UI_COMMAND_LOG.contains(Color.CYAN.getCode())); + } + + @Test + void uiCommandLogWithoutPositionContainsCyan() { + assertTrue(LogMessage.UI_COMMAND_LOG_WITHOUT_POSITION.contains(Color.CYAN.getCode())); + } + + @Test + void repeatFinishedLogContainsCyan() { + assertTrue(LogMessage.REPEAT_FINISHED_LOG.contains(Color.CYAN.getCode())); + } + + @Test + void commandSkippedOnConditionLogContainsOrange() { + assertTrue(LogMessage.COMMAND_SKIPPED_ON_CONDITION_LOG.contains(Color.ORANGE.getCode())); + } + } + + @Nested + class QueryLogs { + @Test + void queryIsNotNull() { + assertNotNull(LogMessage.QUERY); + } + + @Test + void errorSqlQueryContainsRed() { + assertTrue(LogMessage.ERROR_SQL_QUERY.contains(Color.RED.getCode())); + } + + @Test + void successQueryIsNotEmpty() { + assertEquals("Query completed successfully", LogMessage.SUCCESS_QUERY); + } + } + + @Nested + class ScenarioLogs { + @Test + void scenarioNumberAndPathLogContainsPlaceholder() { + assertTrue(LogMessage.SCENARIO_NUMBER_AND_PATH_LOG.contains("%s")); + } + + @Test + void scenarioNumberAndPathLogCanBeFormatted() { + String result = String.format(LogMessage.SCENARIO_NUMBER_AND_PATH_LOG, "#1"); + assertTrue(result.contains("#1")); + } + + @Test + void invalidScenarioLogContainsRed() { + assertTrue(LogMessage.INVALID_SCENARIO_LOG.contains(Color.RED.getCode())); + } + + @Test + void scenarioWithEmptyTagLogContainsYellow() { + assertTrue(LogMessage.SCENARIO_WITH_EMPTY_TAG_LOG.contains(Color.YELLOW.getCode())); + } + } + + @Nested + class FrameLogs { + @Test + void startUiCommandsInFrameContainsCyan() { + assertTrue(LogMessage.START_UI_COMMANDS_IN_FRAME.contains(Color.CYAN.getCode())); + } + + @Test + void endUiCommandsInFrameContainsCyan() { + assertTrue(LogMessage.END_UI_COMMANDS_IN_FRAME.contains(Color.CYAN.getCode())); + } + + @Test + void startUiCommandsInWebviewContainsCyan() { + assertTrue(LogMessage.START_UI_COMMANDS_IN_WEBVIEW.contains(Color.CYAN.getCode())); + } + + @Test + void endUiCommandsInWebviewContainsCyan() { + assertTrue(LogMessage.END_UI_COMMANDS_IN_WEBVIEW.contains(Color.CYAN.getCode())); + } + } + + @Nested + class ConnectionLogs { + @Test + void connectionIntegrationDataContainsPlaceholders() { + assertTrue(LogMessage.CONNECTION_INTEGRATION_DATA.contains("%s")); + } + + @Test + void connectingInfoContainsCyan() { + assertTrue(LogMessage.CONNECTING_INFO.contains(Color.CYAN.getCode())); + } + + @Test + void connectionSuccessContainsGreen() { + assertTrue(LogMessage.CONNECTION_SUCCESS.contains(Color.GREEN.getCode())); + } + + @Test + void connectionAttemptFailedContainsOrange() { + assertTrue(LogMessage.CONNECTION_ATTEMPT_FAILED.contains(Color.ORANGE.getCode())); + } + + @Test + void connectionCompletelyFailedContainsRed() { + assertTrue(LogMessage.CONNECTION_COMPLETELY_FAILED.contains(Color.RED.getCode())); + } + + @Test + void connectionIntegrationDataFormat() { + String result = String.format(LogMessage.CONNECTION_INTEGRATION_DATA, "Redis", "alias-1"); + assertEquals("Redis - [alias-1]", result); + } + } + + @Nested + class TableFormatLogs { + @Test + void dbTypeLogIsNotNull() { + assertNotNull(LogMessage.DB_TYPE_LOG); + } + + @Test + void aliasLogIsNotNull() { + assertNotNull(LogMessage.ALIAS_LOG); + } + + @Test + void executionTimeLogIsNotNull() { + assertNotNull(LogMessage.EXECUTION_TIME_LOG); + } + + @Test + void locatorLogIsNotNull() { + assertNotNull(LogMessage.LOCATOR_LOG); + } + + @Test + void valueLogIsNotNull() { + assertNotNull(LogMessage.VALUE_LOG); + } + } + + @Nested + class DisabledConfigurationLog { + @Test + void disabledConfigurationContainsYellow() { + assertTrue(LogMessage.DISABLED_CONFIGURATION.contains(Color.YELLOW.getCode())); + } + + @Test + void executionStopSignalLogContainsYellow() { + assertTrue(LogMessage.EXECUTION_STOP_SIGNAL_LOG.contains(Color.YELLOW.getCode())); + } + } + + @Nested + class BrowserInfoLogs { + @Test + void browserInfoContainsPlaceholders() { + assertTrue(LogMessage.BROWSER_INFO.contains("%s")); + } + + @Test + void mobileBrowserInfoContainsPlaceholders() { + assertTrue(LogMessage.MOBILE_BROWSER_INFO.contains("%s")); + } + + @Test + void nativeInfoContainsPlaceholders() { + assertTrue(LogMessage.NATIVE_INFO.contains("%s")); + } + } + + @Nested + class WebsocketLogs { + @Test + void websocketHandlerForTopicNotFoundContainsOrange() { + assertTrue(LogMessage.WEBSOCKET_HANDLER_FOR_TOPIC_NOT_FOUND.contains(Color.ORANGE.getCode())); + } + + @Test + void websocketAlreadySubscribedContainsOrange() { + assertTrue(LogMessage.WEBSOCKET_ALREADY_SUBSCRIBED.contains(Color.ORANGE.getCode())); + } + + @Test + void websocketConnectionEstablishedIsNotEmpty() { + assertNotNull(LogMessage.WEBSOCKET_CONNECTION_ESTABLISHED); + assertFalse(LogMessage.WEBSOCKET_CONNECTION_ESTABLISHED.isEmpty()); + } + } + + @Nested + class PrivateConstructor { + @Test + void classIsFinal() { + assertTrue(java.lang.reflect.Modifier.isFinal(LogMessage.class.getModifiers())); + } + } +} diff --git a/common/xml-parser/pom.xml b/common/xml-parser/pom.xml index 8354dd464..750ed7f6f 100644 --- a/common/xml-parser/pom.xml +++ b/common/xml-parser/pom.xml @@ -85,7 +85,6 @@ org.mockito mockito-core - 5.18.0 test diff --git a/engine/pom.xml b/engine/pom.xml index f0a984973..1344a0434 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -13,13 +13,11 @@ engine TESTLUM + pom ../modules/models/src/main/java ${project.basedir}/src/main/resources/schema - - 4.0.0 - 3.3.0 @@ -64,11 +62,6 @@ elasticsearch-rest-client - - org.elasticsearch.client - elasticsearch-rest-high-level-client - - org.apache.logging.log4j log4j-api @@ -86,7 +79,7 @@ org.mongodb - mongo-java-driver + mongodb-driver-sync @@ -232,21 +225,11 @@ xercesImpl - - javax.xml - jaxp-api - - jakarta.xml.bind jakarta.xml.bind-api - - javax.xml.bind - jaxb-api - - jakarta.activation jakarta.activation-api @@ -356,22 +339,15 @@ org.mockito mockito-core - 5.18.0 test org.mockito mockito-junit-jupiter - 5.18.0 test - - com.googlecode.json-simple - json-simple - - com.aventstack extentreports diff --git a/engine/src/main/java/com/knubisoft/testlum/testing/framework/ConnectionManager.java b/engine/src/main/java/com/knubisoft/testlum/testing/framework/ConnectionManager.java index 49fe495aa..da809ca26 100644 --- a/engine/src/main/java/com/knubisoft/testlum/testing/framework/ConnectionManager.java +++ b/engine/src/main/java/com/knubisoft/testlum/testing/framework/ConnectionManager.java @@ -3,7 +3,6 @@ import com.knubisoft.testlum.testing.framework.env.AliasEnv; import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestHighLevelClient; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -23,10 +22,6 @@ public class ConnectionManager { @Qualifier("restClient") private Map elasticRestClientMap; - @Autowired(required = false) - @Qualifier("restHighLevelClient") - private Map elasticRestHighLevelClientMap; - public void closeConnections() { closeRabbitmqConnections(); try { @@ -48,10 +43,5 @@ private void closeElasticsearchConnections() throws IOException { restClient.close(); } } - if (Objects.nonNull(elasticRestHighLevelClientMap)) { - for (RestHighLevelClient restClient : elasticRestHighLevelClientMap.values()) { - restClient.close(); - } - } } } diff --git a/engine/src/main/resources/META-INF/spring.factories b/engine/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 4fa39b4fc..000000000 --- a/engine/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,10 +0,0 @@ -org.springframework.test.context.TestExecutionListener = \ - org.springframework.test.context.web.ServletTestExecutionListener,\ - org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener,\ - org.springframework.test.context.support.DependencyInjectionTestExecutionListener,\ - org.springframework.test.context.support.DirtiesContextTestExecutionListener,\ - org.springframework.test.context.transaction.TransactionalTestExecutionListener,\ - org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener - -org.springframework.context.ApplicationContextInitializer = \ - com.knubisoft.testlum.testing.TestResourceInfrastructureInitializer \ No newline at end of file diff --git a/engine/src/main/resources/META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports b/engine/src/main/resources/META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports new file mode 100644 index 000000000..d70d7ddf0 --- /dev/null +++ b/engine/src/main/resources/META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports @@ -0,0 +1 @@ +com.knubisoft.testlum.testing.TestResourceInfrastructureInitializer \ No newline at end of file diff --git a/engine/src/main/resources/assembly-plugin-configuration.xml b/engine/src/main/resources/assembly-plugin-configuration.xml index 788d72e9b..f93c7496c 100644 --- a/engine/src/main/resources/assembly-plugin-configuration.xml +++ b/engine/src/main/resources/assembly-plugin-configuration.xml @@ -15,7 +15,7 @@ true - META-INF/spring.factories + META-INF/spring/** test @@ -23,8 +23,8 @@ - ${project.basedir}/src/main/resources/META-INF/spring.factories - META-INF + ${project.basedir}/src/main/resources/META-INF/spring/org.springframework.context.ApplicationContextInitializer.imports + META-INF/spring diff --git a/engine/src/test/java/com/knubisoft/testlum/starter/TESTLUMStarterTest.java b/engine/src/test/java/com/knubisoft/testlum/starter/TESTLUMStarterTest.java index 63ebc12d6..fbcec9a97 100644 --- a/engine/src/test/java/com/knubisoft/testlum/starter/TESTLUMStarterTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/starter/TESTLUMStarterTest.java @@ -4,6 +4,8 @@ import org.junit.jupiter.api.Test; import org.junit.platform.launcher.listeners.TestExecutionSummary; +import java.lang.reflect.Method; + import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -41,6 +43,18 @@ void invalidConfigurationHasCodeThree() { void hasFourValues() { assertEquals(4, TESTLUMStarter.ExitCode.values().length); } + + @Test + void valueOfReturnsCorrectEnum() { + assertEquals(TESTLUMStarter.ExitCode.TESTS_PASSED, + TESTLUMStarter.ExitCode.valueOf("TESTS_PASSED")); + assertEquals(TESTLUMStarter.ExitCode.TESTS_FAILED, + TESTLUMStarter.ExitCode.valueOf("TESTS_FAILED")); + assertEquals(TESTLUMStarter.ExitCode.NO_TESTS_FOUND, + TESTLUMStarter.ExitCode.valueOf("NO_TESTS_FOUND")); + assertEquals(TESTLUMStarter.ExitCode.INVALID_CONFIGURATION, + TESTLUMStarter.ExitCode.valueOf("INVALID_CONFIGURATION")); + } } @Nested @@ -88,5 +102,150 @@ void noTestsFoundTakesPriorityOverFailures() { assertEquals(TESTLUMStarter.ExitCode.NO_TESTS_FOUND, result); } + + @Test + void returnsTestsPassedWhenOneTestPassed() { + TestExecutionSummary summary = mock(TestExecutionSummary.class); + when(summary.getTestsFoundCount()).thenReturn(1L); + when(summary.getTestsFailedCount()).thenReturn(0L); + + TESTLUMStarter.ExitCode result = TESTLUMStarter.getExitCode(summary); + + assertEquals(TESTLUMStarter.ExitCode.TESTS_PASSED, result); + } + + @Test + void returnsTestsFailedWhenOneTestFailed() { + TestExecutionSummary summary = mock(TestExecutionSummary.class); + when(summary.getTestsFoundCount()).thenReturn(1L); + when(summary.getTestsFailedCount()).thenReturn(1L); + + TESTLUMStarter.ExitCode result = TESTLUMStarter.getExitCode(summary); + + assertEquals(TESTLUMStarter.ExitCode.TESTS_FAILED, result); + } + } + + @Nested + class InputValidatorTests { + + @Test + void constructCreatesNewInstance() throws Exception { + Class validatorClass = getInputValidatorClass(); + Method constructMethod = validatorClass.getDeclaredMethod("construct"); + constructMethod.setAccessible(true); + + Object validator = constructMethod.invoke(null); + assertNotNull(validator); + } + + @Test + void checkAddsErrorWhenConditionIsTrue() throws Exception { + Class validatorClass = getInputValidatorClass(); + Object validator = createInputValidator(validatorClass); + + Method checkMethod = validatorClass.getDeclaredMethod("check", + String.class, java.util.function.Supplier.class, String.class); + checkMethod.setAccessible(true); + + java.util.function.Supplier trueSupplier = () -> true; + Object result = checkMethod.invoke(validator, "param1", trueSupplier, "error msg"); + assertNotNull(result); + assertSame(validator, result); + } + + @Test + void checkDoesNotAddErrorWhenConditionIsFalse() throws Exception { + Class validatorClass = getInputValidatorClass(); + Object validator = createInputValidator(validatorClass); + + Method checkMethod = validatorClass.getDeclaredMethod("check", + String.class, java.util.function.Supplier.class, String.class); + checkMethod.setAccessible(true); + + java.util.function.Supplier falseSupplier = () -> false; + Object result = checkMethod.invoke(validator, "param1", falseSupplier, "error msg"); + assertNotNull(result); + } + + @Test + void checkCatchesExceptionAndAddsError() throws Exception { + Class validatorClass = getInputValidatorClass(); + Object validator = createInputValidator(validatorClass); + + Method checkMethod = validatorClass.getDeclaredMethod("check", + String.class, java.util.function.Supplier.class, String.class); + checkMethod.setAccessible(true); + + java.util.function.Supplier throwingSupplier = () -> { + throw new RuntimeException("boom"); + }; + + Object result = checkMethod.invoke(validator, "param1", throwingSupplier, "error msg"); + assertNotNull(result); + } + + @Test + void exitIfAnyDoesNothingWhenNoErrors() throws Exception { + Class validatorClass = getInputValidatorClass(); + Object validator = createInputValidator(validatorClass); + + // Add a passing check first + Method checkMethod = validatorClass.getDeclaredMethod("check", + String.class, java.util.function.Supplier.class, String.class); + checkMethod.setAccessible(true); + checkMethod.invoke(validator, "param1", (java.util.function.Supplier) () -> false, "no error"); + + // exitIfAny should not call System.exit when there are no errors + Method exitIfAny = validatorClass.getDeclaredMethod("exitIfAny"); + exitIfAny.setAccessible(true); + assertDoesNotThrow(() -> exitIfAny.invoke(validator)); + } + + @Test + void chainMultipleChecks() throws Exception { + Class validatorClass = getInputValidatorClass(); + Object validator = createInputValidator(validatorClass); + + Method checkMethod = validatorClass.getDeclaredMethod("check", + String.class, java.util.function.Supplier.class, String.class); + checkMethod.setAccessible(true); + + Object r1 = checkMethod.invoke(validator, "p1", (java.util.function.Supplier) () -> false, "ok"); + Object r2 = checkMethod.invoke(r1, "p2", (java.util.function.Supplier) () -> false, "ok"); + assertSame(validator, r2); + } + + private Class getInputValidatorClass() { + Class[] innerClasses = TESTLUMStarter.class.getDeclaredClasses(); + for (Class c : innerClasses) { + if (c.getSimpleName().equals("InputValidator")) { + return c; + } + } + fail("InputValidator inner class not found"); + return null; + } + + private Object createInputValidator(final Class validatorClass) throws Exception { + Method constructMethod = validatorClass.getDeclaredMethod("construct"); + constructMethod.setAccessible(true); + return constructMethod.invoke(null); + } + } + + @Nested + class ClassAnnotations { + + @Test + void hasSpringBootApplicationAnnotation() { + assertNotNull(TESTLUMStarter.class.getAnnotation( + org.springframework.boot.autoconfigure.SpringBootApplication.class)); + } + + @Test + void mainMethodExists() throws NoSuchMethodException { + assertNotNull(TESTLUMStarter.class.getDeclaredMethod("main", String[].class)); + } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/ConnectionManagerTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/ConnectionManagerTest.java index a6ac6d28c..aa9072064 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/ConnectionManagerTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/ConnectionManagerTest.java @@ -2,7 +2,6 @@ import com.knubisoft.testlum.testing.framework.env.AliasEnv; import org.elasticsearch.client.RestClient; -import org.elasticsearch.client.RestHighLevelClient; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; @@ -57,40 +56,22 @@ void closeConnectionsCallsCloseOnElasticRestClients() throws IOException { verify(restClient).close(); } - @Test - void closeConnectionsCallsCloseOnElasticRestHighLevelClients() throws IOException { - AliasEnv alias = mock(AliasEnv.class); - RestHighLevelClient highLevelClient = mock(RestHighLevelClient.class); - - Map highLevelClientMap = Map.of(alias, highLevelClient); - ReflectionTestUtils.setField(connectionManager, "elasticRestHighLevelClientMap", highLevelClientMap); - - connectionManager.closeConnections(); - - verify(highLevelClient).close(); - } - @Test void closeConnectionsClosesAllConnectionTypes() throws IOException { AliasEnv rabbitAlias = mock(AliasEnv.class); AliasEnv restAlias = mock(AliasEnv.class); - AliasEnv highLevelAlias = mock(AliasEnv.class); ConnectionFactory factory = mock(ConnectionFactory.class); RestClient restClient = mock(RestClient.class); - RestHighLevelClient highLevelClient = mock(RestHighLevelClient.class); ReflectionTestUtils.setField(connectionManager, "rabbitConnectionFactoryMap", Map.of(rabbitAlias, factory)); ReflectionTestUtils.setField(connectionManager, "elasticRestClientMap", Map.of(restAlias, restClient)); - ReflectionTestUtils.setField(connectionManager, "elasticRestHighLevelClientMap", - Map.of(highLevelAlias, highLevelClient)); connectionManager.closeConnections(); verify(factory).resetConnection(); verify(restClient).close(); - verify(highLevelClient).close(); } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/TestSetCollectorTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/TestSetCollectorTest.java index 2bfe17ea5..7e3c726a9 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/TestSetCollectorTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/TestSetCollectorTest.java @@ -1,14 +1,17 @@ package com.knubisoft.testlum.testing.framework; +import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; import com.knubisoft.testlum.testing.framework.scenario.ScenarioCollector; import com.knubisoft.testlum.testing.framework.scenario.ScenarioCollector.MappingResult; import com.knubisoft.testlum.testing.framework.scenario.ScenarioFilter; import com.knubisoft.testlum.testing.framework.util.BrowserUtil; import com.knubisoft.testlum.testing.framework.util.MobileUtil; import com.knubisoft.testlum.testing.framework.variations.GlobalVariationsProvider; +import com.knubisoft.testlum.testing.model.global_config.AbstractBrowser; import com.knubisoft.testlum.testing.model.global_config.Environment; import com.knubisoft.testlum.testing.model.scenario.Scenario; import com.knubisoft.testlum.testing.model.scenario.Settings; +import com.knubisoft.testlum.testing.model.scenario.Web; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -18,18 +21,16 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; /** - * Unit tests for {@link TestSetCollector} verifying test set collection - * and argument creation for parameterized tests. + * Unit tests for {@link TestSetCollector} verifying test set collection, + * argument creation for parameterized tests, environment expansion, + * UI/non-UI scenario handling, and variations support. */ @ExtendWith(MockitoExtension.class) class TestSetCollectorTest { @@ -82,10 +83,7 @@ void returnsEmptyStreamForNoScenarios() { @Test void returnsArgumentsForNonUiScenario() { final ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); - final Scenario scenario = new Scenario(); - final Settings settings = new Settings(); - settings.setActive(true); - scenario.setSettings(settings); + final Scenario scenario = createScenarioWithSettings(); final MappingResult mappingResult = new MappingResult( new File("/scenarios/test/scenario.xml"), scenario, null); collectorResult.add(mappingResult); @@ -100,6 +98,25 @@ void returnsArgumentsForNonUiScenario() { final List argumentsList = result.toList(); assertEquals(1, argumentsList.size()); } + + @Test + void returnsMultipleScenariosAsMultipleArguments() { + final ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); + final MappingResult mr1 = new MappingResult( + new File("/scenarios/test1/scenario.xml"), createScenarioWithSettings(), null); + final MappingResult mr2 = new MappingResult( + new File("/scenarios/test2/scenario.xml"), createScenarioWithSettings(), null); + collectorResult.add(mr1); + collectorResult.add(mr2); + + when(scenarioCollector.collect()).thenReturn(collectorResult); + when(scenarioFilter.filterScenarios(collectorResult)).thenReturn(List.of(mr1, mr2)); + when(testResourceSettings.getScenariosFolder()).thenReturn(new File("/scenarios")); + + final List argumentsList = testSetCollector.collect().toList(); + + assertEquals(2, argumentsList.size()); + } } @Nested @@ -108,10 +125,7 @@ class CreateArguments { @Test void nonUiScenarioCreatesOneArgumentPerEnvironment() { final ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); - final Scenario scenario = new Scenario(); - final Settings settings = new Settings(); - settings.setActive(true); - scenario.setSettings(settings); + final Scenario scenario = createScenarioWithSettings(); final MappingResult mappingResult = new MappingResult( new File("/scenarios/api-test/scenario.xml"), scenario, null); collectorResult.add(mappingResult); @@ -125,4 +139,167 @@ void nonUiScenarioCreatesOneArgumentPerEnvironment() { assertEquals(1, argumentsList.size()); } } + + @Nested + class UiScenarios { + + @Test + void webScenarioExpandsByBrowsers() { + // Setup browsers + AbstractBrowser chrome = mock(AbstractBrowser.class); + AbstractBrowser firefox = mock(AbstractBrowser.class); + when(chrome.getAlias()).thenReturn("chrome"); + when(firefox.getAlias()).thenReturn("firefox"); + when(browserUtil.filterDefaultEnabledBrowsers()).thenReturn(List.of(chrome, firefox)); + + Environment env = new Environment(); + env.setFolder("dev"); + + testSetCollector = new TestSetCollector( + scenarioCollector, scenarioFilter, browserUtil, mobileUtil, + testResourceSettings, globalVariationsProvider, List.of(env)); + + // Scenario with web command + Scenario scenario = createScenarioWithSettings(); + scenario.getCommands().add(new Web()); + + MappingResult mr = new MappingResult( + new File("/scenarios/web-test/scenario.xml"), scenario, null); + ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); + collectorResult.add(mr); + + when(scenarioCollector.collect()).thenReturn(collectorResult); + when(scenarioFilter.filterScenarios(collectorResult)).thenReturn(List.of(mr)); + when(testResourceSettings.getScenariosFolder()).thenReturn(new File("/scenarios")); + + List argumentsList = testSetCollector.collect().toList(); + + // 2 browsers x 1 environment = 2 + assertEquals(2, argumentsList.size()); + } + } + + @Nested + class MultipleEnvironments { + + @Test + void expandsScenariosAcrossEnvironments() { + Environment dev = new Environment(); + dev.setFolder("dev"); + Environment staging = new Environment(); + staging.setFolder("staging"); + + when(browserUtil.filterDefaultEnabledBrowsers()).thenReturn(Collections.emptyList()); + when(mobileUtil.filterDefaultEnabledMobileBrowserDevices()).thenReturn(Collections.emptyList()); + when(mobileUtil.filterDefaultEnabledNativeDevices()).thenReturn(Collections.emptyList()); + + testSetCollector = new TestSetCollector( + scenarioCollector, scenarioFilter, browserUtil, mobileUtil, + testResourceSettings, globalVariationsProvider, List.of(dev, staging)); + + ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); + MappingResult mr = new MappingResult( + new File("/scenarios/test/scenario.xml"), createScenarioWithSettings(), null); + collectorResult.add(mr); + + when(scenarioCollector.collect()).thenReturn(collectorResult); + when(scenarioFilter.filterScenarios(collectorResult)).thenReturn(List.of(mr)); + when(testResourceSettings.getScenariosFolder()).thenReturn(new File("/scenarios")); + + List argumentsList = testSetCollector.collect().toList(); + + // 1 scenario x 2 environments = 2 + assertEquals(2, argumentsList.size()); + } + } + + @Nested + class VariationsSupport { + + @Test + void expandsNonUiScenarioWithVariations() { + Scenario scenario = createScenarioWithSettings(); + scenario.getSettings().setVariations("vars.csv"); + + MappingResult mr = new MappingResult( + new File("/scenarios/var-test/scenario.xml"), scenario, null); + ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); + collectorResult.add(mr); + + when(scenarioCollector.collect()).thenReturn(collectorResult); + when(scenarioFilter.filterScenarios(collectorResult)).thenReturn(List.of(mr)); + when(testResourceSettings.getScenariosFolder()).thenReturn(new File("/scenarios")); + + Map v1 = Map.of("user", "alice"); + Map v2 = Map.of("user", "bob"); + when(globalVariationsProvider.getVariations("vars.csv")).thenReturn(List.of(v1, v2)); + + List argumentsList = testSetCollector.collect().toList(); + + // 2 variations x 1 environment = 2 + assertEquals(2, argumentsList.size()); + } + } + + @Nested + class EmptyEnvironments { + + @Test + void throwsWhenNoEnvironmentsDuringCollect() { + when(browserUtil.filterDefaultEnabledBrowsers()).thenReturn(Collections.emptyList()); + when(mobileUtil.filterDefaultEnabledMobileBrowserDevices()).thenReturn(Collections.emptyList()); + when(mobileUtil.filterDefaultEnabledNativeDevices()).thenReturn(Collections.emptyList()); + + TestSetCollector emptyEnvCollector = new TestSetCollector( + scenarioCollector, scenarioFilter, browserUtil, mobileUtil, + testResourceSettings, globalVariationsProvider, Collections.emptyList()); + + assertThrows(DefaultFrameworkException.class, emptyEnvCollector::collect); + } + + @Test + void throwsWhenEnvironmentsNullDuringCollect() { + when(browserUtil.filterDefaultEnabledBrowsers()).thenReturn(Collections.emptyList()); + when(mobileUtil.filterDefaultEnabledMobileBrowserDevices()).thenReturn(Collections.emptyList()); + when(mobileUtil.filterDefaultEnabledNativeDevices()).thenReturn(Collections.emptyList()); + + TestSetCollector nullEnvCollector = new TestSetCollector( + scenarioCollector, scenarioFilter, browserUtil, mobileUtil, + testResourceSettings, globalVariationsProvider, null); + + assertThrows(DefaultFrameworkException.class, nullEnvCollector::collect); + } + } + + @Nested + class ShortPath { + + @Test + void removesScenariosFolderPrefix() { + ScenarioCollector.Result collectorResult = new ScenarioCollector.Result(); + Scenario scenario = createScenarioWithSettings(); + MappingResult mr = new MappingResult( + new File("/scenarios/deep/nested/scenario.xml"), scenario, null); + collectorResult.add(mr); + + when(scenarioCollector.collect()).thenReturn(collectorResult); + when(scenarioFilter.filterScenarios(collectorResult)).thenReturn(List.of(mr)); + when(testResourceSettings.getScenariosFolder()).thenReturn(new File("/scenarios")); + + List argumentsList = testSetCollector.collect().toList(); + + assertEquals(1, argumentsList.size()); + // Verify the named argument contains the short path + Object[] args = argumentsList.get(0).get(); + assertNotNull(args); + } + } + + private Scenario createScenarioWithSettings() { + final Scenario scenario = new Scenario(); + final Settings settings = new Settings(); + settings.setActive(true); + scenario.setSettings(settings); + return scenario; + } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/env/service/EnvironmentExecutionServiceTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/env/service/EnvironmentExecutionServiceTest.java index 4bd1cfeb9..3e743b59b 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/env/service/EnvironmentExecutionServiceTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/env/service/EnvironmentExecutionServiceTest.java @@ -3,10 +3,12 @@ import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; import com.knubisoft.testlum.testing.model.global_config.Environment; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; @@ -23,45 +25,131 @@ void tearDown() { } } - @Test - void constructorCreatesExecutorsForEachEnvironment() { - Environment env1 = createEnvironment("env1", 2); - Environment env2 = createEnvironment("env2", 3); + @Nested + class Constructor { - service = new EnvironmentExecutionService(List.of(env1, env2)); + @Test + void createsExecutorsForEachEnvironment() { + Environment env1 = createEnvironment("env1", 2); + Environment env2 = createEnvironment("env2", 3); - AtomicBoolean executed = new AtomicBoolean(false); - assertDoesNotThrow(() -> service.runInEnvironment("env1", () -> executed.set(true))); - assertTrue(executed.get()); - } + service = new EnvironmentExecutionService(List.of(env1, env2)); + + AtomicBoolean executed = new AtomicBoolean(false); + assertDoesNotThrow(() -> service.runInEnvironment("env1", () -> executed.set(true))); + assertTrue(executed.get()); + } - @Test - void runInEnvironmentExecutesTaskSuccessfully() { - Environment env = createEnvironment("testEnv", 1); - service = new EnvironmentExecutionService(List.of(env)); + @Test + void handlesZeroThreadsGracefully() { + Environment env = createEnvironment("zeroEnv", 0); + service = new EnvironmentExecutionService(List.of(env)); + + AtomicBoolean executed = new AtomicBoolean(false); + service.runInEnvironment("zeroEnv", () -> executed.set(true)); + assertTrue(executed.get()); + } - AtomicBoolean taskExecuted = new AtomicBoolean(false); - service.runInEnvironment("testEnv", () -> taskExecuted.set(true)); + @Test + void handlesNegativeThreadsGracefully() { + Environment env = createEnvironment("negEnv", -5); + service = new EnvironmentExecutionService(List.of(env)); - assertTrue(taskExecuted.get()); + AtomicBoolean executed = new AtomicBoolean(false); + service.runInEnvironment("negEnv", () -> executed.set(true)); + assertTrue(executed.get()); + } } - @Test - void runInEnvironmentThrowsForUnknownEnvironment() { - Environment env = createEnvironment("knownEnv", 1); - service = new EnvironmentExecutionService(List.of(env)); + @Nested + class RunInEnvironment { + + @Test + void executesTaskSuccessfully() { + Environment env = createEnvironment("testEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); + + AtomicBoolean taskExecuted = new AtomicBoolean(false); + service.runInEnvironment("testEnv", () -> taskExecuted.set(true)); + + assertTrue(taskExecuted.get()); + } + + @Test + void throwsForUnknownEnvironment() { + Environment env = createEnvironment("knownEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); + + assertThrows(DefaultFrameworkException.class, + () -> service.runInEnvironment("unknownEnv", () -> { })); + } + + @Test + void propagatesRuntimeExceptionFromTask() { + Environment env = createEnvironment("errEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); + + assertThrows(DefaultFrameworkException.class, + () -> service.runInEnvironment("errEnv", () -> { + throw new RuntimeException("task failed"); + })); + } + + @Test + void propagatesDefaultFrameworkExceptionFromTask() { + Environment env = createEnvironment("fwEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); - assertThrows(DefaultFrameworkException.class, - () -> service.runInEnvironment("unknownEnv", () -> { })); + assertThrows(DefaultFrameworkException.class, + () -> service.runInEnvironment("fwEnv", () -> { + throw new DefaultFrameworkException("framework error"); + })); + } + + @Test + void runsMultipleTasksSequentially() { + Environment env = createEnvironment("seqEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); + + AtomicReference result = new AtomicReference<>(""); + service.runInEnvironment("seqEnv", () -> result.set(result.get() + "A")); + service.runInEnvironment("seqEnv", () -> result.set(result.get() + "B")); + + assertEquals("AB", result.get()); + } } - @Test - void shutdownCompletesWithoutError() { - Environment env = createEnvironment("shutdownEnv", 1); - service = new EnvironmentExecutionService(List.of(env)); + @Nested + class Shutdown { + + @Test + void shutdownCompletesWithoutError() { + Environment env = createEnvironment("shutdownEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); + + assertDoesNotThrow(() -> service.shutdown()); + service = null; + } + + @Test + void shutdownMultipleEnvironments() { + Environment env1 = createEnvironment("env1", 1); + Environment env2 = createEnvironment("env2", 2); + service = new EnvironmentExecutionService(List.of(env1, env2)); + + assertDoesNotThrow(() -> service.shutdown()); + service = null; + } + + @Test + void doubleShutdownDoesNotThrow() { + Environment env = createEnvironment("dblEnv", 1); + service = new EnvironmentExecutionService(List.of(env)); - assertDoesNotThrow(() -> service.shutdown()); - service = null; + service.shutdown(); + assertDoesNotThrow(() -> service.shutdown()); + service = null; + } } private Environment createEnvironment(final String folder, final int threads) { diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/InterpreterProviderTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/InterpreterProviderTest.java index f11dd0857..8468eb6c8 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/InterpreterProviderTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/InterpreterProviderTest.java @@ -1,18 +1,25 @@ package com.knubisoft.testlum.testing.framework.interpreter.lib; +import com.knubisoft.testlum.testing.framework.FileSearcher; +import com.knubisoft.testlum.testing.framework.configuration.ConfigProvider; import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; +import com.knubisoft.testlum.testing.framework.report.CommandResult; +import com.knubisoft.testlum.testing.framework.util.ConditionProvider; +import com.knubisoft.testlum.testing.framework.util.JacksonService; +import com.knubisoft.testlum.testing.framework.util.StringPrettifier; +import com.knubisoft.testlum.testing.model.global_config.GlobalTestConfiguration; import com.knubisoft.testlum.testing.model.scenario.AbstractCommand; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; /** - * Unit tests for {@link InterpreterProvider} verifying interpreter initialization - * and lookup for unknown commands. + * Unit tests for {@link InterpreterProvider} verifying interpreter initialization, + * lookup for unknown commands, successful resolution, and reflective instantiation errors. */ class InterpreterProviderTest { @@ -25,23 +32,146 @@ void setUp() { provider = new InterpreterProvider(scanner); } - @Test - void initCallsScannerGetInterpreters() { - final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); - when(scanner.getInterpreters()).thenReturn(map); - provider.init(); - assertNotNull(provider); + @Nested + class Init { + + @Test + void initCallsScannerGetInterpreters() { + final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); + when(scanner.getInterpreters()).thenReturn(map); + provider.init(); + assertNotNull(provider); + verify(scanner).getInterpreters(); + } + + @Test + void initCanBeCalledMultipleTimes() { + final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); + when(scanner.getInterpreters()).thenReturn(map); + provider.init(); + provider.init(); + verify(scanner, times(2)).getInterpreters(); + } + } + + @Nested + class GetAppropriateInterpreter { + + @Test + void throwsForUnknownCommand() { + final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); + when(scanner.getInterpreters()).thenReturn(map); + provider.init(); + + final AbstractCommand unknownCommand = mock(AbstractCommand.class); + assertThrows(DefaultFrameworkException.class, + () -> provider.getAppropriateInterpreter(unknownCommand, + mock(InterpreterDependencies.class))); + } + + @Test + void throwsForNullMappedInterpreter() { + final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); + when(scanner.getInterpreters()).thenReturn(map); + provider.init(); + + final AbstractCommand command = mock(AbstractCommand.class); + DefaultFrameworkException ex = assertThrows(DefaultFrameworkException.class, + () -> provider.getAppropriateInterpreter(command, mock(InterpreterDependencies.class))); + assertNotNull(ex.getMessage()); + } + + @SuppressWarnings("unchecked") + @Test + void throwsWhenInterpreterClassLacksConstructor() { + final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); + + // Use a class that does not have a constructor taking InterpreterDependencies + Class> badClass = + (Class>) (Class) BadInterpreter.class; + + // Create a concrete AbstractCommand subclass for the test + Class cmdClass = TestCommand.class; + map.put(cmdClass, badClass); + + when(scanner.getInterpreters()).thenReturn(map); + provider.init(); + + TestCommand command = new TestCommand(); + assertThrows(DefaultFrameworkException.class, + () -> provider.getAppropriateInterpreter(command, mock(InterpreterDependencies.class))); + } + + @SuppressWarnings("unchecked") + @Test + void returnsInterpreterWhenCorrectlyMapped() { + final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); + + Class> goodClass = + (Class>) (Class) GoodInterpreter.class; + + Class cmdClass = TestCommand.class; + map.put(cmdClass, goodClass); + + when(scanner.getInterpreters()).thenReturn(map); + provider.init(); + + TestCommand command = new TestCommand(); + InterpreterDependencies deps = createMockDependencies(); + AbstractInterpreter result = + provider.getAppropriateInterpreter(command, deps); + + assertNotNull(result); + assertInstanceOf(GoodInterpreter.class, result); + } + } + + private InterpreterDependencies createMockDependencies() { + InterpreterDependencies deps = mock(InterpreterDependencies.class); + ApplicationContext ctx = mock(ApplicationContext.class); + when(deps.getContext()).thenReturn(ctx); + when(ctx.getBean(ConfigProvider.class)).thenReturn(mock(ConfigProvider.class)); + when(ctx.getBean(ConditionProvider.class)).thenReturn(mock(ConditionProvider.class)); + when(ctx.getBean(FileSearcher.class)).thenReturn(mock(FileSearcher.class)); + when(ctx.getBean(JacksonService.class)).thenReturn(mock(JacksonService.class)); + when(ctx.getBean(StringPrettifier.class)).thenReturn(mock(StringPrettifier.class)); + GlobalTestConfiguration config = mock(GlobalTestConfiguration.class); + when(config.isStopScenarioOnFailure()).thenReturn(false); + when(ctx.getBean(GlobalTestConfiguration.class)).thenReturn(config); + return deps; + } + + // Test helpers + + public static class TestCommand extends AbstractCommand { + } + + /** + * An interpreter without the required InterpreterDependencies constructor. + */ + @InterpreterForClass(TestCommand.class) + public static class BadInterpreter extends AbstractInterpreter { + // No InterpreterDependencies constructor + BadInterpreter() { + super(null); + } + + @Override + protected void acceptImpl(final TestCommand command, final CommandResult result) { + } } - @Test - void getAppropriateInterpreterThrowsForUnknownCommand() { - final CommandToInterpreterClassMap map = new CommandToInterpreterClassMap(); - when(scanner.getInterpreters()).thenReturn(map); - provider.init(); + /** + * An interpreter with the required InterpreterDependencies constructor. + */ + @InterpreterForClass(TestCommand.class) + public static class GoodInterpreter extends AbstractInterpreter { + public GoodInterpreter(final InterpreterDependencies dependencies) { + super(dependencies); + } - final AbstractCommand unknownCommand = mock(AbstractCommand.class); - assertThrows(DefaultFrameworkException.class, - () -> provider.getAppropriateInterpreter(unknownCommand, - mock(InterpreterDependencies.class))); + @Override + protected void acceptImpl(final TestCommand command, final CommandResult result) { + } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/RepeatCommandsRunnerImplTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/RepeatCommandsRunnerImplTest.java index c8f0219a8..6fde6a269 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/RepeatCommandsRunnerImplTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/RepeatCommandsRunnerImplTest.java @@ -11,19 +11,17 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -/** - * Unit tests for {@link RepeatCommandsRunnerImpl} verifying command iteration - * and empty list handling. - */ @ExtendWith(MockitoExtension.class) class RepeatCommandsRunnerImplTest { @@ -78,8 +76,9 @@ void iteratesOverCommands() { when(interpreterProvider.getAppropriateInterpreter(any(), eq(dependencies))) .thenReturn(interpreter); - final org.springframework.context.ApplicationContext ctx = - mock(org.springframework.context.ApplicationContext.class); + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); when(dependencies.getContext()).thenReturn(ctx); final List subResults = new ArrayList<>(); @@ -88,6 +87,97 @@ void iteratesOverCommands() { verify(resultUtil, times(2)) .newCommandResultInstance(anyInt(), any(AbstractCommand.class)); verify(resultUtil).setExecutionResultIfSubCommandsFailed(result); + assertEquals(2, subResults.size()); + } + + @Test + void exceptionDuringExecutionIsHandled() { + final AbstractCommand cmd = mock(AbstractCommand.class); + final List commands = List.of(cmd); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final InterpreterDependencies dependencies = mock(InterpreterDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newCommandResultInstance(anyInt(), any(AbstractCommand.class))) + .thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractInterpreter interpreter = mock(AbstractInterpreter.class); + RuntimeException ex = new RuntimeException("interpreter failed"); + doThrow(ex).when(interpreter).apply(any(), any()); + when(interpreterProvider.getAppropriateInterpreter(any(), eq(dependencies))) + .thenReturn(interpreter); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + final List subResults = new ArrayList<>(); + runner.runCommands(commands, dependencies, result, subResults); + + verify(resultUtil).setExceptionResult(stepResult, ex); + verify(logUtil).logException(ex); + verify(configUtil).checkIfStopScenarioOnFailure(ex); + } + + @Test + void executionTimeIsLogged() { + final AbstractCommand cmd = mock(AbstractCommand.class); + final List commands = List.of(cmd); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final InterpreterDependencies dependencies = mock(InterpreterDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newCommandResultInstance(anyInt(), any(AbstractCommand.class))) + .thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractInterpreter interpreter = mock(AbstractInterpreter.class); + when(interpreterProvider.getAppropriateInterpreter(any(), eq(dependencies))) + .thenReturn(interpreter); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + final List subResults = new ArrayList<>(); + runner.runCommands(commands, dependencies, result, subResults); + + verify(logUtil).logExecutionTime(anyLong(), eq(cmd)); + } + + @Test + void positionIsIncremented() { + final AbstractCommand cmd1 = mock(AbstractCommand.class); + final AbstractCommand cmd2 = mock(AbstractCommand.class); + final List commands = List.of(cmd1, cmd2); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final InterpreterDependencies dependencies = mock(InterpreterDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newCommandResultInstance(anyInt(), any(AbstractCommand.class))) + .thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractInterpreter interpreter = mock(AbstractInterpreter.class); + when(interpreterProvider.getAppropriateInterpreter(any(), eq(dependencies))) + .thenReturn(interpreter); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + final List subResults = new ArrayList<>(); + runner.runCommands(commands, dependencies, result, subResults); + + verify(resultUtil).newCommandResultInstance(eq(1), eq(cmd1)); + verify(resultUtil).newCommandResultInstance(eq(2), eq(cmd2)); } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/SubCommandRunnerImplTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/SubCommandRunnerImplTest.java index b37353c3b..06841273a 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/SubCommandRunnerImplTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/SubCommandRunnerImplTest.java @@ -8,26 +8,24 @@ import com.knubisoft.testlum.testing.framework.util.LogUtil; import com.knubisoft.testlum.testing.framework.util.ResultUtil; import com.knubisoft.testlum.testing.model.scenario.AbstractUiCommand; +import com.knubisoft.testlum.testing.model.scenario.WebAssert; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.context.ApplicationContext; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; -/** - * Unit tests for {@link SubCommandRunnerImpl} verifying UI command iteration - * and empty list handling. - */ @ExtendWith(MockitoExtension.class) class SubCommandRunnerImplTest { @@ -80,8 +78,9 @@ void iteratesOverUiCommands() { final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); - final org.springframework.context.ApplicationContext ctx = - mock(org.springframework.context.ApplicationContext.class); + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); when(dependencies.getContext()).thenReturn(ctx); runner.runCommands(commands, result, dependencies); @@ -89,6 +88,110 @@ void iteratesOverUiCommands() { verify(resultUtil, times(2)).newUiCommandResultInstance(anyInt(), any(AbstractUiCommand.class)); verify(resultUtil).setExecutionResultIfSubCommandsFailed(result); } + + @Test + void webAssertCommandUsesPositionZero() { + final WebAssert webAssert = mock(WebAssert.class); + final List commands = List.of(webAssert); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + when(resultUtil.newUiCommandResultInstance(eq(0), eq(webAssert))).thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); + when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + runner.runCommands(commands, result, dependencies); + + verify(resultUtil).newUiCommandResultInstance(0, webAssert); + } + + @Test + void exceptionDuringExecutionIsHandled() { + final AbstractUiCommand cmd = mock(AbstractUiCommand.class); + final List commands = List.of(cmd); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newUiCommandResultInstance(anyInt(), any(AbstractUiCommand.class))).thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); + RuntimeException ex = new RuntimeException("exec failed"); + doThrow(ex).when(executor).apply(any(), any()); + when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + runner.runCommands(commands, result, dependencies); + + verify(resultUtil).setExceptionResult(stepResult, ex); + verify(logUtil).logException(ex); + verify(configUtil).checkIfStopScenarioOnFailure(ex); + } + + @Test + void thresholdExceededAddsException() { + final AbstractUiCommand cmd = mock(AbstractUiCommand.class); + when(cmd.getThreshold()).thenReturn(0); // 0ms threshold - will always be exceeded + final List commands = List.of(cmd); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newUiCommandResultInstance(anyInt(), any(AbstractUiCommand.class))).thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); + when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + runner.runCommands(commands, result, dependencies); + + verify(logUtil).logExecutionTime(anyLong(), eq(cmd)); + } + + @Test + void nullThresholdDoesNotTriggerSlowProcessing() { + final AbstractUiCommand cmd = mock(AbstractUiCommand.class); + when(cmd.getThreshold()).thenReturn(null); + final List commands = List.of(cmd); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newUiCommandResultInstance(anyInt(), any(AbstractUiCommand.class))).thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); + when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + runner.runCommands(commands, result, dependencies); + + verify(logUtil).logExecutionTime(anyLong(), eq(cmd)); + } } @Nested @@ -123,8 +226,9 @@ void iteratesOverUiCommandsWithExplicitSubResults() { final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); - final org.springframework.context.ApplicationContext ctx = - mock(org.springframework.context.ApplicationContext.class); + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); when(dependencies.getContext()).thenReturn(ctx); final List subResults = new ArrayList<>(); @@ -132,6 +236,34 @@ void iteratesOverUiCommandsWithExplicitSubResults() { verify(resultUtil).newUiCommandResultInstance(anyInt(), any(AbstractUiCommand.class)); verify(resultUtil).setExecutionResultIfSubCommandsFailed(result); + assertEquals(1, subResults.size()); + } + + @Test + void addsResultsToExistingSubResultsList() { + final AbstractUiCommand cmd1 = mock(AbstractUiCommand.class); + final AbstractUiCommand cmd2 = mock(AbstractUiCommand.class); + final List commands = List.of(cmd1, cmd2); + final CommandResult result = new CommandResult(); + final CommandResult stepResult = new CommandResult(); + final ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + final AtomicInteger position = new AtomicInteger(0); + when(dependencies.getPosition()).thenReturn(position); + when(resultUtil.newUiCommandResultInstance(anyInt(), any(AbstractUiCommand.class))).thenReturn(stepResult); + + @SuppressWarnings("unchecked") + final AbstractUiExecutor executor = mock(AbstractUiExecutor.class); + when(executorProvider.getAppropriateExecutor(any(), eq(dependencies))).thenReturn(executor); + + final ApplicationContext ctx = mock(ApplicationContext.class); + final AutowireCapableBeanFactory beanFactory = mock(AutowireCapableBeanFactory.class); + when(ctx.getAutowireCapableBeanFactory()).thenReturn(beanFactory); + when(dependencies.getContext()).thenReturn(ctx); + + final List subResults = new ArrayList<>(); + runner.runCommands(commands, dependencies, result, subResults); + + assertEquals(2, subResults.size()); } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/ExecutorProviderTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/ExecutorProviderTest.java index 34d5c9161..ff809e728 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/ExecutorProviderTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/ExecutorProviderTest.java @@ -1,6 +1,9 @@ package com.knubisoft.testlum.testing.framework.interpreter.lib.ui; +import com.knubisoft.testlum.testing.framework.FileSearcher; import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; +import com.knubisoft.testlum.testing.framework.report.CommandResult; +import com.knubisoft.testlum.testing.framework.util.*; import com.knubisoft.testlum.testing.model.scenario.AbstractUiCommand; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -8,13 +11,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationContext; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; /** - * Unit tests for {@link ExecutorProvider} verifying executor lookup - * and initialization behavior. + * Unit tests for {@link ExecutorProvider} verifying executor lookup, + * initialization behavior, successful resolution, and constructor error handling. */ @ExtendWith(MockitoExtension.class) class ExecutorProviderTest { @@ -41,6 +45,17 @@ void initLoadsExecutorMap() { verify(executorScanner).getExecutors(); } + + @Test + void initCanBeCalledMultipleTimes() { + final CommandToExecutorClassMap map = new CommandToExecutorClassMap(); + when(executorScanner.getExecutors()).thenReturn(map); + + executorProvider.init(); + executorProvider.init(); + + verify(executorScanner, times(2)).getExecutors(); + } } @Nested @@ -58,6 +73,81 @@ void throwsWhenExecutorNotFound() { assertThrows(DefaultFrameworkException.class, () -> executorProvider.getAppropriateExecutor(command, dependencies)); } + + @Test + void throwsWithMeaningfulMessageForUnknownCommand() { + final CommandToExecutorClassMap map = new CommandToExecutorClassMap(); + when(executorScanner.getExecutors()).thenReturn(map); + executorProvider.init(); + + final AbstractUiCommand command = mock(AbstractUiCommand.class); + final ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + + DefaultFrameworkException ex = assertThrows(DefaultFrameworkException.class, + () -> executorProvider.getAppropriateExecutor(command, dependencies)); + assertNotNull(ex.getMessage()); + } + + @SuppressWarnings("unchecked") + @Test + void throwsWhenExecutorClassLacksConstructor() { + final CommandToExecutorClassMap map = new CommandToExecutorClassMap(); + + Class> badClass = + (Class>) (Class) BadExecutor.class; + + map.put(TestUiCommand.class, badClass); + + when(executorScanner.getExecutors()).thenReturn(map); + executorProvider.init(); + + TestUiCommand command = new TestUiCommand(); + ExecutorDependencies dependencies = mock(ExecutorDependencies.class); + + assertThrows(DefaultFrameworkException.class, + () -> executorProvider.getAppropriateExecutor(command, dependencies)); + } + + @SuppressWarnings("unchecked") + @Test + void returnsExecutorWhenCorrectlyMapped() { + final CommandToExecutorClassMap map = new CommandToExecutorClassMap(); + + Class> goodClass = + (Class>) (Class) GoodExecutor.class; + + map.put(TestUiCommand.class, goodClass); + + when(executorScanner.getExecutors()).thenReturn(map); + executorProvider.init(); + + TestUiCommand command = new TestUiCommand(); + ExecutorDependencies dependencies = createMockDependencies(); + + AbstractUiExecutor result = + executorProvider.getAppropriateExecutor(command, dependencies); + + assertNotNull(result); + assertInstanceOf(GoodExecutor.class, result); + } + } + + private ExecutorDependencies createMockDependencies() { + ExecutorDependencies deps = mock(ExecutorDependencies.class); + ApplicationContext ctx = mock(ApplicationContext.class); + when(deps.getContext()).thenReturn(ctx); + when(ctx.getBean(UiUtil.class)).thenReturn(mock(UiUtil.class)); + when(ctx.getBean(ResultUtil.class)).thenReturn(mock(ResultUtil.class)); + when(ctx.getBean(JavascriptUtil.class)).thenReturn(mock(JavascriptUtil.class)); + when(ctx.getBean(ImageComparisonUtil.class)).thenReturn(mock(ImageComparisonUtil.class)); + when(ctx.getBean(ConditionUtil.class)).thenReturn(mock(ConditionUtil.class)); + when(ctx.getBean(ConfigUtil.class)).thenReturn(mock(ConfigUtil.class)); + when(ctx.getBean(FileSearcher.class)).thenReturn(mock(FileSearcher.class)); + when(ctx.getBean(LogUtil.class)).thenReturn(mock(LogUtil.class)); + when(ctx.getBean(ScenarioInjectionUtil.class)).thenReturn(mock(ScenarioInjectionUtil.class)); + when(ctx.getBean(JacksonService.class)).thenReturn(mock(JacksonService.class)); + when(ctx.getBean(StringPrettifier.class)).thenReturn(mock(StringPrettifier.class)); + return deps; } @Nested @@ -72,5 +162,46 @@ void executorMapIsNotNullAfterInit() { assertNotNull(map); } + + @Test + void executorMapIsEmptyWhenNoExecutorsRegistered() { + final CommandToExecutorClassMap map = new CommandToExecutorClassMap(); + when(executorScanner.getExecutors()).thenReturn(map); + + executorProvider.init(); + + assertTrue(map.isEmpty()); + } + } + + // Test helpers + + public static class TestUiCommand extends AbstractUiCommand { + } + + /** + * Executor without the required ExecutorDependencies constructor. + */ + public static class BadExecutor extends AbstractUiExecutor { + BadExecutor() { + super(null, null, null, null, null, null, null, null, null, null, null, null); + } + + @Override + protected void execute(final TestUiCommand command, final CommandResult result) { + } + } + + /** + * Executor with the required ExecutorDependencies constructor. + */ + public static class GoodExecutor extends AbstractUiExecutor { + public GoodExecutor(final ExecutorDependencies dependencies) { + super(dependencies); + } + + @Override + protected void execute(final TestUiCommand command, final CommandResult result) { + } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertEqualityHelperTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertEqualityHelperTest.java new file mode 100644 index 000000000..910a492b9 --- /dev/null +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertEqualityHelperTest.java @@ -0,0 +1,160 @@ +package com.knubisoft.testlum.testing.framework.interpreter.lib.ui.executor; + +import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; +import com.knubisoft.testlum.testing.model.scenario.AssertEqual; +import com.knubisoft.testlum.testing.model.scenario.AssertNotEqual; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class AssertEqualityHelperTest { + + @Nested + class CheckContentIsEqual { + + @Test + void passesWhenAllContentItemsAreIdentical() { + AssertEqual equal = new AssertEqual(); + equal.getContent().addAll(List.of("same", "same")); + + assertDoesNotThrow(() -> AssertEqualityHelper.checkContentIsEqual(equal)); + } + + @Test + void throwsWhenContentItemsDiffer() { + AssertEqual equal = new AssertEqual(); + equal.getContent().addAll(List.of("one", "two")); + + DefaultFrameworkException ex = assertThrows(DefaultFrameworkException.class, + () -> AssertEqualityHelper.checkContentIsEqual(equal)); + assertTrue(ex.getMessage().contains("not equal")); + } + + @Test + void passesWithThreeIdenticalItems() { + AssertEqual equal = new AssertEqual(); + equal.getContent().addAll(List.of("abc", "abc", "abc")); + + assertDoesNotThrow(() -> AssertEqualityHelper.checkContentIsEqual(equal)); + } + + @Test + void throwsWhenOneOfThreeItemsDiffers() { + AssertEqual equal = new AssertEqual(); + equal.getContent().addAll(List.of("abc", "abc", "xyz")); + + assertThrows(DefaultFrameworkException.class, + () -> AssertEqualityHelper.checkContentIsEqual(equal)); + } + + @Test + void passesWhenContentDiffersOnlyByLineEndings() { + AssertEqual equal = new AssertEqual(); + equal.getContent().addAll(List.of("line1\nline2", "line1\r\nline2")); + + assertDoesNotThrow(() -> AssertEqualityHelper.checkContentIsEqual(equal)); + } + + @Test + void passesWhenAllItemsAreNull() { + AssertEqual equal = new AssertEqual(); + equal.getContent().add(null); + equal.getContent().add(null); + + assertDoesNotThrow(() -> AssertEqualityHelper.checkContentIsEqual(equal)); + } + + @Test + void throwsWhenOneItemIsNullAndOtherIsNot() { + AssertEqual equal = new AssertEqual(); + equal.getContent().add(null); + equal.getContent().add("something"); + + assertThrows(DefaultFrameworkException.class, + () -> AssertEqualityHelper.checkContentIsEqual(equal)); + } + } + + @Nested + class CheckContentNotEqual { + + @Test + void passesWhenContentItemsDiffer() { + AssertNotEqual notEqual = new AssertNotEqual(); + notEqual.getContent().addAll(List.of("one", "two")); + + assertDoesNotThrow(() -> AssertEqualityHelper.checkContentNotEqual(notEqual)); + } + + @Test + void throwsWhenAllContentItemsAreIdentical() { + AssertNotEqual notEqual = new AssertNotEqual(); + notEqual.getContent().addAll(List.of("same", "same")); + + DefaultFrameworkException ex = assertThrows(DefaultFrameworkException.class, + () -> AssertEqualityHelper.checkContentNotEqual(notEqual)); + assertTrue(ex.getMessage().contains("is equal")); + } + + @Test + void passesWhenOneOfThreeItemsDiffers() { + AssertNotEqual notEqual = new AssertNotEqual(); + notEqual.getContent().addAll(List.of("abc", "abc", "xyz")); + + assertDoesNotThrow(() -> AssertEqualityHelper.checkContentNotEqual(notEqual)); + } + + @Test + void throwsWhenThreeItemsAreAllIdentical() { + AssertNotEqual notEqual = new AssertNotEqual(); + notEqual.getContent().addAll(List.of("same", "same", "same")); + + assertThrows(DefaultFrameworkException.class, + () -> AssertEqualityHelper.checkContentNotEqual(notEqual)); + } + + @Test + void throwsWhenContentDiffersOnlyByLineEndings() { + AssertNotEqual notEqual = new AssertNotEqual(); + notEqual.getContent().addAll(List.of("line1\nline2", "line1\r\nline2")); + + assertThrows(DefaultFrameworkException.class, + () -> AssertEqualityHelper.checkContentNotEqual(notEqual)); + } + } + + @Nested + class FormatContent { + + @Test + void formatsContentWithComma() { + AssertEqual equal = new AssertEqual(); + equal.getContent().addAll(List.of("one", "two", "three")); + + String formatted = AssertEqualityHelper.formatContent(equal); + assertEquals("one,two,three", formatted); + } + + @Test + void formatsSingleContent() { + AssertEqual equal = new AssertEqual(); + equal.getContent().add("only"); + + String formatted = AssertEqualityHelper.formatContent(equal); + assertEquals("only", formatted); + } + } + + @Nested + class PrivateConstructor { + + @Test + void cannotBeInstantiated() throws Exception { + var constructor = AssertEqualityHelper.class.getDeclaredConstructor(); + assertFalse(constructor.canAccess(null)); + } + } +} diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertExecutorTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertExecutorTest.java index 6825c91e0..3cb342742 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertExecutorTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/AssertExecutorTest.java @@ -2,12 +2,11 @@ import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; import com.knubisoft.testlum.testing.framework.interpreter.lib.ui.ExecutorDependencies; +import com.knubisoft.testlum.testing.framework.interpreter.lib.ui.ExecutorForClass; import com.knubisoft.testlum.testing.framework.report.CommandResult; import com.knubisoft.testlum.testing.framework.scenario.ScenarioContext; import com.knubisoft.testlum.testing.framework.util.*; -import com.knubisoft.testlum.testing.model.scenario.AssertEqual; -import com.knubisoft.testlum.testing.model.scenario.AssertNotEqual; -import com.knubisoft.testlum.testing.model.scenario.WebAssert; +import com.knubisoft.testlum.testing.model.scenario.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -15,6 +14,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; import org.springframework.context.ApplicationContext; import org.springframework.test.util.ReflectionTestUtils; @@ -22,11 +22,9 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class AssertExecutorTest { @@ -42,6 +40,8 @@ class AssertExecutorTest { @Mock private StringPrettifier stringPrettifier; @Mock + private JacksonService jacksonService; + @Mock private WebDriver driver; @Mock private ScenarioContext scenarioContext; @@ -66,6 +66,31 @@ void setUp() { ReflectionTestUtils.setField(executor, "conditionUtil", conditionUtil); ReflectionTestUtils.setField(executor, "logUtil", logUtil); ReflectionTestUtils.setField(executor, "stringPrettifier", stringPrettifier); + ReflectionTestUtils.setField(executor, "jacksonService", jacksonService); + lenient().when(stringPrettifier.prettify(anyString())).thenAnswer(inv -> inv.getArgument(0)); + } + + @Nested + class Annotation { + + @Test + void hasExecutorForClassAnnotationForWebAssert() { + ExecutorForClass annotation = AssertExecutor.class.getAnnotation(ExecutorForClass.class); + assertNotNull(annotation); + assertEquals(WebAssert.class, annotation.value()); + } + } + + @Nested + class CommandMapSetup { + + @Test + void assertCommandMapIsInitializedWithSixEntries() { + Object commandMap = ReflectionTestUtils.getField(executor, "assertCommandMap"); + assertNotNull(commandMap); + assertTrue(commandMap instanceof java.util.Map); + assertEquals(6, ((java.util.Map) commandMap).size()); + } } @Nested @@ -98,6 +123,20 @@ void throwsWhenContentItemsAreNotEqual() { assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); } + + @Test + void doesNotThrowWhenEqualWithThreeIdenticalItems() { + WebAssert webAssert = new WebAssert(); + AssertEqual assertEqual = new AssertEqual(); + assertEqual.getContent().addAll(List.of("x", "x", "x")); + webAssert.getAttributeOrTitleOrEqual().add(assertEqual); + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } } @Nested @@ -132,6 +171,418 @@ void throwsWhenContentItemsAreAllEqual() { } } + @Nested + class TitleAssert { + + @Test + void titleAssertPassesWhenTitleMatches() { + WebAssert webAssert = new WebAssert(); + AssertTitle title = new AssertTitle(); + title.setContent("My Page"); + title.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(title); + + when(driver.getTitle()).thenReturn("My Page"); + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void titleAssertCollectsErrorWhenTitleDoesNotMatch() { + WebAssert webAssert = new WebAssert(); + AssertTitle title = new AssertTitle(); + title.setContent("Expected Title"); + title.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(title); + + when(driver.getTitle()).thenReturn("Actual Title"); + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + + @Test + void negativeTitleAssertPassesWhenTitleDoesNotMatch() { + WebAssert webAssert = new WebAssert(); + AssertTitle title = new AssertTitle(); + title.setContent("Expected Title"); + title.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(title); + + when(driver.getTitle()).thenReturn("Different Title"); + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void negativeTitleAssertCollectsErrorWhenTitleMatches() { + WebAssert webAssert = new WebAssert(); + AssertTitle title = new AssertTitle(); + title.setContent("My Page"); + title.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(title); + + when(driver.getTitle()).thenReturn("My Page"); + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + } + + @Nested + class AlertAssert { + + @Test + void alertAssertPassesWhenAlertTextMatches() { + WebAssert webAssert = new WebAssert(); + AssertAlert alert = new AssertAlert(); + alert.setText("Alert message"); + alert.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(alert); + + WebDriver.TargetLocator targetLocator = mock(WebDriver.TargetLocator.class); + org.openqa.selenium.Alert seleniumAlert = mock(org.openqa.selenium.Alert.class); + when(driver.switchTo()).thenReturn(targetLocator); + when(targetLocator.alert()).thenReturn(seleniumAlert); + when(seleniumAlert.getText()).thenReturn("Alert message"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void alertAssertCollectsErrorWhenAlertTextDoesNotMatch() { + WebAssert webAssert = new WebAssert(); + AssertAlert alert = new AssertAlert(); + alert.setText("Expected alert"); + alert.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(alert); + + WebDriver.TargetLocator targetLocator = mock(WebDriver.TargetLocator.class); + org.openqa.selenium.Alert seleniumAlert = mock(org.openqa.selenium.Alert.class); + when(driver.switchTo()).thenReturn(targetLocator); + when(targetLocator.alert()).thenReturn(seleniumAlert); + when(seleniumAlert.getText()).thenReturn("Actual alert"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + + @Test + void negativeAlertAssertPassesWhenAlertTextDiffers() { + WebAssert webAssert = new WebAssert(); + AssertAlert alert = new AssertAlert(); + alert.setText("Expected alert"); + alert.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(alert); + + WebDriver.TargetLocator targetLocator = mock(WebDriver.TargetLocator.class); + org.openqa.selenium.Alert seleniumAlert = mock(org.openqa.selenium.Alert.class); + when(driver.switchTo()).thenReturn(targetLocator); + when(targetLocator.alert()).thenReturn(seleniumAlert); + when(seleniumAlert.getText()).thenReturn("Different alert"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + } + + @Nested + class PresentAssert { + + @Test + void assertPresentPassesWhenElementIsFound() { + WebAssert webAssert = new WebAssert(); + AssertPresent present = new AssertPresent(); + present.setLocator("myElement"); + present.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(present); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("myElement"), any())) + .thenReturn(mockElement); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void assertPresentCollectsErrorWhenElementNotFoundAndPositive() { + WebAssert webAssert = new WebAssert(); + AssertPresent present = new AssertPresent(); + present.setLocator("missingElement"); + present.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(present); + + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("missingElement"), any())) + .thenThrow(new DefaultFrameworkException("Element not found")); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + + @Test + void negativeAssertPresentPassesWhenElementNotFound() { + WebAssert webAssert = new WebAssert(); + AssertPresent present = new AssertPresent(); + present.setLocator("missingElement"); + present.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(present); + + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("missingElement"), any())) + .thenThrow(new DefaultFrameworkException("Element not found")); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void negativeAssertPresentCollectsErrorWhenElementIsFound() { + WebAssert webAssert = new WebAssert(); + AssertPresent present = new AssertPresent(); + present.setLocator("existingElement"); + present.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(present); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("existingElement"), any())) + .thenReturn(mockElement); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + } + + @Nested + class CheckedAssert { + + @Test + void assertCheckedPassesWhenElementIsSelected() { + WebAssert webAssert = new WebAssert(); + AssertChecked checked = new AssertChecked(); + checked.setLocator("checkbox"); + checked.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(checked); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("checkbox"), any())) + .thenReturn(mockElement); + when(mockElement.isSelected()).thenReturn(true); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void assertCheckedCollectsErrorWhenElementIsNotSelected() { + WebAssert webAssert = new WebAssert(); + AssertChecked checked = new AssertChecked(); + checked.setLocator("checkbox"); + checked.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(checked); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("checkbox"), any())) + .thenReturn(mockElement); + when(mockElement.isSelected()).thenReturn(false); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + + @Test + void negativeAssertCheckedPassesWhenElementIsNotSelected() { + WebAssert webAssert = new WebAssert(); + AssertChecked checked = new AssertChecked(); + checked.setLocator("checkbox"); + checked.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(checked); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("checkbox"), any())) + .thenReturn(mockElement); + when(mockElement.isSelected()).thenReturn(false); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void negativeAssertCheckedCollectsErrorWhenElementIsSelected() { + WebAssert webAssert = new WebAssert(); + AssertChecked checked = new AssertChecked(); + checked.setLocator("checkbox"); + checked.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(checked); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("checkbox"), any())) + .thenReturn(mockElement); + when(mockElement.isSelected()).thenReturn(true); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + } + + @Nested + class AttributeAssert { + + @Test + void attributeAssertPassesWhenValueMatches() { + final WebAssert webAssert = new WebAssert(); + AssertAttribute attr = new AssertAttribute(); + attr.setLocator("inputField"); + attr.setName("value"); + attr.setContent("expected"); + attr.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(attr); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("inputField"), any())) + .thenReturn(mockElement); + when(uiUtil.getElementAttribute(eq(mockElement), eq("value"), eq(driver))) + .thenReturn("expected"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void attributeAssertCollectsErrorWhenValueDoesNotMatch() { + final WebAssert webAssert = new WebAssert(); + AssertAttribute attr = new AssertAttribute(); + attr.setLocator("inputField"); + attr.setName("value"); + attr.setContent("expected"); + attr.setNegative(false); + webAssert.getAttributeOrTitleOrEqual().add(attr); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("inputField"), any())) + .thenReturn(mockElement); + when(uiUtil.getElementAttribute(eq(mockElement), eq("value"), eq(driver))) + .thenReturn("actual-different"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + + @Test + void negativeAttributeAssertPassesWhenValueDoesNotMatch() { + final WebAssert webAssert = new WebAssert(); + AssertAttribute attr = new AssertAttribute(); + attr.setLocator("inputField"); + attr.setName("value"); + attr.setContent("expected"); + attr.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(attr); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("inputField"), any())) + .thenReturn(mockElement); + when(uiUtil.getElementAttribute(eq(mockElement), eq("value"), eq(driver))) + .thenReturn("different"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + } + + @Test + void negativeAttributeAssertCollectsErrorWhenValueMatches() { + final WebAssert webAssert = new WebAssert(); + AssertAttribute attr = new AssertAttribute(); + attr.setLocator("inputField"); + attr.setName("value"); + attr.setContent("same"); + attr.setNegative(true); + webAssert.getAttributeOrTitleOrEqual().add(attr); + + WebElement mockElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("inputField"), any())) + .thenReturn(mockElement); + when(uiUtil.getElementAttribute(eq(mockElement), eq("value"), eq(driver))) + .thenReturn("same"); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + } + @Nested class SkippedByCondition { @@ -149,4 +600,60 @@ void skipsSubCommandWhenConditionIsFalse() { assertDoesNotThrow(() -> executor.execute(webAssert, result)); } } + + @Nested + class UnsupportedCommand { + + @Test + void throwsForUnsupportedAssertCommandType() { + WebAssert webAssert = new WebAssert(); + // Use a generic AbstractCommand subclass that is not in the command map + AbstractCommand unsupported = new AbstractCommand() { }; + webAssert.getAttributeOrTitleOrEqual().add(unsupported); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(webAssert, result)); + } + } + + @Nested + class MultipleCommands { + + @Test + void executesMultipleSubCommands() { + WebAssert webAssert = new WebAssert(); + + AssertEqual assertEqual = new AssertEqual(); + assertEqual.getContent().addAll(List.of("same", "same")); + webAssert.getAttributeOrTitleOrEqual().add(assertEqual); + + AssertNotEqual assertNotEqual = new AssertNotEqual(); + assertNotEqual.getContent().addAll(List.of("one", "two")); + webAssert.getAttributeOrTitleOrEqual().add(assertNotEqual); + + CommandResult result = new CommandResult(); + CommandResult subResult = new CommandResult(); + when(resultUtil.newUiCommandResultInstance(anyInt(), any())).thenReturn(subResult); + when(conditionUtil.isTrue(any(), eq(scenarioContext), eq(subResult))).thenReturn(true); + + assertDoesNotThrow(() -> executor.execute(webAssert, result)); + assertNotNull(result.getSubCommandsResult()); + assertEquals(2, result.getSubCommandsResult().size()); + } + + @Test + void setsSubCommandsResultList() { + WebAssert webAssert = new WebAssert(); + CommandResult result = new CommandResult(); + + executor.execute(webAssert, result); + + assertNotNull(result.getSubCommandsResult()); + assertTrue(result.getSubCommandsResult().isEmpty()); + } + } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/BrowserTabExecutorTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/BrowserTabExecutorTest.java index f46801642..6a7c75ef4 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/BrowserTabExecutorTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/BrowserTabExecutorTest.java @@ -1,5 +1,6 @@ package com.knubisoft.testlum.testing.framework.interpreter.lib.ui.executor; +import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; import com.knubisoft.testlum.testing.framework.interpreter.lib.ui.ExecutorDependencies; import com.knubisoft.testlum.testing.framework.interpreter.lib.ui.UiType; import com.knubisoft.testlum.testing.framework.report.CommandResult; @@ -11,19 +12,20 @@ import com.knubisoft.testlum.testing.model.scenario.CloseTab; import com.knubisoft.testlum.testing.model.scenario.OpenTab; import com.knubisoft.testlum.testing.model.scenario.SwitchTab; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WindowType; import org.springframework.context.ApplicationContext; import org.springframework.test.util.ReflectionTestUtils; import java.util.LinkedHashSet; import java.util.Set; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @@ -39,34 +41,36 @@ class BrowserTabExecutorTest { @Mock private LogUtil logUtil; @Mock - private WebDriver driver; - @Mock - private WebDriver.TargetLocator targetLocator; - @Mock private ApplicationContext context; - private BrowserTabExecutor executor; - - @BeforeEach - void setUp() { + private BrowserTabExecutor createExecutor(final WebDriver driver, final UiType uiType) { when(context.getBean(any(Class.class))).thenAnswer(inv -> mock((Class) inv.getArgument(0))); - Set handles = new LinkedHashSet<>(); - handles.add("tab-1"); - handles.add("tab-2"); - when(driver.getWindowHandles()).thenReturn(handles); - when(driver.switchTo()).thenReturn(targetLocator); - lenient().when(targetLocator.window(anyString())).thenReturn(driver); ExecutorDependencies dependencies = ExecutorDependencies.builder() .context(context) .driver(driver) - .uiType(UiType.WEB) + .uiType(uiType) .environment("dev") .build(); - executor = new BrowserTabExecutor(dependencies); - ReflectionTestUtils.setField(executor, "uiUtil", uiUtil); - ReflectionTestUtils.setField(executor, "resultUtil", resultUtil); - ReflectionTestUtils.setField(executor, "javascriptUtil", javascriptUtil); - ReflectionTestUtils.setField(executor, "logUtil", logUtil); + BrowserTabExecutor exec = new BrowserTabExecutor(dependencies); + ReflectionTestUtils.setField(exec, "uiUtil", uiUtil); + ReflectionTestUtils.setField(exec, "resultUtil", resultUtil); + ReflectionTestUtils.setField(exec, "javascriptUtil", javascriptUtil); + ReflectionTestUtils.setField(exec, "logUtil", logUtil); + return exec; + } + + private WebDriver createDriverWithTabs(final String... tabHandles) { + WebDriver driver = mock(WebDriver.class); + Set handles = new LinkedHashSet<>(); + for (String h : tabHandles) { + handles.add(h); + } + when(driver.getWindowHandles()).thenReturn(handles); + WebDriver.TargetLocator targetLocator = mock(WebDriver.TargetLocator.class); + lenient().when(driver.switchTo()).thenReturn(targetLocator); + lenient().when(targetLocator.window(anyString())).thenReturn(driver); + lenient().when(targetLocator.newWindow(any())).thenReturn(driver); + return driver; } @Nested @@ -74,17 +78,63 @@ class OpenTabTest { @Test void opensNewTabInWebMode() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + BrowserTab browserTab = new BrowserTab(); OpenTab openTab = new OpenTab(); browserTab.setOpen(openTab); CommandResult result = new CommandResult(); - when(targetLocator.newWindow(any())).thenReturn(driver); executor.execute(browserTab, result); + verify(driver.switchTo()).newWindow(WindowType.TAB); verify(resultUtil).addOpenTabMetadata(any(), eq(result)); verify(uiUtil).takeScreenshotAndSaveIfRequired(eq(result), any()); } + + @Test + void opensNewTabWithUrlInWebMode() { + final WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + final BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + WebDriver.Navigation navigation = mock(WebDriver.Navigation.class); + when(driver.navigate()).thenReturn(navigation); + when(uiUtil.getUrl(eq("/page"), eq("dev"), eq(UiType.WEB))).thenReturn("http://localhost/page"); + + BrowserTab browserTab = new BrowserTab(); + OpenTab openTab = new OpenTab(); + openTab.setUrl("/page"); + browserTab.setOpen(openTab); + CommandResult result = new CommandResult(); + + executor.execute(browserTab, result); + + verify(driver.navigate()).to("http://localhost/page"); + } + + @Test + void opensNewTabInMobileBrowserMode() { + final WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + // For mobile browser, need a second getWindowHandles call after window.open() + Set updatedHandles = new LinkedHashSet<>(); + updatedHandles.add("tab-1"); + updatedHandles.add("tab-2"); + updatedHandles.add("tab-3"); + when(driver.getWindowHandles()) + .thenReturn(new LinkedHashSet<>(Set.of("tab-1", "tab-2"))) + .thenReturn(updatedHandles); + BrowserTabExecutor executor = createExecutor(driver, UiType.MOBILE_BROWSER); + + BrowserTab browserTab = new BrowserTab(); + OpenTab openTab = new OpenTab(); + browserTab.setOpen(openTab); + CommandResult result = new CommandResult(); + + executor.execute(browserTab, result); + + verify(javascriptUtil).executeJsScript(eq("window.open()"), eq(driver)); + verify(logUtil).logOpenTabCommand(any()); + } } @Nested @@ -92,6 +142,9 @@ class CloseTabTest { @Test void closesLastTabWhenIndexIsNull() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + BrowserTab browserTab = new BrowserTab(); CloseTab closeTab = new CloseTab(); browserTab.setClose(closeTab); @@ -99,9 +152,51 @@ void closesLastTabWhenIndexIsNull() { executor.execute(browserTab, result); - verify(targetLocator).window("tab-2"); verify(logUtil).logCloseOrSwitchTabCommand(eq(ResultUtil.CLOSE_TAB), any()); } + + @Test + void closesTabBySpecificIndex() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2", "tab-3"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + + BrowserTab browserTab = new BrowserTab(); + CloseTab closeTab = new CloseTab(); + closeTab.setIndex(2); + browserTab.setClose(closeTab); + CommandResult result = new CommandResult(); + + executor.execute(browserTab, result); + + verify(resultUtil).addCloseOrSwitchTabMetadata(eq(ResultUtil.CLOSE_COMMAND), eq(2), eq(result)); + } + + @Test + void throwsWhenClosingWithOnlyOneTab() { + WebDriver driver = createDriverWithTabs("tab-1"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + + BrowserTab browserTab = new BrowserTab(); + CloseTab closeTab = new CloseTab(); + browserTab.setClose(closeTab); + CommandResult result = new CommandResult(); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(browserTab, result)); + } + + @Test + void throwsWhenTabIndexOutOfBounds() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + + BrowserTab browserTab = new BrowserTab(); + CloseTab closeTab = new CloseTab(); + closeTab.setIndex(5); + browserTab.setClose(closeTab); + CommandResult result = new CommandResult(); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(browserTab, result)); + } } @Nested @@ -109,6 +204,9 @@ class SwitchTabTest { @Test void switchesToLastTabWhenIndexIsNull() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + BrowserTab browserTab = new BrowserTab(); SwitchTab switchTab = new SwitchTab(); browserTab.setSwitch(switchTab); @@ -118,5 +216,49 @@ void switchesToLastTabWhenIndexIsNull() { verify(logUtil).logCloseOrSwitchTabCommand(eq(ResultUtil.SWITCH_TAB), any()); } + + @Test + void switchesToTabBySpecificIndex() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2", "tab-3"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + + BrowserTab browserTab = new BrowserTab(); + SwitchTab switchTab = new SwitchTab(); + switchTab.setIndex(1); + browserTab.setSwitch(switchTab); + CommandResult result = new CommandResult(); + + executor.execute(browserTab, result); + + verify(driver.switchTo()).window("tab-1"); + verify(resultUtil).addCloseOrSwitchTabMetadata(eq(ResultUtil.SWITCH_COMMAND), eq(1), eq(result)); + } + + @Test + void throwsWhenSwitchingWithOnlyOneTab() { + WebDriver driver = createDriverWithTabs("tab-1"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + + BrowserTab browserTab = new BrowserTab(); + SwitchTab switchTab = new SwitchTab(); + browserTab.setSwitch(switchTab); + CommandResult result = new CommandResult(); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(browserTab, result)); + } + + @Test + void throwsWhenSwitchIndexOutOfBounds() { + WebDriver driver = createDriverWithTabs("tab-1", "tab-2"); + BrowserTabExecutor executor = createExecutor(driver, UiType.WEB); + + BrowserTab browserTab = new BrowserTab(); + SwitchTab switchTab = new SwitchTab(); + switchTab.setIndex(10); + browserTab.setSwitch(switchTab); + CommandResult result = new CommandResult(); + + assertThrows(DefaultFrameworkException.class, () -> executor.execute(browserTab, result)); + } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/CompareImageExecutorTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/CompareImageExecutorTest.java index ba686192a..59ae02a73 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/CompareImageExecutorTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/CompareImageExecutorTest.java @@ -1,11 +1,16 @@ package com.knubisoft.testlum.testing.framework.interpreter.lib.ui.executor; +import com.github.romankh3.image.comparison.model.ImageComparisonResult; import com.knubisoft.testlum.testing.framework.EnvironmentLoader; import com.knubisoft.testlum.testing.framework.FileSearcher; +import com.knubisoft.testlum.testing.framework.exception.DefaultFrameworkException; import com.knubisoft.testlum.testing.framework.interpreter.lib.ui.ExecutorDependencies; +import com.knubisoft.testlum.testing.framework.interpreter.lib.ui.ExecutorForClass; import com.knubisoft.testlum.testing.framework.report.CommandResult; import com.knubisoft.testlum.testing.framework.util.*; import com.knubisoft.testlum.testing.model.scenario.Image; +import com.knubisoft.testlum.testing.model.scenario.Part; +import com.knubisoft.testlum.testing.model.scenario.WebFullScreen; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -13,12 +18,19 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; import org.springframework.context.ApplicationContext; import org.springframework.test.util.ReflectionTestUtils; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.util.Collections; -import static org.mockito.ArgumentMatchers.any; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) @@ -65,6 +77,26 @@ void setUp() { ReflectionTestUtils.setField(executor, "currentEnvironmentLoader", environmentLoader); } + @Nested + class Annotation { + + @Test + void hasExecutorForClassAnnotationForImage() { + ExecutorForClass annotation = CompareImageExecutor.class.getAnnotation(ExecutorForClass.class); + assertNotNull(annotation); + assertEquals(Image.class, annotation.value()); + } + } + + @Nested + class Initialization { + + @Test + void createsExecutorSuccessfully() { + assertNotNull(executor); + } + } + @Nested class Execute { @@ -74,8 +106,6 @@ void logsAndAddsMetadata() { image.setFile("expected.png"); CommandResult result = new CommandResult(); - // The execute method reads a file and compares images, which requires - // real file I/O. We verify the initial logging and metadata calls. File expectedFile = mock(File.class); when(fileSearcher.searchFileFromDir(any(), any())).thenReturn(expectedFile); @@ -88,5 +118,160 @@ void logsAndAddsMetadata() { verify(logUtil).logImageComparisonInfo(image); verify(resultUtil).addImageComparisonMetaData(image, result); } + + @Test + void fullScreenComparisonWithNoExcludeList() throws IOException { + Image image = new Image(); + image.setFile("expected.png"); + + BufferedImage testImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + File tempFile = File.createTempFile("test-image", ".png"); + tempFile.deleteOnExit(); + ImageIO.write(testImage, "png", tempFile); + + when(fileSearcher.searchFileFromDir(any(), eq("expected.png"))).thenReturn(tempFile); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(testImage, "png", baos); + java.io.File screenshotFile = File.createTempFile("screenshot", ".png"); + screenshotFile.deleteOnExit(); + ImageIO.write(testImage, "png", screenshotFile); + when(uiUtil.takeScreenshot(driver)).thenReturn(screenshotFile); + + ImageComparisonResult comparisonResult = mock(ImageComparisonResult.class); + when(imageComparator.compare(any(Image.class), any(BufferedImage.class), + any(BufferedImage.class), anyList())).thenReturn(comparisonResult); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(image, result)); + verify(imageComparisonUtil).processImageComparisonResult(eq(comparisonResult), + eq("expected.png"), eq(false), any(), eq(result)); + } + + @Test + void partImageComparison() throws IOException { + Image image = new Image(); + image.setFile("expected.png"); + Part part = new Part(); + part.setLocator("partLocator"); + image.setPart(part); + + BufferedImage testImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + File tempFile = File.createTempFile("test-image", ".png"); + tempFile.deleteOnExit(); + ImageIO.write(testImage, "png", tempFile); + + when(fileSearcher.searchFileFromDir(any(), eq("expected.png"))).thenReturn(tempFile); + + WebElement partElement = mock(WebElement.class); + when(uiUtil.findWebElement(any(ExecutorDependencies.class), eq("partLocator"), any())) + .thenReturn(partElement); + + File partScreenshot = File.createTempFile("part-screenshot", ".png"); + partScreenshot.deleteOnExit(); + ImageIO.write(testImage, "png", partScreenshot); + when(uiUtil.takeScreenshot(partElement)).thenReturn(partScreenshot); + + ImageComparisonResult comparisonResult = mock(ImageComparisonResult.class); + when(imageComparator.compare(any(Image.class), any(BufferedImage.class), + any(BufferedImage.class), anyList())).thenReturn(comparisonResult); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(image, result)); + } + + @Test + void wrapsIOExceptionInDefaultFrameworkException() { + Image image = new Image(); + image.setFile("nonexistent.png"); + + when(fileSearcher.searchFileFromDir(any(), eq("nonexistent.png"))) + .thenReturn(new File("/nonexistent/path.png")); + + CommandResult result = new CommandResult(); + assertThrows(DefaultFrameworkException.class, () -> executor.execute(image, result)); + } + } + + @Nested + class ExcludeList { + + @Test + void returnsEmptyListWhenFullScreenIsNull() throws Exception { + Image image = new Image(); + image.setFile("expected.png"); + // fullScreen is null by default + + BufferedImage testImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + File tempFile = File.createTempFile("test-image", ".png"); + tempFile.deleteOnExit(); + ImageIO.write(testImage, "png", tempFile); + + when(fileSearcher.searchFileFromDir(any(), eq("expected.png"))).thenReturn(tempFile); + + File screenshotFile = File.createTempFile("screenshot", ".png"); + screenshotFile.deleteOnExit(); + ImageIO.write(testImage, "png", screenshotFile); + when(uiUtil.takeScreenshot(driver)).thenReturn(screenshotFile); + + ImageComparisonResult comparisonResult = mock(ImageComparisonResult.class); + when(imageComparator.compare(any(Image.class), any(BufferedImage.class), + any(BufferedImage.class), eq(Collections.emptyList()))).thenReturn(comparisonResult); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(image, result)); + + verify(imageComparator).compare(any(Image.class), any(BufferedImage.class), + any(BufferedImage.class), eq(Collections.emptyList())); + } + + @Test + void returnsEmptyListWhenFullScreenHasEmptyExcludeList() throws Exception { + Image image = new Image(); + image.setFile("expected.png"); + WebFullScreen fullScreen = new WebFullScreen(); + image.setFullScreen(fullScreen); + + BufferedImage testImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); + File tempFile = File.createTempFile("test-image", ".png"); + tempFile.deleteOnExit(); + ImageIO.write(testImage, "png", tempFile); + + when(fileSearcher.searchFileFromDir(any(), eq("expected.png"))).thenReturn(tempFile); + + File screenshotFile = File.createTempFile("screenshot", ".png"); + screenshotFile.deleteOnExit(); + ImageIO.write(testImage, "png", screenshotFile); + when(uiUtil.takeScreenshot(driver)).thenReturn(screenshotFile); + + ImageComparisonResult comparisonResult = mock(ImageComparisonResult.class); + when(imageComparator.compare(any(Image.class), any(BufferedImage.class), + any(BufferedImage.class), eq(Collections.emptyList()))).thenReturn(comparisonResult); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(image, result)); + } + } + + @Nested + class ClassStructure { + + @Test + void extendsAbstractUiExecutor() { + assertTrue(com.knubisoft.testlum.testing.framework.interpreter.lib.ui.AbstractUiExecutor.class + .isAssignableFrom(CompareImageExecutor.class)); + } + + @Test + void hasImageComparatorField() { + Object field = ReflectionTestUtils.getField(executor, "imageComparator"); + assertNotNull(field); + } + + @Test + void hasEnvironmentLoaderField() { + Object field = ReflectionTestUtils.getField(executor, "currentEnvironmentLoader"); + assertNotNull(field); + } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DragAndDropExecutorTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DragAndDropExecutorTest.java index 935048db7..298a1fecd 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DragAndDropExecutorTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DragAndDropExecutorTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.openqa.selenium.InvalidArgumentException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.interactions.Interactive; @@ -23,6 +24,7 @@ import java.io.File; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -90,6 +92,22 @@ void dragsElementToTarget() { verify(resultUtil).addDragAndDropMetaDada(eq(dragAndDrop), eq(result)); verify(uiUtil).takeScreenshotAndSaveIfRequired(eq(result), any()); } + + @Test + void addsMetadataBeforeDragElement() { + DragAndDrop dragAndDrop = new DragAndDrop(); + dragAndDrop.setToLocator("to"); + dragAndDrop.setFromLocator("from"); + WebElement target = mock(WebElement.class); + WebElement source = mock(WebElement.class); + when(uiUtil.findWebElement(any(), eq("to"), any())).thenReturn(target); + when(uiUtil.findWebElement(any(), eq("from"), any())).thenReturn(source); + CommandResult result = new CommandResult(); + + executor.execute(dragAndDrop, result); + + verify(resultUtil).addDragAndDropMetaDada(eq(dragAndDrop), eq(result)); + } } @Nested @@ -111,5 +129,92 @@ void throwsWhenFileDoesNotExist() { CommandResult result = new CommandResult(); assertThrows(DefaultFrameworkException.class, () -> executor.execute(dragAndDrop, result)); } + + @Test + void throwsWhenFileExistsButIsNotAFile() { + DragAndDrop dragAndDrop = new DragAndDrop(); + dragAndDrop.setToLocator("drop-zone"); + dragAndDrop.setFileName("dir-name"); + + WebElement target = mock(WebElement.class); + when(uiUtil.findWebElement(any(), eq("drop-zone"), any())).thenReturn(target); + File fakeFile = mock(File.class); + when(fakeFile.exists()).thenReturn(true); + when(fakeFile.isFile()).thenReturn(false); + when(fakeFile.getName()).thenReturn("dir-name"); + when(fileSearcher.searchFileFromDir(any(), eq("dir-name"))).thenReturn(fakeFile); + + CommandResult result = new CommandResult(); + assertThrows(DefaultFrameworkException.class, () -> executor.execute(dragAndDrop, result)); + } + + @Test + void dropsFileToInputElement() { + DragAndDrop dragAndDrop = new DragAndDrop(); + dragAndDrop.setToLocator("file-input"); + dragAndDrop.setFileName("upload.txt"); + + WebElement inputTarget = mock(WebElement.class); + when(inputTarget.getTagName()).thenReturn("input"); + when(uiUtil.findWebElement(any(), eq("file-input"), any())).thenReturn(inputTarget); + + File realFile = mock(File.class); + when(realFile.exists()).thenReturn(true); + when(realFile.isFile()).thenReturn(true); + when(realFile.getAbsolutePath()).thenReturn("/tmp/upload.txt"); + when(fileSearcher.searchFileFromDir(any(), eq("upload.txt"))).thenReturn(realFile); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(dragAndDrop, result)); + verify(inputTarget).sendKeys("/tmp/upload.txt"); + } + + @Test + void dropsFileToNonInputElementViaJavascript() { + DragAndDrop dragAndDrop = new DragAndDrop(); + dragAndDrop.setToLocator("drop-div"); + dragAndDrop.setFileName("image.png"); + + WebElement divTarget = mock(WebElement.class); + when(divTarget.getTagName()).thenReturn("div"); + when(uiUtil.findWebElement(any(), eq("drop-div"), any())).thenReturn(divTarget); + + WebElement inputCreated = mock(WebElement.class); + when(javascriptUtil.executeJsScript(any(String.class), eq(driver), eq(divTarget))).thenReturn(inputCreated); + + File realFile = mock(File.class); + when(realFile.exists()).thenReturn(true); + when(realFile.isFile()).thenReturn(true); + when(realFile.getAbsolutePath()).thenReturn("/tmp/image.png"); + when(fileSearcher.searchFileFromDir(any(), eq("image.png"))).thenReturn(realFile); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(dragAndDrop, result)); + verify(inputCreated).sendKeys("/tmp/image.png"); + } + + @Test + void retriesWithLocalFileDetectorOnInvalidArgument() { + DragAndDrop dragAndDrop = new DragAndDrop(); + dragAndDrop.setToLocator("remote-input"); + dragAndDrop.setFileName("doc.pdf"); + + WebElement inputTarget = mock(org.openqa.selenium.remote.RemoteWebElement.class); + when(inputTarget.getTagName()).thenReturn("input"); + when(uiUtil.findWebElement(any(), eq("remote-input"), any())).thenReturn(inputTarget); + doThrow(new InvalidArgumentException("invalid")) + .doNothing() + .when(inputTarget).sendKeys(any(CharSequence[].class)); + + File realFile = mock(File.class); + when(realFile.exists()).thenReturn(true); + when(realFile.isFile()).thenReturn(true); + when(realFile.getAbsolutePath()).thenReturn("/tmp/doc.pdf"); + when(fileSearcher.searchFileFromDir(any(), eq("doc.pdf"))).thenReturn(realFile); + + CommandResult result = new CommandResult(); + assertDoesNotThrow(() -> executor.execute(dragAndDrop, result)); + verify(inputTarget, times(2)).sendKeys(eq("/tmp/doc.pdf")); + } } } diff --git a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DropDownExecutorTest.java b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DropDownExecutorTest.java index 68c6b4699..2653f88cb 100644 --- a/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DropDownExecutorTest.java +++ b/engine/src/test/java/com/knubisoft/testlum/testing/framework/interpreter/lib/ui/executor/DropDownExecutorTest.java @@ -5,18 +5,25 @@ import com.knubisoft.testlum.testing.framework.report.CommandResult; import com.knubisoft.testlum.testing.framework.util.ResultUtil; import com.knubisoft.testlum.testing.framework.util.UiUtil; -import com.knubisoft.testlum.testing.model.scenario.*; +import com.knubisoft.testlum.testing.model.scenario.AllValues; +import com.knubisoft.testlum.testing.model.scenario.DropDown; +import com.knubisoft.testlum.testing.model.scenario.OneValue; +import com.knubisoft.testlum.testing.model.scenario.SelectOrDeselectBy; +import com.knubisoft.testlum.testing.model.scenario.TypeForAllValues; +import com.knubisoft.testlum.testing.model.scenario.TypeForOneValue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.springframework.context.ApplicationContext; import org.springframework.test.util.ReflectionTestUtils; +import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -66,6 +74,123 @@ void selectsByTextOnSelectElement() { dropDown.setOneValue(oneValue); assertEquals("country-select", dropDown.getLocator()); } + + @Test + void selectsByIndexOnSelectElement() { + DropDown dropDown = new DropDown(); + dropDown.setLocator("idx-select"); + OneValue oneValue = new OneValue(); + oneValue.setType(TypeForOneValue.SELECT); + oneValue.setBy(SelectOrDeselectBy.INDEX); + oneValue.setValue("2"); + dropDown.setOneValue(oneValue); + + WebElement selectElement = mock(WebElement.class); + when(selectElement.getTagName()).thenReturn("select"); + when(uiUtil.findWebElement(any(), eq("idx-select"), any())).thenReturn(selectElement); + + // The Select constructor checks that the element is a