(t *testing.T)
| 3608 | } |
| 3609 | |
| 3610 | func TestSync_FreshClientConcurrent(t *testing.T) { |
| 3611 | // Test the core issue: Fresh client (never synced, lastMaxUSN=0) syncing to a server |
| 3612 | // that already has data uploaded by another client. |
| 3613 | // |
| 3614 | // Scenario: |
| 3615 | // 1. Client A creates local notes (never synced, lastMaxUSN=0, lastSyncAt=0) |
| 3616 | // 2. Client B uploads same book names to server first |
| 3617 | // 3. Client A syncs |
| 3618 | // |
| 3619 | // Expected: Client A should pull server data first, detect duplicate book names, |
| 3620 | // rename local books to avoid conflicts (js→js_2), then upload successfully. |
| 3621 | |
| 3622 | env := setupTestEnv(t) |
| 3623 | |
| 3624 | user := setupUserAndLogin(t, env) |
| 3625 | |
| 3626 | // Client A: Create local data (never sync) |
| 3627 | clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") |
| 3628 | clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") |
| 3629 | |
| 3630 | // Client B: Upload same book names to server via API |
| 3631 | jsBookUUID := apiCreateBook(t, env, user, "js", "client B creating js book") |
| 3632 | cssBookUUID := apiCreateBook(t, env, user, "css", "client B creating css book") |
| 3633 | apiCreateNote(t, env, user, jsBookUUID, "js2", "client B note") |
| 3634 | apiCreateNote(t, env, user, cssBookUUID, "css2", "client B note") |
| 3635 | |
| 3636 | // Client A syncs - should handle the conflict gracefully |
| 3637 | // Expected: pulls server data, renames local books to js_2/css_2, uploads successfully |
| 3638 | clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") |
| 3639 | |
| 3640 | // Verify: Should have 4 books and 4 notes on both client and server |
| 3641 | // USN breakdown: 2 books + 2 notes from Client B (USN 1-4), then 2 books + 2 notes from Client A (USN 5-8) |
| 3642 | checkState(t, env.DB, user, env.ServerDB, systemState{ |
| 3643 | clientNoteCount: 4, |
| 3644 | clientBookCount: 4, |
| 3645 | clientLastMaxUSN: 8, |
| 3646 | clientLastSyncAt: serverTime.Unix(), |
| 3647 | serverNoteCount: 4, |
| 3648 | serverBookCount: 4, |
| 3649 | serverUserMaxUSN: 8, |
| 3650 | }) |
| 3651 | |
| 3652 | // Verify server has all 4 books with correct names |
| 3653 | var svrBookJS, svrBookCSS, svrBookJS2, svrBookCSS2 database.Book |
| 3654 | apitest.MustExec(t, env.ServerDB.Where("label = ?", "js").First(&svrBookJS), "finding server book 'js'") |
| 3655 | apitest.MustExec(t, env.ServerDB.Where("label = ?", "css").First(&svrBookCSS), "finding server book 'css'") |
| 3656 | apitest.MustExec(t, env.ServerDB.Where("label = ?", "js_2").First(&svrBookJS2), "finding server book 'js_2'") |
| 3657 | apitest.MustExec(t, env.ServerDB.Where("label = ?", "css_2").First(&svrBookCSS2), "finding server book 'css_2'") |
| 3658 | |
| 3659 | assert.Equal(t, svrBookJS.Label, "js", "server should have book 'js' (Client B)") |
| 3660 | assert.Equal(t, svrBookCSS.Label, "css", "server should have book 'css' (Client B)") |
| 3661 | assert.Equal(t, svrBookJS2.Label, "js_2", "server should have book 'js_2' (Client A renamed)") |
| 3662 | assert.Equal(t, svrBookCSS2.Label, "css_2", "server should have book 'css_2' (Client A renamed)") |
| 3663 | |
| 3664 | // Verify server has all 4 notes with correct content |
| 3665 | var svrNoteJS1, svrNoteJS2, svrNoteCSS1, svrNoteCSS2 database.Note |
| 3666 | apitest.MustExec(t, env.ServerDB.Where("body = ?", "js1").First(&svrNoteJS1), "finding server note 'js1'") |
| 3667 | apitest.MustExec(t, env.ServerDB.Where("body = ?", "js2").First(&svrNoteJS2), "finding server note 'js2'") |
nothing calls this directly
no test coverage detected