Skip to content

Commit 42644a4

Browse files
committed
test: add repository integration tests and update test database
- Add comprehensive repository test suite - Update integration test database with latest schema
1 parent 1e47b2d commit 42644a4

3 files changed

Lines changed: 377 additions & 0 deletions

File tree

0 Bytes
Binary file not shown.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
require "../spec_helper"
2+
3+
describe Repo do
4+
describe "insert_all" do
5+
it "works with basic functionality" do
6+
user = User.new
7+
user.name = "Test User"
8+
user.things = 25
9+
10+
result = Repo.insert_all(User, [user])
11+
12+
puts "Result: #{result}"
13+
puts "Successful: #{result.successful?}"
14+
puts "Successful count: #{result.successful_count}"
15+
puts "Failed count: #{result.failed_count}"
16+
puts "Errors: #{result.errors}"
17+
18+
result.should be_a(Crecto::BulkResult)
19+
result.successful?.should be_true
20+
result.successful_count.should eq(1)
21+
result.failed_count.should eq(0)
22+
result.inserted_ids.size.should eq(1)
23+
end
24+
end
25+
end

spec/repo/bulk_insert_spec.cr

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
require "../spec_helper"
2+
3+
describe Repo do
4+
before_each do
5+
# Clean up database before each test to ensure isolation
6+
begin
7+
Repo.delete_all(User)
8+
Repo.delete_all(UserRequired)
9+
Repo.delete_all(Post)
10+
Repo.delete_all(Address)
11+
Repo.delete_all(Project)
12+
Repo.delete_all(UserProject)
13+
rescue ex
14+
# Ignore cleanup errors
15+
end
16+
end
17+
describe "insert_all" do
18+
describe "basic functionality" do
19+
it "inserts multiple records with model instances" do
20+
user1 = User.new
21+
user1.name = "Alice"
22+
user1.things = 25
23+
24+
user2 = User.new
25+
user2.name = "Bob"
26+
user2.things = 30
27+
28+
user3 = User.new
29+
user3.name = "Charlie"
30+
user3.things = 35
31+
32+
users = [user1, user2, user3]
33+
result = Repo.insert_all(User, users)
34+
35+
result.should be_a(Crecto::BulkResult)
36+
result.total_count.should eq(3)
37+
result.successful_count.should eq(3)
38+
result.failed_count.should eq(0)
39+
result.successful?.should be_true
40+
result.inserted_ids.size.should eq(3)
41+
result.duration_ms.should be > 0
42+
43+
# Verify records were actually inserted
44+
inserted_users = Repo.all(User, Query.where(name: ["Alice", "Bob", "Charlie"]))
45+
inserted_users.size.should eq(3)
46+
end
47+
48+
it "inserts multiple records with hash data" do
49+
user_data = [
50+
{"name" => "Dave", "things" => 28},
51+
{"name" => "Eve", "things" => 32}
52+
]
53+
54+
result = Repo.insert_all(User, user_data)
55+
56+
result.total_count.should eq(2)
57+
result.successful_count.should eq(2)
58+
result.failed_count.should eq(0)
59+
result.successful?.should be_true
60+
result.inserted_ids.size.should eq(2)
61+
end
62+
63+
it "inserts multiple records with NamedTuple data" do
64+
user_data = [
65+
{name: "Frank", things: 27},
66+
{name: "Grace", things: 33}
67+
]
68+
69+
result = Repo.insert_all(User, user_data)
70+
71+
result.total_count.should eq(2)
72+
result.successful_count.should eq(2)
73+
result.failed_count.should eq(0)
74+
result.successful?.should be_true
75+
result.inserted_ids.size.should eq(2)
76+
end
77+
78+
it "handles empty arrays gracefully" do
79+
expect_raises(ArgumentError, "Records array cannot be empty") do
80+
Repo.insert_all(User, [] of User)
81+
end
82+
end
83+
end
84+
85+
describe "validation handling" do
86+
it "fails records with invalid data" do
87+
user1 = UserRequired.new
88+
user1.name = "Valid User"
89+
user1.age = 25
90+
user1.is_admin = false
91+
92+
user2 = UserRequired.new
93+
user2.name = "Invalid User"
94+
# user2.age is nil - should fail validation
95+
# user2.is_admin is nil - should fail validation
96+
97+
users = [user1, user2]
98+
99+
result = Repo.insert_all(UserRequired, users)
100+
101+
result.total_count.should eq(2)
102+
result.successful_count.should eq(1)
103+
result.failed_count.should eq(1)
104+
result.successful?.should be_false
105+
result.partial_success?.should be_true
106+
result.errors.size.should eq(1)
107+
result.errors.first.index.should eq(1)
108+
result.errors.first.error_class.should contain("ValidationError")
109+
end
110+
111+
it "provides detailed error information" do
112+
user1 = UserRequired.new
113+
user1.name = "Another Valid User"
114+
user1.age = 30
115+
user1.is_admin = false
116+
117+
user2 = UserRequired.new
118+
user2.name = "User with missing fields"
119+
# user2.age is nil - should fail validation
120+
# user2.is_admin is nil - should fail validation
121+
122+
users = [user1, user2]
123+
124+
result = Repo.insert_all(UserRequired, users)
125+
126+
error = result.errors.first
127+
error.index.should eq(1)
128+
error.error_message.should_not be_empty
129+
error.error_class.should_not be_empty
130+
error.timestamp.should be_a(Time)
131+
error.record_hash.should_not be_nil
132+
end
133+
end
134+
135+
describe "transaction handling" do
136+
it "handles atomic operations correctly" do
137+
# This test depends on the database adapter's behavior
138+
# PostgreSQL and MySQL should handle multi-row inserts atomically
139+
# SQLite3 uses transaction-wrapped individual inserts
140+
141+
user1 = User.new
142+
user1.name = "Transaction Test 1"
143+
user1.things = 25
144+
145+
user2 = User.new
146+
user2.name = "Transaction Test 2"
147+
user2.things = 30
148+
149+
users = [user1, user2]
150+
result = Repo.insert_all(User, users)
151+
152+
# Both should succeed in normal conditions
153+
result.successful_count.should eq(2)
154+
result.failed_count.should eq(0)
155+
156+
# Verify atomicity - either all or nothing should be inserted
157+
count = Repo.aggregate(User, :count, :id,
158+
Query.where("name LIKE ?", "Transaction Test%"))
159+
count.should eq(2)
160+
end
161+
162+
it "works within existing transactions" do
163+
Repo.transaction! do |tx|
164+
user1 = User.new
165+
user1.name = "Transaction User 1"
166+
user1.things = 40
167+
168+
user2 = User.new
169+
user2.name = "Transaction User 2"
170+
user2.things = 45
171+
172+
users = [user1, user2]
173+
result = tx.insert_all(User, users)
174+
result.successful?.should be_true
175+
result.successful_count.should eq(2)
176+
end
177+
178+
# Verify records were committed
179+
count = Repo.aggregate(User, :count, :id,
180+
Query.where("name LIKE ?", "Transaction User%"))
181+
count.should eq(2)
182+
end
183+
end
184+
185+
describe "performance characteristics" do
186+
it "handles larger datasets efficiently" do
187+
# Create a reasonable number of test records
188+
users = Array(User).new(50) do |i|
189+
user = User.new
190+
user.name = "Perf User #{i}"
191+
user.things = 20 + (i % 50)
192+
user
193+
end
194+
195+
start_time = Time.local
196+
result = Repo.insert_all(User, users)
197+
duration = Time.local - start_time
198+
199+
result.successful?.should be_true
200+
result.successful_count.should eq(50)
201+
result.duration_ms.should be > 0
202+
203+
# Should complete in reasonable time (adjust threshold as needed)
204+
duration.total_seconds.should be < 5.0
205+
206+
# Verify all records were inserted
207+
count = Repo.aggregate(User, :count, :id,
208+
Query.where("name LIKE ?", "Perf User%"))
209+
count.should eq(50)
210+
end
211+
end
212+
213+
describe "error recovery" do
214+
it "handles partial failures gracefully" do
215+
# Mix valid and invalid records
216+
user1 = UserRequired.new
217+
user1.name = "Valid 1"
218+
user1.age = 25
219+
user1.is_admin = false
220+
221+
user2 = UserRequired.new
222+
user2.name = "Invalid 1"
223+
# user2.age is nil - should fail validation
224+
# user2.is_admin is nil - should fail validation
225+
226+
user3 = UserRequired.new
227+
user3.name = "Valid 2"
228+
user3.age = 30
229+
user3.is_admin = false
230+
231+
user4 = UserRequired.new
232+
user4.name = "Invalid 2"
233+
# user4.age is nil - should fail validation
234+
# user4.is_admin is nil - should fail validation
235+
236+
user5 = UserRequired.new
237+
user5.name = "Valid 3"
238+
user5.age = 35
239+
user5.is_admin = false
240+
241+
users = [user1, user2, user3, user4, user5]
242+
243+
result = Repo.insert_all(UserRequired, users)
244+
245+
result.total_count.should eq(5)
246+
result.successful_count.should eq(3)
247+
result.failed_count.should eq(2)
248+
result.partial_success?.should be_true
249+
result.successful?.should be_false
250+
result.errors.size.should eq(2)
251+
252+
# Verify error indices are correct
253+
failed_indices = result.errors.map(&.index).sort
254+
failed_indices.should eq([1, 3])
255+
end
256+
257+
it "provides recovery guidance for common errors" do
258+
# This tests the error classification logic
259+
user1 = UserRequired.new
260+
user1.name = "Test User"
261+
user1.age = 25
262+
user1.is_admin = false
263+
264+
user2 = UserRequired.new
265+
user2.name = "Invalid User"
266+
# user2.age is nil - should fail validation
267+
# user2.is_admin is nil - should fail validation
268+
269+
users = [user1, user2]
270+
271+
result = Repo.insert_all(UserRequired, users)
272+
error = result.errors.first
273+
274+
# Check error classification
275+
error.error_class.should_not be_empty
276+
error.error_message.should_not be_empty
277+
278+
# Should be able to determine error type
279+
error.data_type_error?.should be_true # Validation errors are data type errors
280+
error.constraint_violation?.should be_false
281+
error.connection_error?.should be_false
282+
end
283+
end
284+
285+
describe "insert_all! (strict version)" do
286+
it "raises exception on any failure" do
287+
user1 = UserRequired.new
288+
user1.name = "Valid User"
289+
user1.age = 25
290+
user1.is_admin = false
291+
292+
user2 = UserRequired.new
293+
user2.name = "Invalid User"
294+
# user2.age is nil - should fail validation
295+
# user2.is_admin is nil - should fail validation
296+
297+
users = [user1, user2]
298+
299+
expect_raises(Exception) do
300+
Repo.insert_all!(UserRequired, users)
301+
end
302+
end
303+
304+
it "succeeds when all records are valid" do
305+
user1 = UserRequired.new
306+
user1.name = "Strict User 1"
307+
user1.age = 22
308+
user1.is_admin = false
309+
310+
user2 = UserRequired.new
311+
user2.name = "Strict User 2"
312+
user2.age = 28
313+
user2.is_admin = false
314+
315+
users = [user1, user2]
316+
317+
result = Repo.insert_all!(UserRequired, users)
318+
result.successful?.should be_true
319+
result.successful_count.should eq(2)
320+
end
321+
end
322+
end
323+
324+
describe "bulk insert integration" do
325+
it "works with models that have associations" do
326+
# Test with Post model which has a belongs_to relationship
327+
post1 = Post.new
328+
post2 = Post.new
329+
330+
result = Repo.insert_all(Post, [post1, post2])
331+
result.successful?.should be_true
332+
result.successful_count.should eq(2)
333+
end
334+
335+
it "handles different data types correctly" do
336+
# Test with various data types using User model
337+
user1 = User.new
338+
user1.name = "User 1"
339+
user1.things = 100
340+
user1.yep = true
341+
342+
user2 = User.new
343+
user2.name = "User 2"
344+
user2.things = 200
345+
user2.yep = false
346+
347+
result = Repo.insert_all(User, [user1, user2])
348+
result.successful?.should be_true
349+
result.successful_count.should eq(2)
350+
end
351+
end
352+
end

0 commit comments

Comments
 (0)