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 | |
| 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')
18 files changed, 104 insertions, 15 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 12b48a7..41cc5f2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -944,9 +944,11 @@ function App() { return () => { gone = true; if (stop) stop(); }; }, [syncAccount]); - // Contacts backup (§9.13), the same way. + // Contacts and messages backup (§9.13), the same way. useEffect(() => { - if (!syncAccount || !platform.contactSync.available) return undefined; + if (!syncAccount || !(platform.contactSync.available || platform.messageSync.available)) { + return undefined; + } let stop = null; let gone = false; import('./phone-sync.js').then((m) => { @@ -962,7 +964,7 @@ function App() { }, }); stop = m.stopPhoneSync; - }).catch((e) => console.warn('[MeshBay] contacts backup did not load:', e.message)); + }).catch((e) => console.warn('[MeshBay] contacts and messages backup did not load:', e.message)); return () => { gone = true; if (stop) stop(); }; }, [syncAccount]); diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index d83d0c0..086f3a9 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -1340,6 +1340,9 @@ export default { 'contactsync.heading': "Kontakte-Sicherung", 'contactsync.intro': "Einmal täglich, wenn sie sich geändert haben, eine Kopie der Kontakte dieses Telefons senden. Jede Kopie ist eine .vcf-Datei, die jedes Telefon importieren kann.", 'contactsync.permission_needed': "MeshBay braucht Zugriff auf die Kontakte dieses Telefons.", + 'messagesync.heading': "SMS-Sicherung", + 'messagesync.intro': "Die SMS dieses Telefons einmal täglich senden: Die erste Kopie enthält alle, jede weitere nur die neuen. Die Dateien sind im XML-Format, das SMS-Sicherungs-Apps wiederherstellen können. MMS sind nicht enthalten.", + 'messagesync.permission_needed': "MeshBay braucht Zugriff auf die SMS dieses Telefons.", 'phonesync.state_sending': "Wird gesendet…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Gesendete Kopien: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index aa25258..66cd23b 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -1321,6 +1321,9 @@ export default { 'contactsync.heading': "Contacts backup", 'contactsync.intro': "Send a copy of this phone's contacts once a day when they have changed. Each copy is a .vcf file any phone can import.", 'contactsync.permission_needed': "MeshBay needs access to this phone's contacts.", + 'messagesync.heading': "Messages backup", + 'messagesync.intro': "Send the text messages on this phone once a day: the first copy holds them all, each next one only the new ones. The files are in the XML format SMS backup applications restore. Multimedia messages are not included.", + 'messagesync.permission_needed': "MeshBay needs access to this phone's text messages.", 'phonesync.state_sending': "Sending…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Copies sent: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index 8a483e4..c53e3e4 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -1334,6 +1334,9 @@ export default { 'contactsync.heading': "Copia de seguridad de contactos", 'contactsync.intro': "Enviar una copia de los contactos de este teléfono una vez al día si han cambiado. Cada copia es un archivo .vcf que cualquier teléfono puede importar.", 'contactsync.permission_needed': "MeshBay necesita acceso a los contactos de este teléfono.", + 'messagesync.heading': "Copia de seguridad de SMS", + 'messagesync.intro': "Enviar los SMS de este teléfono una vez al día: la primera copia los contiene todos y cada una de las siguientes solo los nuevos. Los archivos están en el formato XML que restauran las aplicaciones de copia de seguridad de SMS. Los MMS no se incluyen.", + 'messagesync.permission_needed': "MeshBay necesita acceso a los SMS de este teléfono.", 'phonesync.state_sending': "Enviando…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Copias enviadas: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index 7f15f05..52b40b0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -1349,6 +1349,9 @@ export default { 'contactsync.heading': "Sauvegarde des contacts", 'contactsync.intro': "Envoyer une copie des contacts de ce téléphone une fois par jour s'ils ont changé. Chaque copie est un fichier .vcf que tout téléphone sait importer.", 'contactsync.permission_needed': "MeshBay a besoin d'accéder aux contacts de ce téléphone.", + 'messagesync.heading': "Sauvegarde des SMS", + 'messagesync.intro': "Envoyer les SMS de ce téléphone une fois par jour : la première copie les contient tous, les suivantes seulement les nouveaux. Les fichiers sont au format XML que les applis de sauvegarde SMS savent restaurer. Les MMS ne sont pas inclus.", + 'messagesync.permission_needed': "MeshBay a besoin d'accéder aux SMS de ce téléphone.", 'phonesync.state_sending': "Envoi…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Copies envoyées : {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index d18c373..9387ddd 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -1348,6 +1348,9 @@ export default { 'contactsync.heading': "Backup dei contatti", 'contactsync.intro': "Invia una copia dei contatti di questo telefono una volta al giorno se sono cambiati. Ogni copia è un file .vcf che qualsiasi telefono può importare.", 'contactsync.permission_needed': "MeshBay ha bisogno di accedere ai contatti di questo telefono.", + 'messagesync.heading': "Backup degli SMS", + 'messagesync.intro': "Invia gli SMS di questo telefono una volta al giorno: la prima copia li contiene tutti, le successive solo quelli nuovi. I file sono nel formato XML che le app di backup degli SMS sanno ripristinare. Gli MMS non sono inclusi.", + 'messagesync.permission_needed': "MeshBay ha bisogno di accedere agli SMS di questo telefono.", 'phonesync.state_sending': "Invio…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Copie inviate: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index 1ee0a81..29296d1 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -1332,6 +1332,9 @@ export default { 'contactsync.heading': "連絡先のバックアップ", 'contactsync.intro': "このスマートフォンの連絡先のコピーを、変更があった日に1日1回送ります。各コピーはどのスマートフォンでも読み込める .vcf ファイルです。", 'contactsync.permission_needed': "MeshBay にこのスマートフォンの連絡先へのアクセスを許可してください。", + 'messagesync.heading': "SMS のバックアップ", + 'messagesync.intro': "このスマートフォンの SMS を1日1回送ります。最初のコピーにはすべてが含まれ、以降は新しいものだけが含まれます。ファイルは SMS バックアップアプリで復元できる XML 形式です。MMS は含まれません。", + 'messagesync.permission_needed': "MeshBay にこのスマートフォンの SMS へのアクセスを許可してください。", 'phonesync.state_sending': "送信中…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "送信したコピー: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index ec7be69..ada6813 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -1350,6 +1350,9 @@ export default { 'contactsync.heading': "Back-up van contacten", 'contactsync.intro': "Stuur eenmaal per dag, als ze veranderd zijn, een kopie van de contacten op deze telefoon. Elke kopie is een .vcf-bestand dat elke telefoon kan importeren.", 'contactsync.permission_needed': "MeshBay heeft toegang nodig tot de contacten op deze telefoon.", + 'messagesync.heading': "Back-up van sms'jes", + 'messagesync.intro': "Stuur de sms'jes op deze telefoon eenmaal per dag: de eerste kopie bevat ze allemaal, elke volgende alleen de nieuwe. De bestanden zijn in het XML-formaat dat sms-back-upapps kunnen terugzetten. Mms'jes zijn niet inbegrepen.", + 'messagesync.permission_needed': "MeshBay heeft toegang nodig tot de sms'jes op deze telefoon.", 'phonesync.state_sending': "Bezig met verzenden…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Verzonden kopieën: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index 96da817..3e712bb 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -1376,6 +1376,9 @@ export default { 'contactsync.heading': "Kopia kontaktów", 'contactsync.intro': "Wysyłaj kopię kontaktów z tego telefonu raz dziennie, jeśli się zmieniły. Każda kopia to plik .vcf, który zaimportuje każdy telefon.", 'contactsync.permission_needed': "MeshBay potrzebuje dostępu do kontaktów w tym telefonie.", + 'messagesync.heading': "Kopia SMS-ów", + 'messagesync.intro': "Wysyłaj SMS-y z tego telefonu raz dziennie: pierwsza kopia zawiera wszystkie, każda kolejna tylko nowe. Pliki są w formacie XML, który przywracają aplikacje do kopii SMS-ów. MMS-y nie są uwzględniane.", + 'messagesync.permission_needed': "MeshBay potrzebuje dostępu do SMS-ów w tym telefonie.", 'phonesync.state_sending': "Wysyłanie…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Wysłane kopie: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index cec9604..8fe39e5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -1335,6 +1335,9 @@ export default { 'contactsync.heading': "Backup de contatos", 'contactsync.intro': "Enviar uma cópia dos contatos deste celular uma vez por dia se tiverem mudado. Cada cópia é um arquivo .vcf que qualquer celular consegue importar.", 'contactsync.permission_needed': "O MeshBay precisa de acesso aos contatos deste celular.", + 'messagesync.heading': "Backup de SMS", + 'messagesync.intro': "Enviar os SMS deste celular uma vez por dia: a primeira cópia contém todos, cada uma das seguintes só os novos. Os arquivos estão no formato XML que os apps de backup de SMS conseguem restaurar. MMS não são incluídos.", + 'messagesync.permission_needed': "O MeshBay precisa de acesso aos SMS deste celular.", 'phonesync.state_sending': "Enviando…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "Cópias enviadas: {n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index 212bc5d..e294fe5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -1321,6 +1321,9 @@ export default { 'contactsync.heading': "联系人备份", 'contactsync.intro': "在联系人有变动的日子里,每天一次发送本机联系人的副本。每份副本都是任何手机都能导入的 .vcf 文件。", 'contactsync.permission_needed': "MeshBay 需要访问本机的联系人。", + 'messagesync.heading': "短信备份", + 'messagesync.intro': "每天一次发送本机的短信:第一份副本包含全部短信,之后每份只包含新短信。文件采用短信备份应用可以恢复的 XML 格式。不包括彩信。", + 'messagesync.permission_needed': "MeshBay 需要访问本机的短信。", 'phonesync.state_sending': "正在发送…", 'phonesync.summary': "{group} → {dir}", 'phonesync.copies_sent': "已发送副本:{n}", diff --git a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js index cec8b43..0602e54 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync-settings.js @@ -1,7 +1,7 @@ -// The contacts section of the Android Sync page, on the Android application -// only (docs/MESHBAY_DESIGN.md §9.13). Loaded on demand: no other client has -// it. Where the copies go is the page's destination section; this one only -// turns the backup on or off. +// The contacts and messages sections of the Android Sync page, on the Android +// application only (docs/MESHBAY_DESIGN.md §9.13). Loaded on demand: no other +// client has it. Where the copies go is the page's destination section; these +// only turn each backup on or off. import { html, useState, useEffect, useCallback } from './vendor/htm-preact.js'; import { t } from './i18n.js'; @@ -13,6 +13,10 @@ const TEXT = { intro: 'contactsync.intro', permission: 'contactsync.permission_needed', }, + messages: { + intro: 'messagesync.intro', + permission: 'messagesync.permission_needed', + }, }; function stateLine(st, status) { diff --git a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js index b9db9f7..2c34af0 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/phone-sync.js @@ -1,6 +1,7 @@ -// Contacts backup: a copy of this phone's address book sent to -// `<folder>/<account>-contacts` of the destination every backup shares -// (sync-destination.js), once a day when it changed (docs/MESHBAY_DESIGN.md §9.13). +// Contacts and messages backup: a copy of this phone's address book, and the +// text messages added since the last copy, sent to `<folder>/<account>-contacts` +// and `-messages` of the destination every backup shares (sync-destination.js), +// once a day when there is something new (docs/MESHBAY_DESIGN.md §9.13). // // The same split as photos (photo-sync.js): the phone writes what is new into // one file and remembers what the node took; this module decides when a run @@ -29,6 +30,7 @@ const RETRY_MS = 15 * 60 * 1000; */ export const KINDS = { contacts: { sync: platform.contactSync, heading: 'contactsync.heading', suffix: 'contacts' }, + messages: { sync: platform.messageSync, heading: 'messagesync.heading', suffix: 'messages' }, }; // Refusals that will hold at the next run too: said once (a notification, diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js index 148b2eb..4b507dc 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js @@ -267,7 +267,7 @@ export const phoneSync = { }; /** - * Backing up one kind of this phone's data (the contacts) to the destination + * Backing up one kind of this phone's data (contacts, messages) to the destination * every kind shares (docs/MESHBAY_DESIGN.md §9.13). Android only, like * `photoSync`. See phone-sync.js. */ @@ -292,6 +292,8 @@ function docSync(name) { } export const contactSync = docSync('contactSync'); +/** Absent from the Play build, whose policy keeps text messages from it. */ +export const messageSync = docSync('messageSync'); /** Pick a directory to add as a group root. Returns { path, name } or null. */ export const rootPicker = { 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"] |