git.iko.soy/rowtype-yoga/purescript-yoga-scylladb
on main help sign in
410 lines 15 kB history raw
1module Test.Scylladb.Main where
3import Prelude
5import Data.Array as Array
6import Data.Either (Either(..))
7import Data.Maybe (Maybe(..))
8import Effect (Effect)
9import Effect.Aff (Aff, bracket, delay, launchAff_, throwError, try)
10import Effect.Class (liftEffect)
11import Effect.Console (log)
12import Effect.Exception (error)
13import Data.Time.Duration (Milliseconds(..))
14import Test.Spec (Spec, around, describe, it)
15import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
16import Test.Spec.Reporter.Console (consoleReporter)
17import Test.Spec.Runner (runSpec)
18import Yoga.Test.Docker as Docker
19import Yoga.ScyllaDB.ScyllaDB as Scylla
20import Yoga.ScyllaDB.Schema.Test as SchemaTest
22-- Test configuration
23testContactPoint :: Scylla.ContactPoint
24testContactPoint = Scylla.ContactPoint "127.0.0.1"
26testDatacenter :: Scylla.Datacenter
27testDatacenter = Scylla.Datacenter "datacenter1"
29testKeyspace :: Scylla.Keyspace
30testKeyspace = Scylla.Keyspace "test_ks"
32testPort :: Int
33testPort = 9043 -- Test port from docker-compose.test.yml
35-- Helper to create and manage ScyllaDB connection
36withScylla :: (Scylla.Client -> Aff Unit) -> Aff Unit
37withScylla test = do
38 client <- liftEffect $ Scylla.createClient
39 { contactPoints: [ testContactPoint ]
40 , localDataCenter: testDatacenter
41 , protocolOptions: { port: testPort, maxSchemaAgreementWaitSeconds: 10 }
42 }
43 _ <- Scylla.connect client
45 -- Wait for cluster to be ready
46 delay (Milliseconds 1000.0)
48 -- Clean up test keyspace before each test
49 _ <- try $ Scylla.dropKeyspace testKeyspace client
51 test client
53 _ <- Scylla.shutdown client
54 pure unit
56-- Helper to check if value is Left
57isLeft :: forall a b. Either a b -> Boolean
58isLeft (Left _) = true
59isLeft _ = false
61-- Helper to set up test keyspace
62setupKeyspace :: Scylla.Client -> Aff Unit
63setupKeyspace client = do
64 _ <- Scylla.createKeyspace testKeyspace (Scylla.ReplicationFactor 1) client
65 _ <- Scylla.useKeyspace testKeyspace client
66 pure unit
68-- Helper to set up users table
69setupUsersTable :: Scylla.Client -> Aff Unit
70setupUsersTable client = do
71 _ <- Scylla.execute
72 ( Scylla.CQL
73 """
74 CREATE TABLE test_ks.users (
75 id uuid PRIMARY KEY,
76 name text,
77 email text,
78 age int,
79 active boolean
80 )
81 """
82 )
83 []
84 client
85 pure unit
87spec :: Spec Unit
88spec = do
89 describe "Yoga.ScyllaDB Integration Tests" do
91 -- Connection Management Tests
92 around withScylla do
93 describe "Connection Management" do
94 it "connects to ScyllaDB successfully" \client -> do
95 healthy <- Scylla.ping client
96 healthy `shouldEqual` true
98 it "retrieves cluster hosts" \client -> do
99 hosts <- liftEffect $ Scylla.getHosts client
100 (Array.length hosts) `shouldSatisfy` (\l -> l > 0)
102 describe "Connection Errors" do
103 it "handles connection failures" do
104 result <- try do
105 client <- liftEffect $ Scylla.createClient
106 { contactPoints: [ Scylla.ContactPoint "invalid-host-9999" ]
107 , localDataCenter: testDatacenter
108 , protocolOptions: { port: 9999, maxSchemaAgreementWaitSeconds: 10 }
109 }
110 Scylla.connect client
111 result `shouldSatisfy` isLeft
113 -- Keyspace Operations Tests
114 around withScylla do
115 describe "Keyspace Operations" do
116 it "creates and drops keyspaces" \client -> do
117 _ <- Scylla.createKeyspace testKeyspace (Scylla.ReplicationFactor 1) client
118 _ <- Scylla.dropKeyspace testKeyspace client
119 pure unit
121 it "uses keyspace" \client -> do
122 setupKeyspace client
123 result <- Scylla.useKeyspace testKeyspace client
124 result.rowLength `shouldEqual` 0
126 it "retrieves keyspace metadata" \client -> do
127 setupKeyspace client
128 meta <- liftEffect $ Scylla.getKeyspace testKeyspace client
129 case meta of
130 Just _ -> pure unit
131 Nothing -> throwError (error "Expected to find keyspace metadata")
133 -- Table Operations Tests
134 around withScylla do
135 describe "Table Operations" do
136 it "creates tables with various column types" \client -> do
137 setupKeyspace client
138 result <- Scylla.execute
139 ( Scylla.CQL
140 """
141 CREATE TABLE test_ks.users (
142 id uuid PRIMARY KEY,
143 name text,
144 age int,
145 email text,
146 active boolean,
147 created_at timestamp
148 )
149 """
150 )
151 []
152 client
153 result.rowLength `shouldEqual` 0
155 it "inserts and queries data" \client -> do
156 setupKeyspace client
157 setupUsersTable client
159 userId <- liftEffect Scylla.uuid
160 _ <- Scylla.execute (Scylla.CQL "INSERT INTO test_ks.users (id, name, age) VALUES (?, ?, ?)")
161 [ Scylla.toCQLValue userId, Scylla.toCQLValue "Alice", Scylla.toCQLValue 30 ]
162 client
164 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users WHERE id = ?")
165 [ Scylla.toCQLValue userId ]
166 client
167 result.rowLength `shouldEqual` 1
169 it "updates data" \client -> do
170 setupKeyspace client
171 setupUsersTable client
173 userId <- liftEffect Scylla.uuid
174 _ <- Scylla.execute (Scylla.CQL "INSERT INTO test_ks.users (id, name, email) VALUES (?, ?, ?)")
175 [ Scylla.toCQLValue userId, Scylla.toCQLValue "Bob", Scylla.toCQLValue "bob@example.com" ]
176 client
178 _ <- Scylla.execute (Scylla.CQL "UPDATE test_ks.users SET email = ? WHERE id = ?")
179 [ Scylla.toCQLValue "bob.new@example.com", Scylla.toCQLValue userId ]
180 client
182 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users WHERE id = ?")
183 [ Scylla.toCQLValue userId ]
184 client
185 result.rowLength `shouldEqual` 1
187 it "deletes data" \client -> do
188 setupKeyspace client
189 setupUsersTable client
191 userId <- liftEffect Scylla.uuid
192 _ <- Scylla.execute (Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)")
193 [ Scylla.toCQLValue userId, Scylla.toCQLValue "Charlie" ]
194 client
196 _ <- Scylla.execute (Scylla.CQL "DELETE FROM test_ks.users WHERE id = ?")
197 [ Scylla.toCQLValue userId ]
198 client
200 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users WHERE id = ?")
201 [ Scylla.toCQLValue userId ]
202 client
203 result.rowLength `shouldEqual` 0
205 -- Consistency Level Tests
206 around withScylla do
207 describe "Consistency Levels" do
208 it "executes with ONE consistency level" \client -> do
209 setupKeyspace client
210 result <- Scylla.executeWithOptions
211 (Scylla.CQL "SELECT * FROM system.local")
212 []
213 { consistency: Scylla.One }
214 client
215 result.rowLength `shouldSatisfy` (\l -> l > 0)
217 it "executes with QUORUM consistency level" \client -> do
218 setupKeyspace client
219 result <- Scylla.executeWithOptions
220 (Scylla.CQL "SELECT * FROM system.local")
221 []
222 { consistency: Scylla.Quorum }
223 client
224 result.rowLength `shouldSatisfy` (\l -> l > 0)
226 it "executes with LOCAL_ONE consistency level" \client -> do
227 setupKeyspace client
228 result <- Scylla.executeWithOptions
229 (Scylla.CQL "SELECT * FROM system.local")
230 []
231 { consistency: Scylla.LocalOne }
232 client
233 result.rowLength `shouldSatisfy` (\l -> l > 0)
235 -- Prepared Statement Tests
236 around withScylla do
237 describe "Prepared Statements" do
238 it "prepares and executes statements" \client -> do
239 setupKeyspace client
240 setupUsersTable client
242 prepared <- Scylla.prepare
243 (Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)")
244 client
246 userId <- liftEffect Scylla.uuid
247 result <- Scylla.executePrepared prepared
248 [ Scylla.toCQLValue userId, Scylla.toCQLValue "Diana" ]
249 client
250 result.rowLength `shouldEqual` 0
252 it "reuses prepared statements for multiple inserts" \client -> do
253 setupKeyspace client
254 setupUsersTable client
256 prepared <- Scylla.prepare
257 (Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)")
258 client
260 userId1 <- liftEffect Scylla.uuid
261 userId2 <- liftEffect Scylla.uuid
262 userId3 <- liftEffect Scylla.uuid
264 _ <- Scylla.executePrepared prepared [ Scylla.toCQLValue userId1, Scylla.toCQLValue "Eve" ] client
265 _ <- Scylla.executePrepared prepared [ Scylla.toCQLValue userId2, Scylla.toCQLValue "Frank" ] client
266 _ <- Scylla.executePrepared prepared [ Scylla.toCQLValue userId3, Scylla.toCQLValue "Grace" ] client
268 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users") [] client
269 result.rowLength `shouldEqual` 3
271 -- Batch Operations Tests
272 around withScylla do
273 describe "Batch Operations" do
274 it "executes batch inserts" \client -> do
275 setupKeyspace client
276 setupUsersTable client
278 userId1 <- liftEffect Scylla.uuid
279 userId2 <- liftEffect Scylla.uuid
281 let
282 batchQueries =
283 [ { query: Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)"
284 , params: [ Scylla.toCQLValue userId1, Scylla.toCQLValue "Henry" ]
285 }
286 , { query: Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)"
287 , params: [ Scylla.toCQLValue userId2, Scylla.toCQLValue "Iris" ]
288 }
289 ]
291 _ <- Scylla.batch batchQueries client
293 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users") [] client
294 result.rowLength `shouldEqual` 2
296 it "executes batch with consistency options" \client -> do
297 setupKeyspace client
298 setupUsersTable client
300 userId <- liftEffect Scylla.uuid
302 let
303 batchQueries =
304 [ { query: Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)"
305 , params: [ Scylla.toCQLValue userId, Scylla.toCQLValue "Jack" ]
306 }
307 ]
309 result <- Scylla.batchWithOptions batchQueries
310 { consistency: Scylla.One, logged: true }
311 client
312 result.rowLength `shouldEqual` 0
314 -- UUID Tests
315 around withScylla do
316 describe "UUID Operations" do
317 it "generates UUIDs" \_ -> do
318 _ <- liftEffect Scylla.uuid
319 _ <- liftEffect Scylla.uuid
320 pure unit -- Just check it doesn't error
322 it "generates time-based UUIDs" \_ -> do
323 _ <- liftEffect Scylla.timeUuid
324 _ <- liftEffect Scylla.timeUuid
325 pure unit -- Just check it doesn't error
327 it "parses UUIDs from strings" \_ -> do
328 -- Valid UUID v4 format
329 maybeUuid <- liftEffect $ Scylla.uuidFromString "550e8400-e29b-41d4-a716-446655440000"
330 case maybeUuid of
331 Just _ -> pure unit
332 Nothing -> throwError (error "Expected to parse valid UUID")
334 it "returns Nothing for invalid UUID strings" \_ -> do
335 maybeUuid <- liftEffect $ Scylla.uuidFromString "not-a-valid-uuid"
336 case maybeUuid of
337 Nothing -> pure unit
338 Just _ -> throwError (error "Expected Nothing for invalid UUID")
340 -- Data Type Tests
341 around withScylla do
342 describe "Data Types" do
343 it "handles various CQL data types" \client -> do
344 setupKeyspace client
345 setupUsersTable client
347 userId <- liftEffect Scylla.uuid
348 _ <- Scylla.execute (Scylla.CQL "INSERT INTO test_ks.users (id, name, age, active) VALUES (?, ?, ?, ?)")
349 [ Scylla.toCQLValue userId, Scylla.toCQLValue "Kate", Scylla.toCQLValue 25, Scylla.toCQLValue true ]
350 client
352 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users WHERE id = ?")
353 [ Scylla.toCQLValue userId ]
354 client
355 result.rowLength `shouldEqual` 1
357 it "handles null values" \client -> do
358 setupKeyspace client
359 setupUsersTable client
361 userId <- liftEffect Scylla.uuid
362 _ <- Scylla.execute (Scylla.CQL "INSERT INTO test_ks.users (id, name) VALUES (?, ?)")
363 [ Scylla.toCQLValue userId, Scylla.toCQLValue "Leo" ]
364 client
366 result <- Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.users WHERE id = ?")
367 [ Scylla.toCQLValue userId ]
368 client
369 result.rowLength `shouldEqual` 1
371 -- Error Handling Tests
372 around withScylla do
373 describe "Error Handling" do
374 it "handles invalid CQL syntax" \client -> do
375 result <- try $ Scylla.execute (Scylla.CQL "INVALID CQL SYNTAX") [] client
376 case result of
377 Left _ -> pure unit
378 Right _ -> throwError (error "Expected error for invalid CQL syntax")
380 it "handles missing table errors" \client -> do
381 setupKeyspace client
382 result <- try $ Scylla.execute (Scylla.CQL "SELECT * FROM test_ks.non_existent_table") [] client
383 case result of
384 Left _ -> pure unit
385 Right _ -> throwError (error "Expected error for missing table")
387 it "handles missing keyspace errors" \client -> do
388 result <- try $ Scylla.execute (Scylla.CQL "SELECT * FROM nonexistent_ks.users") [] client
389 case result of
390 Left _ -> pure unit
391 Right _ -> throwError (error "Expected error for missing keyspace")
393main :: Effect Unit
394main = launchAff_ do
395 -- Schema tests are pure (no Docker needed)
396 runSpec [ consoleReporter ] SchemaTest.spec
398 liftEffect $ log "\n🧪 Starting ScyllaDB Integration Tests (with Docker)\n"
400 bracket
401 ( do
402 liftEffect $ log "⏳ Starting ScyllaDB and waiting for it to be ready (may take 30-60s)..."
403 Docker.startService (Docker.ComposeFile "packages/yoga-scylladb/docker-compose.test.yml") (Docker.Timeout (Milliseconds 60000.0))
404 liftEffect $ log "✅ ScyllaDB is ready!\n"
405 )
406 ( \_ -> do
407 Docker.stopService (Docker.ComposeFile "packages/yoga-scylladb/docker-compose.test.yml")
408 liftEffect $ log "✅ Cleanup complete\n"
409 )
410 (\_ -> runSpec [ consoleReporter ] spec)