Skip to content

feat(db): integrity CHECK constraints, financial-FK Restrict & index right-sizing#53

Open
kashkoool wants to merge 1 commit into
mainfrom
feat/db-integrity-hardening
Open

feat(db): integrity CHECK constraints, financial-FK Restrict & index right-sizing#53
kashkoool wants to merge 1 commit into
mainfrom
feat/db-integrity-hardening

Conversation

@kashkoool

@kashkoool kashkoool commented Jun 8, 2026

Copy link
Copy Markdown
Owner

What

Defence-in-depth schema hardening from the full DB audit. Additive migration, no data loss.

Integrity (DB-level CHECK constraints)

Back the domain rules at the database so impossible values are rejected even if app code is bypassed:

  • Money >= 0 on trip.Price, booking.Total/Discount, payment.Amount, refund.Amount, promo_code.DiscountValue
  • review.Rating BETWEEN 1 AND 5
  • Currency ~ '^[A-Z]{3}$' on the money tables
  • Status enums limited to their valid set (trip/booking/payment/refund/company)

Protect financial history

  • payment → booking and refund → payment FKs changed from Cascade → Restrict (deleting a booking can no longer silently erase its payment/refund records)
  • refund.BookingId was an unconstrained column → now a real FK (Restrict) + IX_refund_BookingId

Index right-sizing (faster reads, no extra write cost)

  • Add composites: booking(TripId, Status), booking(CreatedBy, CreatedAtUtc), trip(CompanyId, DepartureUtc), AspNetUsers(CompanyId, StaffType)
  • Drop redundant single-column indexes (each a strict prefix of a new composite): booking TripId/Status/CreatedBy and the standalone trip/AspNetUsers CompanyId — trims the write-heavy booking table from 7 → 6 indexes

Tests

New DbConstraintsTests assert the CHECKs reject bad values and the Restrict FK blocks deleting a booking that has a payment. Full suite green: 65 unit + 65 integration.

Notes

  • The promo-redemption path was reviewed and is already race-safe (atomic conditional UPDATE) — unchanged.
  • Out of scope (deferred): immutable audit-log / soft-delete.

Summary by CodeRabbit

  • Chores

    • Strengthened data validation with database-level integrity checks for amounts, currencies, and status values across key entities.
    • Optimized database indexes to improve system performance for frequently accessed data.
    • Applied schema changes through migration to ensure data consistency and prevent invalid states.
  • Tests

    • Added integration tests to verify database constraint enforcement works correctly.

…right-sizing

Defence-in-depth schema hardening from the DB audit (no data loss; additive migration):

- CHECK constraints backing the domain rules: money >= 0 (trip/booking/payment/refund/promo),
  review.Rating BETWEEN 1 AND 5, currency ~ '^[A-Z]{3}$', and status enums limited to their
  valid set (trip/booking/payment/refund/company).
- Protect financial history: payment->booking and refund->payment FKs are now Restrict, and the
  refund.BookingId column gains a real FK (Restrict) + IX_refund_BookingId.
- Index right-sizing on the write-heavy booking table and reports: add (TripId, Status),
  (CreatedBy, CreatedAtUtc), trip(CompanyId, DepartureUtc), AspNetUsers(CompanyId, StaffType);
  drop the redundant single-column booking TripId/Status/CreatedBy and standalone CompanyId
  indexes (each a strict prefix of a new composite) -> faster reads, no extra write cost.
- DbConstraintsTests assert the CHECKs reject bad values and the Restrict FK blocks deleting a
  booking that has a payment. Full suite green: 65 unit + 65 integration.
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR hardens the PostgreSQL schema with database-level integrity constraints and optimizes indexes across hot tables. It adds CHECK constraints for non-negative amounts, valid currency format (3 uppercase letters), allowed status/rating ranges, and valid enum values; changes payment and refund FK delete behavior from CASCADE to RESTRICT to preserve financial history; introduces a new refund-booking FK; and replaces single-column indexes with right-sized composites for reporting and company/trip query patterns.

Changes

Database Integrity Hardening and Index Optimization

