diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-10 14:21:46 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-10 14:21:46 +0200 |
| commit | 2f2a9a6542d2194e50ffba6f382e4fdffd481838 (patch) | |
| tree | 5437330f9e8b963f8af0904f3c5d80d1afc6dc3b /packages/meshbay-hub/tests/test_phone_sync.py | |
| parent | 914d2f0af848fb80d70d517901f44420a4e47764 (diff) | |
| download | meshbay-2f2a9a6542d2194e50ffba6f382e4fdffd481838.tar.gz | |
feat(android): one backup destination, a group the account owns alone
Chosen once at the top of Android Sync for every kind; photos and contacts
go into <folder>/<account>-photos and -contacts. Owner and sole member are
checked at set-up and before every run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_phone_sync.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_phone_sync.py | 45 |
1 files changed, 32 insertions, 13 deletions
diff --git a/packages/meshbay-hub/tests/test_phone_sync.py b/packages/meshbay-hub/tests/test_phone_sync.py index 7e5a88e..1a35614 100644 --- a/packages/meshbay-hub/tests/test_phone_sync.py +++ b/packages/meshbay-hub/tests/test_phone_sync.py @@ -43,7 +43,8 @@ export class ConnectionPool { PRELUDE = r""" const fake = globalThis.fake = { log: [], pools: [], refuseConnect: null, uploadError: null, - members: [{ username: 'bob' }], invited: [], membersError: null }; + members: [{ user_id: 'u-bob', username: 'bob' }], invited: [], + adminId: 'u-bob', membersError: null }; const win = new EventTarget(); const doc = new EventTarget(); doc.visibilityState = 'visible'; @@ -56,8 +57,8 @@ globalThis.fetch = async (url) => { }; fake.status = { - permission: 'granted', sent: 0, now: Date.now(), - config: { account: 'bob', groupId: 'g1', groupName: 'Mine', owner: 'bob', folder: 'Backups' }, + permission: 'granted', sent: 0, now: Date.now(), on: true, + destination: { account: 'bob', groupId: 'g1', groupName: 'Mine', folder: 'Backups' }, dir: 'Backups/bob-contacts', lastCompleted: null, lastSent: null, failure: null, failureAt: null, }; @@ -86,7 +87,7 @@ fake.transport = { const hubFetch = async (path) => { fake.log.push(['hub', path]); if (fake.membersError) throw new Error(fake.membersError); - return { members: fake.members, invited: fake.invited }; + return { admin_id: fake.adminId, members: fake.members, invited: fake.invited }; }; win.meshbay = { contactSync: { status: async () => fake.status, @@ -111,6 +112,7 @@ def _run(body, tmp_path): script.write_text( PRELUDE + f"const m = await import('{(root / 'phone-sync.js').as_uri()}');\n" + + f"const d = await import('{(root / 'sync-destination.js').as_uri()}');\n" + "const ctx = { hub: 'https://hub.invalid', hubFetch, getUser: () => ({ username: 'bob', " "userId: 'u-bob', token: 'tok' }), getBundleKey: async () => 'bk' };\n" + body @@ -183,8 +185,9 @@ def test_the_group_is_asked_about_at_every_run(tmp_path): @pytest.mark.parametrize("setup", [ - "fake.members = [{ username: 'bob' }, { username: 'carol' }];", - "fake.invited = [{ username: 'carol' }];", + "fake.members.push({ user_id: 'u-carol', username: 'carol' });", + "fake.invited = [{ user_id: 'u-carol', username: 'carol' }];", + "fake.adminId = 'u-carol';", ]) def test_somebody_else_in_the_group_stops_it_before_anything_is_read(tmp_path, setup): log = _run(_start(setup), tmp_path) @@ -202,15 +205,18 @@ def test_an_unreachable_hub_is_not_a_lasting_refusal(tmp_path): assert _of(log, "failed") == [] and _of(log, "plan") == [] -def test_only_groups_this_account_is_alone_in_are_offered(tmp_path): +def test_only_groups_this_account_owns_and_is_alone_in_are_offered(tmp_path): log = _run(""" m.startPhoneSync(ctx); const real = ctx.hubFetch; ctx.hubFetch = async (path) => path.includes('/g2/') - ? { members: [{ username: 'bob' }, { username: 'carol' }] } - : path.includes('/g3/') ? Promise.reject(new Error('offline')) : real(path); -fake.log.push(['offered', (await m.privateGroups( - [{ id: 'g1', name: 'Mine' }, { id: 'g2', name: 'Family' }, { id: 'g3', name: 'Far' }])) + ? { admin_id: 'u-bob', members: [{ user_id: 'u-bob' }, { user_id: 'u-carol' }] } + : path.includes('/g3/') ? Promise.reject(new Error('offline')) + : path.includes('/g4/') ? { admin_id: 'u-carol', members: [{ user_id: 'u-bob' }] } + : real(path); +fake.log.push(['offered', (await d.privateGroups( + [{ id: 'g1', name: 'Mine' }, { id: 'g2', name: 'Family' }, { id: 'g3', name: 'Far' }, + { id: 'g4', name: 'Given' }])) .map((g) => g.id)]); """, tmp_path) assert _of(log, "offered") == [[["g1"]]] @@ -239,7 +245,7 @@ def test_without_the_permission_it_stops_and_says_so(tmp_path): def test_another_account_on_the_phone_sends_nothing(tmp_path): - log = _run(_start("fake.status.config.account = 'alice';"), tmp_path) + log = _run(_start("fake.status.destination.account = 'alice';"), tmp_path) assert _of(log, "plan") == [] and _of(log, "hub") == [] @@ -247,7 +253,7 @@ def test_another_account_on_the_phone_sends_nothing(tmp_path): def test_every_writable_folder_is_offered_not_only_photo_ones(tmp_path): log = _run(""" -fake.log.push(['folders', m.writableFolders( +fake.log.push(['folders', d.writableFolders( { roots: [{ name: 'Media', writable: true, available: true }, { name: 'Archive', writable: false, available: true }, { name: 'Ejected', writable: true, available: false }], @@ -256,6 +262,19 @@ fake.log.push(['folders', m.writableFolders( assert _of(log, "folders") == [[["Media", "Media/Films", "Media/Photos"]]] +def test_photos_outside_the_photos_tab_folders_are_said(tmp_path): + log = _run(""" +fake.log.push(['shows', ['Media/Photos/bob-photos', 'Media/Other/bob-photos', 'Media/PhotosX'] + .map((dir) => d.photosTabShows(dir, ['Media/Photos']))]); +""", tmp_path) + assert _of(log, "shows") == [[[True, False, False]]] + + +def test_turned_off_it_sends_nothing(tmp_path): + log = _run(_start("fake.status.on = false;"), tmp_path) + assert _of(log, "plan") == [] and _of(log, "hub") == [] + + # ── nothing is ever removed ───────────────────────────────────────────────── def test_the_backup_has_no_way_to_remove_anything_on_the_node(): |