A successful import can still update the wrong customer’s record. A retry can quietly restore an older email address. Before a Rails partner integration goes live, its acceptance tests should establish who may change which record, which version wins and what an operator can recover after only part of a batch completes.
This example imports contacts from a synthetic partner directory. It uses Active Record 8.1.3.1, Ruby 3.3.3 and a temporary SQLite database. Eight tests passed with 44 assertions on 20 September 2026, including controls that deliberately reproduce unsafe behavior. The example and its execution were prepared with AI assistance. They represent a local experiment, not a customer implementation or a partner certification.
Write down what the partner owns
In this fixture, the partner owns a contact’s display name and email address. The Rails application owns tenant membership and administrative privileges. Incoming records carry a contact ID, event ID and positive integer source version. Versions increase for each contact; a repeated version with different mapped content is a conflict that needs investigation.
Those are chosen contract rules. A real partner might offer timestamps, change tokens or no ordering guarantee at all. Establish that before borrowing the comparison logic. A field named version does not prove it orders changes.
The adapter receives its allowed tenant IDs from a trusted principal supplied by the calling application. It ignores incoming administrative and tenant attributes and maps only the partner-owned fields. This tests a permission decision after authentication. It does not implement authentication, token verification or every route through a production application.
Make the unsafe lookup fail visibly
The first control creates a contact in tenant 20, then processes a payload through an adapter that looks up only remote_id. It overwrites that tenant’s contact. The test requires the corruption to occur, so the control cannot pass merely because its setup failed to reach the vulnerable lookup.
The corrected adapter looks up the tuple of tenant, partner and remote contact ID. The same remote ID can then identify separate contacts in tenants 10 and 20. A database unique index prevents duplicate identities within that tuple. A separate permission test denies an unauthorized tenant before creating either a contact or an import result.
This establishes selected isolation checks. It does not establish that a multitenant application is secure merely because one adapter scopes its lookup.
Separate old partner data from a stale Rails object
Another unsafe control accepts version 2 after version 3 and overwrites the current name. The corrected path rejects the older version. An identical version and content produces no update; equal versions with different content create a conflict result.
Rails optimistic locking handles a different problem. With a lock_version column, two loaded copies cannot both save over the same intervening update without a stale-object error. The fixture loads two copies, saves the first and confirms that saving the second raises ActiveRecord::StaleObjectError. This follows the tagged Rails implementation.
The partner version expresses source order. The Rails lock version detects an intervening database update. Neither supplies the other’s meaning. This run used sequentially interleaved objects, not concurrent worker processes or a contention benchmark.
Recover a partially processed batch
The example commits each contact and its import result together. Rails transactions protect operations on the same database connection; they do not make remote systems part of that transaction. Active Record transaction documentation
The batch contains a valid contact, an invalid email and another valid contact. An injected interruption occurs after the first two records. Replaying the batch retains the first success and the recorded rejection, then imports the unseen third record. The result is two contacts and three import results, rather than a misleading claim that the entire batch succeeded.
| Acceptance case | Recorded corrected result |
|---|---|
| Same event ID and mapped content repeated | Existing result returned; contact unchanged |
| Same event ID reused with different content | Conflict; existing contact retained |
| Failure between contact save and result save | Both database writes rolled back |
| Rejected contact corrected under a new event ID | Corrected contact imported |
The discrepancy report gives rejected contact IDs, reasons and an operator action. Reusing an event ID with changed content returns a conflict without recording another result; a real caller must also retain that response for investigation.
Agree partner integration acceptance criteria
Before accepting a real connector, select representative records and make the owning team agree the mapping, source authority, conflict policy and correction process. Run the checks against its actual database and access controls. Add pagination, rate limiting, deletion and remote failure cases where the interface requires them; this experiment does not cover those behaviors.
The executable fixture and evidence record preserve the negative controls and limits. A delivery discussion can start with one interface and its acceptance cases through Allerin’s Rails practice. The meaningful handover is a connector whose selected failure cases the next engineer can reproduce and resolve.
Download the experiment
Download the runnable Rails examples and evidence summary (ZIP, 36 KB). This article’s example is in q03/; the shared setup and reproduction instructions are in README.md. The package contains eight synthetic examples, checked on 20 September 2026. The recorded runtime, provider fakes and limits are documented inside.