Skip to content
Merged
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
86 changes: 81 additions & 5 deletions packages/boxel-cli/tests/integration/realm-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,33 @@ async function establishBaseline(
await sleep(1100);
}

// Read a remote source file, retrying for up to `timeoutMs` if the body
// doesn't yet contain `expectedSubstring`. Returns the final body either way
// — callers assert on it. `retries` is the number of additional fetches
// performed beyond the first attempt (0 = matched on first read), so
// `retries > 0` is the unambiguous signal that the post-sync read had to
// wait for the realm to settle. `elapsedMs` is for context only; it tracks
// network + sleep, so it is non-zero even on a first-shot success.
async function fetchRemoteFileEventually(
realmUrl: string,
relPath: string,
expectedSubstring: string,
timeoutMs = 2000,
): Promise<{ body: string; elapsedMs: number; retries: number }> {
const start = Date.now();
let attempts = 0;
let body = '';
while (Date.now() - start < timeoutMs) {
attempts++;
body = await fetchRemoteFile(realmUrl, relPath);
if (body.includes(expectedSubstring)) {
return { body, elapsedMs: Date.now() - start, retries: attempts - 1 };
}
await sleep(100);
}
return { body, elapsedMs: Date.now() - start, retries: attempts - 1 };
}

beforeAll(async () => {
await startTestRealmServer();

Expand Down Expand Up @@ -254,16 +281,45 @@ describe('realm sync (integration)', () => {
'export const v = "remote";\n',
);

// Pre-sync snapshot — captures whether each side actually has the
// content we just wrote. If a future failure shows mismatched state
// here, the bug is upstream of sync (writeLocalFile / writeRemoteFile
// didn't land), not in conflict resolution.
const preLocal = readLocalFile(localDir, 'conflict.gts');
const preRemote = await fetchRemoteFile(realmUrl, 'conflict.gts');

await sync(localDir, realmUrl, {
preferLocal: true,
profileManager,
});

// Local version should win
expect(await fetchRemoteFile(realmUrl, 'conflict.gts')).toContain(
// Poll-retry to distinguish "sync didn't push" from "push landed but a
// brief visibility race made the GET read stale bytes". `retries > 0`
// on a passing assertion points at the latter; a miss with retries
// exhausted means the bytes never settled within the timeout.
const {
body: remoteAfter,
elapsedMs,
retries,
} = await fetchRemoteFileEventually(
realmUrl,
'conflict.gts',
'v = "local"',
);
expect(readLocalFile(localDir, 'conflict.gts')).toContain('v = "local"');
const localAfter = readLocalFile(localDir, 'conflict.gts');

if (!remoteAfter.includes('v = "local"')) {
console.error(
`[conflict-prefer-local diagnostics] preLocal=${JSON.stringify(preLocal)} ` +
`preRemote=${JSON.stringify(preRemote)} ` +
`localAfter=${JSON.stringify(localAfter)} ` +
`remoteAfter=${JSON.stringify(remoteAfter)} ` +
`retries=${retries} elapsedMs=${elapsedMs} realmUrl=${realmUrl}`,
);
}

expect(remoteAfter).toContain('v = "local"');
expect(localAfter).toContain('v = "local"');
});

it('resolves conflict with --prefer-remote: remote version wins', async () => {
Expand All @@ -282,13 +338,33 @@ describe('realm sync (integration)', () => {
'export const v = "remote";\n',
);

const preLocal = readLocalFile(localDir, 'conflict.gts');
const preRemote = await fetchRemoteFile(realmUrl, 'conflict.gts');

await sync(localDir, realmUrl, {
preferRemote: true,
profileManager,
});

// Remote version should win
expect(readLocalFile(localDir, 'conflict.gts')).toContain('v = "remote"');
// For prefer-remote the local file is overwritten by the pulled
// remote bytes. The local-side read is a direct fs read with no
// visibility race, so no retry helper is needed — but the diagnostic
// dump on miss mirrors the prefer-local test so a regression on
// either side surfaces the same shape of evidence.
const localAfter = readLocalFile(localDir, 'conflict.gts');
const remoteAfter = await fetchRemoteFile(realmUrl, 'conflict.gts');

if (!localAfter.includes('v = "remote"')) {
console.error(
`[conflict-prefer-remote diagnostics] preLocal=${JSON.stringify(preLocal)} ` +
`preRemote=${JSON.stringify(preRemote)} ` +
`localAfter=${JSON.stringify(localAfter)} ` +
`remoteAfter=${JSON.stringify(remoteAfter)} ` +
`realmUrl=${realmUrl}`,
);
}

expect(localAfter).toContain('v = "remote"');
});

it('deletes remote file when local is deleted with --prefer-local', async () => {
Expand Down
Loading