Guest User

Untitled

a guest
Sep 29th, 2021
20
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 13.55 KB | None | 0 0
  1. From 69e258f0086d811f7f7b6862bce7ee582f766b4e Mon Sep 17 00:00:00 2001
  2. From: Zabuzard <[email protected]>
  3. Date: Wed, 29 Sep 2021 17:31:38 +0200
  4. Subject: [PATCH] Added JDA mock and tests
  5.  
  6. ---
  7. application/build.gradle                      |   2 +
  8.  .../togetherjava/tjbot/AbstractJdaMock.java   | 109 +++++++++++++
  9.  .../tjbot/command/DatabaseCommandTest.java    | 144 ++++++++++++++++++
  10.  .../tjbot/command/PingCommandTest.java        |  38 +++++
  11.  4 files changed, 293 insertions(+)
  12.  create mode 100644 application/src/test/java/org/togetherjava/tjbot/AbstractJdaMock.java
  13.  create mode 100644 application/src/test/java/org/togetherjava/tjbot/command/DatabaseCommandTest.java
  14.  create mode 100644 application/src/test/java/org/togetherjava/tjbot/command/PingCommandTest.java
  15.  
  16. diff --git a/application/build.gradle b/application/build.gradle
  17. index 6f02cf7..056aef4 100644
  18. --- a/application/build.gradle
  19. +++ b/application/build.gradle
  20. @@ -50,6 +50,8 @@ dependencies {
  21.  
  22.      implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.5'
  23.  
  24. +    testImplementation 'org.mockito:mockito-core:3.12.4'
  25. +    testRuntimeOnly 'org.mockito:mockito-core:3.12.4'
  26.      testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
  27.      testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
  28.  }
  29. diff --git a/application/src/test/java/org/togetherjava/tjbot/AbstractJdaMock.java b/application/src/test/java/org/togetherjava/tjbot/AbstractJdaMock.java
  30. new file mode 100644
  31. index 0000000..f82c35c
  32. --- /dev/null
  33. +++ b/application/src/test/java/org/togetherjava/tjbot/AbstractJdaMock.java
  34. @@ -0,0 +1,109 @@
  35. +package org.togetherjava.tjbot;
  36. +
  37. +import net.dv8tion.jda.api.entities.SelfUser;
  38. +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
  39. +import net.dv8tion.jda.api.requests.restaction.MessageAction;
  40. +import net.dv8tion.jda.api.utils.ConcurrentSessionController;
  41. +import net.dv8tion.jda.api.utils.data.DataObject;
  42. +import net.dv8tion.jda.internal.JDAImpl;
  43. +import net.dv8tion.jda.internal.entities.EntityBuilder;
  44. +import net.dv8tion.jda.internal.entities.PrivateChannelImpl;
  45. +import net.dv8tion.jda.internal.entities.SelfUserImpl;
  46. +import net.dv8tion.jda.internal.entities.UserImpl;
  47. +import net.dv8tion.jda.internal.interactions.CommandInteractionImpl;
  48. +import net.dv8tion.jda.internal.interactions.InteractionHookImpl;
  49. +import net.dv8tion.jda.internal.requests.Requester;
  50. +import net.dv8tion.jda.internal.requests.restaction.MessageActionImpl;
  51. +import net.dv8tion.jda.internal.requests.restaction.interactions.ReplyActionImpl;
  52. +import net.dv8tion.jda.internal.utils.config.AuthorizationConfig;
  53. +import org.junit.jupiter.api.BeforeEach;
  54. +import org.mockito.Mockito;
  55. +import org.slf4j.Logger;
  56. +import org.slf4j.LoggerFactory;
  57. +
  58. +import javax.annotation.Nonnull;
  59. +import java.util.concurrent.ScheduledThreadPoolExecutor;
  60. +
  61. +import static org.mockito.ArgumentMatchers.any;
  62. +import static org.mockito.ArgumentMatchers.anyLong;
  63. +import static org.mockito.ArgumentMatchers.anyString;
  64. +import static org.mockito.Mockito.doReturn;
  65. +import static org.mockito.Mockito.mock;
  66. +import static org.mockito.Mockito.when;
  67. +
  68. +/**
  69. + * This class mocks a valid JDA instance with a user, an api and a message channel mock
  70. + * implementations
  71. + */
  72. +public abstract class AbstractJdaMock {
  73. +
  74. +    private final long userId;
  75. +    private final long applicationId;
  76. +    protected static final Logger logger = LoggerFactory.getLogger(AbstractJdaMock.class);
  77. +    /**
  78. +     * is a mock on which we can stub out methods and simulate a real JDA instance that can send
  79. +     * messages to the bot
  80. +     */
  81. +    protected JDAImpl jdaImplMock;
  82. +    /**
  83. +     * is spied upon to validate actions performed by the bot
  84. +     */
  85. +    protected PrivateChannelImpl privateChannel;
  86. +
  87. +    public AbstractJdaMock(long userId, long applicationId) {
  88. +        this.userId = userId;
  89. +        this.applicationId = applicationId;
  90. +    }
  91. +
  92. +    // TODO - Improve these mocks. Some of them could actually be the real concrete class
  93. +    @BeforeEach
  94. +    protected void setup() {
  95. +        EntityBuilder entityBuilderMock = mock(EntityBuilder.class);
  96. +        jdaImplMock = mock(JDAImpl.class);
  97. +        SelfUser selfUser = mock(SelfUserImpl.class);
  98. +        UserImpl user = Mockito.spy(new UserImpl(userId, jdaImplMock));
  99. +        privateChannel = Mockito.spy(new PrivateChannelImpl(userId, user));
  100. +        MessageAction mockMessageAction = Mockito.mock(MessageActionImpl.class);
  101. +
  102. +        when(entityBuilderMock.createUser(any())).thenReturn(user);
  103. +        when(selfUser.getApplicationId()).thenReturn(String.valueOf(applicationId));
  104. +        when(selfUser.getApplicationIdLong()).thenReturn(applicationId);
  105. +        doReturn(selfUser).when(jdaImplMock).getSelfUser();
  106. +        when(jdaImplMock.getPrivateChannelById(anyLong())).thenReturn(privateChannel);
  107. +        when(jdaImplMock.getGatewayPool()).thenReturn(new ScheduledThreadPoolExecutor(4));
  108. +        when(jdaImplMock.getEntityBuilder()).thenReturn(entityBuilderMock);
  109. +        when(jdaImplMock.getSessionController()).thenReturn(new ConcurrentSessionController());
  110. +        doReturn(new Requester(jdaImplMock, new AuthorizationConfig("TOKEN"))).when(jdaImplMock)
  111. +            .getRequester();
  112. +        doReturn(mockMessageAction).when(privateChannel).sendMessage(anyString());
  113. +
  114. +    }
  115. +
  116. +    protected SlashCommandEvent createSlashCommand(@Nonnull String command) {
  117. +        DataObject dataObject = DataObject.fromJson("""
  118. +                {
  119. +                  "id": 2,
  120. +                  "token": "asd",
  121. +                  "channel_id": 2,
  122. +                  "user": {
  123. +                    "id": 2
  124. +                  },
  125. +                  "type": 2,
  126. +                  "data": {
  127. +                    "id": 2,
  128. +                    "name": "%s"
  129. +                  }
  130. +                }""".formatted(command));
  131. +
  132. +        SlashCommandEvent slashCommandEvent = new SlashCommandEvent(jdaImplMock, 0,
  133. +                new CommandInteractionImpl(jdaImplMock, dataObject));
  134. +
  135. +        slashCommandEvent = Mockito.spy(slashCommandEvent);
  136. +
  137. +        ReplyActionImpl replyActionImplMock = mock(ReplyActionImpl.class);
  138. +
  139. +        doReturn(replyActionImplMock).when(slashCommandEvent).reply(anyString());
  140. +
  141. +        return slashCommandEvent;
  142. +    }
  143. +}
  144. diff --git a/application/src/test/java/org/togetherjava/tjbot/command/DatabaseCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/command/DatabaseCommandTest.java
  145. new file mode 100644
  146. index 0000000..5a8171b
  147. --- /dev/null
  148. +++ b/application/src/test/java/org/togetherjava/tjbot/command/DatabaseCommandTest.java
  149. @@ -0,0 +1,144 @@
  150. +package org.togetherjava.tjbot.command;
  151. +
  152. +import net.dv8tion.jda.api.entities.ChannelType;
  153. +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
  154. +import org.junit.jupiter.api.BeforeEach;
  155. +import org.junit.jupiter.api.DisplayName;
  156. +import org.junit.jupiter.api.Test;
  157. +import org.togetherjava.tjbot.AbstractJdaMock;
  158. +import org.togetherjava.tjbot.commands.Command;
  159. +import org.togetherjava.tjbot.commands.generic.DatabaseGetCommand;
  160. +import org.togetherjava.tjbot.commands.generic.DatabasePutCommand;
  161. +import org.togetherjava.tjbot.db.Database;
  162. +import org.togetherjava.tjbot.db.generated.tables.Storage;
  163. +import org.togetherjava.tjbot.db.generated.tables.records.StorageRecord;
  164. +
  165. +import java.sql.SQLException;
  166. +
  167. +import static org.mockito.Mockito.atLeastOnce;
  168. +import static org.mockito.Mockito.times;
  169. +import static org.mockito.Mockito.verify;
  170. +import static org.mockito.Mockito.when;
  171. +
  172. +class DatabaseCommandTest extends AbstractJdaMock {
  173. +
  174. +    private Database database;
  175. +    private SlashCommandEvent dbGetCommand;
  176. +    private SlashCommandEvent dbPutCommand;
  177. +
  178. +    public DatabaseCommandTest() {
  179. +        super(2, 2);
  180. +    }
  181. +
  182. +    @BeforeEach
  183. +    void setupDatabase() throws SQLException {
  184. +        super.setup();
  185. +        database = new Database("jdbc:sqlite:");
  186. +
  187. +        // TODO - Remover this. We should use the flyway script, but I don't know a thing about
  188. +        // using it with Gradle
  189. +        database.writeTransaction(ctx -> {
  190. +            ctx.ddl(Storage.STORAGE).executeBatch();
  191. +        });
  192. +
  193. +        dbGetCommand = createSlashCommand("dbget ditto");
  194. +        dbPutCommand = createSlashCommand("dbput ditto evolved");
  195. +
  196. +        when(dbGetCommand.getChannelType()).thenReturn(ChannelType.TEXT);
  197. +        when(dbPutCommand.getChannelType()).thenReturn(ChannelType.TEXT);
  198. +    }
  199. +
  200. +    @DisplayName("""
  201. +            Given an existing Database.
  202. +            When a user retrieves a non-existing command.
  203. +            Then an invalid message is returned
  204. +            """)
  205. +    @Test
  206. +    void testGetCommand() {
  207. +
  208. +        Command command = new DatabaseGetCommand(database);
  209. +
  210. +        command.onSlashCommand(dbGetCommand);
  211. +
  212. +        verify(dbGetCommand, times(1)).reply("Nothing found for the key 'ditto'");
  213. +    }
  214. +
  215. +    @DisplayName("""
  216. +            Given an existing Database with commands present
  217. +            When a user retrieves a command.
  218. +            Then the correct response is returned
  219. +            """)
  220. +    @Test
  221. +    void testGetWrongCommand() {
  222. +
  223. +        database.writeTransaction(ctx -> {
  224. +            StorageRecord storageRecord =
  225. +                    ctx.newRecord(Storage.STORAGE).setKey("ditto").setValue("evolution");
  226. +            if (storageRecord.update() == 0) {
  227. +                storageRecord.insert();
  228. +            }
  229. +        });
  230. +
  231. +        Command command = new DatabaseGetCommand(database);
  232. +
  233. +        command.onSlashCommand(dbGetCommand);
  234. +
  235. +        verify(dbGetCommand, times(1)).reply("Saved message: evolution");
  236. +    }
  237. +
  238. +    @DisplayName("""
  239. +            Given an existing Database with commands present
  240. +            When a user updates a command.
  241. +            Then the correct response is returned and the command is updated
  242. +            """)
  243. +    @Test
  244. +    void testUpdateCommand() {
  245. +        SlashCommandEvent failedDbGetCommand = createSlashCommand("dbget pikachu");
  246. +        when(failedDbGetCommand.getChannelType()).thenReturn(ChannelType.TEXT);
  247. +        Command getCommand = new DatabaseGetCommand(database);
  248. +        Command putCommand = new DatabasePutCommand(database);
  249. +
  250. +        putCommand.onSlashCommand(dbPutCommand);
  251. +        getCommand.onSlashCommand(failedDbGetCommand);
  252. +        getCommand.onSlashCommand(dbGetCommand);
  253. +
  254. +        verify(dbPutCommand, times(1)).reply("Saved under 'ditto'.");
  255. +        verify(failedDbGetCommand, times(1)).reply("Nothing found for the key 'pikachu'");
  256. +        verify(dbGetCommand, times(1)).reply("Saved message: evolved");
  257. +    }
  258. +
  259. +    @DisplayName("""
  260. +            Given an existing Database with commands present
  261. +            When a user inserts invalid messages
  262. +            Then the correct responses are returned
  263. +            """)
  264. +    @Test
  265. +    void testErrorMessages() {
  266. +        SlashCommandEvent errorCommand = createSlashCommand("dbget");
  267. +        when(errorCommand.getChannelType()).thenReturn(ChannelType.TEXT);
  268. +        Command command = new DatabaseGetCommand(database);
  269. +
  270. +        command.onSlashCommand(errorCommand);
  271. +
  272. +        verify(errorCommand, atLeastOnce())
  273. +            .reply("Sorry, your message was in the wrong format, try '/dbget key'");
  274. +    }
  275. +
  276. +    @DisplayName("""
  277. +            Given an invalid Database
  278. +            When a user inserts messages
  279. +            Then the correct responses are returned
  280. +            """)
  281. +    @Test
  282. +    void testDatabaseErrorMessages() throws SQLException {
  283. +        Database database = new Database("jdbc:sqlite:");
  284. +        Command getCommand = new DatabaseGetCommand(database);
  285. +        Command putCommand = new DatabasePutCommand(database);
  286. +
  287. +        getCommand.onSlashCommand(dbGetCommand);
  288. +        putCommand.onSlashCommand(dbPutCommand);
  289. +
  290. +        verify(dbGetCommand, times(1)).reply("Sorry, something went wrong.");
  291. +        verify(dbPutCommand, times(1)).reply("Sorry, something went wrong.");
  292. +    }
  293. +}
  294. diff --git a/application/src/test/java/org/togetherjava/tjbot/command/PingCommandTest.java b/application/src/test/java/org/togetherjava/tjbot/command/PingCommandTest.java
  295. new file mode 100644
  296. index 0000000..c6ff9ac
  297. --- /dev/null
  298. +++ b/application/src/test/java/org/togetherjava/tjbot/command/PingCommandTest.java
  299. @@ -0,0 +1,38 @@
  300. +package org.togetherjava.tjbot.command;
  301. +
  302. +import net.dv8tion.jda.api.events.interaction.SlashCommandEvent;
  303. +import org.junit.jupiter.api.DisplayName;
  304. +import org.junit.jupiter.api.Test;
  305. +import org.togetherjava.tjbot.AbstractJdaMock;
  306. +import org.togetherjava.tjbot.commands.Command;
  307. +import org.togetherjava.tjbot.commands.generic.PingCommand;
  308. +
  309. +import static org.mockito.Mockito.anyString;
  310. +import static org.mockito.Mockito.atLeastOnce;
  311. +import static org.mockito.Mockito.times;
  312. +import static org.mockito.Mockito.verify;
  313. +
  314. +class PingCommandTest extends AbstractJdaMock {
  315. +
  316. +    public PingCommandTest() {
  317. +        super(2, 2);
  318. +    }
  319. +
  320. +    @DisplayName("""
  321. +            Given an existing Ping Command implementation
  322. +            When a command is fired that uses the ping command
  323. +            Then a pong message is returned
  324. +            """)
  325. +    @Test
  326. +    void testPingCommand() {
  327. +        Command command = new PingCommand();
  328. +
  329. +        SlashCommandEvent slashCommandEvent = createSlashCommand("ping");
  330. +
  331. +        command.onSlashCommand(slashCommandEvent);
  332. +
  333. +        // Assert and validate that our mock private channel calls the sendMessage event exactly
  334. +        // once.
  335. +        verify(slashCommandEvent, atLeastOnce()).reply("Pong!");
  336. +    }
  337. +}
  338. --
  339. 2.23.0.windows.1
  340.  
  341.  
Advertisement
Add Comment
Please, Sign In to add comment