Skip to content

Commit

Permalink
[#754] Additional tests for MultiTenancy
Browse files Browse the repository at this point in the history
  • Loading branch information
DavideD committed May 17, 2021
1 parent 6bfe964 commit c8fcd61
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 129 deletions.
Expand Up @@ -6,15 +6,49 @@
package org.hibernate.reactive;

import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.hibernate.reactive.containers.DatabaseConfiguration;

/**
* For testing purpose, it allows us to select different tenants before opening the session.
*/
public class MyCurrentTenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
return "hello";
}

@Override
public boolean validateExistingCurrentSessions() {
return false;
}

public enum Tenant {
/**
* Vert.x checks if a database exists before running the queries.
* We set the default one as the regular one we use for the other tests.
*/
DEFAULT( DatabaseConfiguration.DB_NAME ),
TENANT_1( "dbtenant1" ),
TENANT_2( "dbtenant2" );

private String dbName;

Tenant(String dbName) {
this.dbName = dbName;
}

/**
* @return the name of the database for the selected tenant
*/
public String getDbName() {
return dbName;
}
}

private Tenant tenantId = Tenant.DEFAULT;

public void setTenantIdentifier(Tenant tenantId) {
this.tenantId = tenantId;
}

@Override
public String resolveCurrentTenantIdentifier() {
return tenantId.name();
}

@Override
public boolean validateExistingCurrentSessions() {
return false;
}
}

This file was deleted.

Expand Up @@ -5,118 +5,157 @@
*/
package org.hibernate.reactive;

import io.vertx.ext.unit.TestContext;
import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;

import org.hibernate.LockMode;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cfg.Configuration;
import org.hibernate.reactive.provider.Settings;
import org.hibernate.reactive.stage.Stage;
import org.hibernate.reactive.testing.DatabaseSelectionRule;

import org.junit.Rule;
import org.junit.Test;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Version;
import java.util.Objects;
import io.vertx.ext.unit.TestContext;

import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.DEFAULT;
import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.TENANT_1;
import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.TENANT_2;
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL;

/**
* This class creates multiple additional databases so that we can check that queries run
* on the database for the selected tenant.
*/
public class ReactiveMultitenantTest extends BaseReactiveTest {

@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
configuration.addAnnotatedClass( GuineaPig.class );
configuration.setProperty( Settings.MULTI_TENANT, MultiTenancyStrategy.DATABASE.name() );
configuration.setProperty( Settings.MULTI_TENANT_IDENTIFIER_RESOLVER, MyCurrentTenantIdentifierResolver.class.getName() );
configuration.setProperty( Settings.SQL_CLIENT_POOL, MySqlClientPool.class.getName() );
return configuration;
}

@Test
public void reactivePersistFindDelete(TestContext context) {
final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" );
Stage.Session session = getSessionFactory().openSession();
test(
context,
session.persist( guineaPig )
.thenCompose( v -> session.flush() )
.thenAccept( v -> session.detach(guineaPig) )
.thenAccept( v -> context.assertFalse( session.contains(guineaPig) ) )
.thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) )
.thenAccept( actualPig -> {
assertThatPigsAreEqual( context, guineaPig, actualPig );
context.assertTrue( session.contains( actualPig ) );
context.assertFalse( session.contains( guineaPig ) );
context.assertEquals( LockMode.READ, session.getLockMode( actualPig ) );
session.detach( actualPig );
context.assertFalse( session.contains( actualPig ) );
} )
.thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) )
.thenCompose( pig -> session.remove(pig) )
.thenCompose( v -> session.flush() )
.whenComplete( (v, err) -> session.close() )
);
}

private void assertThatPigsAreEqual(TestContext context, GuineaPig expected, GuineaPig actual) {
context.assertNotNull( actual );
context.assertEquals( expected.getId(), actual.getId() );
context.assertEquals( expected.getName(), actual.getName() );
}

