diff --git a/jbang-catalog.json b/jbang-catalog.json index 3627379b3..533e78d8d 100644 --- a/jbang-catalog.json +++ b/jbang-catalog.json @@ -9,5 +9,37 @@ "script-ref": "tooling/jbang/ReactiveTest.java", "description": "A Hibernate Reactive JUnit test using Docker, Testcontainers and Vert.x Unit" } + }, + "templates": { + "pg-reproducer": { + "file-refs": { + "{basename}.java": "tooling/jbang/PostgreSQLReactiveTest.java.qute" + }, + "description": "Template for a test with PostgreSQL using Junit 4, Vert.x Unit and Testcontainers" + }, + "mysql-reproducer": { + "file-refs": { + "{basename}.java": "tooling/jbang/MySQLReactiveTest.java.qute" + }, + "description": "Template for a test with MySQL using Junit 4, Vert.x Unit and Testcontainers" + }, + "mariadb-reproducer": { + "file-refs": { + "{basename}.java": "tooling/jbang/MariaDBReactiveTest.java.qute" + }, + "description": "Template for a test with MariaDB using Junit 4, Vert.x Unit and Testcontainers" + }, + "db2-reproducer": { + "file-refs": { + "{basename}.java": "tooling/jbang/Db2ReactiveTest.java.qute" + }, + "description": "Template for a test with Db2 using Junit 4, Vert.x Unit and Testcontainers" + }, + "cockroachdb-reproducer": { + "file-refs": { + "{basename}.java": "tooling/jbang/CockroachDBReactiveTest.java.qute" + }, + "description": "Template for a test with CockroachDB using Junit 4, Vert.x Unit and Testcontainers" + } } } \ No newline at end of file diff --git a/tooling/jbang/CockroachDBReactiveTest.java.qute b/tooling/jbang/CockroachDBReactiveTest.java.qute new file mode 100755 index 000000000..b20e8aba6 --- /dev/null +++ b/tooling/jbang/CockroachDBReactiveTest.java.qute @@ -0,0 +1,211 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright: Red Hat Inc. and Hibernate Authors + */ + +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.1.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.1.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:1.0.0.CR6} +//DEPS org.assertj:assertj-core:3.19.0 +//DEPS junit:junit:4.13.2 +//DEPS org.testcontainers:cockroachdb:1.15.3 +//DEPS org.slf4j:slf4j-simple:1.7.30 + +//// Testcontainer needs the JDBC drivers to start the container +//// Hibernate Reactive doesn't need it +//DEPS org.postgresql:postgresql:42.2.16 + +import java.io.IOException; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.provider.Settings; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; + +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.testcontainers.containers.CockroachContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +//DESCRIPTION An example of a JUnit test class for Hibernate Reactive using +//DESCRIPTION [Vert.x Unit](https://vertx.io/docs/vertx-unit/java), +//DESCRIPTION [Testcontainers](https://www.testcontainers.org) +//DESCRIPTION and [CockroachDB](https://www.cockroachlabs.com/) +//DESCRIPTION that you can run using [JBang](JBang). +//DESCRIPTION +//DESCRIPTION Before running the tests, Testcontainers will start the selected +//DESCRIPTION Docker image with the required database created. +//DESCRIPTION +//DESCRIPTION Usage example: +//DESCRIPTION 1. Use as jbang template `jbang init -t cockroachdb-reproducer@hibernate/hibernate-reactive mytest.java` +//DESCRIPTION 2. Run the test with JBang: `jbang mytest.java` +//DESCRIPTION 3. (Optional) Edit the file (with IntelliJ IDEA for example): +//DESCRIPTION jbang edit --live --open=idea mytest.java +@RunWith(VertxUnitRunner.class) +public class {baseName} { + + @ClassRule + public final static CockroachContainer database = new CockroachContainer( "cockroachdb/cockroach:v21.1.1" ); + + private Mutiny.SessionFactory sessionFactory; + + @BeforeClass + public static void startContainer() throws IOException, InterruptedException { + database.start(); + // Temporary tables support is an experimental feature for CockroachDB and needs to be enabled. + // Hibernate Reactive sometimes needs it when updating entities in a hierarchy. + database.execInContainer( + "sh", + "-c", + "./cockroach sql --insecure -e \"SET CLUSTER SETTING sql.defaults.experimental_temporary_tables.enabled = 'true';\"" + ); + } + + /** + * The \{@link Configuration} for the \{@link Mutiny.SessionFactory}. + */ + private Configuration createConfiguration() { + Configuration configuration = new Configuration(); + + // JDBC url + configuration.setProperty( Settings.URL, database.getJdbcUrl() ); + + // Credentials + configuration.setProperty( Settings.USER, database.getUsername() ); + configuration.setProperty( Settings.PASS, database.getPassword() ); + + // Schema generation. Supported values are create, drop, create-drop, drop-create, none + configuration.setProperty( Settings.HBM2DDL_AUTO, "create" ); + + // Register new entity classes here + configuration.addAnnotatedClass( MyEntity.class ); + + // (Optional) Log the SQL queries + configuration.setProperty( Settings.SHOW_SQL, "true" ); + configuration.setProperty( Settings.HIGHLIGHT_SQL, "true" ); + configuration.setProperty( Settings.FORMAT_SQL, "true" ); + return configuration; + } + + /* + * Create a new factory and a new schema before each test (see + * property `hibernate.hbm2ddl.auto`). + * This way each test will start with a clean database. + * + * The drawback is that, in a real case scenario with multiple tests, + * it can slow down the whole test suite considerably. If that happens, + * it's possible to make the session factory static and, if necessary, + * delete the content of the tables manually (without dropping them). + */ + @Before + public void createSessionFactory() { + Configuration configuration = createConfiguration(); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder() + .applySettings( configuration.getProperties() ); + StandardServiceRegistry registry = builder.build(); + + sessionFactory = configuration.buildSessionFactory( registry ) + .unwrap( Mutiny.SessionFactory.class ); + } + + @Test + public void testInsertAndSelect(TestContext context) { + // the test will wait until async.complete or context.fail are called + Async async = context.async(); + + MyEntity entity = new MyEntity( "first entity", 1 ); + sessionFactory + // insert the entity in the database + .withTransaction( (session, tx) -> session.persist( entity ) ) + .chain( () -> sessionFactory + .withSession( session -> session + // look for the entity by id + .find( MyEntity.class, entity.getId() ) + // assert that the returned entity is the right one + .invoke( foundEntity -> assertThat( foundEntity.getName() ).isEqualTo( entity.getName() ) ) ) ) + .subscribe() + .with( res -> async.complete(), context::fail ); + } + + @After + public void closeFactory() { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + } + + /** + * Example of a class representing an entity. + *
+ * If you create new entities, be sure to add them in . + * For example: + *
+ * configuration.addAnnotatedClass( MyOtherEntity.class ); + *+ */ + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + public Integer id; + + public String name; + + public MyEntity() { + } + + public MyEntity(String name, Integer id) { + this.name = name; + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "MyEntity" + + "\n\t id = " + id + + "\n\t name = " + name; + } + } + + // This main class is only for JBang so that it can run the tests with `jbang {baseName}.java` + public static void main(String[] args) { + System.out.println( "Starting the test suite with CockroachDB" ); + + Result result = JUnitCore.runClasses( {baseName}.class ); + + for ( Failure failure : result.getFailures() ) { + System.out.println(); + System.err.println( "Test " + failure.getTestHeader() + " FAILED!" ); + System.err.println( "\t" + failure.getTrace() ); + } + + System.out.println(); + System.out.print("Tests result summary: "); + System.out.println( result.wasSuccessful() ? "SUCCESS" : "FAILURE" ); + } +} diff --git a/tooling/jbang/Db2ReactiveTest.java.qute b/tooling/jbang/Db2ReactiveTest.java.qute new file mode 100755 index 000000000..43093ad6f --- /dev/null +++ b/tooling/jbang/Db2ReactiveTest.java.qute @@ -0,0 +1,200 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright: Red Hat Inc. and Hibernate Authors + */ + +//DEPS io.vertx:vertx-db2-client:$\{vertx.version:4.1.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.1.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:1.0.0.CR6} +//DEPS org.assertj:assertj-core:3.19.0 +//DEPS junit:junit:4.13.2 +//DEPS org.testcontainers:db2:1.15.3 +//DEPS org.slf4j:slf4j-simple:1.7.30 + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.provider.Settings; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; + +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.testcontainers.containers.Db2Container; + +import static org.assertj.core.api.Assertions.assertThat; + +//DESCRIPTION An example of a JUnit test class for Hibernate Reactive using +//DESCRIPTION [Vert.x Unit](https://vertx.io/docs/vertx-unit/java), +//DESCRIPTION [Testcontainers](https://www.testcontainers.org) +//DESCRIPTION and [Db2](https://www.ibm.com/products/db2-database) +//DESCRIPTION that you can run using [JBang](JBang). +//DESCRIPTION +//DESCRIPTION Before running the tests, Testcontainers will start the selected +//DESCRIPTION Docker image with the required database created. +//DESCRIPTION +//DESCRIPTION Usage example: +//DESCRIPTION 1. Use as jbang template `jbang init -t db2-reproducer@hibernate/hibernate-reactive mytest.java` +//DESCRIPTION 2. Run the test with JBang: `jbang mytest.java` +//DESCRIPTION 3. (Optional) Edit the file (with IntelliJ IDEA for example): +//DESCRIPTION jbang edit --live --open=idea mytest.java +@RunWith(VertxUnitRunner.class) +public class {baseName} { + + @ClassRule + public final static Db2Container database = new Db2Container( "ibmcom/db2:11.5.5.1" ) + .acceptLicense(); + + private Mutiny.SessionFactory sessionFactory; + + @BeforeClass + public static void startContainer() { + database.start(); + } + + /** + * The \{@link Configuration} for the \{@link Mutiny.SessionFactory}. + */ + private Configuration createConfiguration() { + Configuration configuration = new Configuration(); + + // JDBC url + configuration.setProperty( Settings.URL, database.getJdbcUrl() ); + + // Credentials + configuration.setProperty( Settings.USER, database.getUsername() ); + configuration.setProperty( Settings.PASS, database.getPassword() ); + + // Schema generation. Supported values are create, drop, create-drop, drop-create, none + configuration.setProperty( Settings.HBM2DDL_AUTO, "create" ); + + // Register new entity classes here + configuration.addAnnotatedClass( MyEntity.class ); + + // (Optional) Log the SQL queries + configuration.setProperty( Settings.SHOW_SQL, "true" ); + configuration.setProperty( Settings.HIGHLIGHT_SQL, "true" ); + configuration.setProperty( Settings.FORMAT_SQL, "true" ); + return configuration; + } + + /* + * Create a new factory and a new schema before each test (see + * property `hibernate.hbm2ddl.auto`). + * This way each test will start with a clean database. + * + * The drawback is that, in a real case scenario with multiple tests, + * it can slow down the whole test suite considerably. If that happens, + * it's possible to make the session factory static and, if necessary, + * delete the content of the tables manually (without dropping them). + */ + @Before + public void createSessionFactory() { + Configuration configuration = createConfiguration(); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder() + .applySettings( configuration.getProperties() ); + StandardServiceRegistry registry = builder.build(); + + sessionFactory = configuration.buildSessionFactory( registry ) + .unwrap( Mutiny.SessionFactory.class ); + } + + @Test + public void testInsertAndSelect(TestContext context) { + // the test will wait until async.complete or context.fail are called + Async async = context.async(); + + MyEntity entity = new MyEntity( "first entity", 1 ); + sessionFactory + // insert the entity in the database + .withTransaction( (session, tx) -> session.persist( entity ) ) + .chain( () -> sessionFactory + .withSession( session -> session + // look for the entity by id + .find( MyEntity.class, entity.getId() ) + // assert that the returned entity is the right one + .invoke( foundEntity -> assertThat( foundEntity.getName() ).isEqualTo( entity.getName() ) ) ) ) + .subscribe() + .with( res -> async.complete(), context::fail ); + } + + @After + public void closeFactory() { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + } + + /** + * Example of a class representing an entity. + *
+ * If you create new entities, be sure to add them in . + * For example: + *
+ * configuration.addAnnotatedClass( MyOtherEntity.class ); + *+ */ + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + public Integer id; + + public String name; + + public MyEntity() { + } + + public MyEntity(String name, Integer id) { + this.name = name; + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "MyEntity" + + "\n\t id = " + id + + "\n\t name = " + name; + } + } + + // This main class is only for JBang so that it can run the tests with `jbang {baseName}.java` + public static void main(String[] args) { + System.out.println( "Starting the test suite with Db2" ); + + Result result = JUnitCore.runClasses( {baseName}.class ); + + for ( Failure failure : result.getFailures() ) { + System.out.println(); + System.err.println( "Test " + failure.getTestHeader() + " FAILED!" ); + System.err.println( "\t" + failure.getTrace() ); + } + + System.out.println(); + System.out.print("Tests result summary: "); + System.out.println( result.wasSuccessful() ? "SUCCESS" : "FAILURE" ); + } +} diff --git a/tooling/jbang/MariaDBReactiveTest.java.qute b/tooling/jbang/MariaDBReactiveTest.java.qute new file mode 100755 index 000000000..c46026626 --- /dev/null +++ b/tooling/jbang/MariaDBReactiveTest.java.qute @@ -0,0 +1,203 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright: Red Hat Inc. and Hibernate Authors + */ + +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.1.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.1.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:1.0.0.CR6} +//DEPS org.assertj:assertj-core:3.19.0 +//DEPS junit:junit:4.13.2 +//DEPS org.testcontainers:mariadb:1.15.3 +//DEPS org.slf4j:slf4j-simple:1.7.30 + +//// Testcontainer needs the JDBC drivers to start the container +//// Hibernate Reactive doesn't need it +//DEPS org.mariadb.jdbc:mariadb-java-client:2.7.3 + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.provider.Settings; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; + +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.testcontainers.containers.MariaDBContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +//DESCRIPTION An example of a JUnit test class for Hibernate Reactive using +//DESCRIPTION [Vert.x Unit](https://vertx.io/docs/vertx-unit/java), +//DESCRIPTION [Testcontainers](https://www.testcontainers.org) +//DESCRIPTION and [MySQL](https://www.mysql.com/) +//DESCRIPTION that you can run using [JBang](JBang). +//DESCRIPTION +//DESCRIPTION Before running the tests, Testcontainers will start the selected +//DESCRIPTION Docker image with the required database created. +//DESCRIPTION +//DESCRIPTION Usage example: +//DESCRIPTION 1. Use as jbang template `jbang init -t mariadb-reproducer@hibernate/hibernate-reactive mytest.java` +//DESCRIPTION 2. Run the test with JBang: `jbang mytest.java` +//DESCRIPTION 3. (Optional) Edit the file (with IntelliJ IDEA for example): +//DESCRIPTION jbang edit --live --open=idea mytest.java +@RunWith(VertxUnitRunner.class) +public class {baseName} { + + @ClassRule + public static MariaDBContainer> database = new MariaDBContainer<>( "mariadb:10.5.10" ); + + private Mutiny.SessionFactory sessionFactory; + + @BeforeClass + public static void startContainer() { + database.start(); + } + + /** + * The \{@link Configuration} for the \{@link Mutiny.SessionFactory}. + */ + private Configuration createConfiguration() { + Configuration configuration = new Configuration(); + + // JDBC url + configuration.setProperty( Settings.URL, database.getJdbcUrl() ); + + // Credentials + configuration.setProperty( Settings.USER, database.getUsername() ); + configuration.setProperty( Settings.PASS, database.getPassword() ); + + // Schema generation. Supported values are create, drop, create-drop, drop-create, none + configuration.setProperty( Settings.HBM2DDL_AUTO, "create" ); + + // Register new entity classes here + configuration.addAnnotatedClass( MyEntity.class ); + + // (Optional) Log the SQL queries + configuration.setProperty( Settings.SHOW_SQL, "true" ); + configuration.setProperty( Settings.HIGHLIGHT_SQL, "true" ); + configuration.setProperty( Settings.FORMAT_SQL, "true" ); + return configuration; + } + + /* + * Create a new factory and a new schema before each test (see + * property `hibernate.hbm2ddl.auto`). + * This way each test will start with a clean database. + * + * The drawback is that, in a real case scenario with multiple tests, + * it can slow down the whole test suite considerably. If that happens, + * it's possible to make the session factory static and, if necessary, + * delete the content of the tables manually (without dropping them). + */ + @Before + public void createSessionFactory() { + Configuration configuration = createConfiguration(); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder() + .applySettings( configuration.getProperties() ); + StandardServiceRegistry registry = builder.build(); + + sessionFactory = configuration.buildSessionFactory( registry ) + .unwrap( Mutiny.SessionFactory.class ); + } + + @Test + public void testInsertAndSelect(TestContext context) { + // the test will wait until async.complete or context.fail are called + Async async = context.async(); + + MyEntity entity = new MyEntity( "first entity", 1 ); + sessionFactory + // insert the entity in the database + .withTransaction( (session, tx) -> session.persist( entity ) ) + .chain( () -> sessionFactory + .withSession( session -> session + // look for the entity by id + .find( MyEntity.class, entity.getId() ) + // assert that the returned entity is the right one + .invoke( foundEntity -> assertThat( foundEntity.getName() ).isEqualTo( entity.getName() ) ) ) ) + .subscribe() + .with( res -> async.complete(), context::fail ); + } + + @After + public void closeFactory() { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + } + + /** + * Example of a class representing an entity. + *
+ * If you create new entities, be sure to add them in . + * For example: + *
+ * configuration.addAnnotatedClass( MyOtherEntity.class ); + *+ */ + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + public Integer id; + + public String name; + + public MyEntity() { + } + + public MyEntity(String name, Integer id) { + this.name = name; + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "MyEntity" + + "\n\t id = " + id + + "\n\t name = " + name; + } + } + + // This main class is only for JBang so that it can run the tests with `jbang {baseName}.java` + public static void main(String[] args) { + System.out.println( "Starting the test suite with MariaDB"); + + Result result = JUnitCore.runClasses( {baseName}.class ); + + for ( Failure failure : result.getFailures() ) { + System.out.println(); + System.err.println( "Test " + failure.getTestHeader() + " FAILED!" ); + System.err.println( "\t" + failure.getTrace() ); + } + + System.out.println(); + System.out.print("Tests result summary: "); + System.out.println( result.wasSuccessful() ? "SUCCESS" : "FAILURE" ); + } +} diff --git a/tooling/jbang/MySQLReactiveTest.java.qute b/tooling/jbang/MySQLReactiveTest.java.qute new file mode 100755 index 000000000..ffa8bdaef --- /dev/null +++ b/tooling/jbang/MySQLReactiveTest.java.qute @@ -0,0 +1,206 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright: Red Hat Inc. and Hibernate Authors + */ + +//DEPS io.vertx:vertx-mysql-client:$\{vertx.version:4.1.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.1.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:1.0.0.CR6} +//DEPS org.assertj:assertj-core:3.19.0 +//DEPS junit:junit:4.13.2 +//DEPS org.testcontainers:mysql:1.15.3 +//DEPS org.slf4j:slf4j-simple:1.7.30 + +//// Testcontainer needs the JDBC drivers to start the container +//// Hibernate Reactive doesn't need it +//DEPS mysql:mysql-connector-java:8.0.25 + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.provider.Settings; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; + +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.testcontainers.containers.MySQLContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +//DESCRIPTION An example of a JUnit test class for Hibernate Reactive using +//DESCRIPTION [Vert.x Unit](https://vertx.io/docs/vertx-unit/java), +//DESCRIPTION [Testcontainers](https://www.testcontainers.org) +//DESCRIPTION and [MySQL](https://www.mysql.com/) +//DESCRIPTION that you can run using [JBang](JBang). +//DESCRIPTION +//DESCRIPTION Before running the tests, Testcontainers will start the selected +//DESCRIPTION Docker image with the required database created. +//DESCRIPTION +//DESCRIPTION The `DATABASE` constant define which database to use and +//DESCRIPTION it can be change to any of the values in `Database`. +//DESCRIPTION +//DESCRIPTION Usage example: +//DESCRIPTION 1. Use as jbang template `jbang init -t mysql-reproducer@hibernate/hibernate-reactive mytest.java` +//DESCRIPTION 2. Run the test with JBang: `jbang mytest.java` +//DESCRIPTION 3. (Optional) Edit the file (with IntelliJ IDEA for example): +//DESCRIPTION jbang edit --live --open=idea mytest.java +@RunWith(VertxUnitRunner.class) +public class {baseName} { + + @ClassRule + public static MySQLContainer> database = new MySQLContainer<>( "mysql:8.0.25" ); + + private Mutiny.SessionFactory sessionFactory; + + @BeforeClass + public static void startContainer() { + database.start(); + } + + /** + * The \{@link Configuration} for the \{@link Mutiny.SessionFactory}. + */ + private Configuration createConfiguration() { + Configuration configuration = new Configuration(); + + // JDBC url + configuration.setProperty( Settings.URL, database.getJdbcUrl() ); + + // Credentials + configuration.setProperty( Settings.USER, database.getUsername() ); + configuration.setProperty( Settings.PASS, database.getPassword() ); + + // Schema generation. Supported values are create, drop, create-drop, drop-create, none + configuration.setProperty( Settings.HBM2DDL_AUTO, "create" ); + + // Register new entity classes here + configuration.addAnnotatedClass( MyEntity.class ); + + // (Optional) Log the SQL queries + configuration.setProperty( Settings.SHOW_SQL, "true" ); + configuration.setProperty( Settings.HIGHLIGHT_SQL, "true" ); + configuration.setProperty( Settings.FORMAT_SQL, "true" ); + return configuration; + } + + /* + * Create a new factory and a new schema before each test (see + * property `hibernate.hbm2ddl.auto`). + * This way each test will start with a clean database. + * + * The drawback is that, in a real case scenario with multiple tests, + * it can slow down the whole test suite considerably. If that happens, + * it's possible to make the session factory static and, if necessary, + * delete the content of the tables manually (without dropping them). + */ + @Before + public void createSessionFactory() { + Configuration configuration = createConfiguration(); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder() + .applySettings( configuration.getProperties() ); + StandardServiceRegistry registry = builder.build(); + + sessionFactory = configuration.buildSessionFactory( registry ) + .unwrap( Mutiny.SessionFactory.class ); + } + + @Test + public void testInsertAndSelect(TestContext context) { + // the test will wait until async.complete or context.fail are called + Async async = context.async(); + + MyEntity entity = new MyEntity( "first entity", 1 ); + sessionFactory + // insert the entity in the database + .withTransaction( (session, tx) -> session.persist( entity ) ) + .chain( () -> sessionFactory + .withSession( session -> session + // look for the entity by id + .find( MyEntity.class, entity.getId() ) + // assert that the returned entity is the right one + .invoke( foundEntity -> assertThat( foundEntity.getName() ).isEqualTo( entity.getName() ) ) ) ) + .subscribe() + .with( res -> async.complete(), context::fail ); + } + + @After + public void closeFactory() { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + } + + /** + * Example of a class representing an entity. + *
+ * If you create new entities, be sure to add them in . + * For example: + *
+ * configuration.addAnnotatedClass( MyOtherEntity.class ); + *+ */ + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + public Integer id; + + public String name; + + public MyEntity() { + } + + public MyEntity(String name, Integer id) { + this.name = name; + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "MyEntity" + + "\n\t id = " + id + + "\n\t name = " + name; + } + } + + // This main class is only for JBang so that it can run the tests with `jbang {baseName}.java` + public static void main(String[] args) { + System.out.println( "Starting the test suite with MySQL"); + + Result result = JUnitCore.runClasses( {baseName}.class ); + + for ( Failure failure : result.getFailures() ) { + System.out.println(); + System.err.println( "Test " + failure.getTestHeader() + " FAILED!" ); + System.err.println( "\t" + failure.getTrace() ); + } + + System.out.println(); + System.out.print("Tests result summary: "); + System.out.println( result.wasSuccessful() ? "SUCCESS" : "FAILURE" ); + } +} diff --git a/tooling/jbang/PostgreSQLReactiveTest.java.qute b/tooling/jbang/PostgreSQLReactiveTest.java.qute new file mode 100755 index 000000000..059287ffa --- /dev/null +++ b/tooling/jbang/PostgreSQLReactiveTest.java.qute @@ -0,0 +1,199 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +/* Hibernate, Relational Persistence for Idiomatic Java + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright: Red Hat Inc. and Hibernate Authors + */ + +//DEPS io.vertx:vertx-pg-client:$\{vertx.version:4.1.0} +//DEPS io.vertx:vertx-unit:$\{vertx.version:4.1.0} +//DEPS org.hibernate.reactive:hibernate-reactive-core:$\{hibernate-reactive.version:1.0.0.CR6} +//DEPS org.assertj:assertj-core:3.19.0 +//DEPS junit:junit:4.13.2 +//DEPS org.testcontainers:postgresql:1.15.3 +//DEPS org.slf4j:slf4j-simple:1.7.30 + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.Configuration; +import org.hibernate.reactive.mutiny.Mutiny; +import org.hibernate.reactive.provider.ReactiveServiceRegistryBuilder; +import org.hibernate.reactive.provider.Settings; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.RunWith; +import org.junit.runner.notification.Failure; + +import io.vertx.ext.unit.Async; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.testcontainers.containers.PostgreSQLContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +//DESCRIPTION An example of a JUnit test class for Hibernate Reactive using +//DESCRIPTION [Vert.x Unit](https://vertx.io/docs/vertx-unit/java), +//DESCRIPTION [Testcontainers](https://www.testcontainers.org) +//DESCRIPTION and [PostgreSQL](https://www.postgresql.org/) +//DESCRIPTION that you can run using [JBang](JBang). +//DESCRIPTION +//DESCRIPTION Before running the tests, Testcontainers will start the selected +//DESCRIPTION Docker image with the required database created. +//DESCRIPTION +//DESCRIPTION Usage example: +//DESCRIPTION 1. Use as jbang template `jbang init -t pg-reproducer@hibernate/hibernate-reactive mytest.java` +//DESCRIPTION 2. Run the test with JBang: `jbang mytest.java` +//DESCRIPTION 3. (Optional) Edit the file (with IntelliJ IDEA for example): +//DESCRIPTION jbang edit --live --open=idea mytest.java +@RunWith(VertxUnitRunner.class) +public class {baseName} { + + @ClassRule + public final static PostgreSQLContainer> database = new PostgreSQLContainer<>( "postgres:13.2" ); + + private Mutiny.SessionFactory sessionFactory; + + @BeforeClass + public static void startContainer() { + database.start(); + } + + /** + * The \{@link Configuration} for the \{@link Mutiny.SessionFactory}. + */ + private Configuration createConfiguration() { + Configuration configuration = new Configuration(); + + // JDBC url + configuration.setProperty( Settings.URL, database.getJdbcUrl() ); + + // Credentials + configuration.setProperty( Settings.USER, database.getUsername() ); + configuration.setProperty( Settings.PASS, database.getPassword() ); + + // Schema generation. Supported values are create, drop, create-drop, drop-create, none + configuration.setProperty( Settings.HBM2DDL_AUTO, "create" ); + + // Register new entity classes here + configuration.addAnnotatedClass( MyEntity.class ); + + // (Optional) Log the SQL queries + configuration.setProperty( Settings.SHOW_SQL, "true" ); + configuration.setProperty( Settings.HIGHLIGHT_SQL, "true" ); + configuration.setProperty( Settings.FORMAT_SQL, "true" ); + return configuration; + } + + /* + * Create a new factory and a new schema before each test (see + * property `hibernate.hbm2ddl.auto`). + * This way each test will start with a clean database. + * + * The drawback is that, in a real case scenario with multiple tests, + * it can slow down the whole test suite considerably. If that happens, + * it's possible to make the session factory static and, if necessary, + * delete the content of the tables manually (without dropping them). + */ + @Before + public void createSessionFactory() { + Configuration configuration = createConfiguration(); + StandardServiceRegistryBuilder builder = new ReactiveServiceRegistryBuilder() + .applySettings( configuration.getProperties() ); + StandardServiceRegistry registry = builder.build(); + + sessionFactory = configuration.buildSessionFactory( registry ) + .unwrap( Mutiny.SessionFactory.class ); + } + + @Test + public void testInsertAndSelect(TestContext context) { + // the test will wait until async.complete or context.fail are called + Async async = context.async(); + + MyEntity entity = new MyEntity( "first entity", 1 ); + sessionFactory + // insert the entity in the database + .withTransaction( (session, tx) -> session.persist( entity ) ) + .chain( () -> sessionFactory + .withSession( session -> session + // look for the entity by id + .find( MyEntity.class, entity.getId() ) + // assert that the returned entity is the right one + .invoke( foundEntity -> assertThat( foundEntity.getName() ).isEqualTo( entity.getName() ) ) ) ) + .subscribe() + .with( res -> async.complete(), context::fail ); + } + + @After + public void closeFactory() { + if ( sessionFactory != null ) { + sessionFactory.close(); + } + } + + /** + * Example of a class representing an entity. + *
+ * If you create new entities, be sure to add them in . + * For example: + *
+ * configuration.addAnnotatedClass( MyOtherEntity.class ); + *+ */ + @Entity(name = "MyEntity") + public static class MyEntity { + @Id + public Integer id; + + public String name; + + public MyEntity() { + } + + public MyEntity(String name, Integer id) { + this.name = name; + this.id = id; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "MyEntity" + + "\n\t id = " + id + + "\n\t name = " + name; + } + } + + // This main class is only for JBang so that it can run the tests with `jbang {baseName}.java` + public static void main(String[] args) { + System.out.println( "Starting the test suite with PostgreSQL" ); + + Result result = JUnitCore.runClasses( {baseName}.class ); + + for ( Failure failure : result.getFailures() ) { + System.out.println(); + System.err.println( "Test " + failure.getTestHeader() + " FAILED!" ); + System.err.println( "\t" + failure.getTrace() ); + } + + System.out.println(); + System.out.print("Tests result summary: "); + System.out.println( result.wasSuccessful() ? "SUCCESS" : "FAILURE" ); + } +}