|
1 | 1 | import { describe, vi, expect, beforeEach } from 'vitest'; |
| 2 | +import util from 'node:util'; |
2 | 3 |
|
3 | 4 | import { MockSyncService, mockSyncServiceTest, TestConnector, waitForSyncStatus } from './utils'; |
4 | 5 | import { |
5 | 6 | AbstractPowerSyncDatabase, |
6 | 7 | BucketChecksum, |
| 8 | + createLogger, |
7 | 9 | OplogEntryJSON, |
8 | 10 | PowerSyncConnectionOptions, |
9 | 11 | ProgressWithOperations, |
@@ -537,6 +539,84 @@ function defineSyncTests(impl: SyncClientImplementation) { |
537 | 539 | expect(rows).toStrictEqual([{ name: 'from server' }]); |
538 | 540 | }); |
539 | 541 |
|
| 542 | + mockSyncServiceTest('handles uploads across checkpoints', async ({ syncService }) => { |
| 543 | + const logger = createLogger('test', { logLevel: Logger.TRACE }); |
| 544 | + const logMessages: string[] = []; |
| 545 | + (logger as any).invoke = (level, args) => { |
| 546 | + console.log(...args); |
| 547 | + logMessages.push(util.format(...args)); |
| 548 | + }; |
| 549 | + |
| 550 | + // Regression test for https://github.com/powersync-ja/powersync-js/pull/665 |
| 551 | + let database = await syncService.createDatabase({ logger }); |
| 552 | + const connector = new TestConnector(); |
| 553 | + let finishUpload: () => void; |
| 554 | + const finishUploadPromise = new Promise<void>((resolve, reject) => { |
| 555 | + finishUpload = resolve; |
| 556 | + }); |
| 557 | + connector.uploadData = async (db) => { |
| 558 | + const batch = await db.getCrudBatch(); |
| 559 | + if (batch != null) { |
| 560 | + await finishUploadPromise; |
| 561 | + await batch.complete(); |
| 562 | + } |
| 563 | + }; |
| 564 | + |
| 565 | + await database.execute('INSERT INTO lists (id, name) VALUES (uuid(), ?);', ['local']); |
| 566 | + database.connect(connector, options); |
| 567 | + await vi.waitFor(() => expect(syncService.connectedListeners).toHaveLength(1)); |
| 568 | + |
| 569 | + syncService.pushLine({ checkpoint: { last_op_id: '1', write_checkpoint: '1', buckets: [bucket('a', 1)] } }); |
| 570 | + syncService.pushLine({ |
| 571 | + data: { |
| 572 | + bucket: 'a', |
| 573 | + data: [ |
| 574 | + { |
| 575 | + checksum: 0, |
| 576 | + op_id: '1', |
| 577 | + op: 'PUT', |
| 578 | + object_id: '1', |
| 579 | + object_type: 'lists', |
| 580 | + data: '{"name": "s1"}' |
| 581 | + } |
| 582 | + ] |
| 583 | + } |
| 584 | + }); |
| 585 | + // 1. Could not apply checkpoint due to local data. We will retry [...] after that upload is completed. |
| 586 | + syncService.pushLine({ checkpoint_complete: { last_op_id: '1' } }); |
| 587 | + await vi.waitFor(() => { |
| 588 | + expect(logMessages).toEqual(expect.arrayContaining([expect.stringContaining('due to local data')])); |
| 589 | + }); |
| 590 | + |
| 591 | + // 2. Send additional checkpoint while we're still busy uploading |
| 592 | + syncService.pushLine({ checkpoint: { last_op_id: '2', write_checkpoint: '2', buckets: [bucket('a', 2)] } }); |
| 593 | + syncService.pushLine({ |
| 594 | + data: { |
| 595 | + bucket: 'a', |
| 596 | + data: [ |
| 597 | + { |
| 598 | + checksum: 0, |
| 599 | + op_id: '2', |
| 600 | + op: 'PUT', |
| 601 | + object_id: '2', |
| 602 | + object_type: 'lists', |
| 603 | + data: '{"name": "s2"}' |
| 604 | + } |
| 605 | + ] |
| 606 | + } |
| 607 | + }); |
| 608 | + syncService.pushLine({ checkpoint_complete: { last_op_id: '2' } }); |
| 609 | + |
| 610 | + // 3. Crud upload complete |
| 611 | + finishUpload!(); |
| 612 | + |
| 613 | + // 4. Ensure the database is applying the second checkpoint |
| 614 | + await vi.waitFor(async () => { |
| 615 | + const rows = await database.getAll('SELECT * FROM lists WHERE name = ?', ['s2']); |
| 616 | + expect(rows).toHaveLength(1); |
| 617 | + }); |
| 618 | + }); |
| 619 | + |
540 | 620 | mockSyncServiceTest('should update sync state incrementally', async ({ syncService }) => { |
541 | 621 | const powersync = await syncService.createDatabase(); |
542 | 622 | powersync.connect(new TestConnector(), options); |
|
0 commit comments