From 5eb8c3da7dbf99604662178a4d05abd9c7b0c9f1 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:35:47 +0200 Subject: [PATCH 1/9] Start using assertj-db --- gradle/libs.versions.toml | 7 +- miner/build.gradle.kts | 2 +- .../miner/database/SQLiteDatabaseTest.java | 78 +++++++++++++++++++ viewer/build.gradle.kts | 2 +- 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6aed66a6..c554fa55 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,8 @@ lang3-version = "3.12.0" jetbrains-annotations-version = "23.0.0" websocket-version = "1.5.3" junit-version = "5.9.0" -assertj-version = "3.23.1" +assertj-core-version = "3.23.1" +assertj-db-version = "2.0.2" mockito-version = "4.7.0" awaitility-version = "4.2.0" json-unit-version = "2.35.0" @@ -54,7 +55,8 @@ flyway-mysql = { group = "org.flywaydb", name = "flyway-mysql", version.ref = "f junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit-version" } junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit-version" } junitEngine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit-version" } -assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj-version" } +assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertj-core-version" } +assertj-db = { group = "org.assertj", name = "assertj-db", version.ref = "assertj-db-version" } mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito-version" } mockito-inline = { group = "org.mockito", name = "mockito-inline", version.ref = "mockito-version" } mockito-junit = { group = "org.mockito", name = "mockito-junit-jupiter", version.ref = "mockito-version" } @@ -71,6 +73,7 @@ flyway = ["flyway-core", "flyway-mysql"] junit = ["junit-api", "junit-params"] mockito = ["mockito-core", "mockito-inline", "mockito-junit"] jsonUnit = ["json-unit", "json-unit-assertj"] +assertj = ["assertj-core", "assertj-db"] [plugins] shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow-version" } diff --git a/miner/build.gradle.kts b/miner/build.gradle.kts index 2c802c7d..c7320ae1 100644 --- a/miner/build.gradle.kts +++ b/miner/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { testImplementation(libs.bundles.junit) testRuntimeOnly(libs.junitEngine) - testImplementation(libs.assertj) + testImplementation(libs.bundles.assertj) testImplementation(libs.bundles.mockito) testImplementation(libs.awaitility) testImplementation(libs.unirestMocks) diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java new file mode 100644 index 00000000..ff260f25 --- /dev/null +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -0,0 +1,78 @@ +package fr.raksrinana.channelpointsminer.miner.database; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.assertj.db.type.Table; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import java.nio.file.Path; +import java.sql.SQLException; +import static org.assertj.db.api.Assertions.assertThat; +import static org.assertj.db.output.Outputs.output; + +class SQLiteDatabaseTest{ + private static final String CHANNEL_ID = "channel-id"; + private static final String CHANNEL_USERNAME = "channel-username"; + + @TempDir + private Path tempPath; + + private SQLiteDatabase tested; + private HikariDataSource dataSource; + + private Table tableBalance; + private Table tableChannel; + private Table tablePrediction; + private Table tablePredictionUser; + private Table tableUserPrediction; + + @BeforeEach + void setUp() throws SQLException{ + var poolConfiguration = new HikariConfig(); + poolConfiguration.setJdbcUrl("jdbc:sqlite:" + tempPath.resolve(System.currentTimeMillis() + "_test.db").toAbsolutePath()); + poolConfiguration.setDriverClassName("org.sqlite.JDBC"); + poolConfiguration.setMaximumPoolSize(1); + + dataSource = new HikariDataSource(poolConfiguration); + tested = new SQLiteDatabase(dataSource); + + tested.initDatabase(); + + tableBalance = new Table(dataSource, "Balance"); + tableChannel = new Table(dataSource, "Channel"); + tablePrediction = new Table(dataSource, "Prediction"); + tablePredictionUser = new Table(dataSource, "PredictionUser"); + tableUserPrediction = new Table(dataSource, "UserPrediction"); + } + + @AfterEach + void tearDown(){ + tested.close(); + } + + @Test + void tablesAreCreated(){ + assertThat(tableBalance).exists(); + assertThat(tableChannel).exists(); + assertThat(tablePrediction).exists(); + assertThat(tablePredictionUser).exists(); + assertThat(tableUserPrediction).exists(); + } + + @Test + void newChannelIsInsertedIfNew() throws SQLException{ + assertThat(tableChannel).isEmpty(); + + tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + + output(tableChannel).toConsole(); + assertThat(tableChannel) + .hasNumberOfRows(1) // TODO Hmmm why isn't it passing, if looking manually there is indeed 1 record :/ Because of Hikari that has a pool? + .row() + .column("ID").value().isEqualTo(CHANNEL_ID) + .column("Username").value().isEqualTo(CHANNEL_USERNAME) + .column("LastStatusChange").value().isNotNull(); + } +} \ No newline at end of file diff --git a/viewer/build.gradle.kts b/viewer/build.gradle.kts index faffac17..e21d9e43 100644 --- a/viewer/build.gradle.kts +++ b/viewer/build.gradle.kts @@ -19,7 +19,7 @@ dependencies { testImplementation(libs.bundles.junit) testRuntimeOnly(libs.junitEngine) - testImplementation(libs.assertj) + testImplementation(libs.bundles.assertj) testImplementation("org.mockito:mockito-core") testImplementation("org.mockito:mockito-inline") testImplementation("org.mockito:mockito-junit-jupiter") From bc399f7984e33e55ced9840d32d4bba2e24131e1 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:47:08 +0200 Subject: [PATCH 2/9] Add more DB tests --- .../miner/database/BaseDatabase.java | 19 ++--- .../miner/database/SQLiteDatabase.java | 8 +- .../db/migrations/sqlite/V1_0_0__base.sql | 10 +-- .../miner/database/SQLiteDatabaseTest.java | 79 +++++++++++++------ .../tests/WebsocketMockServerExtension.java | 2 +- miner/src/test/resources/log4j2-test.xml | 10 +-- 6 files changed, 76 insertions(+), 52 deletions(-) diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java index 59b5a583..028045fc 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java @@ -14,8 +14,8 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; import java.time.Instant; -import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.Collection; import java.util.LinkedList; @@ -23,7 +23,6 @@ import java.util.Optional; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import static java.time.ZoneOffset.UTC; @RequiredArgsConstructor @Log4j2 @@ -54,9 +53,7 @@ public void updateChannelStatusTime(@NotNull String channelId, @NotNull Instant WHERE `ID` = ?;""" )){ - var timestamp = LocalDateTime.now(UTC); - - statement.setObject(1, timestamp); + statement.setTimestamp(1, Timestamp.from(instant)); statement.setString(2, channelId); statement.executeUpdate(); @@ -72,7 +69,7 @@ public void addBalance(@NotNull String channelId, int balance, @Nullable String )){ statement.setString(1, channelId); - statement.setObject(2, LocalDateTime.ofInstant(instant, UTC)); + statement.setTimestamp(2, Timestamp.from(instant)); statement.setInt(3, balance); statement.setString(4, reason); @@ -90,7 +87,7 @@ public void addPrediction(@NotNull String channelId, @NotNull String eventId, @N statement.setString(1, channelId); statement.setString(2, eventId); - statement.setObject(3, LocalDateTime.ofInstant(instant, UTC)); + statement.setTimestamp(3, Timestamp.from(instant)); statement.setString(4, type); statement.setString(5, description); @@ -182,8 +179,8 @@ public void cancelPrediction(@NotNull Event event) throws SQLException{ addCanceledPredictionStmt.setString(1, event.getId()); addCanceledPredictionStmt.setString(2, event.getChannelId()); addCanceledPredictionStmt.setString(3, event.getTitle()); - addCanceledPredictionStmt.setObject(4, event.getCreatedAt().withZoneSameInstant(UTC).toLocalDateTime()); - addCanceledPredictionStmt.setObject(5, LocalDateTime.ofInstant(ended, UTC)); + addCanceledPredictionStmt.setTimestamp(4, Timestamp.from(event.getCreatedAt().toInstant())); + addCanceledPredictionStmt.setTimestamp(5, Timestamp.from(ended)); addCanceledPredictionStmt.executeUpdate(); //Remove made predictions @@ -240,8 +237,8 @@ public void resolvePrediction(@NotNull Event event, @NotNull String outcome, @No addResolvedPredictionStmt.setString(1, event.getId()); addResolvedPredictionStmt.setString(2, event.getChannelId()); addResolvedPredictionStmt.setString(3, event.getTitle()); - addResolvedPredictionStmt.setObject(4, event.getCreatedAt().withZoneSameInstant(UTC).toLocalDateTime()); - addResolvedPredictionStmt.setObject(5, LocalDateTime.ofInstant(ended, UTC)); + addResolvedPredictionStmt.setTimestamp(4, Timestamp.from(event.getCreatedAt().toInstant())); + addResolvedPredictionStmt.setTimestamp(5, Timestamp.from(ended)); addResolvedPredictionStmt.setString(6, outcome); addResolvedPredictionStmt.setString(7, badge); addResolvedPredictionStmt.setDouble(8, returnRatioForWin); diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java index a65d186f..697befe4 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java @@ -1,12 +1,12 @@ package fr.raksrinana.channelpointsminer.miner.database; import com.zaxxer.hikari.HikariDataSource; +import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory; import org.jetbrains.annotations.NotNull; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.time.LocalDateTime; -import static java.time.ZoneOffset.UTC; +import java.sql.Timestamp; public class SQLiteDatabase extends BaseDatabase{ public SQLiteDatabase(HikariDataSource dataSource){ @@ -26,11 +26,9 @@ public void createChannel(@NotNull String channelId, @NotNull String username) t VALUES(?, ?, ?);""" )){ - var timestamp = LocalDateTime.now(UTC); - statement.setString(1, channelId); statement.setString(2, username); - statement.setObject(3, timestamp); + statement.setTimestamp(3, Timestamp.from(TimeFactory.now())); statement.executeUpdate(); } diff --git a/miner/src/main/resources/db/migrations/sqlite/V1_0_0__base.sql b/miner/src/main/resources/db/migrations/sqlite/V1_0_0__base.sql index e250838f..5bc1781f 100644 --- a/miner/src/main/resources/db/migrations/sqlite/V1_0_0__base.sql +++ b/miner/src/main/resources/db/migrations/sqlite/V1_0_0__base.sql @@ -2,14 +2,14 @@ CREATE TABLE IF NOT EXISTS `Channel` ( `ID` VARCHAR(32) NOT NULL PRIMARY KEY, `Username` VARCHAR(128) NOT NULL, - `LastStatusChange` DATETIME NOT NULL + `LastStatusChange` TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS `Balance` ( `ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `ChannelID` VARCHAR(32) NOT NULL REFERENCES `Channel` (`ID`), - `BalanceDate` DATETIME(3) NOT NULL, + `BalanceDate` TIMESTAMP NOT NULL, `Balance` INTEGER NOT NULL, `Reason` VARCHAR(16) NULL ); @@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS `Prediction` `ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, `ChannelID` VARCHAR(32) NOT NULL REFERENCES `Channel` (`ID`), `EventID` VARCHAR(36) NOT NULL, - `EventDate` DATETIME NOT NULL, + `EventDate` TIMESTAMP NOT NULL, `Type` VARCHAR(16) NULL, `Description` VARCHAR(255) NULL ); @@ -35,8 +35,8 @@ CREATE TABLE IF NOT EXISTS `ResolvedPrediction` `EventID` VARCHAR(36) NOT NULL PRIMARY KEY, `ChannelID` VARCHAR(32) NOT NULL REFERENCES `Channel` (`ID`), `Title` VARCHAR(64) NOT NULL, - `EventCreated` DATETIME NOT NULL, - `EventEnded` DATETIME NULL, + `EventCreated` TIMESTAMP NOT NULL, + `EventEnded` TIMESTAMP NULL, `Canceled` BOOLEAN NOT NULL, `Outcome` VARCHAR(32) NULL, `Badge` VARCHAR(32) NULL, diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index ff260f25..bfd39423 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -2,6 +2,7 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory; import org.assertj.db.type.Table; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -9,8 +10,14 @@ import org.junit.jupiter.api.io.TempDir; import java.nio.file.Path; import java.sql.SQLException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.util.function.Supplier; import static org.assertj.db.api.Assertions.assertThat; -import static org.assertj.db.output.Outputs.output; +import static org.mockito.Mockito.mockStatic; class SQLiteDatabaseTest{ private static final String CHANNEL_ID = "channel-id"; @@ -22,11 +29,11 @@ class SQLiteDatabaseTest{ private SQLiteDatabase tested; private HikariDataSource dataSource; - private Table tableBalance; - private Table tableChannel; - private Table tablePrediction; - private Table tablePredictionUser; - private Table tableUserPrediction; + private final Supplier tableBalance = () -> new Table(dataSource, "Balance"); + private final Supplier
tableChannel = () -> new Table(dataSource, "Channel"); + private final Supplier
tablePrediction = () -> new Table(dataSource, "Prediction"); + private final Supplier
tablePredictionUser = () -> new Table(dataSource, "PredictionUser"); + private final Supplier
tableUserPrediction = () -> new Table(dataSource, "UserPrediction"); @BeforeEach void setUp() throws SQLException{ @@ -39,12 +46,6 @@ void setUp() throws SQLException{ tested = new SQLiteDatabase(dataSource); tested.initDatabase(); - - tableBalance = new Table(dataSource, "Balance"); - tableChannel = new Table(dataSource, "Channel"); - tablePrediction = new Table(dataSource, "Prediction"); - tablePredictionUser = new Table(dataSource, "PredictionUser"); - tableUserPrediction = new Table(dataSource, "UserPrediction"); } @AfterEach @@ -54,25 +55,59 @@ void tearDown(){ @Test void tablesAreCreated(){ - assertThat(tableBalance).exists(); - assertThat(tableChannel).exists(); - assertThat(tablePrediction).exists(); - assertThat(tablePredictionUser).exists(); - assertThat(tableUserPrediction).exists(); + assertThat(tableBalance.get()).exists(); + assertThat(tableChannel.get()).exists(); + assertThat(tablePrediction.get()).exists(); + assertThat(tablePredictionUser.get()).exists(); + assertThat(tableUserPrediction.get()).exists(); } @Test void newChannelIsInsertedIfNew() throws SQLException{ - assertThat(tableChannel).isEmpty(); - tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); - output(tableChannel).toConsole(); - assertThat(tableChannel) - .hasNumberOfRows(1) // TODO Hmmm why isn't it passing, if looking manually there is indeed 1 record :/ Because of Hikari that has a pool? + assertThat(tableChannel.get()) + .hasNumberOfRows(1) .row() .column("ID").value().isEqualTo(CHANNEL_ID) .column("Username").value().isEqualTo(CHANNEL_USERNAME) .column("LastStatusChange").value().isNotNull(); } + + @Test + void oldChannelIsNotInsertedIfNew() throws SQLException{ + try(var factory = mockStatic(TimeFactory.class)){ + var beforeChange = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); + factory.when(TimeFactory::now).thenReturn(beforeChange); + + tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + assertThat(tableChannel.get()).hasNumberOfRows(1); + + factory.when(TimeFactory::now).thenReturn(beforeChange.plus(1, ChronoUnit.HOURS)); + + tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + + assertThat(tableChannel.get()).hasNumberOfRows(1) + .row() + .column("LastStatusChange").value().isEqualTo(LocalDateTime.ofInstant(beforeChange, ZoneId.systemDefault())); + } + } + + @Test + void updateChannelStatusTime() throws SQLException{ + try(var factory = mockStatic(TimeFactory.class)){ + var beforeChange = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); + factory.when(TimeFactory::now).thenReturn(beforeChange); + + tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + assertThat(tableChannel.get()).hasNumberOfRows(1); + + var newTime = beforeChange.plus(1, ChronoUnit.HOURS); + tested.updateChannelStatusTime(CHANNEL_ID, newTime); + + assertThat(tableChannel.get()).hasNumberOfRows(1) + .row() + .column("LastStatusChange").value().isEqualTo(LocalDateTime.ofInstant(newTime, ZoneId.systemDefault())); + } + } } \ No newline at end of file diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/tests/WebsocketMockServerExtension.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/tests/WebsocketMockServerExtension.java index c47d1667..92db897d 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/tests/WebsocketMockServerExtension.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/tests/WebsocketMockServerExtension.java @@ -15,7 +15,7 @@ public class WebsocketMockServerExtension implements Extension, BeforeAllCallbac private final WebsocketMockServer server; public WebsocketMockServerExtension(){ - server = new WebsocketMockServer(ThreadLocalRandom.current().nextInt(10000, 30000)); + this(ThreadLocalRandom.current().nextInt(10000, 30000)); } public WebsocketMockServerExtension(int port){ diff --git a/miner/src/test/resources/log4j2-test.xml b/miner/src/test/resources/log4j2-test.xml index ca2eea96..c2188970 100644 --- a/miner/src/test/resources/log4j2-test.xml +++ b/miner/src/test/resources/log4j2-test.xml @@ -6,16 +6,10 @@ - + - - - - - - - + From 5cac835e058c4e93b456bfa5198d4875e52a3c49 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sat, 3 Sep 2022 15:50:14 +0200 Subject: [PATCH 3/9] PR template --- .github/PULL_REQUEST_TEMPLATE.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b8f7c1c5..c6783fcf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,13 +3,11 @@ ### Checklist - [ ] Tests have been added in relevant areas -- [ ] Corresponding changes made to the documentation (README.adoc) +- [ ] Corresponding changes made to the documentation (README.adoc) ### Type of change -- [ ] Bug fix -- [ ] New feature -- [ ] Breaking change + ## Description From d6bc3918bf240adf9f73f10c687e7aafcfef946c Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sun, 4 Sep 2022 09:03:29 +0200 Subject: [PATCH 4/9] Add balance test --- .../miner/database/SQLiteDatabaseTest.java | 86 +++++++++++++++---- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index bfd39423..473b4e42 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -3,6 +3,7 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory; +import org.assertj.db.type.Changes; import org.assertj.db.type.Table; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -23,13 +24,21 @@ class SQLiteDatabaseTest{ private static final String CHANNEL_ID = "channel-id"; private static final String CHANNEL_USERNAME = "channel-username"; + private static final String ID_COL = "ID"; + private static final String USERNAME_COL = "Username"; + private static final String LAST_STATUS_CHANGE_COL = "LastStatusChange"; + private static final String CHANNEL_ID_COL = "ChannelID"; + private static final String BALANCE_DATE_COL = "BalanceDate"; + private static final String BALANCE_COL = "Balance"; + private static final String REASON_COL = "Reason"; + @TempDir private Path tempPath; private SQLiteDatabase tested; private HikariDataSource dataSource; - private final Supplier
tableBalance = () -> new Table(dataSource, "Balance"); + private final Supplier
tableBalance = () -> new Table(dataSource, BALANCE_COL); private final Supplier
tableChannel = () -> new Table(dataSource, "Channel"); private final Supplier
tablePrediction = () -> new Table(dataSource, "Prediction"); private final Supplier
tablePredictionUser = () -> new Table(dataSource, "PredictionUser"); @@ -64,50 +73,97 @@ void tablesAreCreated(){ @Test void newChannelIsInsertedIfNew() throws SQLException{ + var table = tableChannel.get(); + var changes = new Changes(table); + + changes.setStartPointNow(); tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + changes.setEndPointNow(); - assertThat(tableChannel.get()) - .hasNumberOfRows(1) - .row() - .column("ID").value().isEqualTo(CHANNEL_ID) - .column("Username").value().isEqualTo(CHANNEL_USERNAME) - .column("LastStatusChange").value().isNotNull(); + assertThat(changes).hasNumberOfChanges(1) + .changeOfCreation() + .column(ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(USERNAME_COL).valueAtEndPoint().isEqualTo(CHANNEL_USERNAME) + .column(LAST_STATUS_CHANGE_COL).valueAtEndPoint().isNotNull(); } @Test void oldChannelIsNotInsertedIfNew() throws SQLException{ try(var factory = mockStatic(TimeFactory.class)){ + var table = tableChannel.get(); + var changes = new Changes(table); + var beforeChange = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); factory.when(TimeFactory::now).thenReturn(beforeChange); tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); - assertThat(tableChannel.get()).hasNumberOfRows(1); factory.when(TimeFactory::now).thenReturn(beforeChange.plus(1, ChronoUnit.HOURS)); + changes.setStartPointNow(); tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + changes.setEndPointNow(); - assertThat(tableChannel.get()).hasNumberOfRows(1) - .row() - .column("LastStatusChange").value().isEqualTo(LocalDateTime.ofInstant(beforeChange, ZoneId.systemDefault())); + assertThat(changes).hasNumberOfChanges(0); } } @Test void updateChannelStatusTime() throws SQLException{ try(var factory = mockStatic(TimeFactory.class)){ + var table = tableChannel.get(); + var changes = new Changes(table); + var beforeChange = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); factory.when(TimeFactory::now).thenReturn(beforeChange); tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); - assertThat(tableChannel.get()).hasNumberOfRows(1); var newTime = beforeChange.plus(1, ChronoUnit.HOURS); + + changes.setStartPointNow(); tested.updateChannelStatusTime(CHANNEL_ID, newTime); + changes.setEndPointNow(); - assertThat(tableChannel.get()).hasNumberOfRows(1) - .row() - .column("LastStatusChange").value().isEqualTo(LocalDateTime.ofInstant(newTime, ZoneId.systemDefault())); + assertThat(changes).hasNumberOfChanges(1) + .changeOfModification() + .column(LAST_STATUS_CHANGE_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(newTime)); } } + + @Test + void addBalance() throws SQLException{ + var table = tableBalance.get(); + var change = new Changes(table); + + var firstInstant = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); + change.setStartPointNow(); + tested.addBalance(CHANNEL_ID, 25, "Test1", firstInstant); + change.setEndPointNow(); + + assertThat(change).hasNumberOfChanges(1) + .changeOfCreation() + .column(ID_COL).valueAtEndPoint().isNotNull() + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(BALANCE_DATE_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(firstInstant)) + .column(BALANCE_COL).valueAtEndPoint().isEqualTo(25) + .column(REASON_COL).valueAtEndPoint().isEqualTo("Test1"); + + var secondInstant = firstInstant.plusSeconds(30); + change.setStartPointNow(); + tested.addBalance(CHANNEL_ID, 50, "Test2", secondInstant); + change.setEndPointNow(); + + assertThat(change).hasNumberOfChanges(1) + .changeOfCreation() + .column(ID_COL).valueAtEndPoint().isNotNull() + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(BALANCE_DATE_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(secondInstant)) + .column(BALANCE_COL).valueAtEndPoint().isEqualTo(50) + .column(REASON_COL).valueAtEndPoint().isEqualTo("Test2"); + } + + private LocalDateTime getExpectedTimestamp(Instant instant){ + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } } \ No newline at end of file From 9d38fe1a8347f9c4b05e4275e47945ec8ff0bf2b Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sun, 4 Sep 2022 09:34:37 +0200 Subject: [PATCH 5/9] Add prediction test --- .../miner/database/IDatabase.java | 6 +-- .../miner/database/SQLiteDatabaseTest.java | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java index 48f52d59..1c82cfa3 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java @@ -12,6 +12,9 @@ public interface IDatabase extends AutoCloseable{ void initDatabase() throws SQLException; + @Override + void close(); + void createChannel(@NotNull String channelId, @NotNull String username) throws SQLException; void updateChannelStatusTime(@NotNull String channelId, @NotNull Instant instant) throws SQLException; @@ -33,9 +36,6 @@ public interface IDatabase extends AutoCloseable{ @NotNull Collection getOutcomeStatisticsForChannel(@NotNull String channelId, int minBetsPlacedByUser) throws SQLException; - @Override - void close(); - @NotNull Optional getStreamerIdFromName(@NotNull String channelName) throws SQLException; } diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index 473b4e42..153c1bab 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -31,6 +31,10 @@ class SQLiteDatabaseTest{ private static final String BALANCE_DATE_COL = "BalanceDate"; private static final String BALANCE_COL = "Balance"; private static final String REASON_COL = "Reason"; + private static final String EVENT_ID_COL = "EventID"; + private static final String EVENT_DATE_COL = "EventDate"; + private static final String TYPE_COL = "Type"; + private static final String DESCRIPTION_COL = "Description"; @TempDir private Path tempPath; @@ -163,6 +167,40 @@ void addBalance() throws SQLException{ .column(REASON_COL).valueAtEndPoint().isEqualTo("Test2"); } + @Test + void addPrediction() throws SQLException{ + var table = tablePrediction.get(); + var change = new Changes(table); + + var firstInstant = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); + change.setStartPointNow(); + tested.addPrediction(CHANNEL_ID, "Event1", "Type1", "Description1", firstInstant); + change.setEndPointNow(); + + assertThat(change).hasNumberOfChanges(1) + .changeOfCreation() + .column(ID_COL).valueAtEndPoint().isNotNull() + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(EVENT_ID_COL).valueAtEndPoint().isEqualTo("Event1") + .column(EVENT_DATE_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(firstInstant)) + .column(TYPE_COL).valueAtEndPoint().isEqualTo("Type1") + .column(DESCRIPTION_COL).valueAtEndPoint().isEqualTo("Description1"); + + var secondInstant = firstInstant.plusSeconds(30); + change.setStartPointNow(); + tested.addPrediction(CHANNEL_ID, "Event2", "Type2", "Description2", secondInstant); + change.setEndPointNow(); + + assertThat(change).hasNumberOfChanges(1) + .changeOfCreation() + .column(ID_COL).valueAtEndPoint().isNotNull() + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(EVENT_ID_COL).valueAtEndPoint().isEqualTo("Event2") + .column(EVENT_DATE_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(secondInstant)) + .column(TYPE_COL).valueAtEndPoint().isEqualTo("Type2") + .column(DESCRIPTION_COL).valueAtEndPoint().isEqualTo("Description2"); + } + private LocalDateTime getExpectedTimestamp(Instant instant){ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } From 56fe2658fda64ee47d2ac19da47d91b5c3b366f3 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sun, 4 Sep 2022 09:46:30 +0200 Subject: [PATCH 6/9] Add user prediction test --- .../miner/database/SQLiteDatabaseTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index 153c1bab..6c0e4997 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -23,6 +23,7 @@ class SQLiteDatabaseTest{ private static final String CHANNEL_ID = "channel-id"; private static final String CHANNEL_USERNAME = "channel-username"; + private static final String USER_USERNAME = "user1"; private static final String ID_COL = "ID"; private static final String USERNAME_COL = "Username"; @@ -35,6 +36,12 @@ class SQLiteDatabaseTest{ private static final String EVENT_DATE_COL = "EventDate"; private static final String TYPE_COL = "Type"; private static final String DESCRIPTION_COL = "Description"; + private static final String PREDICTION_CNT_COL = "PredictionCnt"; + private static final String WIN_CNT_COL = "WinCnt"; + private static final String WIN_RATE_COL = "WinRate"; + private static final String RETURN_ON_INVESTMENT_COL = "ReturnOnInvestment"; + private static final String USER_ID_COL = "UserID"; + private static final String BADGE_COL = "Badge"; @TempDir private Path tempPath; @@ -201,6 +208,55 @@ void addPrediction() throws SQLException{ .column(DESCRIPTION_COL).valueAtEndPoint().isEqualTo("Description2"); } + @Test + void addPredictionFromNewUser() throws SQLException{ + var tableUserPrediction = this.tableUserPrediction.get(); + var tablePredictionUser = this.tablePredictionUser.get(); + var changeUserPrediction = new Changes(tableUserPrediction); + var changePredictionUser = new Changes(tablePredictionUser); + + changeUserPrediction.setStartPointNow(); + changePredictionUser.setStartPointNow(); + tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + changeUserPrediction.setEndPointNow(); + changePredictionUser.setEndPointNow(); + + assertThat(changePredictionUser).hasNumberOfChanges(1) + .changeOfCreation() + .column(ID_COL).valueAtEndPoint().isNotNull() + .column(USERNAME_COL).valueAtEndPoint().isEqualTo(USER_USERNAME) + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(PREDICTION_CNT_COL).valueAtEndPoint().isEqualTo(0) + .column(WIN_CNT_COL).valueAtEndPoint().isEqualTo(0) + .column(WIN_RATE_COL).valueAtEndPoint().isEqualTo(0D) + .column(RETURN_ON_INVESTMENT_COL).valueAtEndPoint().isEqualTo(0D); + + assertThat(changeUserPrediction).hasNumberOfChanges(1) + .changeOfCreation() + .column(USER_ID_COL).valueAtEndPoint().isNotNull() + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(BADGE_COL).valueAtEndPoint().isEqualTo("B1"); + } + + @Test + void addPredictionFromExistingUser() throws SQLException{ + var tableUserPrediction = this.tableUserPrediction.get(); + var tablePredictionUser = this.tablePredictionUser.get(); + var changeUserPrediction = new Changes(tableUserPrediction); + var changePredictionUser = new Changes(tablePredictionUser); + + tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + + changeUserPrediction.setStartPointNow(); + changePredictionUser.setStartPointNow(); + tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B2"); //This should be impossible, we can't change badge + changeUserPrediction.setEndPointNow(); + changePredictionUser.setEndPointNow(); + + assertThat(changePredictionUser).hasNumberOfChanges(0); + assertThat(changeUserPrediction).hasNumberOfChanges(0); + } + private LocalDateTime getExpectedTimestamp(Instant instant){ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } From 83b6fc8a6e60493a9949f97be5dcddd48ac44beb Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sun, 4 Sep 2022 10:23:27 +0200 Subject: [PATCH 7/9] Add cancel prediction test --- .../miner/database/BaseDatabase.java | 130 +++++------- .../miner/database/IDatabase.java | 2 +- .../miner/database/MariaDBDatabase.java | 17 +- .../miner/database/NoOpDatabase.java | 3 +- .../miner/database/SQLiteDatabase.java | 19 +- .../miner/database/SQLiteDatabaseTest.java | 197 +++++++++++++----- 6 files changed, 222 insertions(+), 146 deletions(-) diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java index 028045fc..54615b6b 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java @@ -60,6 +60,13 @@ public void updateChannelStatusTime(@NotNull String channelId, @NotNull Instant } } + @Override + public int addUserPrediction(@NotNull String username, @NotNull String channelId, @NotNull String badge) throws SQLException{ + var userId = getOrCreatePredictionUserId(username, channelId); + addUserPrediction(channelId, userId, badge); + return userId; + } + @Override public void addBalance(@NotNull String channelId, int balance, @Nullable String reason, @NotNull Instant instant) throws SQLException{ try(var conn = getConnection(); @@ -95,31 +102,14 @@ public void addPrediction(@NotNull String channelId, @NotNull String eventId, @N } } - @Override - public void addUserPrediction(@NotNull String username, @NotNull String channelId, @NotNull String badge) throws SQLException{ - - try(var conn = getConnection(); - var predictionStatement = getPredictionStmt(conn)){ - conn.setAutoCommit(false); - - var userId = getOrCreatePredictionUserId(conn, username, channelId); - - predictionStatement.setString(1, channelId); - predictionStatement.setInt(2, userId); - predictionStatement.setString(3, badge); - - predictionStatement.executeUpdate(); - conn.commit(); - } - } - - protected int getOrCreatePredictionUserId(@NotNull Connection conn, @NotNull String username, @NotNull String channelId) throws SQLException{ + protected int getOrCreatePredictionUserId(@NotNull String username, @NotNull String channelId) throws SQLException{ username = username.toLowerCase(Locale.ROOT); var lock = getOrCreatePredictionUserIdLocks[hashToIndex(username.hashCode(), getOrCreatePredictionUserIdLocks.length)]; lock.lock(); - try(var selectUserStatement = conn.prepareStatement(""" - SELECT `ID` FROM `PredictionUser` WHERE `Username`=? AND `ChannelID`=?""")){ + try(var conn = getConnection(); + var selectUserStatement = conn.prepareStatement(""" + SELECT `ID` FROM `PredictionUser` WHERE `Username`=? AND `ChannelID`=?""")){ selectUserStatement.setString(1, username); selectUserStatement.setString(2, channelId); @@ -153,6 +143,8 @@ protected int getOrCreatePredictionUserId(@NotNull Connection conn, @NotNull Str } } + protected abstract void addUserPrediction(@NotNull String channelId, int userId, @NotNull String badge) throws SQLException; + private int hashToIndex(int hash, int length){ if(hash == Integer.MIN_VALUE){ return 0; @@ -160,9 +152,6 @@ private int hashToIndex(int hash, int length){ return Math.abs(hash) % length; } - @NotNull - protected abstract PreparedStatement getPredictionStmt(@NotNull Connection conn) throws SQLException; - @Override public void cancelPrediction(@NotNull Event event) throws SQLException{ var ended = Optional.ofNullable(event.getEndedAt()).map(ZonedDateTime::toInstant).orElseGet(TimeFactory::now); @@ -170,30 +159,17 @@ public void cancelPrediction(@NotNull Event event) throws SQLException{ try(var conn = getConnection(); var addCanceledPredictionStmt = conn.prepareStatement(""" INSERT INTO `ResolvedPrediction`(`EventID`,`ChannelID`, `Title`,`EventCreated`,`EventEnded`,`Canceled`) - VALUES (?,?,?,?,?,true)"""); - var removePredictionsStmt = getDeleteUserPredictionsForChannelStmt(conn) + VALUES (?,?,?,?,?,true)""") ){ - conn.setAutoCommit(false); - try{ - //Add canceled prediction - addCanceledPredictionStmt.setString(1, event.getId()); - addCanceledPredictionStmt.setString(2, event.getChannelId()); - addCanceledPredictionStmt.setString(3, event.getTitle()); - addCanceledPredictionStmt.setTimestamp(4, Timestamp.from(event.getCreatedAt().toInstant())); - addCanceledPredictionStmt.setTimestamp(5, Timestamp.from(ended)); - addCanceledPredictionStmt.executeUpdate(); - - //Remove made predictions - removePredictionsStmt.setString(1, event.getChannelId()); - removePredictionsStmt.executeUpdate(); - - conn.commit(); - } - catch(SQLException e){ - conn.rollback(); - throw e; - } + addCanceledPredictionStmt.setString(1, event.getId()); + addCanceledPredictionStmt.setString(2, event.getChannelId()); + addCanceledPredictionStmt.setString(3, event.getTitle()); + addCanceledPredictionStmt.setTimestamp(4, Timestamp.from(event.getCreatedAt().toInstant())); + addCanceledPredictionStmt.setTimestamp(5, Timestamp.from(ended)); + addCanceledPredictionStmt.executeUpdate(); } + + deleteUserPredictionsForChannel(event.getChannelId()); } @Override @@ -264,21 +240,11 @@ public void resolvePrediction(@NotNull Event event, @NotNull String outcome, @No public void deleteUserPredictions() throws SQLException{ log.debug("Removing all user predictions."); try(var conn = getConnection(); - var statement = conn.prepareStatement(""" - DELETE FROM `UserPrediction`""" - )){ + var statement = conn.prepareStatement("DELETE FROM `UserPrediction`")){ statement.executeUpdate(); } } - @NotNull - private PreparedStatement getDeleteUserPredictionsForChannelStmt(@NotNull Connection conn) throws SQLException{ - return conn.prepareStatement(""" - DELETE FROM `UserPrediction` - WHERE `ChannelID`=?""" - ); - } - @Override public void deleteUserPredictionsForChannel(@NotNull String channelId) throws SQLException{ log.debug("Removing user predictions for channelId '{}'.", channelId); @@ -289,6 +255,28 @@ public void deleteUserPredictionsForChannel(@NotNull String channelId) throws SQ } } + @Override + @NotNull + public Optional getStreamerIdFromName(@NotNull String channelName) throws SQLException{ + log.debug("Getting streamerId from channel {}", channelName); + try(var conn = getConnection(); + var statement = conn.prepareStatement(""" + SELECT `ID` + FROM `Channel` + WHERE `Username`=?""" + )){ + statement.setString(1, channelName); + + try(var result = statement.executeQuery()){ + if(result.next()){ + return Optional.ofNullable(result.getString("ID")); + } + } + + return Optional.empty(); + } + } + @Override @NotNull public Collection getOutcomeStatisticsForChannel(@NotNull String channelId, int minBetsPlacedByUser) throws SQLException{ @@ -327,30 +315,16 @@ public void close(){ dataSource.close(); } - @Override @NotNull - public Optional getStreamerIdFromName(@NotNull String channelName) throws SQLException{ - log.debug("Getting streamerId from channel {}", channelName); - try(var conn = getConnection(); - var statement = conn.prepareStatement(""" - SELECT `ID` - FROM `Channel` - WHERE `Username`=?""" - )){ - statement.setString(1, channelName); - - try(var result = statement.executeQuery()){ - if(result.next()){ - return Optional.ofNullable(result.getString("ID")); - } - } - - return Optional.empty(); - } + protected Connection getConnection() throws SQLException{ + return dataSource.getConnection(); } @NotNull - protected Connection getConnection() throws SQLException{ - return dataSource.getConnection(); + private PreparedStatement getDeleteUserPredictionsForChannelStmt(@NotNull Connection conn) throws SQLException{ + return conn.prepareStatement(""" + DELETE FROM `UserPrediction` + WHERE `ChannelID`=?""" + ); } } diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java index 1c82cfa3..643fb933 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java @@ -23,7 +23,7 @@ public interface IDatabase extends AutoCloseable{ void addPrediction(@NotNull String channelId, @NotNull String eventId, @NotNull String type, @NotNull String description, @NotNull Instant instant) throws SQLException; - void addUserPrediction(@NotNull String username, @NotNull String streamerId, @NotNull String badge) throws SQLException; + int addUserPrediction(@NotNull String username, @NotNull String streamerId, @NotNull String badge) throws SQLException; void cancelPrediction(@NotNull Event event) throws SQLException; diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java index 6603ed79..22f5ab68 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java @@ -31,12 +31,19 @@ public void createChannel(@NotNull String channelId, @NotNull String username) t } } - @NotNull @Override - protected PreparedStatement getPredictionStmt(@NotNull Connection conn) throws SQLException{ - return conn.prepareStatement(""" - INSERT IGNORE INTO `UserPrediction`(`ChannelID`, `UserID`, `Badge`) VALUES (?,?,?)""" - ); + protected void addUserPrediction(@NotNull String channelId, int userId, @NotNull String badge) throws SQLException{ + try(var conn = getConnection(); + var predictionStatement = conn.prepareStatement(""" + INSERT IGNORE INTO `UserPrediction`(`ChannelID`, `UserID`, `Badge`) VALUES (?,?,?)""" + )){ + + predictionStatement.setString(1, channelId); + predictionStatement.setInt(2, userId); + predictionStatement.setString(3, badge); + + predictionStatement.executeUpdate(); + } } @NotNull diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java index 30c799fb..e04538c8 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java @@ -32,7 +32,8 @@ public void addPrediction(@NotNull String channelId, @NotNull String eventId, @N } @Override - public void addUserPrediction(@NotNull String username, @NotNull String streamerName, @NotNull String badge){ + public int addUserPrediction(@NotNull String username, @NotNull String streamerName, @NotNull String badge){ + return -1; } @Override diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java index 697befe4..8ae47fd7 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java @@ -34,13 +34,20 @@ public void createChannel(@NotNull String channelId, @NotNull String username) t } } - @NotNull @Override - protected PreparedStatement getPredictionStmt(@NotNull Connection conn) throws SQLException{ - return conn.prepareStatement(""" - INSERT OR IGNORE INTO `UserPrediction`(`ChannelID`, `UserID`, `Badge`) - VALUES(?,?,?)""" - ); + protected void addUserPrediction(@NotNull String channelId, int userId, @NotNull String badge) throws SQLException{ + try(var conn = getConnection(); + var predictionStatement = conn.prepareStatement(""" + INSERT OR IGNORE INTO `UserPrediction`(`ChannelID`, `UserID`, `Badge`) + VALUES(?,?,?)""" + )){ + + predictionStatement.setString(1, channelId); + predictionStatement.setInt(2, userId); + predictionStatement.setString(3, badge); + + predictionStatement.executeUpdate(); + } } @NotNull diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index 6c0e4997..0f737a60 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -2,28 +2,40 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import fr.raksrinana.channelpointsminer.miner.api.ws.data.message.subtype.Event; import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory; import org.assertj.db.type.Changes; import org.assertj.db.type.Table; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import java.nio.file.Path; import java.sql.SQLException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.util.function.Supplier; import static org.assertj.db.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; +@ExtendWith(MockitoExtension.class) class SQLiteDatabaseTest{ private static final String CHANNEL_ID = "channel-id"; private static final String CHANNEL_USERNAME = "channel-username"; private static final String USER_USERNAME = "user1"; + private static final String EVENT_ID = "event-id"; + private static final String EVENT_TITLE = "event-title"; + private static final ZonedDateTime EVENT_CREATED_AT = ZonedDateTime.of(2022, 2, 15, 12, 0, 0, 0, ZoneId.systemDefault()); + private static final ZonedDateTime EVENT_ENDED_AT = EVENT_CREATED_AT.plusDays(1); private static final String ID_COL = "ID"; private static final String USERNAME_COL = "Username"; @@ -42,18 +54,26 @@ class SQLiteDatabaseTest{ private static final String RETURN_ON_INVESTMENT_COL = "ReturnOnInvestment"; private static final String USER_ID_COL = "UserID"; private static final String BADGE_COL = "Badge"; + private static final String TITLE_COL = "Title"; + private static final String EVENT_CREATED_COL = "EventCreated"; + private static final String EVENT_ENDED_COL = "EventEnded"; + private static final String CANCELED_COL = "Canceled"; + private static final String OUTCOME_COL = "Outcome"; + private static final String RETURN_RATIO_FOR_WIN_COL = "ReturnRatioForWIn"; @TempDir private Path tempPath; + private final Supplier changesBalance = () -> new Changes(new Table(dataSource, "Balance")); private SQLiteDatabase tested; private HikariDataSource dataSource; - - private final Supplier
tableBalance = () -> new Table(dataSource, BALANCE_COL); - private final Supplier
tableChannel = () -> new Table(dataSource, "Channel"); - private final Supplier
tablePrediction = () -> new Table(dataSource, "Prediction"); - private final Supplier
tablePredictionUser = () -> new Table(dataSource, "PredictionUser"); - private final Supplier
tableUserPrediction = () -> new Table(dataSource, "UserPrediction"); + private final Supplier changesChannel = () -> new Changes(new Table(dataSource, "Channel")); + private final Supplier changesPrediction = () -> new Changes(new Table(dataSource, "Prediction")); + private final Supplier changesPredictionUser = () -> new Changes(new Table(dataSource, "PredictionUser")); + private final Supplier changesUserPrediction = () -> new Changes(new Table(dataSource, "UserPrediction")); + private final Supplier changesResolvedPrediction = () -> new Changes(new Table(dataSource, "ResolvedPrediction")); + @Mock + private Event event; @BeforeEach void setUp() throws SQLException{ @@ -66,6 +86,12 @@ void setUp() throws SQLException{ tested = new SQLiteDatabase(dataSource); tested.initDatabase(); + + lenient().when(event.getId()).thenReturn(EVENT_ID); + lenient().when(event.getChannelId()).thenReturn(CHANNEL_ID); + lenient().when(event.getTitle()).thenReturn(EVENT_TITLE); + lenient().when(event.getCreatedAt()).thenReturn(EVENT_CREATED_AT); + lenient().when(event.getEndedAt()).thenReturn(EVENT_ENDED_AT); } @AfterEach @@ -75,17 +101,16 @@ void tearDown(){ @Test void tablesAreCreated(){ - assertThat(tableBalance.get()).exists(); - assertThat(tableChannel.get()).exists(); - assertThat(tablePrediction.get()).exists(); - assertThat(tablePredictionUser.get()).exists(); - assertThat(tableUserPrediction.get()).exists(); + assertThat(new Table(dataSource, "Balance")).exists(); + assertThat(new Table(dataSource, "Channel")).exists(); + assertThat(new Table(dataSource, "Prediction")).exists(); + assertThat(new Table(dataSource, "PredictionUser")).exists(); + assertThat(new Table(dataSource, "UserPrediction")).exists(); } @Test void newChannelIsInsertedIfNew() throws SQLException{ - var table = tableChannel.get(); - var changes = new Changes(table); + var changes = changesChannel.get(); changes.setStartPointNow(); tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); @@ -101,8 +126,7 @@ void newChannelIsInsertedIfNew() throws SQLException{ @Test void oldChannelIsNotInsertedIfNew() throws SQLException{ try(var factory = mockStatic(TimeFactory.class)){ - var table = tableChannel.get(); - var changes = new Changes(table); + var changes = changesChannel.get(); var beforeChange = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); factory.when(TimeFactory::now).thenReturn(beforeChange); @@ -122,8 +146,7 @@ void oldChannelIsNotInsertedIfNew() throws SQLException{ @Test void updateChannelStatusTime() throws SQLException{ try(var factory = mockStatic(TimeFactory.class)){ - var table = tableChannel.get(); - var changes = new Changes(table); + var changes = changesChannel.get(); var beforeChange = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); factory.when(TimeFactory::now).thenReturn(beforeChange); @@ -144,15 +167,14 @@ void updateChannelStatusTime() throws SQLException{ @Test void addBalance() throws SQLException{ - var table = tableBalance.get(); - var change = new Changes(table); + var changes = changesBalance.get(); var firstInstant = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); - change.setStartPointNow(); + changes.setStartPointNow(); tested.addBalance(CHANNEL_ID, 25, "Test1", firstInstant); - change.setEndPointNow(); + changes.setEndPointNow(); - assertThat(change).hasNumberOfChanges(1) + assertThat(changes).hasNumberOfChanges(1) .changeOfCreation() .column(ID_COL).valueAtEndPoint().isNotNull() .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) @@ -161,11 +183,11 @@ void addBalance() throws SQLException{ .column(REASON_COL).valueAtEndPoint().isEqualTo("Test1"); var secondInstant = firstInstant.plusSeconds(30); - change.setStartPointNow(); + changes.setStartPointNow(); tested.addBalance(CHANNEL_ID, 50, "Test2", secondInstant); - change.setEndPointNow(); + changes.setEndPointNow(); - assertThat(change).hasNumberOfChanges(1) + assertThat(changes).hasNumberOfChanges(1) .changeOfCreation() .column(ID_COL).valueAtEndPoint().isNotNull() .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) @@ -176,15 +198,14 @@ void addBalance() throws SQLException{ @Test void addPrediction() throws SQLException{ - var table = tablePrediction.get(); - var change = new Changes(table); + var changes = changesPrediction.get(); var firstInstant = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); - change.setStartPointNow(); + changes.setStartPointNow(); tested.addPrediction(CHANNEL_ID, "Event1", "Type1", "Description1", firstInstant); - change.setEndPointNow(); + changes.setEndPointNow(); - assertThat(change).hasNumberOfChanges(1) + assertThat(changes).hasNumberOfChanges(1) .changeOfCreation() .column(ID_COL).valueAtEndPoint().isNotNull() .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) @@ -194,11 +215,11 @@ void addPrediction() throws SQLException{ .column(DESCRIPTION_COL).valueAtEndPoint().isEqualTo("Description1"); var secondInstant = firstInstant.plusSeconds(30); - change.setStartPointNow(); + changes.setStartPointNow(); tested.addPrediction(CHANNEL_ID, "Event2", "Type2", "Description2", secondInstant); - change.setEndPointNow(); + changes.setEndPointNow(); - assertThat(change).hasNumberOfChanges(1) + assertThat(changes).hasNumberOfChanges(1) .changeOfCreation() .column(ID_COL).valueAtEndPoint().isNotNull() .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) @@ -210,20 +231,18 @@ void addPrediction() throws SQLException{ @Test void addPredictionFromNewUser() throws SQLException{ - var tableUserPrediction = this.tableUserPrediction.get(); - var tablePredictionUser = this.tablePredictionUser.get(); - var changeUserPrediction = new Changes(tableUserPrediction); - var changePredictionUser = new Changes(tablePredictionUser); + var changesUserPrediction = this.changesUserPrediction.get(); + var changesPredictionUser = this.changesPredictionUser.get(); - changeUserPrediction.setStartPointNow(); - changePredictionUser.setStartPointNow(); - tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); - changeUserPrediction.setEndPointNow(); - changePredictionUser.setEndPointNow(); + changesUserPrediction.setStartPointNow(); + changesPredictionUser.setStartPointNow(); + var userId = tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + changesUserPrediction.setEndPointNow(); + changesPredictionUser.setEndPointNow(); - assertThat(changePredictionUser).hasNumberOfChanges(1) + assertThat(changesPredictionUser).hasNumberOfChanges(1) .changeOfCreation() - .column(ID_COL).valueAtEndPoint().isNotNull() + .column(ID_COL).valueAtEndPoint().isEqualTo(userId) .column(USERNAME_COL).valueAtEndPoint().isEqualTo(USER_USERNAME) .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) .column(PREDICTION_CNT_COL).valueAtEndPoint().isEqualTo(0) @@ -231,7 +250,7 @@ void addPredictionFromNewUser() throws SQLException{ .column(WIN_RATE_COL).valueAtEndPoint().isEqualTo(0D) .column(RETURN_ON_INVESTMENT_COL).valueAtEndPoint().isEqualTo(0D); - assertThat(changeUserPrediction).hasNumberOfChanges(1) + assertThat(changesUserPrediction).hasNumberOfChanges(1) .changeOfCreation() .column(USER_ID_COL).valueAtEndPoint().isNotNull() .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) @@ -240,21 +259,89 @@ void addPredictionFromNewUser() throws SQLException{ @Test void addPredictionFromExistingUser() throws SQLException{ - var tableUserPrediction = this.tableUserPrediction.get(); - var tablePredictionUser = this.tablePredictionUser.get(); - var changeUserPrediction = new Changes(tableUserPrediction); - var changePredictionUser = new Changes(tablePredictionUser); + var changesUserPrediction = this.changesUserPrediction.get(); + var changesPredictionUser = this.changesPredictionUser.get(); tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); - changeUserPrediction.setStartPointNow(); - changePredictionUser.setStartPointNow(); + changesUserPrediction.setStartPointNow(); + changesPredictionUser.setStartPointNow(); tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B2"); //This should be impossible, we can't change badge - changeUserPrediction.setEndPointNow(); - changePredictionUser.setEndPointNow(); + changesUserPrediction.setEndPointNow(); + changesPredictionUser.setEndPointNow(); + + assertThat(changesPredictionUser).hasNumberOfChanges(0); + assertThat(changesUserPrediction).hasNumberOfChanges(0); + } + + @Test + void cancelPrediction() throws SQLException{ + var changes = changesResolvedPrediction.get(); + + changes.setStartPointNow(); + tested.cancelPrediction(event); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(1) + .changeOfCreation() + .column(EVENT_ID_COL).valueAtEndPoint().isEqualTo(EVENT_ID) + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(TITLE_COL).valueAtEndPoint().isEqualTo(EVENT_TITLE) + .column(EVENT_CREATED_COL).valueAtEndPoint().isEqualTo(EVENT_CREATED_AT.toLocalDateTime()) + .column(EVENT_ENDED_COL).valueAtEndPoint().isEqualTo(EVENT_ENDED_AT.toLocalDateTime()) + .column(CANCELED_COL).valueAtEndPoint().isEqualTo(1) + .column(OUTCOME_COL).valueAtEndPoint().isNull() + .column(BADGE_COL).valueAtEndPoint().isNull() + .column(RETURN_RATIO_FOR_WIN_COL).valueAtEndPoint().isNull(); + } + + @Test + void cancelPredictionWithNoEndDate() throws SQLException{ + try(var factory = mockStatic(TimeFactory.class)){ + var endInstant = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); + factory.when(TimeFactory::now).thenReturn(endInstant); + + var changes = changesResolvedPrediction.get(); + + when(event.getEndedAt()).thenReturn(null); + + changes.setStartPointNow(); + tested.cancelPrediction(event); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(1) + .changeOfCreation() + .column(EVENT_ID_COL).valueAtEndPoint().isEqualTo(EVENT_ID) + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(TITLE_COL).valueAtEndPoint().isEqualTo(EVENT_TITLE) + .column(EVENT_CREATED_COL).valueAtEndPoint().isEqualTo(EVENT_CREATED_AT.toLocalDateTime()) + .column(EVENT_ENDED_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(endInstant)) + .column(CANCELED_COL).valueAtEndPoint().isEqualTo(1) + .column(OUTCOME_COL).valueAtEndPoint().isNull() + .column(BADGE_COL).valueAtEndPoint().isNull() + .column(RETURN_RATIO_FOR_WIN_COL).valueAtEndPoint().isNull(); + } + } + + @Test + void cancelPredictionClearsUserPredictions() throws SQLException{ + var changes = changesUserPrediction.get(); + + var userId1 = tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + var userId2 = tested.addUserPrediction("user-2", CHANNEL_ID, "B2"); + tested.addUserPrediction(USER_USERNAME, "other-channel", "B1"); + + changes.setStartPointNow(); + tested.cancelPrediction(event); + changes.setEndPointNow(); - assertThat(changePredictionUser).hasNumberOfChanges(0); - assertThat(changeUserPrediction).hasNumberOfChanges(0); + assertThat(changes).hasNumberOfChanges(2) + .changeOfDeletion() + .column(USER_ID_COL).valueAtStartPoint().isEqualTo(userId1) + .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID) + .changeOfDeletion() + .column(USER_ID_COL).valueAtStartPoint().isEqualTo(userId2) + .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID); } private LocalDateTime getExpectedTimestamp(Instant instant){ From 885c2e90967f079e4ca198160554f21ca16b53b6 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sun, 4 Sep 2022 10:43:35 +0200 Subject: [PATCH 8/9] Add resolve prediction test --- .../miner/database/BaseDatabase.java | 67 +++------- .../miner/database/MariaDBDatabase.java | 47 +++++-- .../miner/database/SQLiteDatabase.java | 51 +++++-- .../miner/database/SQLiteDatabaseTest.java | 124 ++++++++++++++++-- 4 files changed, 203 insertions(+), 86 deletions(-) diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java index 54615b6b..af3a43b2 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java @@ -176,65 +176,28 @@ public void cancelPrediction(@NotNull Event event) throws SQLException{ public void resolvePrediction(@NotNull Event event, @NotNull String outcome, @NotNull String badge, double returnRatioForWin) throws SQLException{ var ended = Optional.ofNullable(event.getEndedAt()).map(ZonedDateTime::toInstant).orElseGet(TimeFactory::now); + resolveUserPredictions(returnRatioForWin, event.getChannelId(), badge); + try(var conn = getConnection(); - var getOpenPredictionStmt = conn.prepareStatement(""" - SELECT * FROM `UserPrediction` WHERE `ChannelID`=?"""); - var updatePredictionUserStmt = getUpdatePredictionUserStmt(conn); var addResolvedPredictionStmt = conn.prepareStatement(""" INSERT INTO `ResolvedPrediction`(`EventID`,`ChannelID`, `Title`,`EventCreated`,`EventEnded`,`Canceled`,`Outcome`,`Badge`,`ReturnRatioForWin`) - VALUES (?,?,?,?,?,false,?,?,?)"""); - var removePredictionsStmt = getDeleteUserPredictionsForChannelStmt(conn) + VALUES (?,?,?,?,?,false,?,?,?)""") ){ - conn.setAutoCommit(false); - - try{ - //Get user predictions, determine win/lose and update - double returnOnInvestment = returnRatioForWin - 1; - getOpenPredictionStmt.setString(1, event.getChannelId()); - try(var result = getOpenPredictionStmt.executeQuery()){ - while(result.next()){ - var userPrediction = Converters.convertUserPrediction(result); - if(badge.equals(userPrediction.getBadge())){ - updatePredictionUserStmt.setInt(1, 1); - updatePredictionUserStmt.setDouble(2, returnOnInvestment); - } - else{ - updatePredictionUserStmt.setInt(1, 0); - updatePredictionUserStmt.setDouble(2, -1); - } - updatePredictionUserStmt.setInt(3, userPrediction.getUserId()); - updatePredictionUserStmt.setString(4, userPrediction.getChannelId()); - updatePredictionUserStmt.addBatch(); - } - updatePredictionUserStmt.executeBatch(); - } - - //Add the resolved prediction - addResolvedPredictionStmt.setString(1, event.getId()); - addResolvedPredictionStmt.setString(2, event.getChannelId()); - addResolvedPredictionStmt.setString(3, event.getTitle()); - addResolvedPredictionStmt.setTimestamp(4, Timestamp.from(event.getCreatedAt().toInstant())); - addResolvedPredictionStmt.setTimestamp(5, Timestamp.from(ended)); - addResolvedPredictionStmt.setString(6, outcome); - addResolvedPredictionStmt.setString(7, badge); - addResolvedPredictionStmt.setDouble(8, returnRatioForWin); - addResolvedPredictionStmt.executeUpdate(); - - //Remove Predictions - removePredictionsStmt.setString(1, event.getChannelId()); - removePredictionsStmt.executeUpdate(); - - conn.commit(); - } - catch(SQLException e){ - conn.rollback(); - throw e; - } + addResolvedPredictionStmt.setString(1, event.getId()); + addResolvedPredictionStmt.setString(2, event.getChannelId()); + addResolvedPredictionStmt.setString(3, event.getTitle()); + addResolvedPredictionStmt.setTimestamp(4, Timestamp.from(event.getCreatedAt().toInstant())); + addResolvedPredictionStmt.setTimestamp(5, Timestamp.from(ended)); + addResolvedPredictionStmt.setString(6, outcome); + addResolvedPredictionStmt.setString(7, badge); + addResolvedPredictionStmt.setDouble(8, returnRatioForWin); + addResolvedPredictionStmt.executeUpdate(); } + + deleteUserPredictionsForChannel(event.getChannelId()); } - @NotNull - protected abstract PreparedStatement getUpdatePredictionUserStmt(@NotNull Connection conn) throws SQLException; + protected abstract void resolveUserPredictions(double returnRatioForWin, @NotNull String channelId, @NotNull String badge) throws SQLException; @Override public void deleteUserPredictions() throws SQLException{ diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java index 22f5ab68..cc755374 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/MariaDBDatabase.java @@ -1,9 +1,8 @@ package fr.raksrinana.channelpointsminer.miner.database; import com.zaxxer.hikari.HikariDataSource; +import fr.raksrinana.channelpointsminer.miner.database.converter.Converters; import org.jetbrains.annotations.NotNull; -import java.sql.Connection; -import java.sql.PreparedStatement; import java.sql.SQLException; public class MariaDBDatabase extends BaseDatabase{ @@ -46,16 +45,40 @@ protected void addUserPrediction(@NotNull String channelId, int userId, @NotNull } } - @NotNull @Override - protected PreparedStatement getUpdatePredictionUserStmt(@NotNull Connection conn) throws SQLException{ - return conn.prepareStatement(""" - UPDATE `PredictionUser` - SET - `PredictionCnt`=`PredictionCnt`+1, - `WinCnt`=`WinCnt`+?, - `WinRate`=`WinCnt`/`PredictionCnt`, - `ReturnOnInvestment`=`ReturnOnInvestment`+? - WHERE `ID`=? AND `ChannelID`=?"""); + protected void resolveUserPredictions(double returnRatioForWin, @NotNull String channelId, @NotNull String badge) throws SQLException{ + try(var conn = getConnection(); + var getOpenPredictionStmt = conn.prepareStatement(""" + SELECT * FROM `UserPrediction` WHERE `ChannelID`=?"""); + var updatePredictionUserStmt = conn.prepareStatement(""" + UPDATE `PredictionUser` + SET + `PredictionCnt`=`PredictionCnt`+1, + `WinCnt`=`WinCnt`+?, + `WinRate`=`WinCnt`/`PredictionCnt`, + `ReturnOnInvestment`=`ReturnOnInvestment`+? + WHERE `ID`=? AND `ChannelID`=?""") + ){ + double returnOnInvestment = returnRatioForWin - 1; + + getOpenPredictionStmt.setString(1, channelId); + try(var result = getOpenPredictionStmt.executeQuery()){ + while(result.next()){ + var userPrediction = Converters.convertUserPrediction(result); + if(badge.equals(userPrediction.getBadge())){ + updatePredictionUserStmt.setInt(1, 1); + updatePredictionUserStmt.setDouble(2, returnOnInvestment); + } + else{ + updatePredictionUserStmt.setInt(1, 0); + updatePredictionUserStmt.setDouble(2, -1); + } + updatePredictionUserStmt.setInt(3, userPrediction.getUserId()); + updatePredictionUserStmt.setString(4, userPrediction.getChannelId()); + updatePredictionUserStmt.addBatch(); + } + updatePredictionUserStmt.executeBatch(); + } + } } } \ No newline at end of file diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java index 8ae47fd7..99777ad6 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabase.java @@ -1,10 +1,9 @@ package fr.raksrinana.channelpointsminer.miner.database; import com.zaxxer.hikari.HikariDataSource; +import fr.raksrinana.channelpointsminer.miner.database.converter.Converters; import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory; import org.jetbrains.annotations.NotNull; -import java.sql.Connection; -import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -50,18 +49,42 @@ protected void addUserPrediction(@NotNull String channelId, int userId, @NotNull } } - @NotNull @Override - protected PreparedStatement getUpdatePredictionUserStmt(@NotNull Connection conn) throws SQLException{ - return conn.prepareStatement(""" - WITH wi AS (SELECT ? AS n) - UPDATE `PredictionUser` - SET - `PredictionCnt`=`PredictionCnt`+1, - `WinCnt`=`WinCnt`+wi.n, - `WinRate`=CAST((`WinCnt`+wi.n) AS REAL)/(`PredictionCnt`+1), - `ReturnOnInvestment`=`ReturnOnInvestment`+? - FROM wi - WHERE `ID`=? AND `ChannelID`=?"""); + protected void resolveUserPredictions(double returnRatioForWin, @NotNull String channelId, @NotNull String badge) throws SQLException{ + try(var conn = getConnection(); + var getOpenPredictionStmt = conn.prepareStatement(""" + SELECT * FROM `UserPrediction` WHERE `ChannelID`=?"""); + var updatePredictionUserStmt = conn.prepareStatement(""" + WITH wi AS (SELECT ? AS n) + UPDATE `PredictionUser` + SET + `PredictionCnt`=`PredictionCnt`+1, + `WinCnt`=`WinCnt`+wi.n, + `WinRate`=CAST((`WinCnt`+wi.n) AS REAL)/(`PredictionCnt`+1), + `ReturnOnInvestment`=`ReturnOnInvestment`+? + FROM wi + WHERE `ID`=? AND `ChannelID`=?""") + ){ + double returnOnInvestment = returnRatioForWin - 1; + + getOpenPredictionStmt.setString(1, channelId); + try(var result = getOpenPredictionStmt.executeQuery()){ + while(result.next()){ + var userPrediction = Converters.convertUserPrediction(result); + if(badge.equals(userPrediction.getBadge())){ + updatePredictionUserStmt.setInt(1, 1); + updatePredictionUserStmt.setDouble(2, returnOnInvestment); + } + else{ + updatePredictionUserStmt.setInt(1, 0); + updatePredictionUserStmt.setDouble(2, -1); + } + updatePredictionUserStmt.setInt(3, userPrediction.getUserId()); + updatePredictionUserStmt.setString(4, userPrediction.getChannelId()); + updatePredictionUserStmt.addBatch(); + } + updatePredictionUserStmt.executeBatch(); + } + } } } diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index 0f737a60..5ad7f0fc 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -63,17 +63,18 @@ class SQLiteDatabaseTest{ @TempDir private Path tempPath; - private final Supplier changesBalance = () -> new Changes(new Table(dataSource, "Balance")); + @Mock + private Event event; private SQLiteDatabase tested; private HikariDataSource dataSource; - private final Supplier changesChannel = () -> new Changes(new Table(dataSource, "Channel")); - private final Supplier changesPrediction = () -> new Changes(new Table(dataSource, "Prediction")); - private final Supplier changesPredictionUser = () -> new Changes(new Table(dataSource, "PredictionUser")); - private final Supplier changesUserPrediction = () -> new Changes(new Table(dataSource, "UserPrediction")); - private final Supplier changesResolvedPrediction = () -> new Changes(new Table(dataSource, "ResolvedPrediction")); - @Mock - private Event event; + + private Supplier changesBalance; + private Supplier changesChannel; + private Supplier changesPrediction; + private Supplier changesPredictionUser; + private Supplier changesUserPrediction; + private Supplier changesResolvedPrediction; @BeforeEach void setUp() throws SQLException{ @@ -87,6 +88,13 @@ void setUp() throws SQLException{ tested.initDatabase(); + changesBalance = () -> new Changes(new Table(dataSource, "Balance")); + changesChannel = () -> new Changes(new Table(dataSource, "Channel")); + changesPrediction = () -> new Changes(new Table(dataSource, "Prediction")); + changesPredictionUser = () -> new Changes(new Table(dataSource, "PredictionUser")); + changesUserPrediction = () -> new Changes(new Table(dataSource, "UserPrediction")); + changesResolvedPrediction = () -> new Changes(new Table(dataSource, "ResolvedPrediction")); + lenient().when(event.getId()).thenReturn(EVENT_ID); lenient().when(event.getChannelId()).thenReturn(CHANNEL_ID); lenient().when(event.getTitle()).thenReturn(EVENT_TITLE); @@ -344,6 +352,106 @@ void cancelPredictionClearsUserPredictions() throws SQLException{ .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID); } + @Test + void resolvePrediction() throws SQLException{ + var changes = changesResolvedPrediction.get(); + + changes.setStartPointNow(); + tested.resolvePrediction(event, "Outcome1", "B1", 1.5D); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(1) + .changeOfCreation() + .column(EVENT_ID_COL).valueAtEndPoint().isEqualTo(EVENT_ID) + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(TITLE_COL).valueAtEndPoint().isEqualTo(EVENT_TITLE) + .column(EVENT_CREATED_COL).valueAtEndPoint().isEqualTo(EVENT_CREATED_AT.toLocalDateTime()) + .column(EVENT_ENDED_COL).valueAtEndPoint().isEqualTo(EVENT_ENDED_AT.toLocalDateTime()) + .column(CANCELED_COL).valueAtEndPoint().isEqualTo(0) + .column(OUTCOME_COL).valueAtEndPoint().isEqualTo("Outcome1") + .column(BADGE_COL).valueAtEndPoint().isEqualTo("B1") + .column(RETURN_RATIO_FOR_WIN_COL).valueAtEndPoint().isEqualTo(1.5D); + } + + @Test + void resolvePredictionWithNoEndDate() throws SQLException{ + try(var factory = mockStatic(TimeFactory.class)){ + var endInstant = Instant.now().with(ChronoField.NANO_OF_SECOND, 0); + factory.when(TimeFactory::now).thenReturn(endInstant); + + var changes = changesResolvedPrediction.get(); + + when(event.getEndedAt()).thenReturn(null); + + changes.setStartPointNow(); + tested.resolvePrediction(event, "Outcome1", "B1", 1.5D); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(1) + .changeOfCreation() + .column(EVENT_ID_COL).valueAtEndPoint().isEqualTo(EVENT_ID) + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(TITLE_COL).valueAtEndPoint().isEqualTo(EVENT_TITLE) + .column(EVENT_CREATED_COL).valueAtEndPoint().isEqualTo(EVENT_CREATED_AT.toLocalDateTime()) + .column(EVENT_ENDED_COL).valueAtEndPoint().isEqualTo(getExpectedTimestamp(endInstant)) + .column(CANCELED_COL).valueAtEndPoint().isEqualTo(0) + .column(OUTCOME_COL).valueAtEndPoint().isEqualTo("Outcome1") + .column(BADGE_COL).valueAtEndPoint().isEqualTo("B1") + .column(RETURN_RATIO_FOR_WIN_COL).valueAtEndPoint().isEqualTo(1.5D); + } + } + + @Test + void resolvePredictionClearsUserPredictions() throws SQLException{ + var changes = changesUserPrediction.get(); + + var userId1 = tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + var userId2 = tested.addUserPrediction("user-2", CHANNEL_ID, "B2"); + tested.addUserPrediction(USER_USERNAME, "other-channel", "B1"); + + changes.setStartPointNow(); + tested.resolvePrediction(event, "Outcome1", "B1", 1.5D); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(2) + .changeOfDeletion() + .column(USER_ID_COL).valueAtStartPoint().isEqualTo(userId1) + .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID) + .changeOfDeletion() + .column(USER_ID_COL).valueAtStartPoint().isEqualTo(userId2) + .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID); + } + + @Test + void resolvePredictionUpdatesPredictionUsers() throws SQLException{ + var changes = changesPredictionUser.get(); + + var userId1 = tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + var userId2 = tested.addUserPrediction("user-2", CHANNEL_ID, "B2"); + + changes.setStartPointNow(); + tested.resolvePrediction(event, "Outcome1", "B1", 1.5D); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(2) + .changeOfModification() + .column(ID_COL).valueAtEndPoint().isEqualTo(userId1) + .column(USERNAME_COL).valueAtEndPoint().isEqualTo(USER_USERNAME) + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(PREDICTION_CNT_COL).valueAtEndPoint().isEqualTo(1) + .column(WIN_CNT_COL).valueAtEndPoint().isEqualTo(1) + .column(WIN_RATE_COL).valueAtEndPoint().isEqualTo(1D) + .column(RETURN_ON_INVESTMENT_COL).valueAtEndPoint().isEqualTo(0.5D) + .changeOfModification() + .column(ID_COL).valueAtEndPoint().isEqualTo(userId2) + .column(USERNAME_COL).valueAtEndPoint().isEqualTo("user-2") + .column(CHANNEL_ID_COL).valueAtEndPoint().isEqualTo(CHANNEL_ID) + .column(PREDICTION_CNT_COL).valueAtEndPoint().isEqualTo(1) + .column(WIN_CNT_COL).valueAtEndPoint().isEqualTo(0) + .column(WIN_RATE_COL).valueAtEndPoint().isEqualTo(0D) + .column(RETURN_ON_INVESTMENT_COL).valueAtEndPoint().isEqualTo(-1D); + } + private LocalDateTime getExpectedTimestamp(Instant instant){ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } From 8770fa1e5130074e3e0ecbdd9fe6b5bfddec17c2 Mon Sep 17 00:00:00 2001 From: Thomas Couchoud <1688389+RakSrinaNa@users.noreply.github.com> Date: Sun, 4 Sep 2022 11:09:54 +0200 Subject: [PATCH 9/9] More tests --- .../miner/database/BaseDatabase.java | 4 +- .../miner/database/IDatabase.java | 8 +- .../miner/database/NoOpDatabase.java | 9 +-- .../model/prediction/OutcomeStatistic.java | 27 +++---- .../miner/factory/MinerFactory.java | 2 +- .../miner/database/SQLiteDatabaseTest.java | 81 +++++++++++++++++++ .../miner/factory/MinerFactoryTest.java | 2 +- 7 files changed, 104 insertions(+), 29 deletions(-) diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java index af3a43b2..16a843a7 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/BaseDatabase.java @@ -200,7 +200,7 @@ public void resolvePrediction(@NotNull Event event, @NotNull String outcome, @No protected abstract void resolveUserPredictions(double returnRatioForWin, @NotNull String channelId, @NotNull String badge) throws SQLException; @Override - public void deleteUserPredictions() throws SQLException{ + public void deleteAllUserPredictions() throws SQLException{ log.debug("Removing all user predictions."); try(var conn = getConnection(); var statement = conn.prepareStatement("DELETE FROM `UserPrediction`")){ @@ -256,7 +256,7 @@ public Collection getOutcomeStatisticsForChannel(@NotNull Stri INNER JOIN `PredictionUser` AS pu ON up.`UserID`=pu.`ID` AND up.`ChannelID` = pu.`ChannelID` WHERE up.`ChannelID`=? - AND `PredictionCnt`>? + AND `PredictionCnt`>=? GROUP BY `Badge`""" )){ statement.setString(1, channelId); diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java index 643fb933..4bb01fb3 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/IDatabase.java @@ -19,6 +19,9 @@ public interface IDatabase extends AutoCloseable{ void updateChannelStatusTime(@NotNull String channelId, @NotNull Instant instant) throws SQLException; + @NotNull + Optional getStreamerIdFromName(@NotNull String channelName) throws SQLException; + void addBalance(@NotNull String channelId, int balance, @Nullable String reason, @NotNull Instant instant) throws SQLException; void addPrediction(@NotNull String channelId, @NotNull String eventId, @NotNull String type, @NotNull String description, @NotNull Instant instant) throws SQLException; @@ -29,13 +32,10 @@ public interface IDatabase extends AutoCloseable{ void resolvePrediction(@NotNull Event event, @NotNull String outcome, @NotNull String badge, double returnOnInvestment) throws SQLException; - void deleteUserPredictions() throws SQLException; + void deleteAllUserPredictions() throws SQLException; void deleteUserPredictionsForChannel(@NotNull String channelId) throws SQLException; @NotNull Collection getOutcomeStatisticsForChannel(@NotNull String channelId, int minBetsPlacedByUser) throws SQLException; - - @NotNull - Optional getStreamerIdFromName(@NotNull String channelName) throws SQLException; } diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java index e04538c8..91358d15 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/NoOpDatabase.java @@ -4,7 +4,6 @@ import fr.raksrinana.channelpointsminer.miner.database.model.prediction.OutcomeStatistic; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.sql.SQLException; import java.time.Instant; import java.util.Collection; import java.util.List; @@ -45,7 +44,9 @@ public void resolvePrediction(@NotNull Event event, @NotNull String outcome, @No } @Override - public void deleteUserPredictions(){ + @NotNull + public Optional getStreamerIdFromName(@NotNull String channelName){ + return Optional.empty(); } @Override @@ -63,8 +64,6 @@ public void close(){ } @Override - @NotNull - public Optional getStreamerIdFromName(@NotNull String channelName) throws SQLException{ - return Optional.empty(); + public void deleteAllUserPredictions(){ } } diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/model/prediction/OutcomeStatistic.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/model/prediction/OutcomeStatistic.java index d400722a..e6852fc6 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/model/prediction/OutcomeStatistic.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/database/model/prediction/OutcomeStatistic.java @@ -1,24 +1,19 @@ package fr.raksrinana.channelpointsminer.miner.database.model.prediction; -import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.ToString; +import lombok.Data; import org.jetbrains.annotations.NotNull; -@Getter -@NoArgsConstructor -@AllArgsConstructor +@Data @Builder -@ToString public class OutcomeStatistic{ - - @NotNull - private String badge; - private int userCnt; - private double averageWinRate; - private double averageUserBetsPlaced; - private double averageUserWins; - private double averageReturnOnInvestment; + + @NotNull + private final String badge; + private final int userCnt; + private final double averageWinRate; + private final double averageUserBetsPlaced; + private final double averageUserWins; + private final double averageReturnOnInvestment; } + diff --git a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactory.java b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactory.java index 21b5becc..1ee13efa 100644 --- a/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactory.java +++ b/miner/src/main/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactory.java @@ -46,7 +46,7 @@ public static Miner create(@NotNull AccountConfiguration config){ throw new IllegalStateException("Analytics is enabled but no database is defined"); } - database.deleteUserPredictions(); + database.deleteAllUserPredictions(); miner.addEventHandler(DatabaseFactory.createDatabaseHandler(database)); } diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java index 5ad7f0fc..e54e8b9f 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/database/SQLiteDatabaseTest.java @@ -3,7 +3,9 @@ import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import fr.raksrinana.channelpointsminer.miner.api.ws.data.message.subtype.Event; +import fr.raksrinana.channelpointsminer.miner.database.model.prediction.OutcomeStatistic; import fr.raksrinana.channelpointsminer.miner.factory.TimeFactory; +import org.assertj.core.api.Assertions; import org.assertj.db.type.Changes; import org.assertj.db.type.Table; import org.mockito.Mock; @@ -173,6 +175,17 @@ void updateChannelStatusTime() throws SQLException{ } } + @Test + void getStreamerIdFromName() throws SQLException{ + tested.createChannel("other-id", "other-name"); + + Assertions.assertThat(tested.getStreamerIdFromName(CHANNEL_USERNAME)).isEmpty(); + + tested.createChannel(CHANNEL_ID, CHANNEL_USERNAME); + + Assertions.assertThat(tested.getStreamerIdFromName(CHANNEL_USERNAME)).isPresent().get().isEqualTo(CHANNEL_ID); + } + @Test void addBalance() throws SQLException{ var changes = changesBalance.get(); @@ -452,6 +465,74 @@ void resolvePredictionUpdatesPredictionUsers() throws SQLException{ .column(RETURN_ON_INVESTMENT_COL).valueAtEndPoint().isEqualTo(-1D); } + @Test + void getOutcomeStatisticsForChannel() throws SQLException{ + tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + tested.addUserPrediction("user-2", CHANNEL_ID, "B2"); + + tested.resolvePrediction(event, "Outcome1", "B1", 1.5D); + + tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B3"); + tested.addUserPrediction("user-2", CHANNEL_ID, "B4"); + tested.addUserPrediction("user-3", CHANNEL_ID, "B5"); + + Assertions.assertThat(tested.getOutcomeStatisticsForChannel(CHANNEL_ID, 1)) + .containsExactlyInAnyOrder( + OutcomeStatistic.builder() + .badge("B3") + .userCnt(1) + .averageWinRate(1D) + .averageUserBetsPlaced(1D) + .averageUserWins(1D) + .averageReturnOnInvestment(0.5) + .build(), + OutcomeStatistic.builder() + .badge("B4") + .userCnt(1) + .averageWinRate(0D) + .averageUserBetsPlaced(1D) + .averageUserWins(0D) + .averageReturnOnInvestment(-1D) + .build() + ); + } + + @Test + void deleteAllUserPredictions() throws SQLException{ + var changes = changesUserPrediction.get(); + + tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + tested.addUserPrediction(USER_USERNAME, "channel-2", "B2"); + tested.addUserPrediction("user-2", CHANNEL_ID, "B2"); + + changes.setStartPointNow(); + tested.deleteAllUserPredictions(); + changes.setEndPointNow(); + + assertThat(changes).ofDeletion().hasNumberOfChanges(3); + } + + @Test + void deleteUserPredictionsForChannel() throws SQLException{ + var changes = changesUserPrediction.get(); + + var userId1 = tested.addUserPrediction(USER_USERNAME, CHANNEL_ID, "B1"); + tested.addUserPrediction(USER_USERNAME, "channel-2", "B2"); + var userId3 = tested.addUserPrediction("user-2", CHANNEL_ID, "B2"); + + changes.setStartPointNow(); + tested.deleteUserPredictionsForChannel(CHANNEL_ID); + changes.setEndPointNow(); + + assertThat(changes).hasNumberOfChanges(2) + .changeOfDeletion() + .column(USER_ID_COL).valueAtStartPoint().isEqualTo(userId1) + .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID) + .changeOfDeletion() + .column(USER_ID_COL).valueAtStartPoint().isEqualTo(userId3) + .column(CHANNEL_ID_COL).valueAtStartPoint().isEqualTo(CHANNEL_ID); + } + private LocalDateTime getExpectedTimestamp(Instant instant){ return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } diff --git a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactoryTest.java b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactoryTest.java index d82dadd4..9e2ed200 100644 --- a/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactoryTest.java +++ b/miner/src/test/java/fr/raksrinana/channelpointsminer/miner/factory/MinerFactoryTest.java @@ -150,7 +150,7 @@ void nominalWithAnalytics() throws SQLException{ .hasAtLeastOneElementOfType(LoggerEventListener.class) .hasAtLeastOneElementOfType(DatabaseEventHandler.class); - verify(database).deleteUserPredictions(); + verify(database).deleteAllUserPredictions(); miner.close(); }