}
diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx
index bef14ea276861a..9ff6d3589ef39c 100644
--- a/app/javascript/mastodon/features/video/index.jsx
+++ b/app/javascript/mastodon/features/video/index.jsx
@@ -612,7 +612,6 @@ class Video extends PureComponent {
aria-label={alt}
title={alt}
lang={lang}
- volume={volume}
onClick={this.togglePlay}
onKeyDown={this.handleVideoKeyDown}
onPlay={this.handlePlay}
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 03b656cc473156..7db4bf7bc4d03c 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -702,7 +702,7 @@
"timeline_hint.resources.followers": "Les abonnés",
"timeline_hint.resources.follows": "Les abonnements",
"timeline_hint.resources.statuses": "Messages plus anciens",
- "trends.counter_by_accounts": "{count, plural, one {{counter} personne} other {{counter} personnes}} au cours {days, plural, one {des dernières 24h} other {des {days} derniers jours}}",
+ "trends.counter_by_accounts": "{count, plural, one {{counter} pers.} other {{counter} pers.}} sur {days, plural, one {les dernières 24h} other {les {days} derniers jours}}",
"trends.trending_now": "Tendance en ce moment",
"ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.",
"units.short.billion": "{count}Md",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 9bef814178b830..386b15811a9739 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -13,7 +13,7 @@
"about.rules": "Kiszolgáló szabályai",
"account.account_note_header": "Feljegyzés",
"account.add_or_remove_from_list": "Hozzáadás vagy eltávolítás a listákról",
- "account.badges.bot": "Bot",
+ "account.badges.bot": "Automatizált",
"account.badges.group": "Csoport",
"account.block": "@{name} letiltása",
"account.block_domain": "Domain blokkolása: {domain}",
@@ -63,8 +63,8 @@
"account.share": "@{name} profiljának megosztása",
"account.show_reblogs": "@{name} megtolásainak mutatása",
"account.statuses_counter": "{count, plural, one {{counter} Bejegyzés} other {{counter} Bejegyzés}}",
- "account.unblock": "@{name} tiltásának feloldása",
- "account.unblock_domain": "{domain} domain tiltás feloldása",
+ "account.unblock": "@{name} letiltásának feloldása",
+ "account.unblock_domain": "{domain} domain tiltásának feloldása",
"account.unblock_short": "Tiltás feloldása",
"account.unendorse": "Ne jelenjen meg a profilodon",
"account.unfollow": "Követés megszüntetése",
@@ -138,21 +138,21 @@
"compose.language.search": "Nyelv keresése...",
"compose.published.body": "A bejegyzés publikálásra került.",
"compose.published.open": "Megnyitás",
- "compose.saved.body": "A bejegyzés mentésre került.",
+ "compose.saved.body": "A bejegyzés mentve.",
"compose_form.direct_message_warning_learn_more": "Tudj meg többet",
- "compose_form.encryption_warning": "A bejegyzések Mastodonon nem használnak végpontok közötti titkosítást. Ne ossz meg semmilyen érzékeny információt Mastodonon.",
+ "compose_form.encryption_warning": "A bejegyzések a Mastodonon nem használnak végpontok közti titkosítást. Ne ossz meg semmilyen érzékeny információt a Mastodonon.",
"compose_form.hashtag_warning": "Ez a bejegyzésed nem fog megjelenni semmilyen hashtag alatt, mivel nem nyilvános. Csak a nyilvános bejegyzések kereshetők hashtaggel.",
"compose_form.lock_disclaimer": "A fiókod nincs {locked}. Bárki követni tud, hogy megtekintse a kizárólag követőknek szánt bejegyzéseket.",
- "compose_form.lock_disclaimer.lock": "lezárva",
+ "compose_form.lock_disclaimer.lock": "zárolva",
"compose_form.placeholder": "Mi jár a fejedben?",
"compose_form.poll.add_option": "Lehetőség hozzáadása",
"compose_form.poll.duration": "Szavazás időtartama",
"compose_form.poll.option_placeholder": "{number}. lehetőség",
- "compose_form.poll.remove_option": "Lehetőség törlése",
+ "compose_form.poll.remove_option": "Lehetőség eltávolítása",
"compose_form.poll.switch_to_multiple": "Szavazás megváltoztatása több választásosra",
"compose_form.poll.switch_to_single": "Szavazás megváltoztatása egyetlen választásosra",
"compose_form.publish": "Közzététel",
- "compose_form.publish_form": "Közzététel",
+ "compose_form.publish_form": "Új bejegyzés",
"compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "Módosítások mentése",
"compose_form.sensitive.hide": "{count, plural, one {Média kényesnek jelölése} other {Média kényesnek jelölése}}",
@@ -195,13 +195,13 @@
"copy_icon_button.copied": "A szöveg a vágólapra másolva",
"copypaste.copied": "Másolva",
"copypaste.copy_to_clipboard": "Másolás vágólapra",
- "directory.federated": "Az ismert fediverzumból",
+ "directory.federated": "Az ismert födiverzumból",
"directory.local": "Csak {domain} tartományból",
"directory.new_arrivals": "Új csatlakozók",
"directory.recently_active": "Nemrég aktív",
"disabled_account_banner.account_settings": "Fiókbeállítások",
"disabled_account_banner.text": "A(z) {disabledAccount} fiókod jelenleg le van tiltva.",
- "dismissable_banner.community_timeline": "Ezek a legfrissebb nyilvános bejegyzések, amelyeket {domain} tartományban levő kiszolgáló fiókjait használó emberek tettek közzé.",
+ "dismissable_banner.community_timeline": "Ezek a legfrissebb nyilvános bejegyzések, amelyeket a(z) {domain} kiszolgáló fiókjait használó emberek tették közzé.",
"dismissable_banner.dismiss": "Elvetés",
"dismissable_banner.explore_links": "Jelenleg ezekről a hírekről beszélgetnek az ezen és a központosítás nélküli hálózat többi kiszolgálóján lévő emberek.",
"dismissable_banner.explore_statuses": "Ezek jelenleg népszerűvé váló bejegyzések a háló különböző szegleteiből. Az újabb vagy több megtolással rendelkező bejegyzéseket, illetve a kedvencnek jelöléssel rendelkezőeket rangsoroljuk előrébb.",
@@ -216,14 +216,14 @@
"emoji_button.food": "Étel és Ital",
"emoji_button.label": "Emodzsi beszúrása",
"emoji_button.nature": "Természet",
- "emoji_button.not_found": "Nincsenek emodzsik!! (╯°□°)╯︵ ┻━┻",
+ "emoji_button.not_found": "Nem találhatók emodzsik",
"emoji_button.objects": "Tárgyak",
"emoji_button.people": "Emberek",
"emoji_button.recent": "Gyakran használt",
"emoji_button.search": "Keresés...",
"emoji_button.search_results": "Keresési találatok",
"emoji_button.symbols": "Szimbólumok",
- "emoji_button.travel": "Utazás és Helyek",
+ "emoji_button.travel": "Utazás és helyek",
"empty_column.account_hides_collections": "Ez a felhasználó úgy döntött, hogy nem teszi elérhetővé ezt az információt.",
"empty_column.account_suspended": "Fiók felfüggesztve",
"empty_column.account_timeline": "Itt nincs bejegyzés!",
@@ -245,7 +245,7 @@
"empty_column.mutes": "Még egy felhasználót sem némítottál le.",
"empty_column.notifications": "Jelenleg még nincsenek értesítéseid. Ha mások kapcsolatba lépnek veled, ezek itt lesznek láthatóak.",
"empty_column.public": "Jelenleg itt nincs semmi! Írj valamit nyilvánosan vagy kövess más kiszolgálón levő felhasználókat, hogy megtöltsd.",
- "error.unexpected_crash.explanation": "Egy hiba vagy böngésző inkompatibilitás miatt ez az oldal nem jeleníthető meg rendesen.",
+ "error.unexpected_crash.explanation": "Egy kód- vagy böngészőkompatibilitási hiba miatt ez az oldal nem jeleníthető meg helyesen.",
"error.unexpected_crash.explanation_addons": "Ezt az oldalt nem lehet helyesen megjeleníteni. Ezt a hibát valószínűleg egy böngésző kiegészítő vagy egy automatikus fordító okozza.",
"error.unexpected_crash.next_steps": "Próbáld frissíteni az oldalt. Ha ez nem segít, egy másik böngészőn vagy appon keresztül még mindig használhatod a Mastodont.",
"error.unexpected_crash.next_steps_addons": "Próbáld letiltani őket és frissíteni az oldalt. Ha ez nem segít, egy másik böngészőn vagy appon keresztül még mindig használhatod a Mastodont.",
@@ -278,13 +278,13 @@
"firehose.remote": "Egyéb kiszolgálók",
"follow_request.authorize": "Hitelesítés",
"follow_request.reject": "Elutasítás",
- "follow_requests.unlocked_explanation": "Bár a fiókod nincs zárolva, a(z) {domain} csapata úgy gondolta, hogy talán kézzel szeretnéd ellenőrizni a fiók követési kéréseit.",
+ "follow_requests.unlocked_explanation": "Bár a fiókod nincs zárolva, a(z) {domain} csapata úgy gondolta, hogy talán kézzel szeretnéd ellenőrizni ezen fiókok követési kéréseit.",
"followed_tags": "Követett hashtagek",
"footer.about": "Névjegy",
"footer.directory": "Profiltár",
"footer.get_app": "Alkalmazás beszerzése",
"footer.invite": "Emberek meghívása",
- "footer.keyboard_shortcuts": "Billentyűparancsok",
+ "footer.keyboard_shortcuts": "Gyorsbillentyűk",
"footer.privacy_policy": "Adatvédelmi szabályzat",
"footer.source_code": "Forráskód megtekintése",
"footer.status": "Állapot",
@@ -347,13 +347,13 @@
"keyboard_shortcuts.favourite": "Bejegyzés kedvencnek jelölése",
"keyboard_shortcuts.favourites": "Kedvencek lista megnyitása",
"keyboard_shortcuts.federated": "Föderációs idővonal megnyitása",
- "keyboard_shortcuts.heading": "Billentyűparancsok",
+ "keyboard_shortcuts.heading": "Gyorsbillentyűk",
"keyboard_shortcuts.home": "Saját idővonal megnyitása",
"keyboard_shortcuts.hotkey": "Gyorsbillentyű",
"keyboard_shortcuts.legend": "jelmagyarázat megjelenítése",
- "keyboard_shortcuts.local": "helyi idővonal megnyitása",
+ "keyboard_shortcuts.local": "Helyi idővonal megnyitása",
"keyboard_shortcuts.mention": "Szerző megemlítése",
- "keyboard_shortcuts.muted": "némított felhasználók listájának megnyitása",
+ "keyboard_shortcuts.muted": "Némított felhasználók listájának megnyitása",
"keyboard_shortcuts.my_profile": "Saját profil megnyitása",
"keyboard_shortcuts.notifications": "Értesítések oszlop megnyitása",
"keyboard_shortcuts.open_media": "Média megnyitása",
@@ -389,7 +389,7 @@
"lists.replies_policy.list": "A lista tagjai",
"lists.replies_policy.none": "Senki",
"lists.replies_policy.title": "Nekik mutassuk a válaszokat:",
- "lists.search": "Keresés a követett személyek között",
+ "lists.search": "Keresés a követett emberek között",
"lists.subheading": "Saját listák",
"load_pending": "{count, plural, one {# új elem} other {# új elem}}",
"loading_indicator.label": "Betöltés…",
@@ -399,7 +399,7 @@
"mute_modal.hide_notifications": "Rejtsük el a felhasználótól származó értesítéseket?",
"mute_modal.indefinite": "Határozatlan",
"navigation_bar.about": "Névjegy",
- "navigation_bar.advanced_interface": "Haladó webes felület engedélyezése",
+ "navigation_bar.advanced_interface": "Megnyitás a speciális webes felületben",
"navigation_bar.blocks": "Letiltott felhasználók",
"navigation_bar.bookmarks": "Könyvjelzők",
"navigation_bar.community_timeline": "Helyi idővonal",
@@ -411,9 +411,9 @@
"navigation_bar.explore": "Felfedezés",
"navigation_bar.favourites": "Kedvencek",
"navigation_bar.filters": "Némított szavak",
- "navigation_bar.follow_requests": "Követési kérelmek",
+ "navigation_bar.follow_requests": "Követési kérések",
"navigation_bar.followed_tags": "Követett hashtagek",
- "navigation_bar.follows_and_followers": "Követettek és követők",
+ "navigation_bar.follows_and_followers": "Követések és követők",
"navigation_bar.lists": "Listák",
"navigation_bar.logout": "Kijelentkezés",
"navigation_bar.mutes": "Némított felhasználók",
@@ -449,7 +449,7 @@
"notifications.column_settings.follow_request": "Új követési kérelmek:",
"notifications.column_settings.mention": "Megemlítések:",
"notifications.column_settings.poll": "Szavazási eredmények:",
- "notifications.column_settings.push": "Push értesítések",
+ "notifications.column_settings.push": "Leküldéses értesítések",
"notifications.column_settings.reblog": "Megtolások:",
"notifications.column_settings.show": "Megjelenítés az oszlopban",
"notifications.column_settings.sound": "Hang lejátszása",
@@ -479,8 +479,8 @@
"onboarding.actions.go_to_home": "Ugrás a saját hírfolyamra",
"onboarding.compose.template": "Üdvözlet, #Mastodon!",
"onboarding.follows.empty": "Sajnos jelenleg nem jeleníthető meg eredmény. Kipróbálhatod a keresést vagy böngészheted a felfedező oldalon a követni kívánt személyeket, vagy próbáld meg később.",
- "onboarding.follows.lead": "A saját hírfolyamod az elsődleges tapasztalás a Mastodonon. Minél több embert követsz, annál aktívabb és érdekesebb a dolog. Az induláshoz itt van néhány javaslat:",
- "onboarding.follows.title": "Népszerű a Mastodonon",
+ "onboarding.follows.lead": "A kezdőlapod a Mastodon használatának elsődleges módja. Minél több embert követsz, annál aktívabbak és érdekesebbek lesznek a dolgok. Az induláshoz itt van néhány javaslat:",
+ "onboarding.follows.title": "Szabd személyre a kezdőlapodat",
"onboarding.profile.discoverable": "Saját profil beállítása felfedezhetőként",
"onboarding.profile.discoverable_hint": "A Mastodonon a felfedezhetőség választása esetén a saját bejegyzéseid megjelenhetnek a keresési eredmények és a felkapott tartalmak között, valamint a profilod a hozzád hasonló érdeklődési körrel rendelkező embereknél is ajánlásra kerülhet.",
"onboarding.profile.display_name": "Megjelenített név",
@@ -499,14 +499,14 @@
"onboarding.start.lead": "Az új Mastodon-fiók használatra kész. Így hozhatod ki belőle a legtöbbet:",
"onboarding.start.skip": "Szeretnél előreugrani?",
"onboarding.start.title": "Ez sikerült!",
- "onboarding.steps.follow_people.body": "Te állítod össze a saját hírfolyamodat. Töltsd meg érdekes emberekkel.",
+ "onboarding.steps.follow_people.body": "A Mastodon az érdekes emberek követéséről szól.",
"onboarding.steps.follow_people.title": "{count, plural, one {egy ember} other {# ember}} követése",
"onboarding.steps.publish_status.body": "Üdvözöljük a világot.",
"onboarding.steps.publish_status.title": "Az első bejegyzés létrehozása",
"onboarding.steps.setup_profile.body": "Mások nagyobb valószínűséggel lépnek kapcsolatba veled egy kitöltött profil esetén.",
"onboarding.steps.setup_profile.title": "Profilod testreszabása",
- "onboarding.steps.share_profile.body": "Tudasd az ismerőseiddel, hogyan találhatnak meg a Mastodonon!",
- "onboarding.steps.share_profile.title": "Profilod megosztása",
+ "onboarding.steps.share_profile.body": "Tudasd az ismerőseiddel, hogyan találhatnak meg a Mastodonon",
+ "onboarding.steps.share_profile.title": "Oszd meg a Mastodon profilodat",
"onboarding.tips.2fa": "Tudtad? A fiókod biztonságossá teheted, ha a fiók beállításaiban beállítod a kétlépcsős hitelesítést. Bármilyen választott TOTP alkalmazással működik, nincs szükség telefonszámra!",
"onboarding.tips.accounts_from_other_servers": "Tudtad? Mivel a Mastodon decentralizált, egyes profilok, amelyekkel találkozol, más kiszolgálókon lesznek tárolva. És mégis zökkenőmentesen kommunikálhatsz velük! A kiszolgáló a felhasználónevük második felében található!",
"onboarding.tips.migration": "Tudtad? Ha úgy érzed, hogy a {domain} már nem jó kiszolgáló a számodra, átköltözhetsz egy másik Mastodon kiszolgálóra anélkül, hogy elveszítenéd a követőidet. Akár saját kiszolgálót is üzemeltethetsz!",
@@ -720,7 +720,7 @@
"upload_form.undo": "Törlés",
"upload_form.video_description": "Leírás siket, hallássérült, vak vagy gyengénlátó emberek számára",
"upload_modal.analyzing_picture": "Kép elemzése…",
- "upload_modal.apply": "Alkalmazás",
+ "upload_modal.apply": "Alkalmaz",
"upload_modal.applying": "Alkalmazás…",
"upload_modal.choose_image": "Kép kiválasztása",
"upload_modal.description_placeholder": "A gyors, barna róka átugrik a lusta kutya fölött",
diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json
index e588b853854f68..58c80e4117f275 100644
--- a/app/javascript/mastodon/locales/lt.json
+++ b/app/javascript/mastodon/locales/lt.json
@@ -40,7 +40,7 @@
"account.follows.empty": "Šis (-i) naudotojas (-a) dar nieko neseka.",
"account.follows_you": "Seka tave",
"account.go_to_profile": "Eiti į profilį",
- "account.hide_reblogs": "Slėpti \"boosts\" iš @{name}",
+ "account.hide_reblogs": "Slėpti pakėlimus iš @{name}",
"account.in_memoriam": "Atminimui.",
"account.joined_short": "Prisijungė",
"account.languages": "Keisti prenumeruojamas kalbas",
@@ -49,19 +49,19 @@
"account.media": "Medija",
"account.mention": "Paminėti @{name}",
"account.moved_to": "{name} nurodė, kad dabar jų nauja paskyra yra:",
- "account.mute": "Užtildyti @{name}",
+ "account.mute": "Nutildyti @{name}",
"account.mute_notifications_short": "Nutildyti pranešimus",
"account.mute_short": "Nutildyti",
- "account.muted": "Užtildytas",
+ "account.muted": "Nutildytas",
"account.no_bio": "Nėra pateikto aprašymo.",
- "account.open_original_page": "Atidaryti originalinį tinklalapį",
+ "account.open_original_page": "Atidaryti originalinį puslapį",
"account.posts": "Įrašai",
"account.posts_with_replies": "Įrašai ir atsakymai",
"account.report": "Pranešti @{name}",
- "account.requested": "Laukiama patvirtinimo. Spausk, kad atšaukti sekimo užklausą.",
+ "account.requested": "Laukiama patvirtinimo. Spausk, kad atšaukti sekimo užklausą",
"account.requested_follow": "{name} paprašė tave sekti",
"account.share": "Bendrinti @{name} profilį",
- "account.show_reblogs": "Rodyti \"boosts\" iš @{name}",
+ "account.show_reblogs": "Rodyti pakėlimus iš @{name}",
"account.statuses_counter": "{count, plural, one {{counter} įrašas} few {{counter} įrašai} many {{counter} įrašo} other {{counter} įrašų}}",
"account.unblock": "Atblokuoti @{name}",
"account.unblock_domain": "Atblokuoti domeną {domain}",
@@ -73,7 +73,7 @@
"account.unmute_short": "Atitildyti",
"account_note.placeholder": "Spausk norėdamas (-a) pridėti pastabą",
"admin.dashboard.daily_retention": "Vartotojų išbuvimo rodiklis pagal dieną po registracijos",
- "admin.dashboard.monthly_retention": "Vartotojų išbuvimo rodiklis pagal mėnesį po registracijos",
+ "admin.dashboard.monthly_retention": "Naudotojų išlaikymo rodiklis pagal mėnesį po registracijos",
"admin.dashboard.retention.average": "Vidurkis",
"admin.dashboard.retention.cohort": "Registravimo mėnuo",
"admin.dashboard.retention.cohort_size": "Nauji naudotojai",
@@ -117,9 +117,9 @@
"column.favourites": "Mėgstamiausi",
"column.firehose": "Tiesioginiai padavimai",
"column.follow_requests": "Sekti prašymus",
- "column.home": "Pradžia",
+ "column.home": "Pagrindinis",
"column.lists": "Sąrašai",
- "column.mutes": "Užtildyti naudotojai",
+ "column.mutes": "Nutildyti naudotojai",
"column.notifications": "Pranešimai",
"column.pins": "Prisegti įrašai",
"column.public": "Federacinė laiko skalė",
@@ -141,13 +141,13 @@
"compose.saved.body": "Įrašas išsaugotas.",
"compose_form.direct_message_warning_learn_more": "Sužinoti daugiau",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
- "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
- "compose_form.lock_disclaimer": "Jūsų paskyra nėra {locked}. Kiekvienas gali jus sekti ir peržiūrėti tik sekėjams skirtus įrašus.",
+ "compose_form.hashtag_warning": "Šis įrašas nebus įtraukta į jokį saitažodį, nes ji nėra vieša. Tik viešų įrašų galima ieškoti pagal saitažodį.",
+ "compose_form.lock_disclaimer": "Tavo paskyra nėra {locked}. Bet kas gali sekti tave ir peržiūrėti tik sekėjams skirtus įrašus.",
"compose_form.lock_disclaimer.lock": "užrakinta",
"compose_form.placeholder": "Kas tavo mintyse?",
"compose_form.poll.add_option": "Pridėti pasirinkimą",
"compose_form.poll.duration": "Apklausos trukmė",
- "compose_form.poll.option_placeholder": "Pasirinkimas {number}",
+ "compose_form.poll.option_placeholder": "{number} pasirinkimas",
"compose_form.poll.remove_option": "Pašalinti šį pasirinkimą",
"compose_form.poll.switch_to_multiple": "Keisti apklausą, kad būtų galima pasirinkti kelis pasirinkimus",
"compose_form.poll.switch_to_single": "Pakeisti apklausą, kad būtų galima pasirinkti vieną variantą",
@@ -528,6 +528,8 @@
"search_results.hashtags": "Saitažodžiai",
"search_results.nothing_found": "Nepavyko rasti nieko pagal šiuos paieškos terminus.",
"search_results.statuses": "Toots",
+ "server_banner.about_active_users": "Žmonės, kurie naudojosi šiuo serveriu per pastarąsias 30 dienų (mėnesio aktyvūs naudotojai)",
+ "server_banner.active_users": "aktyvūs naudotojai",
"sign_in_banner.sign_in": "Prisijungimas",
"sign_in_banner.text": "Prisijunk, kad galėtum sekti profilius arba saitažodžius, mėgsti, bendrinti ir atsakyti į įrašus. Taip pat gali bendrauti iš savo paskyros kitame serveryje.",
"status.admin_status": "Open this status in the moderation interface",
diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json
index 4078a4c066f480..917419d172e72b 100644
--- a/app/javascript/mastodon/locales/my.json
+++ b/app/javascript/mastodon/locales/my.json
@@ -21,6 +21,7 @@
"account.blocked": "ဘလော့ထားသည်",
"account.browse_more_on_origin_server": "မူရင်းပရိုဖိုင်တွင် ပိုမိုကြည့်ရှုပါ။",
"account.cancel_follow_request": "စောင့်ကြည့်မှု ပယ်ဖျက်ခြင်း",
+ "account.copy": "လင့်ခ်ကို ပရိုဖိုင်သို့ ကူးယူပါ",
"account.direct": "@{name} သီးသန့် သိရှိနိုင်အောင် မန်းရှင်းခေါ်မည်",
"account.disable_notifications": "@{name} ပို့စ်တင်သည့်အခါ ကျွန်ုပ်ထံ အသိပေးခြင်း မပြုလုပ်ရန်။",
"account.domain_blocked": "ဒိုမိန်း ပိတ်ပင်ထားခဲ့သည်",
@@ -191,6 +192,7 @@
"conversation.mark_as_read": "ဖတ်ပြီးသားအဖြစ်မှတ်ထားပါ",
"conversation.open": "Conversation ကိုကြည့်မည်",
"conversation.with": "{အမည်များ} ဖြင့်",
+ "copy_icon_button.copied": "ကလစ်ဘုတ်သို့ ကူးပါ",
"copypaste.copied": "ကူယူပြီးပါပြီ",
"copypaste.copy_to_clipboard": "ကလစ်ဘုတ်သို့ ကူးပါ",
"directory.federated": "သင် သိသော ဖက်ဒီမှ",
@@ -389,6 +391,7 @@
"lists.search": "မိမိဖောလိုးထားသူများမှရှာဖွေမည်",
"lists.subheading": "သင့်၏စာရင်းများ",
"load_pending": "{count, plural, one {# new item} other {# new items}}",
+ "loading_indicator.label": "လုပ်ဆောင်နေသည်…",
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
"moved_to_account_banner.text": "{movedToAccount} အကောင့်သို့ပြောင်းလဲထားသဖြင့် {disabledAccount} အကောင့်မှာပိတ်ထားသည်",
"mute_modal.duration": "ကြာချိန်",
@@ -477,6 +480,13 @@
"onboarding.follows.empty": "ယခုအချိန် မည်သည့်ရလဒ်ကိုမျှ မပြသနိုင်ပါ။ လူများကိုစောင့်ကြည့်ရန်အတွက် Explore စာမျက်နှာကို အသုံးပြု၍ စမ်းကြည့်နိုင်သည် သို့မဟုတ် နောက်မှ ထပ်စမ်းကြည့်ပါ။",
"onboarding.follows.lead": "သင့်ကိုယ်ပိုင်ပို့စ်များ တင်နိုင်သည်။ သင်စောင့်ကြည့်သူ များလေလေ၊ စိတ်ဝင်စားစရာကောင်းသောပို့စ်များ တွေ့ရလေဖြစ်သည်။ ဤပရိုဖိုင်များမှာ ကောင်းမွန်သောအစပြုမှုတစ်ခုဖြစ်ပြီး ၎င်းတို့ကိုစောင့်ကြည့်ခြင်းမှလည်း အချိန်မရွေး ပယ်ဖျက်နိုင်ပါသည်။",
"onboarding.follows.title": "Mastodon တွင် ရေပန်းစားခြင်း",
+ "onboarding.profile.discoverable": "ပရိုဖိုင် ရှာဖွေနိုင်ပါမည်",
+ "onboarding.profile.display_name": "ဖော်ပြမည့်အမည်",
+ "onboarding.profile.display_name_hint": "သင့်အမည်အပြည့်အစုံ သို့မဟုတ် သင့်အမည်ပြောင်။",
+ "onboarding.profile.note": "ကိုယ်ရေးအကျဉ်း",
+ "onboarding.profile.save_and_continue": "သိမ်းပြီး ဆက်လုပ်ပါ",
+ "onboarding.profile.title": "ပရိုဖိုင်စနစ် ထည့်သွင်းခြင်း",
+ "onboarding.profile.upload_avatar": "ပရိုဖိုင်ပုံ အပ်လုဒ်လုပ်ပါ",
"onboarding.share.lead": "Mastodon တွင် သင့်အား မည်သို့ရှာတွေ့နိုင်သည်ကို အသိပေးပါ။",
"onboarding.share.message": "Mastodon ရှိ ကျွန်ုပ်၏အမည်မှာ {username} ဖြစ်သည်။ ကျွန်ုပ်ကို {url} တွင် စောင့်ကြည့်နိုင်ပါသည်",
"onboarding.share.next_steps": "ဖြစ်နိုင်ချေရှိသော နောက်အဆင့်များ -",
@@ -520,6 +530,7 @@
"privacy.unlisted.short": "စာရင်းမသွင်းထားပါ",
"privacy_policy.last_updated": "နောက်ဆုံး ပြင်ဆင်ခဲ့သည့်ရက်စွဲ {date}",
"privacy_policy.title": "ကိုယ်ရေးအချက်အလက်မူဝါဒ",
+ "recommended": "အကြံပြုသည်",
"refresh": "ပြန်လည်စတင်ပါ",
"regeneration_indicator.label": "လုပ်ဆောင်နေသည်…",
"regeneration_indicator.sublabel": "သင့်ပင်မစာမျက်နှာကို ပြင်ဆင်နေပါသည်။",
@@ -590,6 +601,7 @@
"search.quick_action.status_search": "{x} နှင့် ကိုက်ညီသော ပို့စ်များ",
"search.search_or_paste": "URL ရိုက်ထည့်ပါ သို့မဟုတ် ရှာဖွေပါ",
"search_popout.full_text_search_disabled_message": "{domain} တွင် မရနိုင်ပါ။",
+ "search_popout.full_text_search_logged_out_message": "အကောင့်ဝင်ထားမှသာ ရနိုင်သည်။",
"search_popout.language_code": "ISO ဘာသာစကားကုဒ်",
"search_popout.options": "ရွေးချယ်ထားသည်များ ရှာဖွေရန်",
"search_popout.quick_actions": "အမြန်လုပ်ဆောင်မှုများ",
diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json
index 2d1616960a1bb3..d5055b0dc53b03 100644
--- a/app/javascript/mastodon/locales/pt-PT.json
+++ b/app/javascript/mastodon/locales/pt-PT.json
@@ -7,7 +7,7 @@
"about.domain_blocks.silenced.explanation": "Normalmente não verá perfis e conteúdo deste servidor, a menos que os procure explicitamente ou opte por os seguir.",
"about.domain_blocks.silenced.title": "Limitados",
"about.domain_blocks.suspended.explanation": "Nenhum dado deste servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os utilizadores dessas instâncias.",
- "about.domain_blocks.suspended.title": "Supensos",
+ "about.domain_blocks.suspended.title": "Suspensos",
"about.not_available": "Esta informação não foi disponibilizada neste servidor.",
"about.powered_by": "Rede social descentralizada baseada no {mastodon}",
"about.rules": "Regras do servidor",
diff --git a/app/lib/attachment_batch.rb b/app/lib/attachment_batch.rb
index 13a9da828f5cf2..b28f5c3d7fc14d 100644
--- a/app/lib/attachment_batch.rb
+++ b/app/lib/attachment_batch.rb
@@ -4,7 +4,8 @@ class AttachmentBatch
# Maximum amount of objects you can delete in an S3 API call. It's
# important to remember that this does not correspond to the number
# of records in the batch, since records can have multiple attachments
- LIMIT = 1_000
+ LIMIT = ENV.fetch('S3_BATCH_DELETE_LIMIT', 1000).to_i
+ MAX_RETRY = ENV.fetch('S3_BATCH_DELETE_RETRY', 3).to_i
# Attributes generated and maintained by Paperclip (not all of them
# are always used on every class, however)
@@ -95,6 +96,7 @@ def remove_files
# objects can be processed at once, so we have to potentially
# separate them into multiple calls.
+ retries = 0
keys.each_slice(LIMIT) do |keys_slice|
logger.debug { "Deleting #{keys_slice.size} objects" }
@@ -102,6 +104,17 @@ def remove_files
objects: keys_slice.map { |key| { key: key } },
quiet: true,
})
+ rescue => e
+ retries += 1
+
+ if retries < MAX_RETRY
+ logger.debug "Retry #{retries}/#{MAX_RETRY} after #{e.message}"
+ sleep 2**retries
+ retry
+ else
+ logger.error "Batch deletion from S3 failed after #{e.message}"
+ raise e
+ end
end
end
diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb
index a96612cab0d131..bb031986d68186 100644
--- a/app/lib/link_details_extractor.rb
+++ b/app/lib/link_details_extractor.rb
@@ -37,6 +37,7 @@ def description
def language
lang = json['inLanguage']
+ lang = lang.first if lang.is_a?(Array)
lang.is_a?(Hash) ? (lang['alternateName'] || lang['name']) : lang
end
diff --git a/app/models/preview_cards_status.rb b/app/models/preview_cards_status.rb
index 341771e4d307fe..214eec22e5c590 100644
--- a/app/models/preview_cards_status.rb
+++ b/app/models/preview_cards_status.rb
@@ -9,9 +9,7 @@
# url :string
#
class PreviewCardsStatus < ApplicationRecord
- # Composite primary keys are not properly supported in Rails. However,
- # we shouldn't need this anyway...
- self.primary_key = nil
+ self.primary_key = [:preview_card_id, :status_id]
belongs_to :preview_card
belongs_to :status
diff --git a/app/models/status.rb b/app/models/status.rb
index d179b93355c53b..5dea348ca7f29f 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -108,8 +108,7 @@ class Status < ApplicationRecord
has_and_belongs_to_many :tags
- # Because of a composite primary key, the `dependent` option cannot be used on this association
- has_one :preview_cards_status, inverse_of: :status # rubocop:disable Rails/HasManyOrHasOneDependent
+ has_one :preview_cards_status, inverse_of: :status, dependent: :delete
has_one :notification, as: :activity, dependent: :destroy
has_one :status_stat, inverse_of: :status, dependent: nil
@@ -178,7 +177,6 @@ class Status < ApplicationRecord
# The `prepend: true` option below ensures this runs before
# the `dependent: destroy` callbacks remove relevant records
before_destroy :unlink_from_conversations!, prepend: true
- before_destroy :reset_preview_card!
cache_associated :application,
:media_attachments,
diff --git a/config/application.rb b/config/application.rb
index 99ee4ffd76a06a..b6426516eee17e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -14,7 +14,6 @@
# require 'action_mailbox/engine'
# require 'action_text/engine'
# require 'rails/test_unit/railtie'
-require 'sprockets/railtie'
# Used to be implicitly required in action_mailbox/engine
require 'mail'
diff --git a/config/environments/development.rb b/config/environments/development.rb
index e601fc014c4715..3c13ada380a19c 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -71,17 +71,6 @@
# Highlight code that enqueued background job in logs.
config.active_job.verbose_enqueue_logs = true
- # Debug mode disables concatenation and preprocessing of assets.
- config.assets.debug = true
-
- # Suppress logger output for asset requests.
- config.assets.quiet = true
-
- # Adds additional error checking when serving assets at runtime.
- # Checks for improperly declared sprockets dependencies.
- # Raises helpful error messages.
- config.assets.raise_runtime_errors = true
-
# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
deleted file mode 100644
index e1fd5f8cedb3b8..00000000000000
--- a/config/initializers/assets.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-# Be sure to restart your server when you modify this file.
-
-# Version of your assets, change this if you want to expire all your assets.
-Rails.application.config.assets.version = '1.0'
-
-# Add additional assets to the asset load path.
-# Rails.application.config.assets.paths << Emoji.images_path
-
-# Precompile additional assets.
-# application.js, application.css, and all non-JS/CSS in the app/assets
-# folder are already added.
-# Rails.application.config.assets.precompile += %w( admin.js admin.css )
-
-Rails.application.config.assets.initialize_on_precompile = true
diff --git a/config/locales/activerecord.hu.yml b/config/locales/activerecord.hu.yml
index e5757ba64360e4..f34ade0440cdcf 100644
--- a/config/locales/activerecord.hu.yml
+++ b/config/locales/activerecord.hu.yml
@@ -20,7 +20,7 @@ hu:
attributes:
username:
invalid: csak betűket, számokat vagy alávonást tartalmazhat
- reserved: fenntartott
+ reserved: foglalt
admin/webhook:
attributes:
url:
diff --git a/config/locales/devise.hu.yml b/config/locales/devise.hu.yml
index bbf6a6399b534a..6eb1524ed19809 100644
--- a/config/locales/devise.hu.yml
+++ b/config/locales/devise.hu.yml
@@ -44,7 +44,7 @@ hu:
action: Jelszó módosítása
explanation: A fiókodhoz tartozó jelszó módosítását kezdeményezted.
extra: Amennyiben nem te kezdeményezted a módosítást, kérjük tekintsd ezt az emailt tárgytalannak. A jelszavad változatlan marad mindaddig, amíg újat nem hozol létre a fenti linkre kattintva.
- subject: 'Mastodon: Jelszó visszaállítási lépések'
+ subject: 'Mastodon: Jelszóvisszaállítási utasítások'
title: Jelszó visszaállítása
two_factor_disabled:
explanation: A fiókod kétlépcsős hitelesítését kikapcsoltuk. A bejelentkezés mostantól csak az e-mail cím és a jelszó használatával lesz lehetséges.
@@ -52,14 +52,14 @@ hu:
title: Kétlépcsős hitelesítés kikapcsolva
two_factor_enabled:
explanation: A kétlépcsős hitelesítést engedélyeztük a fiókodban. A bejelentkezéshez a párosított TOTP alkalmazás által generált tokenre lesz szükség.
- subject: Kétlépcsős azonosítás engedélyezve
+ subject: 'Mastodon: Kétlépcsős azonosítás engedélyezve'
title: Kétlépcsős hitelesítés engedélyezve
two_factor_recovery_codes_changed:
explanation: A korábbi helyreállítási kódok letiltásra és újragenerálásra kerültek.
- subject: Kétlépcsős helyreállítási kódok újra létrejöttek
+ subject: 'Mastodon: Kétlépcsős helyreállítási kódok újból előállítva'
title: A kétlépcsős kódok megváltoztak
unlock_instructions:
- subject: 'Mastodon: Feloldási lépések'
+ subject: 'Mastodon: Feloldási utasítások'
webauthn_credential:
added:
explanation: A következő biztonsági kulcsot hozzáadtuk a fiókodhoz
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 7057883e184471..5da1a4e0668fd3 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -4,7 +4,7 @@ hu:
about_mastodon_html: 'A jövő közösségi hálózata: Hirdetések és céges megfigyelés nélkül, etikus dizájnnal és decentralizációval! Legyél a saját adataid ura a Mastodonnal!'
contact_missing: Nincs megadva
contact_unavailable: N/A
- hosted_on: "%{domain} Mastodon szerver"
+ hosted_on: "%{domain} Mastodon-kiszolgáló"
title: Névjegy
accounts:
follow: Követés
@@ -747,7 +747,7 @@ hu:
desc_html: Ez hCaptcha-ból származó külső scripteket használ, mely biztonsági vagy adatvédelmi résnek bizonyulhat. Ezen kívül ez a regisztrációs folyamatot jelentősen megnehezítheti bizonyos (kifejezetten különleges szükségletű) emberek számára. Emiatt fontold meg más módszerek, mint pl. jóváhagyás-alapú vagy meghívásalapú regisztráció használatát.
title: Az új felhasználóknak egy CAPTCHA-t kell megoldaniuk, hogy megerősítsék a fiókjuk regisztrációját
content_retention:
- preamble: Felhasználók által generált tartalom Mastodonon való tárolásának szabályozása.
+ preamble: A felhasználók által előállított tartalom Mastodonon való tárolásának szabályozása.
title: Tartalom megtartása
default_noindex:
desc_html: Azokat a felhasználókat érinti, akik nem módosították ezt a beállítást
@@ -1007,8 +1007,8 @@ hu:
hint_html: Ha másik fiókról kívánsz átlépni erre a fiókra, itt létrehozhatsz egy aliast, amelyre szükség van, mielőtt folytathatod a követők áthelyezését a régi fiókból erre. Ez az áthelyezés önmagában ártalmatlan és visszafordítható folyamat. A fiók áttelepítése a régi fiókból indul el.
remove: Alias szétkapcsolása
appearance:
- advanced_web_interface: Haladó webes felület
- advanced_web_interface_hint: 'Ha szeretnéd, a teljes képernyőszélességet felhasználhatod. A haladó webes felülettel különböző oszlopokat állíthatsz be, hogy egyszerre annyi infót láthass, amennyit csak akarsz: Saját idővonal, értesítések, föderációs idővonal, bármennyi lista vagy hashtag.'
+ advanced_web_interface: Speciális webes felület
+ advanced_web_interface_hint: 'Ha szeretnéd, a képernyő teljes szélességét kihasználhatod. A speciális webes felülettel különböző oszlopokat állíthatsz be, hogy egyszerre annyi információt láthass, amennyit csak akarsz: Kezdőoldal, értesítések, föderációs idővonal, bármennyi lista vagy hashtag.'
animations_and_accessibility: Animáció és akadálymentesítés
confirmation_dialogs: Megerősítő párbeszédablakok
discovery: Felfedezés
@@ -1052,7 +1052,7 @@ hu:
delete_account: Felhasználói fiók törlése
delete_account_html: Felhasználói fiókod törléséhez kattints ide. A rendszer újbóli megerősítést fog kérni.
description:
- prefix_invited_by_user: "@%{name} meghív téged, hogy csatlakozz ehhez a Mastodon kiszolgálóhoz."
+ prefix_invited_by_user: "@%{name} meghív téged, hogy csatlakozz ehhez a Mastodon-kiszolgálóhoz."
prefix_sign_up: Regisztrláj még ma a Mastodonra!
suffix: Egy fiókkal követhetsz másokat, bejegyzéseket tehetsz közzé, eszmét cserélhetsz más Mastodon szerverek felhasználóival!
didnt_get_confirmation: Nem kaptál visszaigazoló hivatkozást?
@@ -1101,7 +1101,7 @@ hu:
title: 'Bejelentkezés ide: %{domain}'
sign_up:
manual_review: A(z) %{domain} regisztrációi a moderátorok kézi felülvizsgálatán mennek át. Hogy segítsd a regisztráció feldolgozását, írj röviden magadról, és hogy miért szeretnél fiókot a(z) %{domain} oldalon.
- preamble: Egy fiókkal ezen a Mastodon kiszolgálón követhetsz bárkit a hálózaton, függetlenül attól, hogy az illető fiókja melyik kiszolgálón található.
+ preamble: Egy fiókkal ezen a Mastodon-kiszolgálón követhetsz bárkit a hálózaton, függetlenül attól, hogy az illető fiókja melyik kiszolgálón található.
title: Állítsuk be a fiókod a %{domain} kiszolgálón.
status:
account_status: Fiók állapota
@@ -1234,7 +1234,7 @@ hu:
filters:
contexts:
account: Profil
- home: Saját idővonal
+ home: Kezdőlap és listák
notifications: Értesítések
public: Nyilvános idővonalak
thread: Beszélgetések
@@ -1245,7 +1245,7 @@ hu:
statuses_hint_html: Ez a szűrő egyedi bejegyzések kiválasztására vonatkozik, függetlenül attól, hogy megfelelnek-e a lenti kulcsszavaknak. Engedélyezze vagy távolítsa el a bejegyzéseket a szűrőből.
title: Szűrő szerkesztése
errors:
- deprecated_api_multiple_keywords: Ezek a paraméterek nem módosíthatóak az alkalmazásból, mert több mint egy szűrőkulcsszóra is hatással vannak. Használd az alkalmazás vagy a webes felület újabb verzióját.
+ deprecated_api_multiple_keywords: Ezek a paraméterek nem módosíthatók az alkalmazásból, mert egynél több szűrőkulcsszóra is hatással vannak. Használd az alkalmazás vagy a webes felület újabb verzióját.
invalid_context: A megadott kontextus hamis vagy hiányzik
index:
contexts: 'Szűrés helye: %{contexts}'
@@ -1396,7 +1396,7 @@ hu:
unsubscribe:
action: Igen, leiratkozás
complete: Leiratkozva
- confirmation_html: Biztos vagy benne, hogy le szeretnél iratkozni arról, hogy %{type} típusú üzeneteket kapj a %{domain} Mastodon kiszolgálón a %{email} címedre? Bármikor újra feliratkozhatsz az email értesítések beállításainál.
+ confirmation_html: 'Biztos, hogy leiratkozol arról, hogy %{type} típusú üzeneteket kapj a %{domain} Mastodon-kiszolgálótól erre a címedre: %{email}? Bármikor újra feliratkozhatsz az e-mail-értesítések beállításánál.'
emails:
notification_emails:
favourite: kedvencnek jelölésről email értesítő
@@ -1405,7 +1405,7 @@ hu:
mention: megemlítésről email értesítő
reblog: megtolásról email értesítő
resubscribe_html: Ha tévedésből iratkoztál le, újra feliratkozhatsz az email értesítések beállításainál.
- success_html: Mostantól nem kapsz %{type} típusú üzeneket a %{domain} Mastodon kiszolgálón a %{email} címedre.
+ success_html: 'Mostantól nem kapsz %{type} típusú üzeneket a(z) %{domain} Mastodon-kiszolgálón erre a címedre: %{email}.'
title: Leiratkozás
media_attachments:
validations:
@@ -1743,9 +1743,9 @@ hu:
tags:
does_not_match_previous_name: nem illeszkedik az előző névvel
themes:
- contrast: Mastodon (Nagy kontrasztú)
- default: Mastodon (Sötét)
- mastodon-light: Mastodon (Világos)
+ contrast: Mastodon (nagy kontrasztú)
+ default: Mastodon (sötét)
+ mastodon-light: Mastodon (világos)
time:
formats:
default: "%Y. %b %d., %H:%M"
@@ -1782,7 +1782,7 @@ hu:
subject: A %{date}-i fellebbezésedet visszautasították
title: Fellebbezés visszautasítva
backup_ready:
- explanation: A Mastodon fiókod teljes mentését kérted. A mentés kész ás letölthető!
+ explanation: A Mastodon-fiókod teljes mentését kérted. A mentés elkészült, és letölthető.
subject: Az adataidról készült archív letöltésre kész
title: Archiválás
suspicious_sign_in:
diff --git a/config/locales/my.yml b/config/locales/my.yml
index 03ed771a42b57d..4ba4fcfad3aa67 100644
--- a/config/locales/my.yml
+++ b/config/locales/my.yml
@@ -1324,6 +1324,7 @@ my:
'86400': ၁ ရက်
expires_in_prompt: ဘယ်တော့မှ
generate: ဖိတ်ကြားချက်လင့်ခ် ဖန်တီးပါ
+ invalid: ဤဖိတ်ကြားချက်မှာ မမှန်ကန်ပါ
invited_by: သင့်ကို ဖိတ်ခေါ်ထားသည် -
max_uses:
other: "%{count} အသုံးပြုမှုများ"
diff --git a/config/locales/simple_form.hu.yml b/config/locales/simple_form.hu.yml
index 70c225e8ff994c..6e11f7fb9346a4 100644
--- a/config/locales/simple_form.hu.yml
+++ b/config/locales/simple_form.hu.yml
@@ -81,8 +81,8 @@ hu:
bootstrap_timeline_accounts: Ezek a fiókok ki lesznek tűzve az új felhasználók követési javaslatainak élére.
closed_registrations_message: Akkor jelenik meg, amikor a regisztráció le van zárva
content_cache_retention_period: A más kiszolgálókról származó bejegyzések megadott számú nap után törölve lesznek, ha pozitív értékre van állítva. Ez lehet, hogy nem fordítható vissza.
- custom_css: A Mastodon webes verziójában használhatsz egyedi stílusokat.
- mascot: Felülvágja a haladó webes felületen található illusztrációt.
+ custom_css: A Mastodon webes verziójában használhatsz egyéni stílusokat.
+ mascot: Felülbírálja a speciális webes felületen található illusztrációt.
media_cache_retention_period: A letöltött médiafájlok megadott számú nap után törölve lesznek, ha pozitív értékre van állítva, és igény szerint újból le lesznek töltve.
peers_api_enabled: Azon domainek listája, melyekkel ez a kiszolgáló találkozott a fediverzumban. Nem csatolunk adatot arról, hogy föderált kapcsolatban vagy-e az adott kiszolgálóval, csak arról, hogy a kiszolgálód tud a másikról. Ezt olyan szolgáltatások használják, melyek általában a föderációról készítenek statisztikákat.
profile_directory: A profilok jegyzéke minden olyan felhasználót felsorol, akik engedélyezték a felfedezhetőségüket.
@@ -103,7 +103,7 @@ hu:
form_challenge:
current_password: Beléptél egy biztonsági térben
imports:
- data: Egy másik Mastodon kiszolgálóról exportált CSV-fájl
+ data: Egy másik Mastodon-kiszolgálóról exportált CSV-fájl
invite_request:
text: Ez segít nekünk átnézni a jelentkezésedet
ip_block:
@@ -199,7 +199,7 @@ hu:
otp_attempt: Kétlépcsős azonosító kód
password: Jelszó
phrase: Kulcsszó vagy kifejezés
- setting_advanced_layout: Haladó webes felület engedélyezése
+ setting_advanced_layout: Speciális webes felület engedélyezése
setting_aggregate_reblogs: Megtolások csoportosítása az idővonalakon
setting_always_send_emails: E-mail értesítések küldése mindig
setting_auto_play_gif: GIF-ek automatikus lejátszása
diff --git a/config/locales/simple_form.hy.yml b/config/locales/simple_form.hy.yml
index 56aa1d66b1ce6a..9dbcd1301e590e 100644
--- a/config/locales/simple_form.hy.yml
+++ b/config/locales/simple_form.hy.yml
@@ -43,7 +43,7 @@ hy:
setting_display_media_hide_all: Երբեք մեդիա ցոյց չտալ
setting_display_media_show_all: Մեդիա միշտ ցոյց տալ
setting_use_blurhash: Կտորները հիմնուում են թաքցուած վիզուալի վրայ՝ խամրեցնելով դետալները
- setting_use_pending_items: Թաքցնել հոսքի թարմացումները կտտոի ետեւում՝ աւտօմատ թարմացուող հոսքի փոխարէն
+ setting_use_pending_items: Թաքցնել հոսքի թարմացումները կոճակի ետեւում՝ աւտօմատ թարմացուող հոսքի փոխարէն
username: Միայն լատինատառեր, թուեր եւ տակի գծիկ
whole_word: Եթէ բանալի բառը կամ արտայայտութիւնը պարունակում է միայն այբբենական նիշեր եւ թուեր, ապա այն կիրառուելու է ամբողջ բառի հետ համընկնելու դէպքում միայն
domain_allow:
diff --git a/config/locales/simple_form.lt.yml b/config/locales/simple_form.lt.yml
index d53b7105ef0673..6eb90340dc7c64 100644
--- a/config/locales/simple_form.lt.yml
+++ b/config/locales/simple_form.lt.yml
@@ -63,6 +63,8 @@ lt:
setting_use_pending_items: Slėpti laiko skalės naujienas po paspaudimo, vietoj automatinio kanalo slinkimo
username: Gali naudoti raides, skaičius ir pabraukimus
whole_word: Kai raktažodis ar frazė yra tik raidinis ir skaitmeninis, jis bus taikomas tik tada, jei atitiks visą žodį
+ email_domain_block:
+ with_dns_records: Bus bandoma išspręsti nurodyto domeno DNS įrašus, o rezultatai taip pat bus blokuojami
featured_tag:
name: 'Štai keletas pastaruoju metu dažniausiai saitažodžių, kurių tu naudojai:'
filters:
@@ -77,15 +79,98 @@ lt:
site_contact_email: Kaip žmonės gali su tavimi susisiekti teisiniais ar pagalbos užklausimais.
site_contact_username: Kaip žmonės gali tave pasiekti Mastodon.
site_extended_description: Bet kokia papildoma informacija, kuri gali būti naudinga lankytojams ir naudotojams. Gali būti struktūrizuota naudojant Markdown sintaksę.
+ thumbnail: Maždaug 2:1 dydžio vaizdas, rodomas šalia tavo serverio informacijos.
+ timeline_preview: Atsijungę lankytojai galės naršyti naujausius viešus įrašus, esančius serveryje.
trends: Trendai rodo, kurios įrašai, saitažodžiai ir naujienų istorijos tavo serveryje sulaukia didžiausio susidomėjimo.
sessions:
otp: 'Įvesk telefono programėlėje sugeneruotą dviejų tapatybės kodą arba naudok vieną iš atkūrimo kodų:'
webauthn: Jei tai USB raktas, būtinai jį įkišk ir, jei reikia, paspausk.
settings:
indexable: Tavo profilio puslapis gali būti rodomas paieškos rezultatuose Google, Bing ir kituose.
+ user:
+ chosen_languages: Kai pažymėta, viešose laiko skalėse bus rodomi tik įrašai pasirinktomis kalbomis
+ role: Vaidmuo valdo, kokius leidimus naudotojas (-a) turi
labels:
+ account:
+ indexable: Įtraukti viešus įrašus į paieškos rezultatus
+ show_collections: Rodyti sekimus ir sekėjus profilyje
+ unlocked: Automatiškai priimti naujus sekėjus
+ account_warning_preset:
+ title: Pavadinimas
+ admin_account_action:
+ include_statuses: Įtraukti praneštus įrašus į el. laišką
+ defaults:
+ avatar: Profilio nuotrauka
+ bot: Tai automatinė paskyra
+ chosen_languages: Filtruoti kalbas
+ display_name: Rodomas vardas
+ email: El. pašto adresas
+ expires_in: Nustoja galioti po
+ fields: Papildomi laukai
+ irreversible: Mesti vietoj slėpti
+ locale: Sąsajos kalba
+ max_uses: Maksimalus naudojimo skaičius
+ new_password: Naujas slaptažodis
+ note: Biografija
+ password: Slaptažodis
+ phrase: Raktažodis arba frazė
+ setting_auto_play_gif: Automatiškai leisti animuotų GIF
+ setting_boost_modal: Rodyti patvirtinimo dialogą prieš pakėliant įrašą
+ setting_default_language: Skelbimo kalba
+ setting_default_privacy: Skelbimo privatumas
+ setting_default_sensitive: Visada žymėti mediją kaip jautrią
+ setting_delete_modal: Rodyti patvirtinimo dialogą prieš ištrinant įrašą
+ setting_display_media: Medijos rodymas
+ setting_display_media_hide_all: Slėpti viską
+ setting_display_media_show_all: Rodyti viską
+ setting_expand_spoilers: Visada išplėsti įrašus, pažymėtus turinio įspėjimais
+ setting_hide_network: Slėpti savo socialinę diagramą
+ setting_system_font_ui: Naudoti numatytąjį sistemos šriftą
+ setting_theme: Svetainės tema
+ setting_use_pending_items: Lėtas režimas
+ title: Pavadinimas
+ type: Importo tipas
+ username: Naudotojo vardas
+ username_or_email: Naudotojo vardas arba el. paštas
+ whole_word: Visas žodis
+ email_domain_block:
+ with_dns_records: Įtraukti MX įrašus ir domeno IP adresus
featured_tag:
name: Saitažodis
+ filters:
+ actions:
+ hide: Slėpti visiškai
+ warn: Slėpti su įspėjimu
+ form_admin_settings:
+ activity_api_enabled: Skelbti suvestinį statistiką apie naudotojų veiklą per API
+ bootstrap_timeline_accounts: Visada rekomenduoti šias paskyras naujiems naudotojams
+ content_cache_retention_period: Turinio talpyklos išlaikymo laikotarpis
+ custom_css: Pasirinktinis CSS
+ mascot: Pasirinktinis talismanas (pasenęs)
+ registrations_mode: Kas gali užsiregistruoti
+ show_domain_blocks_rationale: Rodyti, kodėl domenai buvo užblokuoti
+ site_extended_description: Išplėstas aprašymas
+ site_short_description: Serverio aprašymas
+ site_terms: Privatumo politika
+ site_title: Serverio pavadinimas
+ theme: Numatytoji tema
+ thumbnail: Serverio miniatūra
+ invite_request:
+ text: Kodėl nori prisijungti?
+ notification_emails:
+ favourite: Kažkas pamėgo tavo įrašą
+ follow: Kažkas seka tave
+ follow_request: Kažkas paprašė sekti tave
+ mention: Kažkas paminėjo tave
+ pending_account: Reikia peržiūros naujam paskyrui
+ reblog: Kažkas pakėlė tavo įrašą
+ software_updates:
+ label: Yra nauja Mastodon versija
+ patch: Pranešti apie klaidų ištaisymo atnaujinimus
+ rule:
+ text: Taisyklė
+ settings:
+ show_application: Rodyti, iš kurios programėles išsiuntei įrašą
tag:
listable: Leisti šį saitažodį rodyti paieškose ir pasiūlymuose
name: Saitažodis
@@ -93,11 +178,15 @@ lt:
usable: Leisti įrašams naudoti šį saitažodį
user:
role: Vaidmuo
+ time_zone: Laiko juosta
user_role:
+ color: Ženklelio spalva
+ highlighted: Rodyti vaidmenį kaip ženklelį naudotojo profiliuose
+ name: Pavadinimas
permissions_as_keys: Leidimai
position: Prioritetas
webhook:
- events: Įgalinti įvykiai
+ events: Įjungti įvykiai
template: Naudingosios apkrovos šablonas
url: Galutinio taško URL
'no': Ne
diff --git a/lib/mastodon/cli/accounts.rb b/lib/mastodon/cli/accounts.rb
index 33520df25d3a8e..4146753035eca9 100644
--- a/lib/mastodon/cli/accounts.rb
+++ b/lib/mastodon/cli/accounts.rb
@@ -472,15 +472,13 @@ def reset_relationships(username)
end
total = 0
- total += Account.where(id: ::Follow.where(account: account).select(:target_account_id)).count if options[:follows]
- total += Account.where(id: ::Follow.where(target_account: account).select(:account_id)).count if options[:followers]
+ total += account.following.reorder(nil).count if options[:follows]
+ total += account.followers.reorder(nil).count if options[:followers]
progress = create_progress_bar(total)
processed = 0
if options[:follows]
- scope = Account.where(id: ::Follow.where(account: account).select(:target_account_id))
-
- scope.find_each do |target_account|
+ account.following.reorder(nil).find_each do |target_account|
UnfollowService.new.call(account, target_account)
rescue => e
progress.log pastel.red("Error processing #{target_account.id}: #{e}")
@@ -493,9 +491,7 @@ def reset_relationships(username)
end
if options[:followers]
- scope = Account.where(id: ::Follow.where(target_account: account).select(:account_id))
-
- scope.find_each do |target_account|
+ account.followers.reorder(nil).find_each do |target_account|
UnfollowService.new.call(target_account, account)
rescue => e
progress.log pastel.red("Error processing #{target_account.id}: #{e}")
diff --git a/lib/mastodon/cli/domains.rb b/lib/mastodon/cli/domains.rb
index 329f1716725b52..e092497dc9edb6 100644
--- a/lib/mastodon/cli/domains.rb
+++ b/lib/mastodon/cli/domains.rb
@@ -97,6 +97,8 @@ def purge(*domains)
say("Removed #{custom_emojis_count} custom emojis#{dry_run_mode_suffix}", :green)
end
+ CRAWL_SLEEP_TIME = 20
+
option :concurrency, type: :numeric, default: 50, aliases: [:c]
option :format, type: :string, default: 'summary', aliases: [:f]
option :exclude_suspended, type: :boolean, default: false, aliases: [:x]
@@ -168,8 +170,8 @@ def crawl(start = nil)
pool.post(domain, &work_unit)
end
- sleep 20
- sleep 20 until pool.queue_length.zero?
+ sleep CRAWL_SLEEP_TIME
+ sleep CRAWL_SLEEP_TIME until pool.queue_length.zero?
pool.shutdown
pool.wait_for_termination(20)
diff --git a/lib/mastodon/cli/maintenance.rb b/lib/mastodon/cli/maintenance.rb
index c53d742548c463..d0eff7da619af5 100644
--- a/lib/mastodon/cli/maintenance.rb
+++ b/lib/mastodon/cli/maintenance.rb
@@ -346,7 +346,7 @@ def deduplicate_announcement_reactions!
remove_index_if_exists!(:announcement_reactions, 'index_announcement_reactions_on_account_id_and_announcement_id')
- say 'Removing duplicate account identity proofs…'
+ say 'Removing duplicate announcement reactions…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM announcement_reactions GROUP BY account_id, announcement_id, name HAVING count(*) > 1").each do |row|
AnnouncementReaction.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end
@@ -431,7 +431,7 @@ def deduplicate_domain_allows!
def deduplicate_domain_blocks!
remove_index_if_exists!(:domain_blocks, 'index_domain_blocks_on_domain')
- say 'Deduplicating domain_allows…'
+ say 'Deduplicating domain_blocks…'
ActiveRecord::Base.connection.select_all("SELECT string_agg(id::text, ',') AS ids FROM domain_blocks GROUP BY domain HAVING count(*) > 1").each do |row|
domain_blocks = DomainBlock.where(id: row['ids'].split(',')).by_severity.reverse.to_a
@@ -462,7 +462,7 @@ def deduplicate_unavailable_domains!
UnavailableDomain.where(id: row['ids'].split(',')).sort_by(&:id).reverse.drop(1).each(&:destroy)
end
- say 'Restoring domain_allows indexes…'
+ say 'Restoring unavailable_domains indexes…'
ActiveRecord::Base.connection.add_index :unavailable_domains, ['domain'], name: 'index_unavailable_domains_on_domain', unique: true
end
diff --git a/lib/mastodon/cli/statuses.rb b/lib/mastodon/cli/statuses.rb
index 0d6018a2b9e7fc..7acf3f9b770c40 100644
--- a/lib/mastodon/cli/statuses.rb
+++ b/lib/mastodon/cli/statuses.rb
@@ -120,7 +120,7 @@ def remove_orphans_media_attachments
say('Beginning removal of now-orphaned media attachments to free up disk space...')
- scope = MediaAttachment.reorder(nil).unattached.where('created_at < ?', options[:days].pred.days.ago)
+ scope = MediaAttachment.unattached.where('created_at < ?', options[:days].pred.days.ago)
processed = 0
removed = 0
progress = create_progress_bar(scope.count)
diff --git a/package.json b/package.json
index 382ab0598052ab..8908268b2227cd 100644
--- a/package.json
+++ b/package.json
@@ -198,7 +198,7 @@
"eslint-plugin-jsx-a11y": "~6.8.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "~6.1.1",
- "eslint-plugin-react": "~7.33.0",
+ "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.3",
"jest": "^29.5.0",
diff --git a/spec/fixtures/files/elite-assets.tar.gz b/spec/fixtures/files/elite-assets.tar.gz
new file mode 100644
index 00000000000000..7b4f4425705c62
Binary files /dev/null and b/spec/fixtures/files/elite-assets.tar.gz differ
diff --git a/spec/generators/post_deployment_migration_generator_spec.rb b/spec/generators/post_deployment_migration_generator_spec.rb
index d770a78e97c1e4..55e70a7917032c 100644
--- a/spec/generators/post_deployment_migration_generator_spec.rb
+++ b/spec/generators/post_deployment_migration_generator_spec.rb
@@ -12,7 +12,7 @@
include FileUtils
tests described_class
- destination File.expand_path('../../tmp', __dir__)
+ destination Rails.root.join('tmp', 'generator-test')
before { prepare_destination }
after { rm_rf(destination_root) }
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 3cc88014cd052a..0a55770bad7f4f 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -29,15 +29,25 @@
describe 'body_classes' do
context 'with a body class string from a controller' do
- before do
- without_partial_double_verification do
- allow(helper).to receive_messages(body_class_string: 'modal-layout compose-standalone', current_theme: 'default', current_account: Fabricate(:account))
- end
- end
+ before { helper.extend controller_helpers }
it 'uses the controller body classes in the result' do
expect(helper.body_classes).to match(/modal-layout compose-standalone/)
end
+
+ private
+
+ def controller_helpers
+ Module.new do
+ def body_class_string = 'modal-layout compose-standalone'
+
+ def current_account
+ @current_account ||= Fabricate(:account)
+ end
+
+ def current_theme = 'default'
+ end
+ end
end
end
@@ -122,9 +132,7 @@
describe 'available_sign_up_path' do
context 'when registrations are closed' do
before do
- without_partial_double_verification do
- allow(Setting).to receive(:registrations_mode).and_return('none')
- end
+ allow(Setting).to receive(:[]).with('registrations_mode').and_return 'none'
end
it 'redirects to joinmastodon site' do
diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb
index c6baec5a1ff754..befc8a5c80db49 100644
--- a/spec/helpers/home_helper_spec.rb
+++ b/spec/helpers/home_helper_spec.rb
@@ -23,12 +23,19 @@
context 'with a valid account' do
let(:account) { Fabricate(:account) }
+ before { helper.extend controller_helpers }
+
it 'returns a link to the account' do
- without_partial_double_verification do
- allow(helper).to receive_messages(current_account: account, prefers_autoplay?: false)
- result = helper.account_link_to(account)
+ result = helper.account_link_to(account)
+
+ expect(result).to match "@#{account.acct}"
+ end
+
+ private
- expect(result).to match "@#{account.acct}"
+ def controller_helpers
+ Module.new do
+ def current_account = Account.last
end
end
end
diff --git a/spec/helpers/media_component_helper_spec.rb b/spec/helpers/media_component_helper_spec.rb
index 149f6a83adb269..af5d92769ca249 100644
--- a/spec/helpers/media_component_helper_spec.rb
+++ b/spec/helpers/media_component_helper_spec.rb
@@ -3,16 +3,12 @@
require 'rails_helper'
describe MediaComponentHelper do
+ before { helper.extend controller_helpers }
+
describe 'render_video_component' do
let(:media) { Fabricate(:media_attachment, type: :video, status: Fabricate(:status)) }
let(:result) { helper.render_video_component(media.status) }
- before do
- without_partial_double_verification do
- allow(helper).to receive(:current_account).and_return(media.account)
- end
- end
-
it 'renders a react component for the video' do
expect(parsed_html.div['data-component']).to eq('Video')
end
@@ -22,12 +18,6 @@
let(:media) { Fabricate(:media_attachment, type: :audio, status: Fabricate(:status)) }
let(:result) { helper.render_audio_component(media.status) }
- before do
- without_partial_double_verification do
- allow(helper).to receive(:current_account).and_return(media.account)
- end
- end
-
it 'renders a react component for the audio' do
expect(parsed_html.div['data-component']).to eq('Audio')
end
@@ -37,12 +27,6 @@
let(:media) { Fabricate(:media_attachment, type: :audio, status: Fabricate(:status)) }
let(:result) { helper.render_media_gallery_component(media.status) }
- before do
- without_partial_double_verification do
- allow(helper).to receive(:current_account).and_return(media.account)
- end
- end
-
it 'renders a react component for the media gallery' do
expect(parsed_html.div['data-component']).to eq('MediaGallery')
end
@@ -54,10 +38,6 @@
before do
PreviewCardsStatus.create(status: status, preview_card: Fabricate(:preview_card))
-
- without_partial_double_verification do
- allow(helper).to receive(:current_account).and_return(status.account)
- end
end
it 'returns the correct react component markup' do
@@ -69,12 +49,6 @@
let(:status) { Fabricate(:status, poll: Fabricate(:poll)) }
let(:result) { helper.render_poll_component(status) }
- before do
- without_partial_double_verification do
- allow(helper).to receive(:current_account).and_return(status.account)
- end
- end
-
it 'returns the correct react component markup' do
expect(parsed_html.div['data-component']).to eq('Poll')
end
@@ -85,4 +59,10 @@
def parsed_html
Nokogiri::Slop(result)
end
+
+ def controller_helpers
+ Module.new do
+ def current_account = Account.last
+ end
+ end
end
diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb
index 3216d0d1bd9f28..06860c2ff86ffd 100644
--- a/spec/lib/mastodon/cli/accounts_spec.rb
+++ b/spec/lib/mastodon/cli/accounts_spec.rb
@@ -4,7 +4,11 @@
require 'mastodon/cli/accounts'
describe Mastodon::CLI::Accounts do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
@@ -27,15 +31,17 @@ def stub_parallelize_with_progress!
end
describe '#create' do
+ let(:action) { :create }
+
shared_examples 'a new user with given email address and username' do
it 'creates a new user with the specified email address' do
- cli.invoke(:create, arguments, options)
+ subject
expect(User.find_by(email: options[:email])).to be_present
end
it 'creates a new local account with the specified username' do
- cli.invoke(:create, arguments, options)
+ subject
expect(Account.find_local('tootctl_username')).to be_present
end
@@ -43,9 +49,8 @@ def stub_parallelize_with_progress!
it 'returns "OK" and newly generated password' do
allow(SecureRandom).to receive(:hex).and_return('test_password')
- expect { cli.invoke(:create, arguments, options) }.to output(
- a_string_including("OK\nNew password: test_password")
- ).to_stdout
+ expect { subject }
+ .to output_results("OK\nNew password: test_password")
end
end
@@ -61,9 +66,8 @@ def stub_parallelize_with_progress!
let(:options) { { email: 'invalid' } }
it 'exits with an error message' do
- expect { cli.invoke(:create, arguments, options) }.to output(
- a_string_including('Failure/Error: email')
- ).to_stdout
+ expect { subject }
+ .to output_results('Failure/Error: email')
.and raise_error(SystemExit)
end
end
@@ -75,7 +79,7 @@ def stub_parallelize_with_progress!
it_behaves_like 'a new user with given email address and username'
it 'creates a new user with confirmed status' do
- cli.invoke(:create, arguments, options)
+ subject
user = User.find_by(email: options[:email])
@@ -93,7 +97,7 @@ def stub_parallelize_with_progress!
it_behaves_like 'a new user with given email address and username'
it 'creates a new user with approved status' do
- cli.invoke(:create, arguments, options)
+ subject
user = User.find_by(email: options[:email])
@@ -109,7 +113,7 @@ def stub_parallelize_with_progress!
it_behaves_like 'a new user with given email address and username'
it 'creates a new user and assigns the specified role' do
- cli.invoke(:create, arguments, options)
+ subject
role = User.find_by(email: options[:email])&.role
@@ -121,9 +125,8 @@ def stub_parallelize_with_progress!
let(:options) { { email: 'tootctl@example.com', role: '404' } }
it 'exits with an error message indicating the role name was not found' do
- expect { cli.invoke(:create, arguments, options) }.to output(
- a_string_including('Cannot find user role with that name')
- ).to_stdout
+ expect { subject }
+ .to output_results('Cannot find user role with that name')
.and raise_error(SystemExit)
end
end
@@ -139,16 +142,15 @@ def stub_parallelize_with_progress!
end
it 'returns an error message indicating the username is already taken' do
- expect { cli.invoke(:create, arguments, options) }.to output(
- a_string_including("The chosen username is currently in use\nUse --force to reattach it anyway and delete the other user")
- ).to_stdout
+ expect { subject }
+ .to output_results("The chosen username is currently in use\nUse --force to reattach it anyway and delete the other user")
end
context 'with --force option' do
let(:options) { { email: 'tootctl_new@example.com', reattach: true, force: true } }
it 'reattaches the account to the new user and deletes the previous user' do
- cli.invoke(:create, arguments, options)
+ subject
user = Account.find_local('tootctl_username')&.user
@@ -173,20 +175,21 @@ def stub_parallelize_with_progress!
let(:arguments) { ['tootctl_username'] }
it 'raises a required argument missing error (Thor::RequiredArgumentMissingError)' do
- expect { cli.invoke(:create, arguments) }
+ expect { subject }
.to raise_error(Thor::RequiredArgumentMissingError)
end
end
end
describe '#modify' do
+ let(:action) { :modify }
+
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating the user was not found' do
- expect { cli.invoke(:modify, arguments) }.to output(
- a_string_including('No user with such username')
- ).to_stdout
+ expect { subject }
+ .to output_results('No user with such username')
.and raise_error(SystemExit)
end
end
@@ -197,13 +200,12 @@ def stub_parallelize_with_progress!
context 'when no option is provided' do
it 'returns a successful message' do
- expect { cli.invoke(:modify, arguments) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
end
it 'does not modify the user' do
- cli.invoke(:modify, arguments)
+ subject
expect(user).to eq(user.reload)
end
@@ -214,9 +216,8 @@ def stub_parallelize_with_progress!
let(:options) { { role: '404' } }
it 'exits with an error message indicating the role was not found' do
- expect { cli.invoke(:modify, arguments, options) }.to output(
- a_string_including('Cannot find user role with that name')
- ).to_stdout
+ expect { subject }
+ .to output_results('Cannot find user role with that name')
.and raise_error(SystemExit)
end
end
@@ -226,7 +227,7 @@ def stub_parallelize_with_progress!
let(:options) { { role: default_role.name } }
it "updates the user's role to the specified role" do
- cli.invoke(:modify, arguments, options)
+ subject
role = user.reload.role
@@ -241,7 +242,7 @@ def stub_parallelize_with_progress!
let(:user) { Fabricate(:user, role: role) }
it "removes the user's role successfully" do
- cli.invoke(:modify, arguments, options)
+ subject
role = user.reload.role
@@ -254,13 +255,13 @@ def stub_parallelize_with_progress!
let(:options) { { email: 'new_email@email.com' } }
it "sets the user's unconfirmed email to the provided email address" do
- cli.invoke(:modify, arguments, options)
+ subject
expect(user.reload.unconfirmed_email).to eq(options[:email])
end
it "does not update the user's original email address" do
- cli.invoke(:modify, arguments, options)
+ subject
expect(user.reload.email).to eq('old_email@email.com')
end
@@ -270,13 +271,13 @@ def stub_parallelize_with_progress!
let(:options) { { email: 'new_email@email.com', confirm: true } }
it "updates the user's email address to the provided email" do
- cli.invoke(:modify, arguments, options)
+ subject
expect(user.reload.email).to eq(options[:email])
end
it "sets the user's email address as confirmed" do
- cli.invoke(:modify, arguments, options)
+ subject
expect(user.reload.confirmed?).to be(true)
end
@@ -288,7 +289,7 @@ def stub_parallelize_with_progress!
let(:options) { { confirm: true } }
it "confirms the user's email address" do
- cli.invoke(:modify, arguments, options)
+ subject
expect(user.reload.confirmed?).to be(true)
end
@@ -303,7 +304,7 @@ def stub_parallelize_with_progress!
end
it 'approves the user' do
- expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.approved }.from(false).to(true)
+ expect { subject }.to change { user.reload.approved }.from(false).to(true)
end
end
@@ -312,7 +313,7 @@ def stub_parallelize_with_progress!
let(:options) { { disable: true } }
it 'disables the user' do
- expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(false).to(true)
+ expect { subject }.to change { user.reload.disabled }.from(false).to(true)
end
end
@@ -321,7 +322,7 @@ def stub_parallelize_with_progress!
let(:options) { { enable: true } }
it 'enables the user' do
- expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.disabled }.from(true).to(false)
+ expect { subject }.to change { user.reload.disabled }.from(true).to(false)
end
end
@@ -331,9 +332,8 @@ def stub_parallelize_with_progress!
it 'returns a new password for the user' do
allow(SecureRandom).to receive(:hex).and_return('new_password')
- expect { cli.invoke(:modify, arguments, options) }.to output(
- a_string_including('new_password')
- ).to_stdout
+ expect { subject }
+ .to output_results('new_password')
end
end
@@ -342,7 +342,7 @@ def stub_parallelize_with_progress!
let(:options) { { disable_2fa: true } }
it 'disables the two-factor authentication for the user' do
- expect { cli.invoke(:modify, arguments, options) }.to change { user.reload.otp_required_for_login }.from(true).to(false)
+ expect { subject }.to change { user.reload.otp_required_for_login }.from(true).to(false)
end
end
@@ -351,9 +351,8 @@ def stub_parallelize_with_progress!
let(:options) { { email: 'invalid' } }
it 'exits with an error message' do
- expect { cli.invoke(:modify, arguments, options) }.to output(
- a_string_including('Failure/Error: email')
- ).to_stdout
+ expect { subject }
+ .to output_results('Failure/Error: email')
.and raise_error(SystemExit)
end
end
@@ -361,9 +360,8 @@ def stub_parallelize_with_progress!
end
describe '#delete' do
+ let(:action) { :delete }
let(:account) { Fabricate(:account) }
- let(:arguments) { [account.username] }
- let(:options) { { email: account.user.email } }
let(:delete_account_service) { instance_double(DeleteAccountService) }
before do
@@ -372,26 +370,29 @@ def stub_parallelize_with_progress!
end
context 'when both username and --email are provided' do
+ let(:arguments) { [account.username] }
+ let(:options) { { email: account.user.email } }
+
it 'exits with an error message indicating that only one should be used' do
- expect { cli.invoke(:delete, arguments, options) }.to output(
- a_string_including('Use username or --email, not both')
- ).to_stdout
+ expect { subject }
+ .to output_results('Use username or --email, not both')
.and raise_error(SystemExit)
end
end
context 'when neither username nor --email are provided' do
it 'exits with an error message indicating that no username was provided' do
- expect { cli.invoke(:delete) }.to output(
- a_string_including('No username provided')
- ).to_stdout
+ expect { subject }
+ .to output_results('No username provided')
.and raise_error(SystemExit)
end
end
context 'when username is provided' do
+ let(:arguments) { [account.username] }
+
it 'deletes the specified user successfully' do
- cli.invoke(:delete, arguments)
+ subject
expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once
end
@@ -400,15 +401,14 @@ def stub_parallelize_with_progress!
let(:options) { { dry_run: true } }
it 'does not delete the specified user' do
- cli.invoke(:delete, arguments, options)
+ subject
expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false)
end
it 'outputs a successful message in dry run mode' do
- expect { cli.invoke(:delete, arguments, options) }.to output(
- a_string_including('OK (DRY RUN)')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK (DRY RUN)')
end
end
@@ -416,17 +416,18 @@ def stub_parallelize_with_progress!
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that no user was found' do
- expect { cli.invoke(:delete, arguments) }.to output(
- a_string_including('No user with such username')
- ).to_stdout
+ expect { subject }
+ .to output_results('No user with such username')
.and raise_error(SystemExit)
end
end
end
context 'when --email is provided' do
+ let(:options) { { email: account.user.email } }
+
it 'deletes the specified user successfully' do
- cli.invoke(:delete, nil, options)
+ subject
expect(delete_account_service).to have_received(:call).with(account, reserve_email: false).once
end
@@ -435,15 +436,14 @@ def stub_parallelize_with_progress!
let(:options) { { email: account.user.email, dry_run: true } }
it 'does not delete the user' do
- cli.invoke(:delete, nil, options)
+ subject
expect(delete_account_service).to_not have_received(:call).with(account, reserve_email: false)
end
it 'outputs a successful message in dry run mode' do
- expect { cli.invoke(:delete, nil, options) }.to output(
- a_string_including('OK (DRY RUN)')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK (DRY RUN)')
end
end
@@ -451,9 +451,8 @@ def stub_parallelize_with_progress!
let(:options) { { email: '404@example.com' } }
it 'exits with an error message indicating that no user was found' do
- expect { cli.invoke(:delete, nil, options) }.to output(
- a_string_including('No user with such email')
- ).to_stdout
+ expect { subject }
+ .to output_results('No user with such email')
.and raise_error(SystemExit)
end
end
@@ -461,6 +460,7 @@ def stub_parallelize_with_progress!
end
describe '#approve' do
+ let(:action) { :approve }
let(:total_users) { 4 }
before do
@@ -469,8 +469,10 @@ def stub_parallelize_with_progress!
end
context 'with --all option' do
+ let(:options) { { all: true } }
+
it 'approves all pending registrations' do
- cli.invoke(:approve, nil, all: true)
+ subject
expect(User.pluck(:approved).all?(true)).to be(true)
end
@@ -481,7 +483,7 @@ def stub_parallelize_with_progress!
let(:options) { { number: 2 } }
it 'approves the earliest n pending registrations' do
- cli.invoke(:approve, nil, options)
+ subject
n_earliest_pending_registrations = User.order(created_at: :asc).first(options[:number])
@@ -489,7 +491,7 @@ def stub_parallelize_with_progress!
end
it 'does not approve the remaining pending registrations' do
- cli.invoke(:approve, nil, options)
+ subject
pending_registrations = User.order(created_at: :asc).last(total_users - options[:number])
@@ -498,10 +500,11 @@ def stub_parallelize_with_progress!
end
context 'when the number is negative' do
+ let(:options) { { number: -1 } }
+
it 'exits with an error message indicating that the number must be positive' do
- expect { cli.invoke(:approve, nil, number: -1) }.to output(
- a_string_including('Number must be positive')
- ).to_stdout
+ expect { subject }
+ .to output_results('Number must be positive')
.and raise_error(SystemExit)
end
end
@@ -510,13 +513,13 @@ def stub_parallelize_with_progress!
let(:options) { { number: total_users * 2 } }
it 'approves all users' do
- cli.invoke(:approve, nil, options)
+ subject
expect(User.pluck(:approved).all?(true)).to be(true)
end
it 'does not raise any error' do
- expect { cli.invoke(:approve, nil, options) }
+ expect { subject }
.to_not raise_error
end
end
@@ -528,7 +531,7 @@ def stub_parallelize_with_progress!
let(:arguments) { [user.account.username] }
it 'approves the specified user successfully' do
- cli.invoke(:approve, arguments)
+ subject
expect(user.reload.approved?).to be(true)
end
@@ -538,9 +541,8 @@ def stub_parallelize_with_progress!
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that no such account was found' do
- expect { cli.invoke(:approve, arguments) }.to output(
- a_string_including('No such account')
- ).to_stdout
+ expect { subject }
+ .to output_results('No such account')
.and raise_error(SystemExit)
end
end
@@ -548,13 +550,14 @@ def stub_parallelize_with_progress!
end
describe '#follow' do
+ let(:action) { :follow }
+
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that no account with the given username was found' do
- expect { cli.invoke(:follow, arguments) }.to output(
- a_string_including('No such account')
- ).to_stdout
+ expect { subject }
+ .to output_results('No such account')
.and raise_error(SystemExit)
end
end
@@ -565,6 +568,7 @@ def stub_parallelize_with_progress!
let!(:follower_rony) { Fabricate(:account, username: 'rony') }
let!(:follower_charles) { Fabricate(:account, username: 'charles') }
let(:follow_service) { instance_double(FollowService, call: nil) }
+ let(:arguments) { [target_account.username] }
before do
allow(FollowService).to receive(:new).and_return(follow_service)
@@ -572,7 +576,7 @@ def stub_parallelize_with_progress!
end
it 'makes all local accounts follow the target account' do
- cli.follow(target_account.username)
+ subject
expect(follow_service).to have_received(:call).with(follower_bob, target_account, any_args).once
expect(follow_service).to have_received(:call).with(follower_rony, target_account, any_args).once
@@ -580,21 +584,21 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message' do
- expect { cli.follow(target_account.username) }.to output(
- a_string_including("OK, followed target from #{Account.local.count} accounts")
- ).to_stdout
+ expect { subject }
+ .to output_results("OK, followed target from #{Account.local.count} accounts")
end
end
end
describe '#unfollow' do
+ let(:action) { :unfollow }
+
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that no account with the given username was found' do
- expect { cli.invoke(:unfollow, arguments) }.to output(
- a_string_including('No such account')
- ).to_stdout
+ expect { subject }
+ .to output_results('No such account')
.and raise_error(SystemExit)
end
end
@@ -605,6 +609,7 @@ def stub_parallelize_with_progress!
let!(:follower_rambo) { Fabricate(:account, username: 'rambo', domain: nil) }
let!(:follower_ana) { Fabricate(:account, username: 'ana', domain: nil) }
let(:unfollow_service) { instance_double(UnfollowService, call: nil) }
+ let(:arguments) { [target_account.username] }
before do
accounts = [follower_chris, follower_rambo, follower_ana]
@@ -614,7 +619,7 @@ def stub_parallelize_with_progress!
end
it 'makes all local accounts unfollow the target account' do
- cli.unfollow(target_account.username)
+ subject
expect(unfollow_service).to have_received(:call).with(follower_chris, target_account).once
expect(unfollow_service).to have_received(:call).with(follower_rambo, target_account).once
@@ -622,21 +627,21 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message' do
- expect { cli.unfollow(target_account.username) }.to output(
- a_string_including('OK, unfollowed target from 3 accounts')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK, unfollowed target from 3 accounts')
end
end
end
describe '#backup' do
+ let(:action) { :backup }
+
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
it 'exits with an error message indicating that there is no such account' do
- expect { cli.invoke(:backup, arguments) }.to output(
- a_string_including('No user with such username')
- ).to_stdout
+ expect { subject }
+ .to output_results('No user with such username')
.and raise_error(SystemExit)
end
end
@@ -647,22 +652,21 @@ def stub_parallelize_with_progress!
let(:arguments) { [account.username] }
it 'creates a new backup for the specified user' do
- expect { cli.invoke(:backup, arguments) }.to change { user.backups.count }.by(1)
+ expect { subject }.to change { user.backups.count }.by(1)
end
it 'creates a backup job' do
allow(BackupWorker).to receive(:perform_async)
- cli.invoke(:backup, arguments)
+ subject
latest_backup = user.backups.last
expect(BackupWorker).to have_received(:perform_async).with(latest_backup.id).once
end
it 'displays a successful message' do
- expect { cli.invoke(:backup, arguments) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
end
end
end
@@ -724,9 +728,8 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message' do
- expect { cli.refresh }.to output(
- a_string_including('Refreshed 2 accounts')
- ).to_stdout
+ expect { cli.refresh }
+ .to output_results('Refreshed 2 accounts')
end
context 'with --dry-run option' do
@@ -761,9 +764,8 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message with (DRY RUN)' do
- expect { cli.refresh }.to output(
- a_string_including('Refreshed 2 accounts (DRY RUN)')
- ).to_stdout
+ expect { cli.refresh }
+ .to output_results('Refreshed 2 accounts (DRY RUN)')
end
end
end
@@ -823,9 +825,7 @@ def stub_parallelize_with_progress!
allow(account_example_com_a).to receive(:reset_avatar!).and_raise(Mastodon::UnexpectedResponseError)
expect { cli.refresh(*arguments) }
- .to output(
- a_string_including("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}")
- ).to_stdout
+ .to output_results("Account failed: #{account_example_com_a.username}@#{account_example_com_a.domain}")
end
end
@@ -833,9 +833,8 @@ def stub_parallelize_with_progress!
it 'exits with an error message' do
allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil)
- expect { cli.refresh(*arguments) }.to output(
- a_string_including('No such account')
- ).to_stdout
+ expect { cli.refresh(*arguments) }
+ .to output_results('No such account')
.and raise_error(SystemExit)
end
end
@@ -878,7 +877,6 @@ def stub_parallelize_with_progress!
allow(cli).to receive(:parallelize_with_progress).and_yield(account_example_com_a)
.and_yield(account_example_com_b)
.and_return([2, nil])
-
cli.options = { domain: domain }
end
@@ -925,32 +923,33 @@ def stub_parallelize_with_progress!
context 'when neither a list of accts nor options are provided' do
it 'exits with an error message' do
- expect { cli.refresh }.to output(
- a_string_including('No account(s) given')
- ).to_stdout
+ expect { cli.refresh }
+ .to output_results('No account(s) given')
.and raise_error(SystemExit)
end
end
end
describe '#rotate' do
+ let(:action) { :rotate }
+
context 'when neither username nor --all option are given' do
it 'exits with an error message' do
- expect { cli.rotate }.to output(
- a_string_including('No account(s) given')
- ).to_stdout
+ expect { subject }
+ .to output_results('No account(s) given')
.and raise_error(SystemExit)
end
end
context 'when a username is given' do
let(:account) { Fabricate(:account) }
+ let(:arguments) { [account.username] }
it 'correctly rotates keys for the specified account' do
old_private_key = account.private_key
old_public_key = account.public_key
- cli.rotate(account.username)
+ subject
account.reload
expect(account.private_key).to_not eq(old_private_key)
@@ -960,16 +959,17 @@ def stub_parallelize_with_progress!
it 'broadcasts the new keys for the specified account' do
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in)
- cli.rotate(account.username)
+ subject
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once
end
context 'when the given username is not found' do
+ let(:arguments) { ['non_existent_username'] }
+
it 'exits with an error message when the specified username is not found' do
- expect { cli.rotate('non_existent_username') }.to output(
- a_string_including('No such account')
- ).to_stdout
+ expect { subject }
+ .to output_results('No such account')
.and raise_error(SystemExit)
end
end
@@ -977,17 +977,13 @@ def stub_parallelize_with_progress!
context 'when --all option is provided' do
let!(:accounts) { Fabricate.times(2, :account) }
- let(:options) { { all: true } }
-
- before do
- cli.options = { all: true }
- end
+ let(:options) { { all: true } }
it 'correctly rotates keys for all local accounts' do
old_private_keys = accounts.map(&:private_key)
old_public_keys = accounts.map(&:public_key)
- cli.rotate
+ subject
accounts.each(&:reload)
expect(accounts.map(&:private_key)).to_not eq(old_private_keys)
@@ -997,7 +993,7 @@ def stub_parallelize_with_progress!
it 'broadcasts the new keys for each account' do
allow(ActivityPub::UpdateDistributionWorker).to receive(:perform_in)
- cli.rotate
+ subject
accounts.each do |account|
expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_in).with(anything, account.id, anything).once
@@ -1007,11 +1003,12 @@ def stub_parallelize_with_progress!
end
describe '#merge' do
+ let(:action) { :merge }
+
shared_examples 'an account not found' do |acct|
it 'exits with an error message indicating that there is no such account' do
- expect { cli.invoke(:merge, arguments) }.to output(
- a_string_including("No such account (#{acct})")
- ).to_stdout
+ expect { subject }
+ .to output_results("No such account (#{acct})")
.and raise_error(SystemExit)
end
end
@@ -1061,9 +1058,8 @@ def stub_parallelize_with_progress!
end
it 'exits with an error message indicating that the accounts do not have the same pub key' do
- expect { cli.invoke(:merge, arguments) }.to output(
- a_string_including("Accounts don't have the same public key, might not be duplicates!\nOverride with --force")
- ).to_stdout
+ expect { subject }
+ .to output_results("Accounts don't have the same public key, might not be duplicates!\nOverride with --force")
.and raise_error(SystemExit)
end
@@ -1076,13 +1072,13 @@ def stub_parallelize_with_progress!
end
it 'merges "from_account" into "to_account"' do
- cli.invoke(:merge, arguments, options)
+ subject
expect(to_account).to have_received(:merge_with!).with(from_account).once
end
it 'deletes "from_account"' do
- cli.invoke(:merge, arguments, options)
+ subject
expect(from_account).to have_received(:destroy).once
end
@@ -1104,13 +1100,13 @@ def stub_parallelize_with_progress!
end
it 'merges "from_account" into "to_account"' do
- cli.invoke(:merge, arguments)
+ subject
expect(to_account).to have_received(:merge_with!).with(from_account).once
end
it 'deletes "from_account"' do
- cli.invoke(:merge, arguments)
+ subject
expect(from_account).to have_received(:destroy)
end
@@ -1118,6 +1114,7 @@ def stub_parallelize_with_progress!
end
describe '#cull' do
+ let(:action) { :cull }
let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) }
let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com', protocol: :activitypub) }
let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org', protocol: :activitypub) }
@@ -1138,14 +1135,14 @@ def stub_parallelize_with_progress!
end
it 'deletes all inactive remote accounts that longer exist in the origin server' do
- cli.cull
+ subject
expect(delete_account_service).to have_received(:call).with(bob, reserve_username: false).once
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
end
it 'does not delete any active remote account that still exists in the origin server' do
- cli.cull
+ subject
expect(delete_account_service).to_not have_received(:call).with(tom, reserve_username: false)
expect(delete_account_service).to_not have_received(:call).with(ana, reserve_username: false)
@@ -1153,18 +1150,17 @@ def stub_parallelize_with_progress!
end
it 'touches inactive remote accounts that have not been deleted' do
- expect { cli.cull }.to(change { tales.reload.updated_at })
+ expect { subject }.to(change { tales.reload.updated_at })
end
it 'displays the summary correctly' do
- expect { cli.cull }.to output(
- a_string_including('Visited 5 accounts, removed 2')
- ).to_stdout
+ expect { subject }
+ .to output_results('Visited 5 accounts, removed 2')
end
end
context 'when a domain is specified' do
- let(:domain) { 'example.net' }
+ let(:arguments) { ['example.net'] }
before do
stub_parallelize_with_progress!
@@ -1173,16 +1169,15 @@ def stub_parallelize_with_progress!
end
it 'deletes inactive remote accounts that longer exist in the specified domain' do
- cli.cull(domain)
+ subject
expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once
expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once
end
it 'displays the summary correctly' do
- expect { cli.cull(domain) }.to output(
- a_string_including('Visited 2 accounts, removed 2')
- ).to_stdout
+ expect { subject }
+ .to output_results('Visited 2 accounts, removed 2')
end
end
@@ -1195,15 +1190,14 @@ def stub_parallelize_with_progress!
end
it 'skips accounts from the unavailable domain' do
- cli.cull
+ subject
expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false)
end
it 'displays the summary correctly' do
- expect { cli.cull }.to output(
- a_string_including("Visited 5 accounts, removed 0\nThe following domains were not available during the check:\n example.net")
- ).to_stdout
+ expect { subject }
+ .to output_results("Visited 5 accounts, removed 0\nThe following domains were not available during the check:\n example.net")
end
end
@@ -1242,25 +1236,25 @@ def stub_parallelize_with_progress!
end
describe '#reset_relationships' do
+ let(:action) { :reset_relationships }
let(:target_account) { Fabricate(:account) }
let(:arguments) { [target_account.username] }
context 'when no option is given' do
it 'exits with an error message indicating that at least one option is required' do
- expect { cli.invoke(:reset_relationships, arguments) }.to output(
- a_string_including('Please specify either --follows or --followers, or both')
- ).to_stdout
+ expect { subject }
+ .to output_results('Please specify either --follows or --followers, or both')
.and raise_error(SystemExit)
end
end
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
+ let(:options) { { follows: true } }
it 'exits with an error message indicating that there is no such account' do
- expect { cli.invoke(:reset_relationships, arguments, follows: true) }.to output(
- a_string_including('No such account')
- ).to_stdout
+ expect { subject }
+ .to output_results('No such account')
.and raise_error(SystemExit)
end
end
@@ -1277,7 +1271,7 @@ def stub_parallelize_with_progress!
end
it 'resets all "following" relationships from the target account' do
- cli.invoke(:reset_relationships, arguments, options)
+ subject
expect(target_account.reload.following).to be_empty
end
@@ -1285,15 +1279,14 @@ def stub_parallelize_with_progress!
it 'calls BootstrapTimelineWorker once to rebuild the timeline' do
allow(BootstrapTimelineWorker).to receive(:perform_async)
- cli.invoke(:reset_relationships, arguments, options)
+ subject
expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once
end
it 'displays a successful message' do
- expect { cli.invoke(:reset_relationships, arguments, options) }.to output(
- a_string_including("Processed #{total_relationships} relationships")
- ).to_stdout
+ expect { subject }
+ .to output_results("Processed #{total_relationships} relationships")
end
end
@@ -1305,15 +1298,14 @@ def stub_parallelize_with_progress!
end
it 'resets all "followers" relationships from the target account' do
- cli.invoke(:reset_relationships, arguments, options)
+ subject
expect(target_account.reload.followers).to be_empty
end
it 'displays a successful message' do
- expect { cli.invoke(:reset_relationships, arguments, options) }.to output(
- a_string_including("Processed #{total_relationships} relationships")
- ).to_stdout
+ expect { subject }
+ .to output_results("Processed #{total_relationships} relationships")
end
end
@@ -1326,13 +1318,13 @@ def stub_parallelize_with_progress!
end
it 'resets all "followers" relationships from the target account' do
- cli.invoke(:reset_relationships, arguments, options)
+ subject
expect(target_account.reload.followers).to be_empty
end
it 'resets all "following" relationships from the target account' do
- cli.invoke(:reset_relationships, arguments, options)
+ subject
expect(target_account.reload.following).to be_empty
end
@@ -1340,21 +1332,21 @@ def stub_parallelize_with_progress!
it 'calls BootstrapTimelineWorker once to rebuild the timeline' do
allow(BootstrapTimelineWorker).to receive(:perform_async)
- cli.invoke(:reset_relationships, arguments, options)
+ subject
expect(BootstrapTimelineWorker).to have_received(:perform_async).with(target_account.id).once
end
it 'displays a successful message' do
- expect { cli.invoke(:reset_relationships, arguments, options) }.to output(
- a_string_including("Processed #{total_relationships} relationships")
- ).to_stdout
+ expect { subject }
+ .to output_results("Processed #{total_relationships} relationships")
end
end
end
end
describe '#prune' do
+ let(:action) { :prune }
let!(:local_account) { Fabricate(:account) }
let!(:bot_account) { Fabricate(:account, bot: true, domain: 'example.com') }
let!(:group_account) { Fabricate(:account, actor_type: 'Group', domain: 'example.com') }
@@ -1369,7 +1361,7 @@ def stub_parallelize_with_progress!
end
it 'prunes all remote accounts with no interactions with local users' do
- cli.prune
+ subject
prunable_account_ids = prunable_accounts.pluck(:id)
@@ -1377,42 +1369,39 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message' do
- expect { cli.prune }.to output(
- a_string_including("OK, pruned #{prunable_accounts.size} accounts")
- ).to_stdout
+ expect { subject }
+ .to output_results("OK, pruned #{prunable_accounts.size} accounts")
end
it 'does not prune local accounts' do
- cli.prune
+ subject
expect(Account.exists?(id: local_account.id)).to be(true)
end
it 'does not prune bot accounts' do
- cli.prune
+ subject
expect(Account.exists?(id: bot_account.id)).to be(true)
end
it 'does not prune group accounts' do
- cli.prune
+ subject
expect(Account.exists?(id: group_account.id)).to be(true)
end
it 'does not prune accounts that have been mentioned' do
- cli.prune
+ subject
expect(Account.exists?(id: mentioned_account.id)).to be true
end
context 'with --dry-run option' do
- before do
- cli.options = { dry_run: true }
- end
+ let(:options) { { dry_run: true } }
it 'does not prune any account' do
- cli.prune
+ subject
prunable_account_ids = prunable_accounts.pluck(:id)
@@ -1420,14 +1409,14 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message with (DRY RUN)' do
- expect { cli.prune }.to output(
- a_string_including("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)")
- ).to_stdout
+ expect { subject }
+ .to output_results("OK, pruned #{prunable_accounts.size} accounts (DRY RUN)")
end
end
end
describe '#migrate' do
+ let(:action) { :migrate }
let!(:source_account) { Fabricate(:account) }
let!(:target_account) { Fabricate(:account, domain: 'example.com') }
let(:arguments) { [source_account.username] }
@@ -1441,7 +1430,7 @@ def stub_parallelize_with_progress!
shared_examples 'a successful migration' do
it 'calls the MoveService for the last migration' do
- cli.invoke(:migrate, arguments, options)
+ subject
last_migration = source_account.migrations.last
@@ -1449,9 +1438,8 @@ def stub_parallelize_with_progress!
end
it 'displays a successful message' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including("OK, migrated #{source_account.acct} to #{target_account.acct}")
- ).to_stdout
+ expect { subject }
+ .to output_results("OK, migrated #{source_account.acct} to #{target_account.acct}")
end
end
@@ -1459,29 +1447,27 @@ def stub_parallelize_with_progress!
let(:options) { { replay: true, target: "#{target_account.username}@example.com" } }
it 'exits with an error message indicating that using both options is not possible' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including('Use --replay or --target, not both')
- ).to_stdout
+ expect { subject }
+ .to output_results('Use --replay or --target, not both')
.and raise_error(SystemExit)
end
end
context 'when no option is given' do
it 'exits with an error message indicating that at least one option must be used' do
- expect { cli.invoke(:migrate, arguments, {}) }.to output(
- a_string_including('Use either --replay or --target')
- ).to_stdout
+ expect { subject }
+ .to output_results('Use either --replay or --target')
.and raise_error(SystemExit)
end
end
context 'when the given username is not found' do
let(:arguments) { ['non_existent_username'] }
+ let(:options) { { replay: true } }
it 'exits with an error message indicating that there is no such account' do
- expect { cli.invoke(:migrate, arguments, replay: true) }.to output(
- a_string_including("No such account: #{arguments.first}")
- ).to_stdout
+ expect { subject }
+ .to output_results("No such account: #{arguments.first}")
.and raise_error(SystemExit)
end
end
@@ -1491,9 +1477,8 @@ def stub_parallelize_with_progress!
context 'when the specified account has no previous migrations' do
it 'exits with an error message indicating that the given account has no previous migrations' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including('The specified account has not performed any migration')
- ).to_stdout
+ expect { subject }
+ .to output_results('The specified account has not performed any migration')
.and raise_error(SystemExit)
end
end
@@ -1515,9 +1500,8 @@ def stub_parallelize_with_progress!
end
it 'exits with an error message' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway')
- ).to_stdout
+ expect { subject }
+ .to output_results('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway')
.and raise_error(SystemExit)
end
end
@@ -1544,9 +1528,8 @@ def stub_parallelize_with_progress!
end
it 'exits with an error message indicating that there is no such account' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including("The specified target account could not be found: #{options[:target]}")
- ).to_stdout
+ expect { subject }
+ .to output_results("The specified target account could not be found: #{options[:target]}")
.and raise_error(SystemExit)
end
end
@@ -1557,7 +1540,7 @@ def stub_parallelize_with_progress!
end
it 'creates a migration for the specified account with the target account' do
- cli.invoke(:migrate, arguments, options)
+ subject
last_migration = source_account.migrations.last
@@ -1569,9 +1552,8 @@ def stub_parallelize_with_progress!
context 'when the migration record is invalid' do
it 'exits with an error indicating that the validation failed' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including('Error: Validation failed')
- ).to_stdout
+ expect { subject }
+ .to output_results('Error: Validation failed')
.and raise_error(SystemExit)
end
end
@@ -1582,9 +1564,8 @@ def stub_parallelize_with_progress!
end
it 'exits with an error message' do
- expect { cli.invoke(:migrate, arguments, options) }.to output(
- a_string_including('The specified account is redirecting to a different target account. Use --force if you want to change the migration target')
- ).to_stdout
+ expect { subject }
+ .to output_results('The specified account is redirecting to a different target account. Use --force if you want to change the migration target')
.and raise_error(SystemExit)
end
end
diff --git a/spec/lib/mastodon/cli/cache_spec.rb b/spec/lib/mastodon/cli/cache_spec.rb
index c1ce04710c735c..b1515801eb5e7a 100644
--- a/spec/lib/mastodon/cli/cache_spec.rb
+++ b/spec/lib/mastodon/cli/cache_spec.rb
@@ -4,22 +4,29 @@
require 'mastodon/cli/cache'
describe Mastodon::CLI::Cache do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#clear' do
+ let(:action) { :clear }
+
before { allow(Rails.cache).to receive(:clear) }
it 'clears the Rails cache' do
- expect { cli.invoke(:clear) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
expect(Rails.cache).to have_received(:clear)
end
end
describe '#recount' do
+ let(:action) { :recount }
+
context 'with the `accounts` argument' do
let(:arguments) { ['accounts'] }
let(:account_stat) { Fabricate(:account_stat) }
@@ -29,9 +36,8 @@
end
it 're-calculates account records in the cache' do
- expect { cli.invoke(:recount, arguments) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
expect(account_stat.reload.statuses_count).to be_zero
end
@@ -46,9 +52,8 @@
end
it 're-calculates account records in the cache' do
- expect { cli.invoke(:recount, arguments) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
expect(status_stat.reload.replies_count).to be_zero
end
@@ -58,9 +63,9 @@
let(:arguments) { ['other-type'] }
it 'Exits with an error message' do
- expect { cli.invoke(:recount, arguments) }.to output(
- a_string_including('Unknown')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('Unknown')
+ .and raise_error(SystemExit)
end
end
end
diff --git a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb
index 6e4675748e1756..1745ea01bf3311 100644
--- a/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb
+++ b/spec/lib/mastodon/cli/canonical_email_blocks_spec.rb
@@ -4,42 +4,45 @@
require 'mastodon/cli/canonical_email_blocks'
describe Mastodon::CLI::CanonicalEmailBlocks do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#find' do
+ let(:action) { :find }
let(:arguments) { ['user@example.com'] }
context 'when a block is present' do
before { Fabricate(:canonical_email_block, email: 'user@example.com') }
it 'announces the presence of the block' do
- expect { cli.invoke(:find, arguments) }.to output(
- a_string_including('user@example.com is blocked')
- ).to_stdout
+ expect { subject }
+ .to output_results('user@example.com is blocked')
end
end
context 'when a block is not present' do
it 'announces the absence of the block' do
- expect { cli.invoke(:find, arguments) }.to output(
- a_string_including('user@example.com is not blocked')
- ).to_stdout
+ expect { subject }
+ .to output_results('user@example.com is not blocked')
end
end
end
describe '#remove' do
+ let(:action) { :remove }
let(:arguments) { ['user@example.com'] }
context 'when a block is present' do
before { Fabricate(:canonical_email_block, email: 'user@example.com') }
it 'removes the block' do
- expect { cli.invoke(:remove, arguments) }.to output(
- a_string_including('Unblocked user@example.com')
- ).to_stdout
+ expect { subject }
+ .to output_results('Unblocked user@example.com')
expect(CanonicalEmailBlock.matching_email('user@example.com')).to be_empty
end
@@ -47,9 +50,8 @@
context 'when a block is not present' do
it 'announces the absence of the block' do
- expect { cli.invoke(:remove, arguments) }.to output(
- a_string_including('user@example.com is not blocked')
- ).to_stdout
+ expect { subject }
+ .to output_results('user@example.com is not blocked')
end
end
end
diff --git a/spec/lib/mastodon/cli/domains_spec.rb b/spec/lib/mastodon/cli/domains_spec.rb
index add754159caa1a..24f341c1247ebe 100644
--- a/spec/lib/mastodon/cli/domains_spec.rb
+++ b/spec/lib/mastodon/cli/domains_spec.rb
@@ -4,22 +4,75 @@
require 'mastodon/cli/domains'
describe Mastodon::CLI::Domains do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#purge' do
+ let(:action) { :purge }
+
context 'with accounts from the domain' do
- let(:options) { {} }
let(:domain) { 'host.example' }
let!(:account) { Fabricate(:account, domain: domain) }
+ let(:arguments) { [domain] }
it 'removes the account' do
- expect { cli.invoke(:purge, [domain], options) }.to output(
- a_string_including('Removed 1 accounts')
- ).to_stdout
+ expect { subject }
+ .to output_results('Removed 1 accounts')
+
expect { account.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
+
+ describe '#crawl' do
+ let(:action) { :crawl }
+
+ context 'with accounts from the domain' do
+ let(:domain) { 'host.example' }
+
+ before do
+ Fabricate(:account, domain: domain)
+ stub_request(:get, 'https://host.example/api/v1/instance').to_return(status: 200, body: {}.to_json)
+ stub_request(:get, 'https://host.example/api/v1/instance/peers').to_return(status: 200, body: {}.to_json)
+ stub_request(:get, 'https://host.example/api/v1/instance/activity').to_return(status: 200, body: {}.to_json)
+ stub_const('Mastodon::CLI::Domains::CRAWL_SLEEP_TIME', 0)
+ end
+
+ context 'with --format of summary' do
+ let(:options) { { format: 'summary' } }
+
+ it 'crawls the domains and summarizes results' do
+ expect { subject }
+ .to output_results('Visited 1 domains, 0 failed')
+ end
+ end
+
+ context 'with --format of domains' do
+ let(:options) { { format: 'domains' } }
+
+ it 'crawls the domains and summarizes results' do
+ expect { subject }
+ .to output_results(domain)
+ end
+ end
+
+ context 'with --format of json' do
+ let(:options) { { format: 'json' } }
+
+ it 'crawls the domains and summarizes results' do
+ expect { subject }
+ .to output_results(json_summary)
+ end
+
+ def json_summary
+ Oj.dump('host.example': { activity: {} })
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb
index f5cb6c332b74c3..13deb05b6cf277 100644
--- a/spec/lib/mastodon/cli/email_domain_blocks_spec.rb
+++ b/spec/lib/mastodon/cli/email_domain_blocks_spec.rb
@@ -4,96 +4,99 @@
require 'mastodon/cli/email_domain_blocks'
describe Mastodon::CLI::EmailDomainBlocks do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#list' do
+ let(:action) { :list }
+
context 'with email domain block records' do
let!(:parent_block) { Fabricate(:email_domain_block) }
let!(:child_block) { Fabricate(:email_domain_block, parent: parent_block) }
- let(:options) { {} }
it 'lists the blocks' do
- expect { cli.invoke(:list, [], options) }.to output(
- a_string_including(parent_block.domain)
- .and(a_string_including(child_block.domain))
- ).to_stdout
+ expect { subject }
+ .to output_results(
+ parent_block.domain,
+ child_block.domain
+ )
end
end
end
describe '#add' do
- context 'without any options' do
- let(:options) { {} }
+ let(:action) { :add }
+ context 'without any options' do
it 'warns about usage and exits' do
- expect { cli.invoke(:add, [], options) }.to output(
- a_string_including('No domain(s) given')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('No domain(s) given')
+ .and raise_error(SystemExit)
end
end
context 'when blocks exist' do
let(:options) { {} }
let(:domain) { 'host.example' }
+ let(:arguments) { [domain] }
before { Fabricate(:email_domain_block, domain: domain) }
it 'does not add a new block' do
- expect { cli.invoke(:add, [domain], options) }.to output(
- a_string_including('is already blocked')
- ).to_stdout
+ expect { subject }
+ .to output_results('is already blocked')
.and(not_change(EmailDomainBlock, :count))
end
end
context 'when no blocks exist' do
- let(:options) { {} }
let(:domain) { 'host.example' }
+ let(:arguments) { [domain] }
it 'adds a new block' do
- expect { cli.invoke(:add, [domain], options) }.to output(
- a_string_including('Added 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Added 1')
.and(change(EmailDomainBlock, :count).by(1))
end
end
end
describe '#remove' do
- context 'without any options' do
- let(:options) { {} }
+ let(:action) { :remove }
+ context 'without any options' do
it 'warns about usage and exits' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('No domain(s) given')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('No domain(s) given')
+ .and raise_error(SystemExit)
end
end
context 'when blocks exist' do
- let(:options) { {} }
let(:domain) { 'host.example' }
+ let(:arguments) { [domain] }
before { Fabricate(:email_domain_block, domain: domain) }
it 'removes the block' do
- expect { cli.invoke(:remove, [domain], options) }.to output(
- a_string_including('Removed 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Removed 1')
.and(change(EmailDomainBlock, :count).by(-1))
end
end
context 'when no blocks exist' do
- let(:options) { {} }
let(:domain) { 'host.example' }
+ let(:arguments) { [domain] }
it 'does not remove a block' do
- expect { cli.invoke(:remove, [domain], options) }.to output(
- a_string_including('is not yet blocked')
- ).to_stdout
+ expect { subject }
+ .to output_results('is not yet blocked')
.and(not_change(EmailDomainBlock, :count))
end
end
diff --git a/spec/lib/mastodon/cli/emoji_spec.rb b/spec/lib/mastodon/cli/emoji_spec.rb
index 5d109eb5247fb6..d05e972e77c15a 100644
--- a/spec/lib/mastodon/cli/emoji_spec.rb
+++ b/spec/lib/mastodon/cli/emoji_spec.rb
@@ -4,5 +4,61 @@
require 'mastodon/cli/emoji'
describe Mastodon::CLI::Emoji do
+ subject { cli.invoke(action, arguments, options) }
+
+ let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
+
it_behaves_like 'CLI Command'
+
+ describe '#purge' do
+ let(:action) { :purge }
+
+ context 'with existing custom emoji' do
+ before { Fabricate(:custom_emoji) }
+
+ it 'reports a successful purge' do
+ expect { subject }
+ .to output_results('OK')
+ end
+ end
+ end
+
+ describe '#import' do
+ context 'with existing custom emoji' do
+ let(:import_path) { Rails.root.join('spec', 'fixtures', 'files', 'elite-assets.tar.gz') }
+ let(:action) { :import }
+ let(:arguments) { [import_path] }
+
+ it 'reports about imported emoji' do
+ expect { subject }
+ .to output_results('Imported 1')
+ .and change(CustomEmoji, :count).by(1)
+ end
+ end
+ end
+
+ describe '#export' do
+ context 'with existing custom emoji' do
+ before do
+ FileUtils.rm_rf(export_path.dirname)
+ FileUtils.mkdir_p(export_path.dirname)
+
+ Fabricate(:custom_emoji)
+ end
+
+ after { FileUtils.rm_rf(export_path.dirname) }
+
+ let(:export_path) { Rails.root.join('tmp', 'cli-tests', 'export.tar.gz') }
+ let(:arguments) { [export_path.dirname.to_s] }
+ let(:action) { :export }
+
+ it 'reports about exported emoji' do
+ expect { subject }
+ .to output_results('Exported 1')
+ .and change { File.exist?(export_path) }.from(false).to(true)
+ end
+ end
+ end
end
diff --git a/spec/lib/mastodon/cli/feeds_spec.rb b/spec/lib/mastodon/cli/feeds_spec.rb
index e16113c8546fdb..199798052705a6 100644
--- a/spec/lib/mastodon/cli/feeds_spec.rb
+++ b/spec/lib/mastodon/cli/feeds_spec.rb
@@ -4,20 +4,25 @@
require 'mastodon/cli/feeds'
describe Mastodon::CLI::Feeds do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#build' do
+ let(:action) { :build }
+
before { Fabricate(:account) }
context 'with --all option' do
let(:options) { { all: true } }
it 'regenerates feeds for all accounts' do
- expect { cli.invoke(:build, [], options) }.to output(
- a_string_including('Regenerated feeds')
- ).to_stdout
+ expect { subject }
+ .to output_results('Regenerated feeds')
end
end
@@ -27,9 +32,8 @@
let(:arguments) { ['alice'] }
it 'regenerates feeds for the account' do
- expect { cli.invoke(:build, arguments) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
end
end
@@ -37,22 +41,23 @@
let(:arguments) { ['invalid-username'] }
it 'displays an error and exits' do
- expect { cli.invoke(:build, arguments) }.to output(
- a_string_including('No such account')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('No such account')
+ .and raise_error(SystemExit)
end
end
end
describe '#clear' do
+ let(:action) { :clear }
+
before do
allow(redis).to receive(:del).with(key_namespace)
end
it 'clears the redis `feed:*` namespace' do
- expect { cli.invoke(:clear) }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
expect(redis).to have_received(:del).with(key_namespace).once
end
diff --git a/spec/lib/mastodon/cli/ip_blocks_spec.rb b/spec/lib/mastodon/cli/ip_blocks_spec.rb
index 684314dc7ad8bc..dc967a69c97be7 100644
--- a/spec/lib/mastodon/cli/ip_blocks_spec.rb
+++ b/spec/lib/mastodon/cli/ip_blocks_spec.rb
@@ -4,11 +4,16 @@
require 'mastodon/cli/ip_blocks'
describe Mastodon::CLI::IpBlocks do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#add' do
+ let(:action) { :add }
let(:ip_list) do
[
'192.0.2.1',
@@ -25,10 +30,11 @@
]
end
let(:options) { { severity: 'no_access' } }
+ let(:arguments) { ip_list }
shared_examples 'ip address blocking' do
it 'blocks all specified IP addresses' do
- cli.invoke(:add, ip_list, options)
+ subject
blocked_ip_addresses = IpBlock.where(ip: ip_list).pluck(:ip)
expected_ip_addresses = ip_list.map { |ip| IPAddr.new(ip) }
@@ -37,7 +43,7 @@
end
it 'sets the severity for all blocked IP addresses' do
- cli.invoke(:add, ip_list, options)
+ subject
blocked_ips_severity = IpBlock.where(ip: ip_list).pluck(:severity).all?(options[:severity])
@@ -45,9 +51,8 @@
end
it 'displays a success message with a summary' do
- expect { cli.invoke(:add, ip_list, options) }.to output(
- a_string_including("Added #{ip_list.size}, skipped 0, failed 0")
- ).to_stdout
+ expect { subject }
+ .to output_results("Added #{ip_list.size}, skipped 0, failed 0")
end
end
@@ -57,19 +62,19 @@
context 'when a specified IP address is already blocked' do
let!(:blocked_ip) { IpBlock.create(ip: ip_list.last, severity: options[:severity]) }
+ let(:arguments) { ip_list }
it 'skips the already blocked IP address' do
allow(IpBlock).to receive(:new).and_call_original
- cli.invoke(:add, ip_list, options)
+ subject
expect(IpBlock).to_not have_received(:new).with(ip: ip_list.last)
end
it 'displays the correct summary' do
- expect { cli.invoke(:add, ip_list, options) }.to output(
- a_string_including("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{ip_list.last} is already blocked\nAdded #{ip_list.size - 1}, skipped 1, failed 0")
end
context 'with --force option' do
@@ -77,7 +82,7 @@
let(:options) { { severity: 'sign_up_requires_approval', force: true } }
it 'overwrites the existing IP block record' do
- expect { cli.invoke(:add, ip_list, options) }
+ expect { subject }
.to change { blocked_ip.reload.severity }
.from('no_access')
.to('sign_up_requires_approval')
@@ -89,11 +94,11 @@
context 'when a specified IP address is invalid' do
let(:ip_list) { ['320.15.175.0', '9.5.105.255', '0.0.0.0'] }
+ let(:arguments) { ip_list }
it 'displays the correct summary' do
- expect { cli.invoke(:add, ip_list, options) }.to output(
- a_string_including("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{ip_list.first} is invalid\nAdded #{ip_list.size - 1}, skipped 0, failed 1")
end
end
@@ -124,6 +129,7 @@
context 'when a specified IP address fails to be blocked' do
let(:ip_address) { '127.0.0.1' }
let(:ip_block) { instance_double(IpBlock, ip: ip_address, save: false) }
+ let(:arguments) { [ip_address] }
before do
allow(IpBlock).to receive(:new).and_return(ip_block)
@@ -132,24 +138,25 @@
end
it 'displays an error message' do
- expect { cli.invoke(:add, [ip_address], options) }
- .to output(
- a_string_including("#{ip_address} could not be saved")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{ip_address} could not be saved")
end
end
context 'when no IP address is provided' do
+ let(:arguments) { [] }
+
it 'exits with an error message' do
- expect { cli.add }.to output(
- a_string_including('No IP(s) given')
- ).to_stdout
+ expect { subject }
+ .to output_results('No IP(s) given')
.and raise_error(SystemExit)
end
end
end
describe '#remove' do
+ let(:action) { :remove }
+
context 'when removing exact matches' do
let(:ip_list) do
[
@@ -166,21 +173,21 @@
'::/128',
]
end
+ let(:arguments) { ip_list }
before do
ip_list.each { |ip| IpBlock.create(ip: ip, severity: :no_access) }
end
it 'removes exact IP blocks' do
- cli.invoke(:remove, ip_list)
+ subject
expect(IpBlock.where(ip: ip_list)).to_not exist
end
it 'displays success message with a summary' do
- expect { cli.invoke(:remove, ip_list) }.to output(
- a_string_including("Removed #{ip_list.size}, skipped 0")
- ).to_stdout
+ expect { subject }
+ .to output_results("Removed #{ip_list.size}, skipped 0")
end
end
@@ -192,13 +199,13 @@
let(:options) { { force: true } }
it 'removes blocks for IP ranges that cover given IP(s)' do
- cli.invoke(:remove, arguments, options)
+ subject
expect(IpBlock.where(id: [first_ip_range_block.id, second_ip_range_block.id])).to_not exist
end
it 'does not remove other IP ranges' do
- cli.invoke(:remove, arguments, options)
+ subject
expect(IpBlock.where(id: third_ip_range_block.id)).to exist
end
@@ -206,47 +213,46 @@
context 'when a specified IP address is not blocked' do
let(:unblocked_ip) { '192.0.2.1' }
+ let(:arguments) { [unblocked_ip] }
it 'skips the IP address' do
- expect { cli.invoke(:remove, [unblocked_ip]) }.to output(
- a_string_including("#{unblocked_ip} is not yet blocked")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{unblocked_ip} is not yet blocked")
end
it 'displays the summary correctly' do
- expect { cli.invoke(:remove, [unblocked_ip]) }.to output(
- a_string_including('Removed 0, skipped 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Removed 0, skipped 1')
end
end
context 'when a specified IP address is invalid' do
let(:invalid_ip) { '320.15.175.0' }
+ let(:arguments) { [invalid_ip] }
it 'skips the invalid IP address' do
- expect { cli.invoke(:remove, [invalid_ip]) }.to output(
- a_string_including("#{invalid_ip} is invalid")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{invalid_ip} is invalid")
end
it 'displays the summary correctly' do
- expect { cli.invoke(:remove, [invalid_ip]) }.to output(
- a_string_including('Removed 0, skipped 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Removed 0, skipped 1')
end
end
context 'when no IP address is provided' do
it 'exits with an error message' do
- expect { cli.remove }.to output(
- a_string_including('No IP(s) given')
- ).to_stdout
+ expect { subject }
+ .to output_results('No IP(s) given')
.and raise_error(SystemExit)
end
end
end
describe '#export' do
+ let(:action) { :export }
+
let(:first_ip_range_block) { IpBlock.create(ip: '192.168.0.0/24', severity: :no_access) }
let(:second_ip_range_block) { IpBlock.create(ip: '10.0.0.0/16', severity: :no_access) }
let(:third_ip_range_block) { IpBlock.create(ip: '127.0.0.1', severity: :sign_up_block) }
@@ -255,15 +261,13 @@
let(:options) { { format: 'plain' } }
it 'exports blocked IPs with "no_access" severity in plain format' do
- expect { cli.invoke(:export, nil, options) }.to output(
- a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
end
it 'does not export bloked IPs with different severities' do
- expect { cli.invoke(:export, nil, options) }.to_not output(
- a_string_including("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
- ).to_stdout
+ expect { subject }
+ .to_not output_results("#{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}")
end
end
@@ -271,23 +275,20 @@
let(:options) { { format: 'nginx' } }
it 'exports blocked IPs with "no_access" severity in plain format' do
- expect { cli.invoke(:export, nil, options) }.to output(
- a_string_including("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
- ).to_stdout
+ expect { subject }
+ .to output_results("deny #{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};\ndeny #{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix};")
end
it 'does not export bloked IPs with different severities' do
- expect { cli.invoke(:export, nil, options) }.to_not output(
- a_string_including("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
- ).to_stdout
+ expect { subject }
+ .to_not output_results("deny #{third_ip_range_block.ip}/#{first_ip_range_block.ip.prefix};")
end
end
context 'when --format option is not provided' do
it 'exports blocked IPs in plain format by default' do
- expect { cli.export }.to output(
- a_string_including("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
- ).to_stdout
+ expect { subject }
+ .to output_results("#{first_ip_range_block.ip}/#{first_ip_range_block.ip.prefix}\n#{second_ip_range_block.ip}/#{second_ip_range_block.ip.prefix}")
end
end
end
diff --git a/spec/lib/mastodon/cli/main_spec.rb b/spec/lib/mastodon/cli/main_spec.rb
index b5b5d69062124b..59f1fc47841954 100644
--- a/spec/lib/mastodon/cli/main_spec.rb
+++ b/spec/lib/mastodon/cli/main_spec.rb
@@ -4,13 +4,20 @@
require 'mastodon/cli/main'
describe Mastodon::CLI::Main do
+ subject { cli.invoke(action, arguments, options) }
+
+ let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
+
it_behaves_like 'CLI Command'
- describe 'version' do
+ describe '#version' do
+ let(:action) { :version }
+
it 'returns the Mastodon version' do
- expect { described_class.new.invoke(:version) }.to output(
- a_string_including(Mastodon::Version.to_s)
- ).to_stdout
+ expect { subject }
+ .to output_results(Mastodon::Version.to_s)
end
end
end
diff --git a/spec/lib/mastodon/cli/maintenance_spec.rb b/spec/lib/mastodon/cli/maintenance_spec.rb
index 95e695ab559641..02169b7a42a998 100644
--- a/spec/lib/mastodon/cli/maintenance_spec.rb
+++ b/spec/lib/mastodon/cli/maintenance_spec.rb
@@ -4,20 +4,26 @@
require 'mastodon/cli/maintenance'
describe Mastodon::CLI::Maintenance do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#fix_duplicates' do
+ let(:action) { :fix_duplicates }
+
context 'when the database version is too old' do
before do
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2000_01_01_000000) # Earlier than minimum
end
it 'Exits with error message' do
- expect { cli.invoke :fix_duplicates }.to output(
- a_string_including('is too old')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('is too old')
+ .and raise_error(SystemExit)
end
end
@@ -28,9 +34,9 @@
end
it 'Exits with error message' do
- expect { cli.invoke :fix_duplicates }.to output(
- a_string_including('more recent')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('more recent')
+ .and raise_error(SystemExit)
end
end
@@ -41,9 +47,9 @@
end
it 'Exits with error message' do
- expect { cli.invoke :fix_duplicates }.to output(
- a_string_including('Sidekiq is running')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('Sidekiq is running')
+ .and raise_error(SystemExit)
end
end
end
diff --git a/spec/lib/mastodon/cli/media_spec.rb b/spec/lib/mastodon/cli/media_spec.rb
index 6d510c1f5a4902..24e1467a3ca8c9 100644
--- a/spec/lib/mastodon/cli/media_spec.rb
+++ b/spec/lib/mastodon/cli/media_spec.rb
@@ -4,18 +4,24 @@
require 'mastodon/cli/media'
describe Mastodon::CLI::Media do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#remove' do
+ let(:action) { :remove }
+
context 'with --prune-profiles and --remove-headers' do
let(:options) { { prune_profiles: true, remove_headers: true } }
it 'warns about usage and exits' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('--prune-profiles and --remove-headers should not be specified simultaneously')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('--prune-profiles and --remove-headers should not be specified simultaneously')
+ .and raise_error(SystemExit)
end
end
@@ -23,9 +29,9 @@
let(:options) { { include_follows: true } }
it 'warns about usage and exits' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('--include-follows can only be used with --prune-profiles or --remove-headers')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('--include-follows can only be used with --prune-profiles or --remove-headers')
+ .and raise_error(SystemExit)
end
end
@@ -38,9 +44,8 @@
let(:options) { { prune_profiles: true } }
it 'removes account avatars' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('Visited 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Visited 1')
expect(account.reload.avatar).to be_blank
end
@@ -50,9 +55,8 @@
let(:options) { { remove_headers: true } }
it 'removes account header' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('Visited 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Visited 1')
expect(account.reload.header).to be_blank
end
@@ -64,9 +68,8 @@
context 'without options' do
it 'removes account avatars' do
- expect { cli.invoke(:remove) }.to output(
- a_string_including('Removed 1')
- ).to_stdout
+ expect { subject }
+ .to output_results('Removed 1')
expect(media_attachment.reload.file).to be_blank
expect(media_attachment.reload.thumbnail).to be_blank
@@ -76,25 +79,50 @@
end
describe '#usage' do
- context 'without options' do
- let(:options) { {} }
+ let(:action) { :usage }
+ context 'without options' do
it 'reports about storage size' do
- expect { cli.invoke(:usage, [], options) }.to output(
- a_string_including('0 Bytes')
- ).to_stdout
+ expect { subject }
+ .to output_results('0 Bytes')
+ end
+ end
+ end
+
+ describe '#lookup' do
+ let(:action) { :lookup }
+ let(:arguments) { [url] }
+
+ context 'with valid url not connected to a record' do
+ let(:url) { 'https://example.host/assets/1' }
+
+ it 'warns about url and exits' do
+ expect { subject }
+ .to output_results('Not a media URL')
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'with a valid media url' do
+ let(:status) { Fabricate(:status) }
+ let(:media_attachment) { Fabricate(:media_attachment, status: status) }
+ let(:url) { media_attachment.file.url(:original) }
+
+ it 'displays the url of a connected status' do
+ expect { subject }
+ .to output_results(status.id.to_s)
end
end
end
describe '#refresh' do
- context 'without any options' do
- let(:options) { {} }
+ let(:action) { :refresh }
+ context 'without any options' do
it 'warns about usage and exits' do
- expect { cli.invoke(:refresh, [], options) }.to output(
- a_string_including('Specify the source')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('Specify the source')
+ .and raise_error(SystemExit)
end
end
@@ -108,9 +136,8 @@
let(:status) { Fabricate(:status) }
it 'redownloads the attachment file' do
- expect { cli.invoke(:refresh, [], options) }.to output(
- a_string_including('Downloaded 1 media')
- ).to_stdout
+ expect { subject }
+ .to output_results('Downloaded 1 media')
end
end
@@ -119,9 +146,9 @@
let(:options) { { account: 'not-real-user@example.host' } }
it 'warns about usage and exits' do
- expect { cli.invoke(:refresh, [], options) }.to output(
- a_string_including('No such account')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('No such account')
+ .and raise_error(SystemExit)
end
end
@@ -135,9 +162,8 @@
let(:account) { Fabricate(:account) }
it 'redownloads the attachment file' do
- expect { cli.invoke(:refresh, [], options) }.to output(
- a_string_including('Downloaded 1 media')
- ).to_stdout
+ expect { subject }
+ .to output_results('Downloaded 1 media')
end
end
end
@@ -153,9 +179,8 @@
let(:account) { Fabricate(:account, domain: domain) }
it 'redownloads the attachment file' do
- expect { cli.invoke(:refresh, [], options) }.to output(
- a_string_including('Downloaded 1 media')
- ).to_stdout
+ expect { subject }
+ .to output_results('Downloaded 1 media')
end
end
end
diff --git a/spec/lib/mastodon/cli/preview_cards_spec.rb b/spec/lib/mastodon/cli/preview_cards_spec.rb
index a766d250eb72f5..951ae3758f0642 100644
--- a/spec/lib/mastodon/cli/preview_cards_spec.rb
+++ b/spec/lib/mastodon/cli/preview_cards_spec.rb
@@ -4,11 +4,17 @@
require 'mastodon/cli/preview_cards'
describe Mastodon::CLI::PreviewCards do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#remove' do
+ let(:action) { :remove }
+
context 'with relevant preview cards' do
before do
Fabricate(:preview_card, updated_at: 10.years.ago, type: :link)
@@ -18,10 +24,11 @@
context 'with no arguments' do
it 'deletes thumbnails for local preview cards' do
- expect { cli.invoke(:remove) }.to output(
- a_string_including('Removed 2 preview cards')
- .and(a_string_including('approx. 119 KB'))
- ).to_stdout
+ expect { subject }
+ .to output_results(
+ 'Removed 2 preview cards',
+ 'approx. 119 KB'
+ )
end
end
@@ -29,10 +36,11 @@
let(:options) { { link: true } }
it 'deletes thumbnails for local preview cards' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('Removed 1 link-type preview cards')
- .and(a_string_including('approx. 59.6 KB'))
- ).to_stdout
+ expect { subject }
+ .to output_results(
+ 'Removed 1 link-type preview cards',
+ 'approx. 59.6 KB'
+ )
end
end
@@ -40,10 +48,11 @@
let(:options) { { days: 365 } }
it 'deletes thumbnails for local preview cards' do
- expect { cli.invoke(:remove, [], options) }.to output(
- a_string_including('Removed 1 preview cards')
- .and(a_string_including('approx. 59.6 KB'))
- ).to_stdout
+ expect { subject }
+ .to output_results(
+ 'Removed 1 preview cards',
+ 'approx. 59.6 KB'
+ )
end
end
end
diff --git a/spec/lib/mastodon/cli/search_spec.rb b/spec/lib/mastodon/cli/search_spec.rb
index 785dc2bd619d97..cb0c80c11dbfd7 100644
--- a/spec/lib/mastodon/cli/search_spec.rb
+++ b/spec/lib/mastodon/cli/search_spec.rb
@@ -4,5 +4,79 @@
require 'mastodon/cli/search'
describe Mastodon::CLI::Search do
+ subject { cli.invoke(action, arguments, options) }
+
+ let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
+
it_behaves_like 'CLI Command'
+
+ describe '#deploy' do
+ let(:action) { :deploy }
+
+ context 'with concurrency out of range' do
+ let(:options) { { concurrency: -100 } }
+
+ it 'Exits with error message' do
+ expect { subject }
+ .to output_results('this concurrency setting')
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'with batch size out of range' do
+ let(:options) { { batch_size: -100_000 } }
+
+ it 'Exits with error message' do
+ expect { subject }
+ .to output_results('this batch_size setting')
+ .and raise_error(SystemExit)
+ end
+ end
+
+ context 'without options' do
+ before { stub_search_indexes }
+
+ let(:indexed_count) { 1 }
+ let(:deleted_count) { 2 }
+
+ it 'reports about storage size' do
+ expect { subject }
+ .to output_results(
+ "Indexed #{described_class::INDICES.size * indexed_count} records",
+ "de-indexed #{described_class::INDICES.size * deleted_count}"
+ )
+ end
+ end
+
+ def stub_search_indexes
+ described_class::INDICES.each do |index|
+ allow(index)
+ .to receive_messages(
+ specification: instance_double(Chewy::Index::Specification, changed?: true, lock!: nil),
+ purge: nil
+ )
+
+ importer_double = importer_double_for(index)
+ allow(importer_double).to receive(:on_progress).and_yield([indexed_count, deleted_count])
+ allow("Importer::#{index}Importer".constantize)
+ .to receive(:new)
+ .and_return(importer_double)
+ end
+ end
+
+ def importer_double_for(index)
+ instance_double(
+ "Importer::#{index}Importer".constantize,
+ clean_up!: nil,
+ estimate!: 100,
+ import!: nil,
+ on_failure: nil,
+ # on_progress: nil,
+ optimize_for_import!: nil,
+ optimize_for_search!: nil
+ )
+ end
+ end
end
diff --git a/spec/lib/mastodon/cli/settings_spec.rb b/spec/lib/mastodon/cli/settings_spec.rb
index 7dcd1110baedb8..02d1042c567aaf 100644
--- a/spec/lib/mastodon/cli/settings_spec.rb
+++ b/spec/lib/mastodon/cli/settings_spec.rb
@@ -7,59 +7,64 @@
it_behaves_like 'CLI Command'
describe 'subcommand "registrations"' do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { Mastodon::CLI::Registrations.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
before do
Setting.registrations_mode = nil
end
describe '#open' do
+ let(:action) { :open }
+
it 'changes "registrations_mode" to "open"' do
- expect { cli.open }.to change(Setting, :registrations_mode).from(nil).to('open')
+ expect { subject }.to change(Setting, :registrations_mode).from(nil).to('open')
end
it 'displays success message' do
- expect { cli.open }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
end
end
describe '#approved' do
+ let(:action) { :approved }
+
it 'changes "registrations_mode" to "approved"' do
- expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
+ expect { subject }.to change(Setting, :registrations_mode).from(nil).to('approved')
end
it 'displays success message' do
- expect { cli.approved }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
end
context 'with --require-reason' do
- before do
- cli.options = { require_reason: true }
- end
+ let(:options) { { require_reason: true } }
it 'changes "registrations_mode" to "approved"' do
- expect { cli.approved }.to change(Setting, :registrations_mode).from(nil).to('approved')
+ expect { subject }.to change(Setting, :registrations_mode).from(nil).to('approved')
end
it 'sets "require_invite_text" to "true"' do
- expect { cli.approved }.to change(Setting, :require_invite_text).from(false).to(true)
+ expect { subject }.to change(Setting, :require_invite_text).from(false).to(true)
end
end
end
describe '#close' do
+ let(:action) { :close }
+
it 'changes "registrations_mode" to "none"' do
- expect { cli.close }.to change(Setting, :registrations_mode).from(nil).to('none')
+ expect { subject }.to change(Setting, :registrations_mode).from(nil).to('none')
end
it 'displays success message' do
- expect { cli.close }.to output(
- a_string_including('OK')
- ).to_stdout
+ expect { subject }
+ .to output_results('OK')
end
end
end
diff --git a/spec/lib/mastodon/cli/statuses_spec.rb b/spec/lib/mastodon/cli/statuses_spec.rb
index 70e4e2c086df6c..63d494bbb65203 100644
--- a/spec/lib/mastodon/cli/statuses_spec.rb
+++ b/spec/lib/mastodon/cli/statuses_spec.rb
@@ -4,26 +4,31 @@
require 'mastodon/cli/statuses'
describe Mastodon::CLI::Statuses do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#remove', use_transactional_tests: false do
+ let(:action) { :remove }
+
context 'with small batch size' do
let(:options) { { batch_size: 0 } }
it 'exits with error message' do
- expect { cli.invoke :remove, [], options }.to output(
- a_string_including('Cannot run')
- ).to_stdout.and raise_error(SystemExit)
+ expect { subject }
+ .to output_results('Cannot run')
+ .and raise_error(SystemExit)
end
end
context 'with default batch size' do
it 'removes unreferenced statuses' do
- expect { cli.invoke :remove }.to output(
- a_string_including('Done after')
- ).to_stdout
+ expect { subject }
+ .to output_results('Done after')
end
end
end
diff --git a/spec/lib/mastodon/cli/upgrade_spec.rb b/spec/lib/mastodon/cli/upgrade_spec.rb
index 0d6494eeeee45d..6861e04887655f 100644
--- a/spec/lib/mastodon/cli/upgrade_spec.rb
+++ b/spec/lib/mastodon/cli/upgrade_spec.rb
@@ -4,23 +4,26 @@
require 'mastodon/cli/upgrade'
describe Mastodon::CLI::Upgrade do
+ subject { cli.invoke(action, arguments, options) }
+
let(:cli) { described_class.new }
+ let(:arguments) { [] }
+ let(:options) { {} }
it_behaves_like 'CLI Command'
describe '#storage_schema' do
- context 'with records that dont need upgrading' do
- let(:options) { {} }
+ let(:action) { :storage_schema }
+ context 'with records that dont need upgrading' do
before do
Fabricate(:account)
Fabricate(:media_attachment)
end
it 'does not upgrade storage for the attachments' do
- expect { cli.invoke(:storage_schema, [], options) }.to output(
- a_string_including('Upgraded storage schema of 0 records')
- ).to_stdout
+ expect { subject }
+ .to output_results('Upgraded storage schema of 0 records')
end
end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index d30e7201c4e69b..4394b470e6e284 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -88,6 +88,7 @@ def sign_in(resource, _deprecated = nil, scope: nil)
config.include Chewy::Rspec::Helpers
config.include Redisable
config.include SignedRequestHelpers, type: :request
+ config.include CommandLineHelpers, type: :cli
config.around(:each, use_transactional_tests: false) do |example|
self.use_transactional_tests = false
diff --git a/spec/controllers/api/v2/search_controller_spec.rb b/spec/requests/api/v2/search_spec.rb
similarity index 79%
rename from spec/controllers/api/v2/search_controller_spec.rb
rename to spec/requests/api/v2/search_spec.rb
index a16716a10c4309..d0778cba4dd486 100644
--- a/spec/controllers/api/v2/search_controller_spec.rb
+++ b/spec/requests/api/v2/search_spec.rb
@@ -2,25 +2,21 @@
require 'rails_helper'
-RSpec.describe Api::V2::SearchController do
- render_views
-
+describe 'Search API' do
context 'with token' do
- let(:user) { Fabricate(:user) }
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') }
-
- before do
- allow(controller).to receive(:doorkeeper_token) { token }
- end
+ let(:user) { Fabricate(:user) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+ let(:scopes) { 'read:search' }
+ let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
- describe 'GET #index' do
+ describe 'GET /api/v2/search' do
let!(:bob) { Fabricate(:account, username: 'bob_test') }
let!(:ana) { Fabricate(:account, username: 'ana_test') }
let!(:tom) { Fabricate(:account, username: 'tom_test') }
let(:params) { { q: 'test' } }
it 'returns http success' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200)
end
@@ -29,7 +25,7 @@
let(:params) { { q: 'test', type: 'accounts' } }
it 'returns all matching accounts' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(bob.id.to_s, ana.id.to_s, tom.id.to_s)
end
@@ -38,7 +34,7 @@
let(:params) { { q: 'test1', resolve: '1' } }
it 'returns http unauthorized' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200)
end
@@ -48,7 +44,7 @@
let(:params) { { q: 'test1', offset: 1 } }
it 'returns http unauthorized' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(200)
end
@@ -62,7 +58,7 @@
end
it 'returns only the followed accounts' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(body_as_json[:accounts].pluck(:id)).to contain_exactly(ana.id.to_s)
end
@@ -73,7 +69,7 @@
before { allow(Search).to receive(:new).and_raise(Mastodon::SyntaxError) }
it 'returns http unprocessable_entity' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(422)
end
@@ -83,7 +79,7 @@
before { allow(Search).to receive(:new).and_raise(ActiveRecord::RecordNotFound) }
it 'returns http not_found' do
- get :index, params: params
+ get '/api/v2/search', headers: headers, params: params
expect(response).to have_http_status(404)
end
@@ -92,11 +88,11 @@
end
context 'without token' do
- describe 'GET #index' do
+ describe 'GET /api/v2/search' do
let(:search_params) { nil }
before do
- get :index, params: search_params
+ get '/api/v2/search', params: search_params
end
context 'without a `q` param' do
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index f0885ff6725f2d..28b254e0878a7d 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -378,12 +378,10 @@
end
end
- it 'creates at least some accounts' do
- expect { subject }.to change { Account.remote.count }.by_at_least(2)
- end
-
- it 'creates no more account than the limit allows' do
- expect { subject }.to change { Account.remote.count }.by_at_most(5)
+ it 'creates accounts without exceeding rate limit' do
+ expect { subject }
+ .to create_some_remote_accounts
+ .and create_fewer_than_rate_limit_accounts
end
end
@@ -445,12 +443,20 @@
end
end
- it 'creates at least some accounts' do
- expect { subject.call('user1', 'foo.test', payload) }.to change { Account.remote.count }.by_at_least(2)
+ it 'creates accounts without exceeding rate limit' do
+ expect { subject.call('user1', 'foo.test', payload) }
+ .to create_some_remote_accounts
+ .and create_fewer_than_rate_limit_accounts
end
+ end
- it 'creates no more account than the limit allows' do
- expect { subject.call('user1', 'foo.test', payload) }.to change { Account.remote.count }.by_at_most(5)
- end
+ private
+
+ def create_some_remote_accounts
+ change(Account.remote, :count).by_at_least(2)
+ end
+
+ def create_fewer_than_rate_limit_accounts
+ change(Account.remote, :count).by_at_most(5)
end
end
diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb
index 7948bf308020f8..5defd5bbabea7f 100644
--- a/spec/services/delete_account_service_spec.rb
+++ b/spec/services/delete_account_service_spec.rb
@@ -47,8 +47,15 @@
let!(:account_note) { Fabricate(:account_note, account: account) }
- it 'deletes associated owned records' do
- expect { subject }.to change {
+ it 'deletes associated owned and target records and target notifications' do
+ expect { subject }
+ .to delete_associated_owned_records
+ .and delete_associated_target_records
+ .and delete_associated_target_notifications
+ end
+
+ def delete_associated_owned_records
+ change do
[
account.statuses,
account.media_attachments,
@@ -61,7 +68,7 @@
account.polls,
account.account_notes,
].map(&:count)
- }.from([3, 1, 1, 1, 1, 1, 2, 2, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
+ end.from([3, 1, 1, 1, 1, 1, 2, 2, 1, 1]).to([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
end
it 'deletes associated owned record groups' do
@@ -82,20 +89,20 @@
expect { bookmark_category_status.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
- it 'deletes associated target records' do
- expect { subject }.to change {
+ def delete_associated_target_records
+ change do
[
AccountPin.where(target_account: account),
].map(&:count)
- }.from([1]).to([0])
+ end.from([1]).to([0])
end
- it 'deletes associated target notifications' do
- expect { subject }.to change {
+ def delete_associated_target_notifications
+ change do
%w(
poll favourite emoji_reaction status mention follow
).map { |type| Notification.where(type: type).count }
- }.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
+ end.from([1, 1, 1, 1, 1, 1]).to([0, 0, 0, 0, 0, 0])
end
end
diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb
index edb70500833336..c258995b7e4ccc 100644
--- a/spec/services/suspend_account_service_spec.rb
+++ b/spec/services/suspend_account_service_spec.rb
@@ -18,14 +18,15 @@
account.suspend!
end
- it "unmerges from local followers' feeds" do
- subject
+ it 'unmerges from feeds of local followers and preserves suspended flag' do
+ expect { subject }
+ .to_not change_suspended_flag
expect(FeedManager.instance).to have_received(:unmerge_from_home).with(account, local_follower)
expect(FeedManager.instance).to have_received(:unmerge_from_list).with(account, list)
end
- it 'does not change the “suspended” flag' do
- expect { subject }.to_not change(account, :suspended?)
+ def change_suspended_flag
+ change(account, :suspended?)
end
end
diff --git a/spec/services/unsuspend_account_service_spec.rb b/spec/services/unsuspend_account_service_spec.rb
index c555b661ecb776..2f737c62159085 100644
--- a/spec/services/unsuspend_account_service_spec.rb
+++ b/spec/services/unsuspend_account_service_spec.rb
@@ -45,14 +45,19 @@ def match_update_actor_request(req, account)
remote_follower.follow!(account)
end
- it "merges back into local followers' feeds" do
+ it 'merges back into feeds of local followers and sends update' do
subject
+
+ expect_feeds_merged
+ expect_updates_sent
+ end
+
+ def expect_feeds_merged
expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
end
- it 'sends an update actor to followers and reporters' do
- subject
+ def expect_updates_sent
expect(a_request(:post, remote_follower.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
expect(a_request(:post, remote_reporter.inbox_url).with { |req| match_update_actor_request(req, account) }).to have_been_made.once
end
@@ -73,19 +78,20 @@ def match_update_actor_request(req, account)
allow(resolve_account_service).to receive(:call).with(account).and_return(account)
end
- it 're-fetches the account' do
- subject
+ it 're-fetches the account, merges feeds, and preserves suspended' do
+ expect { subject }
+ .to_not change_suspended_flag
+ expect_feeds_merged
expect(resolve_account_service).to have_received(:call).with(account)
end
- it "merges back into local followers' feeds" do
- subject
+ def expect_feeds_merged
expect(FeedManager.instance).to have_received(:merge_into_home).with(account, local_follower)
expect(FeedManager.instance).to have_received(:merge_into_list).with(account, list)
end
- it 'does not change the “suspended” flag' do
- expect { subject }.to_not change(account, :suspended?)
+ def change_suspended_flag
+ change(account, :suspended?)
end
end
@@ -97,19 +103,20 @@ def match_update_actor_request(req, account)
end
end
- it 're-fetches the account' do
- subject
+ it 're-fetches the account, does not merge feeds, marks suspended' do
+ expect { subject }
+ .to change_suspended_to_true
expect(resolve_account_service).to have_received(:call).with(account)
+ expect_feeds_not_merged
end
- it "does not merge back into local followers' feeds" do
- subject
+ def expect_feeds_not_merged
expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
end
- it 'marks account as suspended' do
- expect { subject }.to change(account, :suspended?).from(false).to(true)
+ def change_suspended_to_true
+ change(account, :suspended?).from(false).to(true)
end
end
@@ -118,13 +125,14 @@ def match_update_actor_request(req, account)
allow(resolve_account_service).to receive(:call).with(account).and_return(nil)
end
- it 're-fetches the account' do
+ it 're-fetches the account and does not merge feeds' do
subject
+
expect(resolve_account_service).to have_received(:call).with(account)
+ expect_feeds_not_merged
end
- it "does not merge back into local followers' feeds" do
- subject
+ def expect_feeds_not_merged
expect(FeedManager.instance).to_not have_received(:merge_into_home).with(account, local_follower)
expect(FeedManager.instance).to_not have_received(:merge_into_list).with(account, list)
end
diff --git a/spec/support/command_line_helpers.rb b/spec/support/command_line_helpers.rb
new file mode 100644
index 00000000000000..6f9d63d9390281
--- /dev/null
+++ b/spec/support/command_line_helpers.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module CommandLineHelpers
+ def output_results(*args)
+ output(
+ include(*args)
+ ).to_stdout
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 3d8afed41b191f..d0997469d400bd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2390,7 +2390,7 @@ __metadata:
eslint-plugin-jsx-a11y: "npm:~6.8.0"
eslint-plugin-prettier: "npm:^5.0.0"
eslint-plugin-promise: "npm:~6.1.1"
- eslint-plugin-react: "npm:~7.33.0"
+ eslint-plugin-react: "npm:^7.33.2"
eslint-plugin-react-hooks: "npm:^4.6.0"
file-loader: "npm:^6.2.0"
font-awesome: "npm:^4.7.0"
@@ -5979,9 +5979,9 @@ __metadata:
linkType: hard
"core-js@npm:^3.30.2":
- version: 3.33.3
- resolution: "core-js@npm:3.33.3"
- checksum: 08abdc9470c8228b9d09f61e62ab312738681202c4c34e9638889125b304b235f34c4fe22e9d41c20906ac0fcc807dca57c5ff7d6b90021bf64e8fe23461d9ab
+ version: 3.34.0
+ resolution: "core-js@npm:3.34.0"
+ checksum: 408a77898abe03bf3e5dec2a451c36f4745081cca9022f8bdf9b817d57bb6d3a534d555f47a4b95e1daa5e21dbc79122eac2402e25720d425f5925127e55dcd8
languageName: node
linkType: hard
@@ -7529,7 +7529,7 @@ __metadata:
languageName: node
linkType: hard
-"eslint-plugin-react@npm:~7.33.0":
+"eslint-plugin-react@npm:^7.33.2":
version: 7.33.2
resolution: "eslint-plugin-react@npm:7.33.2"
dependencies:
@@ -14698,8 +14698,8 @@ __metadata:
linkType: hard
"sass-loader@npm:^10.2.0":
- version: 10.4.1
- resolution: "sass-loader@npm:10.4.1"
+ version: 10.5.0
+ resolution: "sass-loader@npm:10.5.0"
dependencies:
klona: "npm:^2.0.4"
loader-utils: "npm:^2.0.0"
@@ -14708,7 +14708,7 @@ __metadata:
semver: "npm:^7.3.2"
peerDependencies:
fibers: ">= 3.1.0"
- node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
+ node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
sass: ^1.3.0
webpack: ^4.36.0 || ^5.0.0
peerDependenciesMeta:
@@ -14718,7 +14718,7 @@ __metadata:
optional: true
sass:
optional: true
- checksum: bf04a440fe471928f3cf884bc12c6b70bc391795b35510b1b9021e8a2cca3b8f966aef9518f4171e87e9cb78193a774f695921e6b61881a1580ae0a3c7b1b5e4
+ checksum: be5da7784fd21c4f526cc3afaa1a765ba44cdc2f9798ecbac87b296ab44184ac5ba9bbda68a7a86f8cdcb6130acceefeb8912260fca14bdfc97f9dad02658400
languageName: node
linkType: hard