@Entity(name="GuineaPig")
@Table(name="Pig")
public static class GuineaPig {
@Id
private Integer id;
private String name;
@Version
private int version;

public GuineaPig() {
}

public GuineaPig(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return id + ": " + name;
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
GuineaPig guineaPig = (GuineaPig) o;
return Objects.equals( name, guineaPig.name );
}

@Override
public int hashCode() {
return Objects.hash( name );
}
}
private static final MyCurrentTenantIdentifierResolver TENANT_RESOLVER = new MyCurrentTenantIdentifierResolver();

// To check if we are using the right database we run native queries for PostgreSQL
@Rule
public DatabaseSelectionRule selectionRule = DatabaseSelectionRule.runOnlyFor( POSTGRESQL );

@Override
protected Configuration constructConfiguration() {
Configuration configuration = super.constructConfiguration();
configuration.addAnnotatedClass( GuineaPig.class );
configuration.setProperty( Settings.MULTI_TENANT, MultiTenancyStrategy.DATABASE.name() );
configuration.getProperties().put( Settings.MULTI_TENANT_IDENTIFIER_RESOLVER, TENANT_RESOLVER );
// Contains the SQL scripts for the creation of the additional databases
configuration.setProperty( Settings.HBM2DDL_IMPORT_FILES, "/multitenancy-test.sql" );
configuration.setProperty( Settings.SQL_CLIENT_POOL, TenantDependentPool.class.getName() );
return configuration;
}

@Test
public void reactivePersistFindDelete(TestContext context) {
TENANT_RESOLVER.setTenantIdentifier( DEFAULT );
final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" );
Stage.Session session = getSessionFactory().openSession();
test(
context,
session.persist( guineaPig )
.thenCompose( v -> session.flush() )
.thenAccept( v -> session.detach( guineaPig ) )
.thenAccept( v -> context.assertFalse( session.contains( guineaPig ) ) )
.thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) )
.thenAccept( actualPig -> {
assertThatPigsAreEqual( context, guineaPig, actualPig );
context.assertTrue( session.contains( actualPig ) );
context.assertFalse( session.contains( guineaPig ) );
context.assertEquals( LockMode.READ, session.getLockMode( actualPig ) );
session.detach( actualPig );
context.assertFalse( session.contains( actualPig ) );
} )
.thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) )
.thenCompose( pig -> session.remove( pig ) )
.thenCompose( v -> session.flush() )
.whenComplete( (v, err) -> session.close() )
);
}

@Test
public void testTenantSelection(TestContext context) {
TENANT_RESOLVER.setTenantIdentifier( TENANT_1 );
test( context, openSession()
.thenCompose( session -> session
.createNativeQuery( "select current_database()" )
.getSingleResult()
.thenAccept( result -> context.assertEquals( TENANT_1.getDbName(), result ) ) )
.thenAccept( unused -> TENANT_RESOLVER.setTenantIdentifier( TENANT_2 ) )
.thenCompose( unused -> openSession() )
.thenCompose( session -> session
.createNativeQuery( "select current_database()" )
.getSingleResult()
.thenAccept( result -> context.assertEquals( TENANT_2.getDbName(), result ) ) )
);
}

private void assertThatPigsAreEqual(TestContext context, GuineaPig expected, GuineaPig actual) {
context.assertNotNull( actual );
context.assertEquals( expected.getId(), actual.getId() );
context.assertEquals( expected.getName(), actual.getName() );
}

@Entity(name = "GuineaPig")
@Table(name = "Pig")
public static class GuineaPig {
@Id
private Integer id;
private String name;
@Version
private int version;

public GuineaPig() {
}

public GuineaPig(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return id + ": " + name;
}

@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
GuineaPig guineaPig = (GuineaPig) o;
return Objects.equals( name, guineaPig.name );
}

@Override
public int hashCode() {
return Objects.hash( name );
}
}

}

0 comments on commit c8fcd61

Please sign in to comment.