1+ package org .evomaster .client .java .instrumentation .coverage .methodreplacement .thirdpartyclasses ;
2+
3+ import com .datastax .oss .driver .api .core .CqlSession ;
4+ import org .evomaster .client .java .instrumentation .AdditionalInfo ;
5+ import org .evomaster .client .java .instrumentation .CassandraTableSchema ;
6+ import org .evomaster .client .java .instrumentation .object .ClassToSchema ;
7+ import org .evomaster .client .java .instrumentation .staticstate .ExecutionTracer ;
8+ import org .junit .jupiter .api .AfterAll ;
9+ import org .junit .jupiter .api .BeforeAll ;
10+ import org .junit .jupiter .api .BeforeEach ;
11+ import org .junit .jupiter .api .Test ;
12+ import org .springframework .data .cassandra .core .CassandraTemplate ;
13+ import org .testcontainers .containers .GenericContainer ;
14+ import org .testcontainers .containers .wait .strategy .Wait ;
15+
16+ import java .net .InetSocketAddress ;
17+ import java .time .Duration ;
18+ import java .util .Collections ;
19+ import java .util .List ;
20+ import java .util .Set ;
21+ import java .util .UUID ;
22+
23+ import static org .junit .jupiter .api .Assertions .*;
24+
25+ public class CassandraTemplateClassReplacementTest {
26+
27+ private static CqlSession cqlSession ;
28+ private static CassandraTemplate cassandraTemplate ;
29+
30+ private static final int CASSANDRA_PORT = 9042 ;
31+ private static final String CASSANDRA_IMAGE = "cassandra" ;
32+ private static final String CASSANDRA_VERSION = "4.1" ;
33+
34+ private static final GenericContainer <?> cassandra = new GenericContainer <>(CASSANDRA_IMAGE + ":" + CASSANDRA_VERSION )
35+ .withExposedPorts (CASSANDRA_PORT )
36+ .waitingFor (Wait .forLogMessage (".*Starting listening for CQL clients.*" , 1 )
37+ .withStartupTimeout (Duration .ofMinutes (2 )));
38+
39+ private static final String KEYSPACE = "testks" ;
40+ private static final String TABLE_NAME = "cassandra_template_test_dto" ;
41+ private static final String HOST_NAME = "localhost" ;
42+ private static final String DATA_CENTER = "datacenter1" ;
43+
44+ @ BeforeAll
45+ static void startCassandra () {
46+ cassandra .start ();
47+
48+ InetSocketAddress contactPoint =
49+ new InetSocketAddress (HOST_NAME , cassandra .getMappedPort (CASSANDRA_PORT ));
50+
51+ // Bootstrap session, only to create the keyspace (which doesn't exist yet, so we
52+ // can't bind to it as a default keyspace until after this point)
53+ try (CqlSession bootstrap = CqlSession .builder ()
54+ .addContactPoint (contactPoint )
55+ .withLocalDatacenter (DATA_CENTER )
56+ .build ()) {
57+ bootstrap .execute ("CREATE KEYSPACE IF NOT EXISTS " + KEYSPACE +
58+ " WITH replication = {'class':'SimpleStrategy','replication_factor':1}" );
59+ }
60+
61+ // CassandraTemplate's convenience methods (insert/select/selectOneById) build CQL
62+ // using just the unqualified table name, so the session needs a default keyspace.
63+ cqlSession = CqlSession .builder ()
64+ .addContactPoint (contactPoint )
65+ .withLocalDatacenter (DATA_CENTER )
66+ .withKeyspace (KEYSPACE )
67+ .build ();
68+
69+ // Setup: call directly on session so it is NOT intercepted by any replacement
70+ cqlSession .execute ("CREATE TABLE IF NOT EXISTS " + TABLE_NAME +
71+ " (id uuid PRIMARY KEY, name text, age int)" );
72+
73+ cassandraTemplate = new CassandraTemplate (cqlSession );
74+
75+ ExecutionTracer .reset ();
76+ }
77+
78+ @ AfterAll
79+ static void cleanup () {
80+ if (cqlSession != null ) {
81+ cqlSession .close ();
82+ }
83+ ExecutionTracer .reset ();
84+ }
85+
86+ @ BeforeEach
87+ void clearTable () {
88+ // Direct call — not intercepted
89+ cqlSession .execute ("TRUNCATE " + TABLE_NAME );
90+ ExecutionTracer .reset ();
91+ }
92+
93+ private static String expectedSchema () {
94+ return ClassToSchema .getOrDeriveSchemaWithItsRef (CassandraTemplateTestDto .class , true , Collections .emptyList ());
95+ }
96+
97+ private static void assertSingleRecordedTableType () {
98+ List <AdditionalInfo > additionalInfoList = ExecutionTracer .exposeAdditionalInfoList ();
99+ assertEquals (1 , additionalInfoList .size ());
100+
101+ Set <CassandraTableSchema > tableTypes = additionalInfoList .get (0 ).getCassandraTableTypeData ();
102+ assertEquals (1 , tableTypes .size ());
103+
104+ CassandraTableSchema tableSchema = tableTypes .iterator ().next ();
105+ assertEquals (TABLE_NAME , tableSchema .getTableName ());
106+ assertEquals (expectedSchema (), tableSchema .getTableSchema ());
107+ }
108+
109+ @ Test
110+ void testInsert () {
111+ UUID id = UUID .randomUUID ();
112+ CassandraTemplateTestDto dto = new CassandraTemplateTestDto (id , "Alice" , 30 );
113+
114+ CassandraTemplateClassReplacement .insert (cassandraTemplate , dto );
115+
116+ // verify the row was actually persisted, via a direct (untracked) query
117+ CassandraTemplateTestDto persisted =
118+ cassandraTemplate .selectOneById (id , CassandraTemplateTestDto .class );
119+ assertNotNull (persisted );
120+ assertEquals ("Alice" , persisted .name );
121+ assertEquals (30 , persisted .age );
122+
123+ assertSingleRecordedTableType ();
124+ }
125+
126+ @ Test
127+ void testSelectOne () {
128+ UUID id = UUID .randomUUID ();
129+ // Direct insert — not intercepted
130+ cqlSession .execute ("INSERT INTO " + TABLE_NAME + " (id, name, age) VALUES (" + id + ", 'Bob', 25)" );
131+
132+ CassandraTemplateTestDto retrieved = CassandraTemplateClassReplacement .selectOne (
133+ cassandraTemplate , "SELECT * FROM " + TABLE_NAME + " WHERE id = " + id , CassandraTemplateTestDto .class );
134+
135+ assertNotNull (retrieved );
136+ assertEquals ("Bob" , retrieved .name );
137+ assertEquals (25 , retrieved .age );
138+
139+ assertSingleRecordedTableType ();
140+ }
141+
142+ @ Test
143+ void testSelect () {
144+ UUID id1 = UUID .randomUUID ();
145+ UUID id2 = UUID .randomUUID ();
146+ // Direct inserts — not intercepted
147+ cqlSession .execute ("INSERT INTO " + TABLE_NAME + " (id, name, age) VALUES (" + id1 + ", 'Carol', 40)" );
148+ cqlSession .execute ("INSERT INTO " + TABLE_NAME + " (id, name, age) VALUES (" + id2 + ", 'Dave', 45)" );
149+
150+ List <CassandraTemplateTestDto > retrieved = CassandraTemplateClassReplacement .select (
151+ cassandraTemplate , "SELECT * FROM " + TABLE_NAME , CassandraTemplateTestDto .class );
152+
153+ assertEquals (2 , retrieved .size ());
154+
155+ assertSingleRecordedTableType ();
156+ }
157+
158+ @ Test
159+ void testDirectCallsAreNotTracked () {
160+ UUID id = UUID .randomUUID ();
161+ CassandraTemplateTestDto dto = new CassandraTemplateTestDto (id , "Erin" , 22 );
162+
163+ // Calls that bypass the replacement must not appear in the tracker
164+ cassandraTemplate .insert (dto );
165+
166+ List <AdditionalInfo > additionalInfoList = ExecutionTracer .exposeAdditionalInfoList ();
167+ assertEquals (1 , additionalInfoList .size ());
168+ assertTrue (additionalInfoList .get (0 ).getCassandraTableTypeData ().isEmpty ());
169+ }
170+ }
0 commit comments