Skip to content

Commit

Permalink
feat: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Citymonstret committed Dec 17, 2023
1 parent ccb8e2f commit 0d0be91
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,5 @@ dependencies {

fun DependencyHandlerScope.testDependencies() {
testImplementation(libs.truth)
testImplementation(libs.jupiterEngine)
testImplementation(libs.jupiterApi)
testImplementation(libs.awaitility)
}
2 changes: 2 additions & 0 deletions cloud-spring/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ dependencies {
compileOnlyApi(libs.cloud.annotations)

testImplementation(libs.spring.boot.starter.test)
testImplementation(libs.spring.shell.test)
testImplementation(libs.spring.shell.test.autoconfiguration)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@
import org.springframework.context.ApplicationEvent;
import org.springframework.shell.command.CommandContext;

/**
* Event emitted when a command is executed.
*
* @param <C> the command sender type
* @since 1.0.0
*/
@API(status = API.Status.STABLE, since = "1.0.0")
public final class CommandExecutionEvent<C> extends ApplicationEvent {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import org.incendo.cloud.spring.SpringCommandManager;
import org.incendo.cloud.spring.annotation.ScanCommands;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@ConditionalOnClass(AnnotationParser.class)
@ConditionalOnBean(AnnotationParser.class)
@Component
@API(status = API.Status.INTERNAL, consumers = "org.incendo.cloud.spring.*", since = "1.0.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import jakarta.annotation.PostConstruct;
import java.util.Collection;
import java.util.List;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.spring.SpringCommandManager;
import org.slf4j.Logger;
Expand All @@ -34,6 +35,7 @@

@SuppressWarnings({"unchecked", "rawtypes"})
@Service
@API(status = API.Status.INTERNAL, consumers = "org.incendo.cloud.spring.*", since = "1.0.0")
public class CommandRegistrationCoordinator {

private static final Logger LOGGER = LoggerFactory.getLogger(CommandRegistrationCoordinator.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// MIT License
//
// Copyright (c) 2023 Incendo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package org.incendo.cloud.spring;

import cloud.commandframework.Command;
import cloud.commandframework.CommandBean;
import cloud.commandframework.CommandProperties;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.internal.CommandNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

import static com.google.common.truth.Truth.assertThat;

/**
* Test that spins up a custom spring application to make sure that things wire up like they're supposed to.
*/
@SpringBootTest
class ApplicationIntegrationTest {

@Autowired
private ApplicationContext applicationContext;

@Autowired
private SpringCommandManager<TestCommandSender> springCommandManager;

@Test
@DisplayName("Verify that the application started")
void test() {
assertThat(this.applicationContext).isNotNull();
assertThat(this.springCommandManager).isNotNull();
}

@Test
@DisplayName("Verify that the command execution coordinator was overridden")
void testCommandExecutionCoordinator() {
assertThat(this.springCommandManager.commandExecutionCoordinator())
.isInstanceOf(AsynchronousCommandExecutionCoordinator.class);
}

@Test
@DisplayName("Verify that the command bean was registered")
void testCommandRegistration() {
// Act
final CommandNode<TestCommandSender> commandNode = this.springCommandManager.commandTree().getNamedNode("test");

// Assert
assertThat(commandNode).isNotNull();
}


@SpringBootApplication
static class TestApplication {
}

@TestConfiguration
static class TestConfig {

@Bean
@NonNull CommandSenderSupplier<TestCommandSender> commandSenderSupplier() {
return TestCommandSender::new;
}

@Bean
@NonNull SpringCommandExecutionCoordinatorResolver<TestCommandSender> commandExecutionCoordinatorResolver() {
return AsynchronousCommandExecutionCoordinator.<TestCommandSender>builder().withAsynchronousParsing().build()::apply;
}

@Bean
@NonNull TestCommandBean commandBean() {
return new TestCommandBean();
}
}

static class TestCommandSender {

}

static class TestCommandBean extends CommandBean<TestCommandSender> {

@Override
protected @NonNull CommandProperties properties() {
return CommandProperties.of("test");
}

@Override
protected Command.Builder<TestCommandSender> configure(final Command.Builder<TestCommandSender> builder) {
return builder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//
// MIT License
//
// Copyright (c) 2023 Incendo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package org.incendo.cloud.spring;

import cloud.commandframework.Command;
import cloud.commandframework.CommandBean;
import cloud.commandframework.CommandDescription;
import cloud.commandframework.CommandProperties;
import cloud.commandframework.arguments.flags.CommandFlag;
import cloud.commandframework.context.CommandContext;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.spring.config.CloudSpringConfig;
import org.incendo.cloud.spring.registrar.BeanRegistrar;
import org.incendo.cloud.spring.registrar.CommandRegistrationCoordinator;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.shell.test.ShellAssertions;
import org.springframework.shell.test.ShellTestClient;
import org.springframework.shell.test.autoconfigure.ShellTest;
import org.springframework.test.context.ContextConfiguration;

import static cloud.commandframework.arguments.standard.IntegerParser.integerParser;
import static cloud.commandframework.arguments.standard.StringParser.stringParser;
import static com.google.common.truth.Truth.assertThat;
import static org.awaitility.Awaitility.await;

@Import(CloudSpringConfig.class)
@ContextConfiguration(classes = {
SpringCommandRegistrationHandler.class,
SpringCommandManager.class,
ShellIntegrationTest.TestConfig.class
})
@ShellTest
public class ShellIntegrationTest {

@Autowired
private ShellTestClient client;

@Test
void testCommandParsing() {
// Arrange
final ShellTestClient.InteractiveShellSession session = this.client.interactive().run();
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
ShellAssertions.assertThat(session.screen()).containsText("shell");
});

// Act
session.write(session.writeSequence().text("help").carriageReturn().build());
session.write(session.writeSequence().text("command abc --flag 123").carriageReturn().build());

// Assert
await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(TestCommand.EXECUTED.get()).isTrue();
});

final CommandContext<SpringCommandSender> context = TestCommand.CONTEXT.get();
assertThat(context.<String>get("string")).isEqualTo("abc");
assertThat(context.flags().<Integer>get("flag")).isEqualTo(123);
}


@SpringBootApplication
static class TestApplication {

}

@TestConfiguration
static class TestConfig {

@Bean
@NonNull CommandRegistrationCoordinator commandRegistrationCoordinator(
final @NonNull SpringCommandManager<SpringCommandSender> springCommandManager
) {
return new CommandRegistrationCoordinator(
springCommandManager,
List.of(new BeanRegistrar<>(List.of(this.testCommand())))
);
}

@Bean
@NonNull TestCommand testCommand() {
return new TestCommand();
}
}

static class TestCommand extends CommandBean<SpringCommandSender> {

private static final AtomicBoolean EXECUTED = new AtomicBoolean(false);
private static final AtomicReference<CommandContext<SpringCommandSender>> CONTEXT = new AtomicReference<>(null);

@Override
protected @NonNull CommandProperties properties() {
return CommandProperties.of("command");
}

@Override
protected Command.Builder<SpringCommandSender> configure(final Command.Builder<SpringCommandSender> builder) {
return builder.commandDescription(CommandDescription.commandDescription("Description!"))
.required("string", stringParser())
.flag(CommandFlag.builder("flag").withComponent(integerParser()));
}

@Override
public void execute(final @NonNull CommandContext<SpringCommandSender> commandContext) {
EXECUTED.set(true);
CONTEXT.set(commandContext);
}
}
}
7 changes: 4 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ cloud = "2.0.0-SNAPSHOT"
springShellBom = "3.1.6"

# Test
jupiter = "5.10.1"
truth = "1.1.4"
awaitility = "4.2.0"

[libraries]
spotless = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" }
Expand All @@ -29,20 +29,21 @@ gradleKotlinJvm = { group = "org.jetbrains.kotlin.jvm", name = "org.jetbrains.ko
checkerQual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerQual" }
apiguardian = { group = "org.apiguardian", name = "apiguardian-api", version.ref = "apiguardian" }
slf4j = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
awaitility = { group = "org.awaitility", name = "awaitility", version.ref = "awaitility" }

# Spring
spring-shell = { group = "org.springframework.shell", name = "spring-shell-starter" }
spring-shell-dependencies = { group = "org.springframework.shell", name = "spring-shell-dependencies", version.ref = "springShellBom" }
spring-boot-autoconfigure = { group = "org.springframework.boot", name = "spring-boot-autoconfigure" }
spring-boot-starter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test" }
spring-shell-test = { group = "org.springframework.shell", name = "spring-shell-test" }
spring-shell-test-autoconfiguration = { group = "org.springframework.shell", name = "spring-shell-test-autoconfigure" }

# Cloud
cloud-core = { group = "cloud.commandframework", name = "cloud-core", version.ref = "cloud" }
cloud-annotations = { group = "cloud.commandframework", name = "cloud-annotations", version.ref = "cloud" }

# Test
jupiterApi = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "jupiter" }
jupiterEngine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiter" }
truth = { group = "com.google.truth", name = "truth", version.ref = "truth" }

[bundles]

0 comments on commit 0d0be91

Please sign in to comment.