diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-10 14:40:05 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-10 14:40:05 +0200 |
| commit | aed32f20625a6206628b577d743d96552f81e91a (patch) | |
| tree | af2eaf3336804f2e4af37445326e52a19b2af169 /packages/meshbay-hub/tests | |
| parent | 2f2a9a6542d2194e50ffba6f382e4fdffd481838 (diff) | |
| download | meshbay-aed32f20625a6206628b577d743d96552f81e91a.tar.gz | |
feat(android): back text messages up, in a build Play does not get
A Messages section sends the SMS added since the last copy, as restorable
<smses> XML, into <folder>/<account>-messages/YYYY. A play flavor has neither
READ_SMS nor the code that reads messages; full is the default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/harness/photo_sync_probe.py | 9 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_android_shell.py | 23 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_phone_sync.py | 19 | ||||
| -rw-r--r-- | packages/meshbay-hub/tests/test_photo_sync_ui.py | 6 |
4 files changed, 53 insertions, 4 deletions
diff --git a/packages/meshbay-hub/tests/harness/photo_sync_probe.py b/packages/meshbay-hub/tests/harness/photo_sync_probe.py index c1c4599..3ae73c1 100644 --- a/packages/meshbay-hub/tests/harness/photo_sync_probe.py +++ b/packages/meshbay-hub/tests/harness/photo_sync_probe.py @@ -97,6 +97,15 @@ window.meshbay = { permit: async () => ({}), configure: async () => ({}), plan: async () => ({ item: null }), sent: async () => true, completed: async () => ({}), failed: async () => true, }, + messageSync: { + status: async () => ({ + permission: 'granted', on: MODE === 'configured', destination: dest, + dir: dest && '__FOLDER__/bob-messages', sent: 2, lastCompleted: Date.now(), + lastSent: dest && '__FOLDER__/bob-messages/2026/sms-2026-10-10-0900.xml', failure: null, + }), + permit: async () => ({}), configure: async () => ({}), plan: async () => ({ item: null }), + sent: async () => true, completed: async () => ({}), failed: async () => true, + }, }; </script> <main class="main"><div id=root></div></main> diff --git a/packages/meshbay-hub/tests/test_android_shell.py b/packages/meshbay-hub/tests/test_android_shell.py index 426b560..7b2e79f 100644 --- a/packages/meshbay-hub/tests/test_android_shell.py +++ b/packages/meshbay-hub/tests/test_android_shell.py @@ -147,7 +147,8 @@ def test_the_shim_offers_desktop_channels_and_native_answers_each(): # A phone has a push distributor to talk to and a camera roll and an # address book to back up, and a desktop has none of them; those families are the ones the desktop lacks # rather than the ones it shares. - phone_only = {c for c in shim if c.startswith(("push:", "photosync:", "phonesync:", "contactsync:"))} + phone_only = {c for c in shim if c.startswith(("push:", "photosync:", "phonesync:", "contactsync:", + "messagesync:"))} assert any(c.startswith("push:") for c in phone_only), "the push channels are gone" assert any(c.startswith("photosync:") for c in phone_only), "the backup channels are gone" assert shim - phone_only <= preload, f"channels the desktop does not have: {shim - preload}" @@ -255,13 +256,29 @@ def test_a_release_is_signed_with_the_release_key_or_not_built(): assert 'signingConfigs.getByName("debug")' not in build assert 'signingConfigs.findByName("release")' in build assert 'providers.gradleProperty("meshbayReleaseStorePassword")' in build - assert '"preReleaseBuild"' in build and "throw GradleException" in build + assert 'name.endsWith("ReleaseBuild")' in build and "throw GradleException" in build assert not re.search(r'storePassword = "', build) +def test_the_play_build_cannot_read_text_messages(): + """Play's policy keeps READ_SMS for the default SMS application: the Play + build has neither the permission nor the code that reads messages.""" + main = _read(APP / "src" / "main" / "AndroidManifest.xml") + assert "READ_SMS" not in main + assert "READ_SMS" in _read(APP / "src" / "full" / "AndroidManifest.xml") + assert not (APP / "src" / "play" / "AndroidManifest.xml").exists() or \ + "READ_SMS" not in _read(APP / "src" / "play" / "AndroidManifest.xml") + play = list((APP / "src" / "play").rglob("*.kt")) + assert play and all("Telephony" not in _read(f) and "SmsSource" not in _read(f) for f in play) + assert not list((APP / "src" / "main").rglob("SmsSource.kt")) + + @pytest.mark.skipif(not os.environ.get("ANDROID_HOME") or shutil.which("java") is None, reason="no Android SDK in the environment") def test_the_jvm_unit_tests_pass(): - result = subprocess.run(["./gradlew", "--no-daemon", "-q", "testDebugUnitTest"], + # The tests are shared; the Play build is compiled too, so a flavor file + # missing from it fails here rather than at release time. + result = subprocess.run(["./gradlew", "--no-daemon", "-q", "testFullDebugUnitTest", + "compilePlayDebugKotlin"], cwd=ANDROID, capture_output=True, text=True, timeout=900) assert result.returncode == 0, result.stdout[-3000:] + result.stderr[-3000:] diff --git a/packages/meshbay-hub/tests/test_phone_sync.py b/packages/meshbay-hub/tests/test_phone_sync.py index 1a35614..0798ef0 100644 --- a/packages/meshbay-hub/tests/test_phone_sync.py +++ b/packages/meshbay-hub/tests/test_phone_sync.py @@ -99,6 +99,13 @@ win.meshbay = { contactSync: { return fake.status; }, failed: async (code, text) => { fake.log.push(['failed', code, text]); return true; }, +}, messageSync: { + status: async () => ({ ...fake.status, on: !!fake.messagesOn, dir: 'Backups/bob-messages' }), + plan: async () => ({ item: { token: 'm1', name: 'sms-2026-10-10-0900.xml', + dir: 'Backups/bob-messages/2026', size: 4, count: 300 } }), + sent: async (token, dir, name) => { fake.log.push(['sent', token, dir, name]); return true; }, + completed: async () => { fake.log.push(['completed']); return fake.status; }, + failed: async (code, text) => { fake.log.push(['failed', code, text]); return true; }, } }; """ @@ -177,6 +184,18 @@ await m.runDocSync('contacts', { force: true }); assert len(_of(log, "upload")) == 1 +def test_messages_go_under_the_year_in_their_own_folder(tmp_path): + log = _run(""" +fake.messagesOn = true; +m.startPhoneSync(ctx); +await m.runDocSync('contacts'); +await m.runDocSync('messages'); +""", tmp_path) + assert _of(log, "mkdir")[-2:] == [["Backups/bob-messages"], ["Backups/bob-messages/2026"]] + assert ["upload", "Backups/bob-messages/2026", "sms-2026-10-10-0900.xml", "tr-4"] in log + assert ["sent", "m1", "Backups/bob-messages/2026", "sms-2026-10-10-0900.xml"] in log + + # ── alone in the group, at every run ──────────────────────────────────────── def test_the_group_is_asked_about_at_every_run(tmp_path): diff --git a/packages/meshbay-hub/tests/test_photo_sync_ui.py b/packages/meshbay-hub/tests/test_photo_sync_ui.py index bcd26c8..b101310 100644 --- a/packages/meshbay-hub/tests/test_photo_sync_ui.py +++ b/packages/meshbay-hub/tests/test_photo_sync_ui.py @@ -57,7 +57,7 @@ def test_limited_access_is_said_rather_than_failing_quietly(probe): def test_where_backups_go_lists_each_kinds_folder(probe): text = probe["dest360"]["text"] - assert "Media/bob-photos" in text and "Media/bob-contacts" in text + assert all(f"Media/bob-{k}" in text for k in ("photos", "contacts", "messages")) def test_photos_the_photos_tab_will_not_show_are_said(probe): @@ -66,3 +66,7 @@ def test_photos_the_photos_tab_will_not_show_are_said(probe): def test_the_contacts_backup_says_where_its_copies_go(probe): assert "bob-contacts/contacts-2026-10-10-0900.vcf" in probe["configured360"]["text"] + + +def test_the_messages_backup_says_where_its_copies_go(probe): + assert "bob-messages/2026/sms-2026-10-10-0900.xml" in probe["configured360"]["text"] |