You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by xt...@apache.org on 2025/07/14 04:56:50 UTC
(flink-agents) branch main updated: [API][Java] agent java api (#62)
This is an automated email from the ASF dual-hosted git repository.
xtsong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git
The following commit(s) were added to refs/heads/main by this push:
new 60dabfb [API][Java] agent java api (#62)
60dabfb is described below
commit 60dabfba1eaa3c5ce5f22c9cac0e597797dff79b
Author: Hao Li <11...@users.noreply.github.com>
AuthorDate: Sun Jul 13 21:56:45 2025 -0700
[API][Java] agent java api (#62)
---
.gitignore | 2 +
.../java/org/apache/flink/agents/api/Action.java | 51 ++++++++
.../java/org/apache/flink/agents/api/Agent.java | 28 ++++
plan/pom.xml | 6 +
.../org/apache/flink/agents/plan/AgentPlan.java | 55 ++++++++
.../apache/flink/agents/plan/AgentPlanTest.java | 141 +++++++++++++++++++++
.../serializer/AgentPlanJsonSerializerTest.java | 140 ++++++++++++--------
pom.xml | 6 +
runtime/pom.xml | 8 --
9 files changed, 375 insertions(+), 62 deletions(-)
diff --git a/.gitignore b/.gitignore
index 421cbb6..b2d3bdd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.idea/*
target/
+.vscode
*.iml
.DS_Store
python/.idea/
@@ -7,5 +8,6 @@ python/flink_agents.egg-info/
python/flink_agents/flink_agents.egg-info/
python/build/
python/dist
+python/uv.lock
python/.DS_Store
__pycache__
diff --git a/api/src/main/java/org/apache/flink/agents/api/Action.java b/api/src/main/java/org/apache/flink/agents/api/Action.java
new file mode 100644
index 0000000..c5fc7c1
--- /dev/null
+++ b/api/src/main/java/org/apache/flink/agents/api/Action.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation for marking a method as an agent action.
+ *
+ * <p>This annotation specifies which event types the action should respond to. The annotated method
+ * will be triggered when any of the specified event types occur.
+ *
+ * <p>Example usage:
+ *
+ * <pre>{@code
+ * @Action(listenEvents = {InputEvent.class, CustomEvent.class})
+ * public void handleEvents(Event event) {
+ * // Action logic here
+ * }
+ * }</pre>
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Action {
+ /**
+ * List of event types that this action should respond to. At least one event type must be
+ * specified.
+ *
+ * @return Array of Event classes that this action listens to
+ */
+ Class<? extends Event>[] listenEvents();
+}
diff --git a/api/src/main/java/org/apache/flink/agents/api/Agent.java b/api/src/main/java/org/apache/flink/agents/api/Agent.java
new file mode 100644
index 0000000..1f04d83
--- /dev/null
+++ b/api/src/main/java/org/apache/flink/agents/api/Agent.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.api;
+
+/**
+ * Base class for defining agent logic. Currently, Agent is an empty interface, we may add
+ * interactive methods like addAction(), addModel() later.
+ */
+public abstract class Agent {
+ // Currently empty interface - may be extended with interactive methods
+ // like addAction(), addModel() in the future
+}
diff --git a/plan/pom.xml b/plan/pom.xml
index 6d1d69d..99fc3a3 100644
--- a/plan/pom.xml
+++ b/plan/pom.xml
@@ -44,6 +44,12 @@ under the License.
<artifactId>pemja</artifactId>
<version>${pemja.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
\ No newline at end of file
diff --git a/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java b/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
index da82b61..bdffee8 100644
--- a/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
+++ b/plan/src/main/java/org/apache/flink/agents/plan/AgentPlan.java
@@ -18,6 +18,8 @@
package org.apache.flink.agents.plan;
+import org.apache.flink.agents.api.Agent;
+import org.apache.flink.agents.api.Event;
import org.apache.flink.agents.plan.serializer.AgentPlanJsonDeserializer;
import org.apache.flink.agents.plan.serializer.AgentPlanJsonSerializer;
import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
@@ -28,6 +30,9 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -49,6 +54,19 @@ public class AgentPlan implements Serializable {
this.actionsByEvent = actionsByEvent;
}
+ /**
+ * Constructor that creates an AgentPlan from an Agent instance by scanning for @Action
+ * annotations.
+ *
+ * @param agent the agent instance to scan for actions
+ * @throws Exception if there's an error creating actions from the agent
+ */
+ public AgentPlan(Agent agent) throws Exception {
+ this.actions = new HashMap<>();
+ this.actionsByEvent = new HashMap<>();
+ extractActionsFromAgent(agent);
+ }
+
public Map<String, Action> getActions() {
return actions;
}
@@ -72,4 +90,41 @@ public class AgentPlan implements Serializable {
this.actions = agentPlan.getActions();
this.actionsByEvent = agentPlan.getActionsByEvent();
}
+
+ private void extractActionsFromAgent(Agent agent) throws Exception {
+ // Scan the agent class for methods annotated with @Action
+ Class<?> agentClass = agent.getClass();
+ for (Method method : agentClass.getDeclaredMethods()) {
+ if (method.isAnnotationPresent(org.apache.flink.agents.api.Action.class)) {
+ org.apache.flink.agents.api.Action actionAnnotation =
+ method.getAnnotation(org.apache.flink.agents.api.Action.class);
+
+ // Get the event types this action listens to
+ Class<? extends Event>[] listenEventTypes = actionAnnotation.listenEvents();
+
+ // Convert event types to string names
+ List<String> eventTypeNames = new ArrayList<>();
+ for (Class<? extends Event> eventType : listenEventTypes) {
+ eventTypeNames.add(eventType.getName());
+ }
+
+ // Create a JavaFunction for this method
+ JavaFunction javaFunction =
+ new JavaFunction(agentClass, method.getName(), method.getParameterTypes());
+
+ // Create an Action
+ Action action = new Action(method.getName(), javaFunction, eventTypeNames);
+
+ // Add to actions map
+ actions.put(action.getName(), action);
+
+ // Add to actionsByEvent map
+ for (String eventTypeName : eventTypeNames) {
+ actionsByEvent
+ .computeIfAbsent(eventTypeName, k -> new ArrayList<>())
+ .add(action);
+ }
+ }
+ }
+ }
}
diff --git a/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java b/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java
new file mode 100644
index 0000000..4196d39
--- /dev/null
+++ b/plan/src/test/java/org/apache/flink/agents/plan/AgentPlanTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.plan;
+
+import org.apache.flink.agents.api.Agent;
+import org.apache.flink.agents.api.Event;
+import org.apache.flink.agents.api.InputEvent;
+import org.apache.flink.agents.api.OutputEvent;
+import org.apache.flink.agents.api.context.RunnerContext;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test for {@link AgentPlan} constructor that takes an Agent. */
+public class AgentPlanTest {
+
+ /** Test event class for testing. */
+ public static class TestEvent extends Event {
+ private final String data;
+
+ public TestEvent(String data) {
+ this.data = data;
+ }
+
+ public String getData() {
+ return data;
+ }
+ }
+
+ /** Test agent class with annotated methods. */
+ public static class TestAgent extends Agent {
+
+ @org.apache.flink.agents.api.Action(listenEvents = {InputEvent.class})
+ public void handleInputEvent(InputEvent event, RunnerContext context) {
+ // Test action implementation
+ }
+
+ @org.apache.flink.agents.api.Action(listenEvents = {TestEvent.class, OutputEvent.class})
+ public void handleMultipleEvents(Event event, RunnerContext context) {
+ // Test action implementation
+ }
+
+ // This method is not annotated, so it should not be included
+ public void nonAnnotatedMethod(Event event, RunnerContext context) {
+ // This should not be included in the agent plan
+ }
+ }
+
+ @Test
+ public void testConstructorWithAgent() throws Exception {
+ // Create an agent instance
+ TestAgent agent = new TestAgent();
+
+ // Create AgentPlan using the new constructor
+ AgentPlan agentPlan = new AgentPlan(agent);
+
+ // Verify that actions were collected correctly
+ assertThat(agentPlan.getActions().size()).isEqualTo(2);
+ assertThat(agentPlan.getActions()).containsKey("handleInputEvent");
+ assertThat(agentPlan.getActions()).containsKey("handleMultipleEvents");
+
+ // Verify action details for handleInputEvent
+ Action inputAction = agentPlan.getActions().get("handleInputEvent");
+ assertThat(inputAction).isNotNull();
+ assertThat(inputAction.getName()).isEqualTo("handleInputEvent");
+ assertThat(inputAction.getListenEventTypes())
+ .isEqualTo(List.of(InputEvent.class.getName()));
+ assertThat(inputAction.getExec()).isInstanceOf(JavaFunction.class);
+
+ // Check that the JavaFunction instance has the correct method/class/params
+ JavaFunction exec = (JavaFunction) inputAction.getExec();
+ assertThat(exec.getQualName()).isEqualTo(TestAgent.class.getName());
+ assertThat(exec.getMethodName()).isEqualTo("handleInputEvent");
+ assertThat(exec.getParameterTypes()).containsExactly(InputEvent.class, RunnerContext.class);
+
+ // Verify action details for handleMultipleEvents
+ Action multiAction = agentPlan.getActions().get("handleMultipleEvents");
+ assertThat(multiAction).isNotNull();
+ assertThat(multiAction.getName()).isEqualTo("handleMultipleEvents");
+ assertThat(multiAction.getListenEventTypes())
+ .isEqualTo(List.of(TestEvent.class.getName(), OutputEvent.class.getName()));
+ assertThat(multiAction.getExec()).isInstanceOf(JavaFunction.class);
+
+ // Verify actionsByEvent mapping
+ assertThat(agentPlan.getActionsByEvent().size()).isEqualTo(3);
+
+ // Check InputEvent mapping
+ List<Action> inputEventActions =
+ agentPlan.getActionsByEvent().get(InputEvent.class.getName());
+ assertThat(inputEventActions).isNotNull();
+ assertThat(inputEventActions.size()).isEqualTo(1);
+ assertThat(inputEventActions.get(0).getName()).isEqualTo("handleInputEvent");
+
+ // Check TestEvent mapping
+ List<Action> testEventActions =
+ agentPlan.getActionsByEvent().get(TestEvent.class.getName());
+ assertThat(testEventActions).isNotNull();
+ assertThat(testEventActions.size()).isEqualTo(1);
+ assertThat(testEventActions.get(0).getName()).isEqualTo("handleMultipleEvents");
+
+ // Check OutputEvent mapping
+ List<Action> outputEventActions =
+ agentPlan.getActionsByEvent().get(OutputEvent.class.getName());
+ assertThat(outputEventActions).isNotNull();
+ assertThat(outputEventActions.size()).isEqualTo(1);
+ assertThat(outputEventActions.get(0).getName()).isEqualTo("handleMultipleEvents");
+ }
+
+ @Test
+ public void testConstructorWithAgentNoActions() throws Exception {
+ // Create an agent with no annotated methods
+ Agent emptyAgent = new Agent() {
+ // No annotated methods
+ };
+
+ // Create AgentPlan using the new constructor
+ AgentPlan agentPlan = new AgentPlan(emptyAgent);
+
+ // Verify that no actions were collected
+ assertThat(agentPlan.getActions().size()).isEqualTo(0);
+ assertThat(agentPlan.getActionsByEvent().size()).isEqualTo(0);
+ }
+}
diff --git a/plan/src/test/java/org/apache/flink/agents/plan/serializer/AgentPlanJsonSerializerTest.java b/plan/src/test/java/org/apache/flink/agents/plan/serializer/AgentPlanJsonSerializerTest.java
index d259f4f..2e3058c 100644
--- a/plan/src/test/java/org/apache/flink/agents/plan/serializer/AgentPlanJsonSerializerTest.java
+++ b/plan/src/test/java/org/apache/flink/agents/plan/serializer/AgentPlanJsonSerializerTest.java
@@ -18,6 +18,8 @@
package org.apache.flink.agents.plan.serializer;
+import org.apache.flink.agents.api.Agent;
+import org.apache.flink.agents.api.Event;
import org.apache.flink.agents.api.InputEvent;
import org.apache.flink.agents.api.OutputEvent;
import org.apache.flink.agents.api.context.RunnerContext;
@@ -31,10 +33,35 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
/** Test for {@link AgentPlanJsonSerializer}. */
public class AgentPlanJsonSerializerTest {
+
+ /** Test Agent class with @Action annotated methods. */
+ public static class TestAgent extends Agent {
+
+ @org.apache.flink.agents.api.Action(listenEvents = {InputEvent.class})
+ public void handleInputEvent(InputEvent event, RunnerContext context) {
+ // Test action logic
+ }
+
+ @org.apache.flink.agents.api.Action(listenEvents = {OutputEvent.class})
+ public void processOutputEvent(OutputEvent event, RunnerContext context) {
+ // Test action logic
+ }
+
+ @org.apache.flink.agents.api.Action(listenEvents = {InputEvent.class, OutputEvent.class})
+ public void handleMultipleEvents(Event event, RunnerContext context) {
+ // Test action logic for multiple event types
+ }
+
+ // Non-annotated method should be ignored
+ public void regularMethod() {
+ // This should not be included in the AgentPlan
+ }
+ }
+
@Test
public void testSerializeAgentPlanWithActions() throws Exception {
// Create a JavaFunction
@@ -58,27 +85,16 @@ public class AgentPlanJsonSerializerTest {
String json = new ObjectMapper().writeValueAsString(agentPlan);
// Verify the JSON contains the expected fields
- assertTrue(json.contains("\"actions\":{"), "JSON should contain the actions field");
- assertTrue(json.contains("\"testAction\":{"), "JSON should contain the action name");
- assertTrue(json.contains("\"name\":\"testAction\""), "JSON should contain the action name");
- assertTrue(json.contains("\"exec\":{"), "JSON should contain the exec field");
- assertTrue(
- json.contains("\"func_type\":\"JavaFunction\""),
- "JSON should contain the function type");
- assertTrue(
- json.contains("\"qualname\":\"org.apache.flink.agents.plan.TestAction\""),
- "JSON should contain the function's qualified name");
- assertTrue(
- json.contains("\"method_name\":\"legal\""), "JSON should contain the method name");
- assertTrue(
- json.contains("\"listen_event_types\":["),
- "JSON should contain the listen event types");
- assertTrue(
- json.contains("\"org.apache.flink.agents.api.InputEvent\""),
- "JSON should contain the event type class name");
- assertTrue(
- json.contains("\"actions_by_event\":{}"),
- "JSON should contain empty event trigger actions");
+ assertThat(json).contains("\"actions\":{");
+ assertThat(json).contains("\"testAction\":{");
+ assertThat(json).contains("\"name\":\"testAction\"");
+ assertThat(json).contains("\"exec\":{");
+ assertThat(json).contains("\"func_type\":\"JavaFunction\"");
+ assertThat(json).contains("\"qualname\":\"org.apache.flink.agents.plan.TestAction\"");
+ assertThat(json).contains("\"method_name\":\"legal\"");
+ assertThat(json).contains("\"listen_event_types\":[");
+ assertThat(json).contains("\"org.apache.flink.agents.api.InputEvent\"");
+ assertThat(json).contains("\"actions_by_event\":{}");
}
@Test
@@ -104,16 +120,10 @@ public class AgentPlanJsonSerializerTest {
String json = new ObjectMapper().writeValueAsString(agentPlan);
// Verify the JSON contains the expected fields
- assertTrue(json.contains("\"actions\":{}"), "JSON should contain empty actions");
- assertTrue(
- json.contains("\"actions_by_event\":{"),
- "JSON should contain the event trigger actions field");
- assertTrue(
- json.contains("\"org.apache.flink.agents.api.InputEvent\":["),
- "JSON should contain the event class name and an array of action names");
- assertTrue(
- json.contains("\"testAction\""),
- "JSON should contain the action name in event triggers");
+ assertThat(json).contains("\"actions\":{}");
+ assertThat(json).contains("\"actions_by_event\":{");
+ assertThat(json).contains("\"org.apache.flink.agents.api.InputEvent\":[");
+ assertThat(json).contains("\"testAction\"");
}
@Test
@@ -151,24 +161,14 @@ public class AgentPlanJsonSerializerTest {
String json = new ObjectMapper().writeValueAsString(agentPlan);
// Verify the JSON contains the expected fields
- assertTrue(json.contains("\"actions\":{"), "JSON should contain the actions field");
- assertTrue(json.contains("\"action1\":{"), "JSON should contain the first action name");
- assertTrue(json.contains("\"action2\":{"), "JSON should contain the second action name");
- assertTrue(
- json.contains("\"actions_by_event\":{"),
- "JSON should contain the event trigger actions field");
- assertTrue(
- json.contains("\"org.apache.flink.agents.api.InputEvent\":["),
- "JSON should contain the InputEvent class name and an array of action names");
- assertTrue(
- json.contains("\"org.apache.flink.agents.api.OutputEvent\":["),
- "JSON should contain the OutputEvent class name and an array of action names");
- assertTrue(
- json.contains("\"action1\""),
- "JSON should contain the first action name in event triggers");
- assertTrue(
- json.contains("\"action2\""),
- "JSON should contain the second action name in event triggers");
+ assertThat(json).contains("\"actions\":{");
+ assertThat(json).contains("\"action1\":{");
+ assertThat(json).contains("\"action2\":{");
+ assertThat(json).contains("\"actions_by_event\":{");
+ assertThat(json).contains("\"org.apache.flink.agents.api.InputEvent\":[");
+ assertThat(json).contains("\"org.apache.flink.agents.api.OutputEvent\":[");
+ assertThat(json).contains("\"action1\"");
+ assertThat(json).contains("\"action2\"");
}
@Test
@@ -180,9 +180,41 @@ public class AgentPlanJsonSerializerTest {
String json = new ObjectMapper().writeValueAsString(agentPlan);
// Verify the JSON contains the expected fields
- assertTrue(json.contains("\"actions\":{}"), "JSON should contain empty actions");
- assertTrue(
- json.contains("\"actions_by_event\":{}"),
- "JSON should contain empty event trigger actions");
+ assertThat(json).contains("\"actions\":{}");
+ assertThat(json).contains("\"actions_by_event\":{}");
+ }
+
+ @Test
+ public void testSerializeAgentPlanCreatedFromAgent() throws Exception {
+ // Create a test agent
+ TestAgent agent = new TestAgent();
+
+ // Create AgentPlan from the agent
+ AgentPlan agentPlan = new AgentPlan(agent);
+
+ // Serialize the agent plan to JSON
+ String json = new ObjectMapper().writeValueAsString(agentPlan);
+
+ // Verify the JSON contains the expected fields
+ assertThat(json).contains("\"actions\":{");
+ assertThat(json).contains("\"actions_by_event\":{");
+
+ // Verify that the actions from @Action annotated methods are present
+ assertThat(json).contains("\"handleInputEvent\"");
+ assertThat(json).contains("\"processOutputEvent\"");
+ assertThat(json).contains("\"handleMultipleEvents\"");
+
+ // Verify event mappings
+ assertThat(json).contains("\"org.apache.flink.agents.api.InputEvent\"");
+ assertThat(json).contains("\"org.apache.flink.agents.api.OutputEvent\"");
+
+ // Verify function details
+ assertThat(json).contains("\"func_type\":\"JavaFunction\"");
+ assertThat(json)
+ .contains(
+ "\"qualname\":\"org.apache.flink.agents.plan.serializer.AgentPlanJsonSerializerTest$TestAgent\"");
+
+ // Verify that regularMethod is not included (not annotated)
+ assertThat(json).doesNotContain("\"regularMethod\"");
}
}
diff --git a/pom.xml b/pom.xml
index 71fd697..f52f255 100644
--- a/pom.xml
+++ b/pom.xml
@@ -65,6 +65,12 @@ under the License.
<version>${junit5.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>${assertj.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/runtime/pom.xml b/runtime/pom.xml
index 3f69580..be95de6 100644
--- a/runtime/pom.xml
+++ b/runtime/pom.xml
@@ -119,14 +119,6 @@ under the License.
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2.version}</version>
</dependency>
-
- <!-- TEST -->
- <dependency>
- <groupId>org.assertj</groupId>
- <artifactId>assertj-core</artifactId>
- <version>${assertj.version}</version>
- <scope>test</scope>
- </dependency>
</dependencies>
</project>
\ No newline at end of file