Layer / File(s) Summary
CHECK constraints for domain invariants
src/TransportPlatform.Infrastructure/Persistence/Configurations/BookingConfiguration.cs, PaymentConfiguration.cs, RefundConfiguration.cs, ReviewConfiguration.cs, TripConfiguration.cs, CompanyConfiguration.cs, PromoCodeConfiguration.cs
Booking, Payment, Refund, Review, Trip, Company, and PromoCode configurations add table-level CHECK constraints enforcing non-negative amounts/prices/discounts, 3-letter uppercase currency, valid booking/refund/trip statuses, review rating 1–5, and company status set.
Foreign key delete behavior and Refund-Booking relationship
src/TransportPlatform.Infrastructure/Persistence/Configurations/PaymentConfiguration.cs, RefundConfiguration.cs
Payment BookingId FK changes from CASCADE to RESTRICT; Refund adds new FK to Booking with RESTRICT, and changes Payment FK from CASCADE to RESTRICT, preventing financial history deletion when a booking is deleted.
Index optimization for query patterns
src/TransportPlatform.Infrastructure/Persistence/Configurations/ApplicationUserConfiguration.cs, BookingConfiguration.cs, TripConfiguration.cs
ApplicationUser uses composite (CompanyId, StaffType) index; Booking uses (TripId, Status) and (CreatedBy, CreatedAtUtc) composites; Trip uses (CompanyId, DepartureUtc) composite. Single-column indexes are dropped; CreatedAtUtc index retained for date-range scans.
Migration orchestration
src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs
Migration Up drops old FKs and single-column indexes, adds CHECK constraints across all entities, creates new composite indexes and refund-booking FK, re-adds FKs with RESTRICT behavior. Down reverses all changes: drops FKs, indexes, and constraints; recreates prior single-column indexes; restores CASCADE behavior.
Integration test validation
tests/TransportPlatform.IntegrationTests/DbConstraintsTests.cs
Three tests validate database constraint enforcement: FK RESTRICT prevents booking deletion with existing payments; CHECK constraint rejects negative payment amounts; CHECK constraint rejects review ratings outside 1–5 range. Includes SeedPendingBookingAsync and reflection-based ForcePrivate helper for constraint testing.
Migration and schema documentation
PROJECT_STATUS.md
Documents AddIntegrityConstraintsAndIndexTuning migration, new CHECK constraints, RESTRICT delete behavior, refund-booking FK introduction, and index strategy. Schema descriptions for Booking, Payment, and Refund updated to reflect constraints and composite indexes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • kashkoool/Transport#40: Both PRs update the Booking persistence model; #40 introduces Booking.DiscountAmount and promo-code discounting flow, while this PR adds CHECK constraints (including non-negative discount) and BookingConfiguration index tuning—directly coupled at the schema level.
  • kashkoool/Transport#18: Both PRs modify Company.Status handling and Trip indexing strategy via CompanyConfiguration/TripConfiguration and migration-level index changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding database integrity CHECK constraints, changing financial FK delete behavior to Restrict, and optimizing indexes through right-sizing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/db-integrity-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@PROJECT_STATUS.md`:
- Around line 131-136: The summary overstates test coverage by saying "Covered
by `DbConstraintsTests`" even though `DbConstraintsTests` only verifies a subset
(e.g., RESTRICT delete behavior, payment amount CHECK, review rating CHECK);
update the wording in the `AddIntegrityConstraintsAndIndexTuning` section to
indicate partial rather than full coverage (e.g., "Partial coverage by
`DbConstraintsTests` — tests validate RESTRICT delete, payment amount CHECK, and
review rating CHECK"), and mention remaining untested items (currency regex,
status enums, refund→booking FK, index changes) so readers know what
`DbConstraintsTests` actually covers.

In
`@src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs`:
- Around line 46-155: This migration adds multiple validating AddCheckConstraint
and AddForeignKey calls (see AddCheckConstraint for "CK_*" constraints and
AddForeignKey calls "FK_payment_booking_BookingId",
"FK_refund_booking_BookingId") which will fail if existing data violates them;
change the AddCheckConstraint/AddForeignKey work to create constraints NOT VALID
(use raw SQL via migrationBuilder.Sql to run ALTER TABLE ... ADD CONSTRAINT
<name> ... NOT VALID for each "CK_*" and ALTER TABLE ... ADD CONSTRAINT <fkname>
FOREIGN KEY (...) REFERENCES ... NOT VALID for the FK pairs), ensure any needed
indexes (e.g., IX_refund_BookingId, IX_booking_TripId_Status) are created first,
then add a separate follow-up migration that runs ALTER TABLE ... VALIDATE
CONSTRAINT <name> (and validates FKs) after data backfill/cleanup so the rollout
is phased and won’t fail on existing bad rows.

