Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion spec/alumna-sqlite_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,113 @@ Alumna::Testing::AdapterSuite.run("Alumna::SqliteAdapter") do
.int("score").float("price").int("order_index").str("category").bool("is_published").bytes("blob")
.str("title", required: false).str("sequence", required: false)
.str("first_name", required: false).str("last_name", required: false)
.int("view_count", required: false).nullable("metadata", required: false)
.int("view_count", required: false).any("metadata", nullable: true, required: false)

Alumna::SqliteAdapter.new(SHARED_DB, "adapter_test", schema)
end

describe "SqliteAdapter Specific Scenarios" do
it "handles unique constraints, nested JSON indexes, and edge cases natively" do
# 1. Setup a specific table for these coverage tests
SHARED_DB.exec("DROP TABLE IF EXISTS specific_scenarios")
SHARED_DB.exec("
CREATE TABLE specific_scenarios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT,
profile TEXT,
tenant_id INTEGER,
role TEXT
)
")

# 2. Define a schema that triggers all the uncovered initialization blocks
schema = Alumna::Schema.new(strict: false)
.str("email", unique: true)
.hash("profile") do |p|
p.str("handle")
end
.int("tenant_id")
.str("role")
# Define the unique constraint on the nested field at the schema level
.index("profile.handle", unique: true)
.index(["tenant_id", "role"], unique: true)
.index("email") # Simple non-unique index to trigger create_indexes! coverage

adapter = Alumna::SqliteAdapter.new(SHARED_DB, "specific_scenarios", schema)

# COVERAGE: create_indexes!
adapter.create_indexes!

# 3. Create a valid record
# COVERAGE: db_exists?(query, arg) (1-arg variant)
# COVERAGE: db_exists?(query, args Array) (Array variant)
ctx1 = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Create, data: {
"email" => "test@test.com",
"profile" => {"handle" => "tester"} of String => Alumna::AnyData,
"tenant_id" => 1_i64,
"role" => "admin",
} of String => Alumna::AnyData)

res1 = adapter.create(ctx1).as(Hash(String, Alumna::AnyData))
id = res1["id"].as(String)

# 4. Trigger duplicate single field (email)
ctx_dup1 = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Create, data: {
"email" => "test@test.com",
"profile" => {"handle" => "other"} of String => Alumna::AnyData,
"tenant_id" => 2_i64,
"role" => "user",
} of String => Alumna::AnyData)
adapter.create(ctx_dup1).should be_a(Alumna::ServiceError)

# 5. Trigger duplicate nested JSON field (profile.handle)
ctx_dup2 = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Create, data: {
"email" => "other@test.com",
"profile" => {"handle" => "tester"} of String => Alumna::AnyData,
"tenant_id" => 2_i64,
"role" => "user",
} of String => Alumna::AnyData)
adapter.create(ctx_dup2).should be_a(Alumna::ServiceError)

# 6. Trigger duplicate compound (tenant_id + role)
ctx_dup3 = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Create, data: {
"email" => "other2@test.com",
"profile" => {"handle" => "tester3"} of String => Alumna::AnyData,
"tenant_id" => 1_i64,
"role" => "admin",
} of String => Alumna::AnyData)
adapter.create(ctx_dup3).should be_a(Alumna::ServiceError)

# 7. Update with skip_id (updating itself with same data should succeed)
# COVERAGE: db_exists?(query, arg1, arg2) (2-arg variant for skip_id)
ctx_upd = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Update, id: id, data: {
"email" => "test@test.com",
"profile" => {"handle" => "tester"} of String => Alumna::AnyData,
"tenant_id" => 1_i64,
"role" => "admin",
} of String => Alumna::AnyData)
adapter.update(ctx_upd).should be_a(Hash(String, Alumna::AnyData))

# 8. Patch fast-path (sending exactly all 4 updatable columns)
# COVERAGE: if columns.size == @columns_to_update.size branch in patch
ctx_patch_all = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Patch, id: id, data: {
"email" => "patch@test.com",
"profile" => {"handle" => "patcher"} of String => Alumna::AnyData,
"tenant_id" => 1_i64,
"role" => "superadmin",
} of String => Alumna::AnyData)
adapter.patch(ctx_patch_all).should be_a(Hash(String, Alumna::AnyData))

# 9. Float64 to Int64 coercion in cast_from_db
# COVERAGE: when Float64 then val.to_i64
# Manually insert a float into an int column by bypassing the adapter
SHARED_DB.exec("INSERT INTO specific_scenarios (email, tenant_id) VALUES ('float@test.com', 99.9)")
ctx_get = Alumna::Testing.build_ctx(service: adapter, method: Alumna::ServiceMethod::Find, params: {"email" => "float@test.com"})
float_rec = adapter.find(ctx_get).as(Array(Hash(String, Alumna::AnyData))).first
float_rec["tenant_id"].should eq(99_i64) # 99.9 safely truncated to 99
end
end

Spec.after_suite do
File.delete(FILE_PATH) if File.exists?(FILE_PATH)
end
Loading
Loading