Skip to content

Commit 77d9748

Browse files
committed
[#764] Test multitenacy without CurrentTenantIdentifierResolver
1 parent 0b62ea3 commit 77d9748

File tree

1 file changed

+311
-0
lines changed

1 file changed

+311
-0
lines changed
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
/* Hibernate, Relational Persistence for Idiomatic Java
2+
*
3+
* SPDX-License-Identifier: LGPL-2.1-or-later
4+
* Copyright: Red Hat Inc. and Hibernate Authors
5+
*/
6+
package org.hibernate.reactive;
7+
8+
import java.util.Arrays;
9+
import java.util.Objects;
10+
import java.util.concurrent.CompletionStage;
11+
import javax.persistence.Entity;
12+
import javax.persistence.Id;
13+
import javax.persistence.Table;
14+
import javax.persistence.Version;
15+
16+
import org.hibernate.HibernateException;
17+
import org.hibernate.LockMode;
18+
import org.hibernate.MultiTenancyStrategy;
19+
import org.hibernate.cfg.Configuration;
20+
import org.hibernate.reactive.mutiny.Mutiny;
21+
import org.hibernate.reactive.provider.Settings;
22+
import org.hibernate.reactive.stage.Stage;
23+
import org.hibernate.reactive.testing.DatabaseSelectionRule;
24+
import org.hibernate.reactive.util.impl.CompletionStages;
25+
26+
import org.junit.AfterClass;
27+
import org.junit.Rule;
28+
import org.junit.Test;
29+
import org.junit.rules.ExpectedException;
30+
31+
import io.smallrye.mutiny.Uni;
32+
import io.vertx.ext.unit.TestContext;
33+
34+
import static org.hamcrest.CoreMatchers.isA;
35+
import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.DEFAULT;
36+
import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.TENANT_1;
37+
import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.TENANT_2;
38+
import static org.hibernate.reactive.MyCurrentTenantIdentifierResolver.Tenant.values;
39+
import static org.hibernate.reactive.containers.DatabaseConfiguration.DBType.POSTGRESQL;
40+
41+
/**
42+
* Test Multitenancy without a {@link org.hibernate.context.spi.CurrentTenantIdentifierResolver}.
43+
* <p>
44+
* This class creates multiple additional databases so that we can check that queries run
45+
* on the database for the selected tenant.
46+
* </p>
47+
*
48+
* @see ReactiveMultitenantTest
49+
*/
50+
public class ReactiveMultitenantNoResolverTest extends BaseReactiveTest {
51+
52+
// To check if we are using the right database we run native queries for PostgreSQL
53+
@Rule
54+
public DatabaseSelectionRule selectionRule = DatabaseSelectionRule.runOnlyFor( POSTGRESQL );
55+
56+
@Rule
57+
public ExpectedException thrown = ExpectedException.none();
58+
59+
@Override
60+
protected Configuration constructConfiguration() {
61+
Configuration configuration = super.constructConfiguration();
62+
configuration.addAnnotatedClass( GuineaPig.class );
63+
configuration.setProperty( Settings.MULTI_TENANT, MultiTenancyStrategy.DATABASE.name() );
64+
// Contains the SQL scripts for the creation of the additional databases
65+
configuration.setProperty( Settings.HBM2DDL_IMPORT_FILES, "/multitenancy-test.sql" );
66+
configuration.setProperty( Settings.SQL_CLIENT_POOL, TenantDependentPool.class.getName() );
67+
return configuration;
68+
}
69+
70+
@Test
71+
public void reactivePersistFindDelete(TestContext context) {
72+
final GuineaPig guineaPig = new GuineaPig( 5, "Aloi" );
73+
Stage.Session session = getSessionFactory().openSession( DEFAULT.name() );
74+
test(
75+
context,
76+
session.persist( guineaPig )
77+
.thenCompose( v -> session.flush() )
78+
.thenAccept( v -> session.detach( guineaPig ) )
79+
.thenAccept( v -> context.assertFalse( session.contains( guineaPig ) ) )
80+
.thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) )
81+
.thenAccept( actualPig -> {
82+
assertThatPigsAreEqual( context, guineaPig, actualPig );
83+
context.assertTrue( session.contains( actualPig ) );
84+
context.assertFalse( session.contains( guineaPig ) );
85+
context.assertEquals( LockMode.READ, session.getLockMode( actualPig ) );
86+
session.detach( actualPig );
87+
context.assertFalse( session.contains( actualPig ) );
88+
} )
89+
.thenCompose( v -> session.find( GuineaPig.class, guineaPig.getId() ) )
90+
.thenCompose( session::remove )
91+
.thenCompose( v -> session.flush() )
92+
.whenComplete( (v, err) -> session.close() )
93+
);
94+
}
95+
96+
@Test
97+
public void testWithSessionWithTenant(TestContext context) {
98+
test( context, getSessionFactory()
99+
.withSession( TENANT_1.name(), session -> selectCurrentDB( session )
100+
.thenAccept( result -> context.assertEquals( TENANT_1.getDbName(), result ) ) )
101+
.thenCompose( unused -> getSessionFactory()
102+
.withSession( TENANT_2.name(), session -> selectCurrentDB( session )
103+
.thenAccept( result -> context.assertEquals( TENANT_2.getDbName(), result ) ) ) )
104+
);
105+
}
106+
107+
@Test
108+
public void testWithSessionWithTenantWithMutiny(TestContext context) {
109+
test( context, getMutinySessionFactory()
110+
.withSession( TENANT_1.name(), session -> selectCurrentDB( session )
111+
.invoke( result -> context.assertEquals( TENANT_1.getDbName(), result ) ) )
112+
.call( () -> getMutinySessionFactory()
113+
.withSession( TENANT_2.name(), session -> selectCurrentDB( session )
114+
.invoke( result -> context.assertEquals( TENANT_2.getDbName(), result ) ) ) )
115+
);
116+
}
117+
118+
@Test
119+
public void testWithTransactionWithTenant(TestContext context) {
120+
test( context, getSessionFactory()
121+
.withTransaction( TENANT_1.name(), (session, tx) -> selectCurrentDB( session )
122+
.thenAccept( result -> context.assertEquals( TENANT_1.getDbName(), result ) ) )
123+
.thenCompose( unused -> getSessionFactory()
124+
.withTransaction( TENANT_2.name(), (session, tx) -> selectCurrentDB( session )
125+
.thenAccept( result -> context.assertEquals( TENANT_2.getDbName(), result ) ) ) )
126+
);
127+
}
128+
129+
@Test
130+
public void testWithTransactionWithTenantWithMutiny(TestContext context) {
131+
test( context, getMutinySessionFactory()
132+
.withTransaction( TENANT_1.name(), (session, tx) -> selectCurrentDB( session )
133+
.invoke( result -> context.assertEquals( TENANT_1.getDbName(), result ) ) )
134+
.chain( () -> getMutinySessionFactory()
135+
.withTransaction( TENANT_2.name(), (session, tx) -> selectCurrentDB( session )
136+
.invoke( result -> context.assertEquals( TENANT_2.getDbName(), result ) ) ) )
137+
);
138+
}
139+
140+
@Test
141+
public void testOpenSessionWithTenant(TestContext context) {
142+
Stage.Session t1Session = getSessionFactory().openSession( TENANT_1.name() );
143+
test( context, selectCurrentDB( t1Session )
144+
.thenAccept( result -> context.assertEquals( TENANT_1.getDbName(), result ) )
145+
.whenComplete( (v, e) -> t1Session.close() )
146+
.thenApply( unused -> getSessionFactory().openSession( TENANT_2.name() ) )
147+
.thenCompose( t2Session -> selectCurrentDB( t2Session )
148+
.thenAccept( result -> context.assertEquals( TENANT_2.getDbName(), result ) )
149+
.whenComplete( (v, e) -> t2Session.close() ) )
150+
);
151+
}
152+
153+
private CompletionStage<Object> selectCurrentDB(Stage.Session session) {
154+
return session
155+
.createNativeQuery( "select current_database()" )
156+
.getSingleResult();
157+
}
158+
159+
private Uni<Object> selectCurrentDB(Mutiny.Session session) {
160+
return session
161+
.createNativeQuery( "select current_database()" )
162+
.getSingleResult();
163+
}
164+
165+
@Test
166+
public void testOpenSessionWithTenantWithMutin(TestContext context) {
167+
Mutiny.Session t1Session = getMutinySessionFactory().openSession( TENANT_1.name() );
168+
test( context, t1Session
169+
.createNativeQuery( "select current_database()" )
170+
.getSingleResult()
171+
.invoke( result -> context.assertEquals( TENANT_1.getDbName(), result ) )
172+
.eventually( t1Session::close )
173+
.replaceWith( () -> getMutinySessionFactory().openSession( TENANT_2.name() ) )
174+
.call( t2Session -> t2Session
175+
.createNativeQuery( "select current_database()" )
176+
.getSingleResult()
177+
.invoke( result -> context.assertEquals( TENANT_2.getDbName(), result ) )
178+
.call( t2Session::close ) )
179+
);
180+
}
181+
182+
@Test
183+
public void testOpenSessionThrowsExceptionWithoutTenant(TestContext context) {
184+
thrown.expectCause( isA( HibernateException.class ) );
185+
thrown.expectMessage( "no tenant identifier" );
186+
187+
test( context, openSession().thenCompose( this::selectCurrentDB ) );
188+
}
189+
190+
@Test
191+
public void testWithSessionThrowsExceptionWithoutTenant(TestContext context) {
192+
thrown.expectCause( isA( HibernateException.class ) );
193+
thrown.expectMessage( "no tenant identifier" );
194+
195+
test( context, getSessionFactory().withSession( this::selectCurrentDB ) );
196+
}
197+
198+
@Test
199+
public void testWithTransactionThrowsExceptionWithoutTenant(TestContext context) {
200+
thrown.expectCause( isA( HibernateException.class ) );
201+
thrown.expectMessage( "no tenant identifier" );
202+
203+
test( context, getSessionFactory().withTransaction( (s, t) -> selectCurrentDB( s ) ) );
204+
}
205+
206+
@Test
207+
public void testOpenSessionThrowsExceptionWithoutTenantWithMutiny(TestContext context) {
208+
thrown.expect( HibernateException.class );
209+
thrown.expectMessage( "no tenant identifier" );
210+
211+
test( context, openMutinySession().call( this::selectCurrentDB ) );
212+
}
213+
214+
@Test
215+
public void testWithSessionThrowsExceptionWithoutTenantWithMutiny(TestContext context) {
216+
thrown.expect( HibernateException.class );
217+
thrown.expectMessage( "no tenant identifier" );
218+
219+
test( context, getMutinySessionFactory().withSession( this::selectCurrentDB ) );
220+
}
221+
222+
@Test
223+
public void testWithTransactionThrowsExceptionWithoutTenantWithMutiny(TestContext context) {
224+
thrown.expect( HibernateException.class );
225+
thrown.expectMessage( "no tenant identifier" );
226+
227+
test( context, getMutinySessionFactory().withTransaction( (s, t) -> selectCurrentDB( s ) ) );
228+
}
229+
230+
@AfterClass
231+
public static void dropDatabases(TestContext context) {
232+
if ( factoryManager.isStarted() ) {
233+
test( context, getSessionFactory()
234+
.withSession( DEFAULT.name(), session -> Arrays
235+
.stream( values() )
236+
.filter( tenant -> tenant != DEFAULT )
237+
.collect(
238+
CompletionStages::voidFuture,
239+
(stage, tenant) -> session
240+
.createNativeQuery( "drop database if exists " + tenant.getDbName() + ";" )
241+
.executeUpdate()
242+
.thenCompose( CompletionStages::voidFuture ),
243+
(stage, stage2) -> stage.thenCompose( v -> stage2 )
244+
)
245+
) );
246+
}
247+
}
248+
249+
private void assertThatPigsAreEqual(TestContext context, GuineaPig expected, GuineaPig actual) {
250+
context.assertNotNull( actual );
251+
context.assertEquals( expected.getId(), actual.getId() );
252+
context.assertEquals( expected.getName(), actual.getName() );
253+
}
254+
255+
@Entity(name = "GuineaPig")
256+
@Table(name = "Pig")
257+
public static class GuineaPig {
258+
@Id
259+
private Integer id;
260+
private String name;
261+
@Version
262+
private int version;
263+
264+
public GuineaPig() {
265+
}
266+
267+
public GuineaPig(Integer id, String name) {
268+
this.id = id;
269+
this.name = name;
270+
}
271+
272+
public Integer getId() {
273+
return id;
274+
}
275+
276+
public void setId(Integer id) {
277+
this.id = id;
278+
}
279+
280+
public String getName() {
281+
return name;
282+
}
283+
284+
public void setName(String name) {
285+
this.name = name;
286+
}
287+
288+
@Override
289+
public String toString() {
290+
return id + ": " + name;
291+
}
292+
293+
@Override
294+
public boolean equals(Object o) {
295+
if ( this == o ) {
296+
return true;
297+
}
298+
if ( o == null || getClass() != o.getClass() ) {
299+
return false;
300+
}
301+
GuineaPig guineaPig = (GuineaPig) o;
302+
return Objects.equals( name, guineaPig.name );
303+
}
304+
305+
@Override
306+
public int hashCode() {
307+
return Objects.hash( name );
308+
}
309+
}
310+
311+
}

0 commit comments

Comments
 (0)