In `@tests/TransportPlatform.IntegrationTests/DbConstraintsTests.cs`:
- Around line 37-38: Replace the broad
Assert.ThrowsAnyAsync<DbUpdateException>(() => db.SaveChangesAsync()) checks
with an Assert.ThrowsAsync<DbUpdateException> that captures the exception (e.g.,
var ex = await Assert.ThrowsAsync<DbUpdateException>(...)) and then assert the
specific constraint/foreign-key identifier in the underlying DB error
message/inner exception (e.g., Assert.Contains("EXPECTED_CONSTRAINT_NAME",
ex.InnerException?.Message ?? ex.Message)); apply the same change for the other
occurrences referenced (lines shown as 53-54 and 68-69) so each test explicitly
verifies the expected constraint/FK name rather than any DB update error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: afb97c1c-9fb6-4b48-8a40-c1950cf972b2

📥 Commits

Reviewing files that changed from the base of the PR and between b2c88ea and d76ad78.

⛔ Files ignored due to path filters (2)
  • src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.Designer.cs is excluded by !**/Migrations/*.Designer.cs
  • src/TransportPlatform.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs is excluded by !**/Migrations/*ModelSnapshot.cs
📒 Files selected for processing (11)
  • PROJECT_STATUS.md
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/ApplicationUserConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/BookingConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/CompanyConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/PaymentConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/PromoCodeConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/RefundConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/ReviewConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Configurations/TripConfiguration.cs
  • src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs
  • tests/TransportPlatform.IntegrationTests/DbConstraintsTests.cs

Comment thread PROJECT_STATUS.md
Comment on lines +131 to +136
> **DB integrity hardening** (migration `AddIntegrityConstraintsAndIndexTuning`): DB-level **CHECK
> constraints** now back the domain rules (money ≥ 0, `review.Rating` 1–5, currency `^[A-Z]{3}$`,
> status enums in their valid set); **payment/refund FKs are `Restrict`** + a real `refund→booking`
> FK so financial history can't be deleted; and `booking`/`trip`/`AspNetUsers` indexes were
> **right-sized** (report composites added, redundant single-column indexes dropped — faster reads,
> no extra write cost). Covered by `DbConstraintsTests`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid overstating test coverage in the hardening summary.

At Line 136, “Covered by DbConstraintsTests” reads as full coverage, but tests currently validate a subset (RESTRICT delete, payment amount CHECK, review rating CHECK). Consider clarifying this as partial coverage.

Suggested wording
-> **right-sized** (report composites added, redundant single-column indexes dropped — faster reads,
-> no extra write cost). Covered by `DbConstraintsTests`.
+> **right-sized** (report composites added, redundant single-column indexes dropped — faster reads,
+> no extra write cost). Partially covered by `DbConstraintsTests` (RESTRICT + representative CHECKs).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PROJECT_STATUS.md` around lines 131 - 136, The summary overstates test
coverage by saying "Covered by `DbConstraintsTests`" even though
`DbConstraintsTests` only verifies a subset (e.g., RESTRICT delete behavior,
payment amount CHECK, review rating CHECK); update the wording in the
`AddIntegrityConstraintsAndIndexTuning` section to indicate partial rather than
full coverage (e.g., "Partial coverage by `DbConstraintsTests` — tests validate
RESTRICT delete, payment amount CHECK, and review rating CHECK"), and mention
remaining untested items (currency regex, status enums, refund→booking FK, index
changes) so readers know what `DbConstraintsTests` actually covers.

Comment on lines +46 to +155
migrationBuilder.AddCheckConstraint(
name: "CK_trip_currency_format",
table: "trip",
sql: "\"Currency\" ~ '^[A-Z]{3}$'");

migrationBuilder.AddCheckConstraint(
name: "CK_trip_price_nonneg",
table: "trip",
sql: "\"Price\" >= 0");

migrationBuilder.AddCheckConstraint(
name: "CK_trip_status_valid",
table: "trip",
sql: "\"Status\" IN ('Scheduled', 'InProgress', 'Completed', 'Cancelled')");

migrationBuilder.AddCheckConstraint(
name: "CK_review_rating_range",
table: "review",
sql: "\"Rating\" BETWEEN 1 AND 5");

migrationBuilder.CreateIndex(
name: "IX_refund_BookingId",
table: "refund",
column: "BookingId");

migrationBuilder.AddCheckConstraint(
name: "CK_refund_amount_nonneg",
table: "refund",
sql: "\"Amount\" >= 0");

migrationBuilder.AddCheckConstraint(
name: "CK_refund_currency_format",
table: "refund",
sql: "\"Currency\" ~ '^[A-Z]{3}$'");

migrationBuilder.AddCheckConstraint(
name: "CK_refund_status_valid",
table: "refund",
sql: "\"Status\" IN ('Pending', 'Completed', 'Failed')");

migrationBuilder.AddCheckConstraint(
name: "CK_promo_code_discount_nonneg",
table: "promo_code",
sql: "\"DiscountValue\" >= 0");

migrationBuilder.AddCheckConstraint(
name: "CK_payment_amount_nonneg",
table: "payment",
sql: "\"Amount\" >= 0");

migrationBuilder.AddCheckConstraint(
name: "CK_payment_currency_format",
table: "payment",
sql: "\"Currency\" ~ '^[A-Z]{3}$'");

migrationBuilder.AddCheckConstraint(
name: "CK_payment_status_valid",
table: "payment",
sql: "\"Status\" IN ('Pending', 'Completed', 'Failed', 'Refunded')");

migrationBuilder.AddCheckConstraint(
name: "CK_company_status_valid",
table: "company",
sql: "\"Status\" IN ('Pending', 'Active', 'Suspended')");

migrationBuilder.CreateIndex(
name: "IX_booking_CreatedBy_CreatedAtUtc",
table: "booking",
columns: new[] { "CreatedBy", "CreatedAtUtc" });

migrationBuilder.CreateIndex(
name: "IX_booking_TripId_Status",
table: "booking",
columns: new[] { "TripId", "Status" });

migrationBuilder.AddCheckConstraint(
name: "CK_booking_amounts_nonneg",
table: "booking",
sql: "\"TotalAmount\" >= 0 AND \"DiscountAmount\" >= 0");

migrationBuilder.AddCheckConstraint(
name: "CK_booking_currency_format",
table: "booking",
sql: "\"Currency\" ~ '^[A-Z]{3}$'");

migrationBuilder.AddCheckConstraint(
name: "CK_booking_status_valid",
table: "booking",
sql: "\"Status\" IN ('PendingPayment', 'Confirmed', 'Cancelled', 'Expired')");

migrationBuilder.CreateIndex(
name: "IX_AspNetUsers_CompanyId_StaffType",
table: "AspNetUsers",
columns: new[] { "CompanyId", "StaffType" });

migrationBuilder.AddForeignKey(
name: "FK_payment_booking_BookingId",
table: "payment",
column: "BookingId",
principalTable: "booking",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);

migrationBuilder.AddForeignKey(
name: "FK_refund_booking_BookingId",
table: "refund",
column: "BookingId",
principalTable: "booking",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the migration file and print relevant parts with line numbers
FILE="src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

echo "=== File: $FILE ==="
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n

echo
echo "=== Search for NOT VALID / VALIDATE in this migration ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|NOT VALID|not valid|NotValid" "$FILE" || true

echo
echo "=== Search for any project-wide pattern for phased constraint rollout ==="
# Look for similar phrases in all migrations (bounded output by filtering)
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|notValid" src/TransportPlatform.Infrastructure/Persistence/Migrations || true

Repository: kashkoool/Transport

Length of output: 9830


🏁 Script executed:

[capability:shell]
#!/bin/bash
set -euo pipefail

FILE="src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

echo "=== File: $FILE ==="
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n

echo
echo "=== Search for NOT VALID / VALIDATE in this migration ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|NOT VALID|not valid|NotValid" "$FILE" || true

echo
echo "=== Search for any project-wide pattern for phased constraint rollout ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|notValid" src/TransportPlatform.Infrastructure/Persistence/Migrations || true

Repository: kashkoool/Transport

Length of output: 9905


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

echo "=== File: $FILE ==="
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n

echo
echo "=== Search for NOT VALID / VALIDATE in this migration ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|NOT VALID|not valid|NotValid" "$FILE" || true

echo
echo "=== Search for any project-wide pattern for phased constraint rollout ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|notValid" src/TransportPlatform.Infrastructure/Persistence/Migrations || true

Repository: kashkoool/Transport

Length of output: 9830


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

echo "=== File: $FILE ==="
wc -l "$FILE"
sed -n '1,220p' "$FILE" | cat -n

echo
echo "=== Search for NOT VALID / VALIDATE in this migration ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|NOT VALID|not valid|NotValid" "$FILE" || true

echo
echo "=== Search for any project-wide pattern for phased constraint rollout ==="
rg -n "NOT VALID|VALIDATE CONSTRAINT|ValidateConstraint|notValid" src/TransportPlatform.Infrastructure/Persistence/Migrations || true

Repository: kashkoool/Transport

Length of output: 9830


Major rollout risk: validating CHECK constraints + FKs in a single migration
Up(...) adds multiple validating AddCheckConstraints (trip/review/refund/promo_code/payment/company/booking) and also adds/validates foreign keys with Restrict (payment.BookingId -> booking.Id, refund.BookingId -> booking.Id, refund.PaymentId -> payment.Id) in one step, with no phased NOT VALID + later VALIDATE (or preflight/backfill) path—any existing non-conforming row/orphaned reference will fail the migration at apply time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/TransportPlatform.Infrastructure/Persistence/Migrations/20260608041228_AddIntegrityConstraintsAndIndexTuning.cs`
around lines 46 - 155, This migration adds multiple validating
AddCheckConstraint and AddForeignKey calls (see AddCheckConstraint for "CK_*"
constraints and AddForeignKey calls "FK_payment_booking_BookingId",
"FK_refund_booking_BookingId") which will fail if existing data violates them;
change the AddCheckConstraint/AddForeignKey work to create constraints NOT VALID
(use raw SQL via migrationBuilder.Sql to run ALTER TABLE ... ADD CONSTRAINT
<name> ... NOT VALID for each "CK_*" and ALTER TABLE ... ADD CONSTRAINT <fkname>
FOREIGN KEY (...) REFERENCES ... NOT VALID for the FK pairs), ensure any needed
indexes (e.g., IX_refund_BookingId, IX_booking_TripId_Status) are created first,
then add a separate follow-up migration that runs ALTER TABLE ... VALIDATE
CONSTRAINT <name> (and validates FKs) after data backfill/cleanup so the rollout
is phased and won’t fail on existing bad rows.

Comment on lines +37 to +38
await Assert.ThrowsAnyAsync<DbUpdateException>(() => db.SaveChangesAsync());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten exception assertions to prove the exact constraint under test.

Assert.ThrowsAnyAsync<DbUpdateException> is too broad here; these tests can pass on unrelated DB errors and still look green. Assert the expected constraint/FK name per case.

Suggested change
- await Assert.ThrowsAnyAsync<DbUpdateException>(() => db.SaveChangesAsync());
+ var ex = await Assert.ThrowsAsync<DbUpdateException>(() => db.SaveChangesAsync());
+ Assert.Contains("FK_payment_booking_BookingId", ex.InnerException?.Message ?? ex.Message);

- await Assert.ThrowsAnyAsync<DbUpdateException>(() => db.SaveChangesAsync());
+ var ex = await Assert.ThrowsAsync<DbUpdateException>(() => db.SaveChangesAsync());
+ Assert.Contains("CK_payment_amount_nonneg", ex.InnerException?.Message ?? ex.Message);

- await Assert.ThrowsAnyAsync<DbUpdateException>(() => db.SaveChangesAsync());
+ var ex = await Assert.ThrowsAsync<DbUpdateException>(() => db.SaveChangesAsync());
+ Assert.Contains("CK_review_rating_range", ex.InnerException?.Message ?? ex.Message);

Also applies to: 53-54, 68-69

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/TransportPlatform.IntegrationTests/DbConstraintsTests.cs` around lines
37 - 38, Replace the broad Assert.ThrowsAnyAsync<DbUpdateException>(() =>
db.SaveChangesAsync()) checks with an Assert.ThrowsAsync<DbUpdateException> that
captures the exception (e.g., var ex = await
Assert.ThrowsAsync<DbUpdateException>(...)) and then assert the specific
constraint/foreign-key identifier in the underlying DB error message/inner
exception (e.g., Assert.Contains("EXPECTED_CONSTRAINT_NAME",
ex.InnerException?.Message ?? ex.Message)); apply the same change for the other
occurrences referenced (lines shown as 53-54 and 68-69) so each test explicitly
verifies the expected constraint/FK name rather than any DB update error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant