Add Linkki broadcast image
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 48s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 55s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 54s
Build custom container images / build (map[base_image:python:3.12-slim build_args:PYTHON_VERSION=3.12-slim
context:linkki-tiedotus fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:linkki-tiedotus oci_labels:… (push) Successful in 39s
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 48s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 55s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 54s
Build custom container images / build (map[base_image:python:3.12-slim build_args:PYTHON_VERSION=3.12-slim
context:linkki-tiedotus fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:linkki-tiedotus oci_labels:… (push) Successful in 39s
This commit is contained in:
@@ -0,0 +1,588 @@
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from linkki_poster.models import Language, LanguageBinding, PublishBinding, TextSegmentBinding
|
||||
from webapp.storage import (
|
||||
append_status_output,
|
||||
clear_status_history,
|
||||
create_broadcast,
|
||||
is_telegram_delete_expired,
|
||||
list_broadcasts,
|
||||
load_metadata,
|
||||
mark_publish_success,
|
||||
mark_publish_success_with_result,
|
||||
mark_scheduled_publish_failure,
|
||||
mark_scheduled,
|
||||
move_broadcast,
|
||||
normalize_saved_text,
|
||||
scheduled_retry_delay,
|
||||
save_broadcast_changes,
|
||||
telegram_delete_deadline,
|
||||
normalize_discussion_pinning_mode,
|
||||
unschedule,
|
||||
)
|
||||
|
||||
|
||||
def test_title_change_renames_directory_and_updates_slug(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "First title")
|
||||
post_dir = tmp_path / directory_name
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"title": {
|
||||
"base_revision": 0,
|
||||
"value": "Renamed title",
|
||||
},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
renamed_dir = result["post_dir"]
|
||||
assert renamed_dir.name == "renamed-title"
|
||||
assert renamed_dir.exists()
|
||||
metadata = load_metadata(renamed_dir)
|
||||
assert metadata["title"] == "Renamed title"
|
||||
assert metadata["slug"] == "renamed-title"
|
||||
|
||||
|
||||
def test_linked_broadcast_allows_title_and_text_changes_but_rejects_other_locked_fields(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Linked")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
)
|
||||
},
|
||||
discussion_message_ids={},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"title": {"base_revision": 0, "value": "Renamed linked"},
|
||||
"language_order": {"base_revision": 0, "value": ["en", "fi", "sv"]},
|
||||
"images": {
|
||||
"global": {"base_revision": 0, "value": []},
|
||||
},
|
||||
"texts": {
|
||||
"fi": {"base_revision": 0, "value": "Updated"},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
renamed_dir = result["post_dir"]
|
||||
assert renamed_dir.name == "renamed-linked"
|
||||
assert result["results"]["title"]["status"] == "saved"
|
||||
assert result["results"]["language_order"]["status"] == "rejected"
|
||||
assert result["results"]["images"]["status"] == "rejected"
|
||||
assert result["results"]["fi.text"]["status"] == "saved"
|
||||
metadata = load_metadata(renamed_dir)
|
||||
assert metadata["title"] == "Renamed linked"
|
||||
assert metadata["status"] == "modified"
|
||||
assert metadata["dirty_text_languages"] == ["fi"]
|
||||
|
||||
|
||||
def test_normalize_saved_text_ignores_trailing_whitespace() -> None:
|
||||
assert normalize_saved_text("Line 1 \nLine 2\t\t\n\n") == "Line 1\nLine 2"
|
||||
|
||||
|
||||
def test_unschedule_restores_modified_when_linked_and_dirty(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Scheduled update")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
)
|
||||
},
|
||||
discussion_message_ids={},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"texts": {
|
||||
"fi": {"base_revision": 0, "value": "Updated"},
|
||||
},
|
||||
},
|
||||
)
|
||||
mark_scheduled(post_dir, "2026-03-02T12:00:00+02:00")
|
||||
|
||||
unschedule(post_dir)
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["status"] == "modified"
|
||||
assert metadata["scheduled_for"] is None
|
||||
|
||||
|
||||
def test_create_broadcast_uses_slug_only_directory_name(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Test Broadcast")
|
||||
assert directory_name == "test-broadcast"
|
||||
assert (tmp_path / "test-broadcast").exists()
|
||||
|
||||
|
||||
def test_create_broadcast_defaults_pinning_mode_to_first(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Pin first")
|
||||
|
||||
metadata = load_metadata(tmp_path / directory_name)
|
||||
|
||||
assert metadata["discussion_pinning_mode"] == "first"
|
||||
assert metadata["delete_pin_service_messages"] is True
|
||||
|
||||
|
||||
def test_normalize_discussion_pinning_mode_accepts_none() -> None:
|
||||
assert normalize_discussion_pinning_mode("none") == "none"
|
||||
|
||||
|
||||
def test_append_status_output_keeps_previous_entries() -> None:
|
||||
first = append_status_output("", "First log line", "published", "2026-04-17T10:00:00+00:00")
|
||||
combined = append_status_output(first, "Second log line", "updated", "2026-04-17T11:00:00+00:00")
|
||||
|
||||
assert "[2026-04-17T10:00:00+00:00] published" in combined
|
||||
assert "First log line" in combined
|
||||
assert "[2026-04-17T11:00:00+00:00] updated" in combined
|
||||
assert "Second log line" in combined
|
||||
|
||||
|
||||
def test_load_metadata_recovers_from_broken_post_json_and_manual_directory_name(tmp_path: Path) -> None:
|
||||
post_dir = tmp_path / "manual broadcast_name"
|
||||
post_dir.mkdir()
|
||||
(post_dir / "post.json").write_text("{broken", encoding="utf-8")
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
|
||||
assert metadata["title"] == "Manual broadcast name"
|
||||
assert metadata["slug"] == "manual-broadcast-name"
|
||||
assert metadata["status"] == "draft"
|
||||
|
||||
|
||||
def test_schedule_draft_can_be_saved_without_scheduling(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Saved schedule")
|
||||
post_dir = tmp_path / directory_name
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"schedule_draft_for": {
|
||||
"base_revision": 0,
|
||||
"value": "2026-03-30T14:45",
|
||||
},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["schedule_draft_for"]["status"] == "saved"
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["schedule_draft_for"] == "2026-03-30T14:45"
|
||||
assert metadata["scheduled_for"] is None
|
||||
assert metadata["status"] == "draft"
|
||||
|
||||
mark_scheduled(post_dir, "2026-03-30T14:45:00+02:00", "2026-03-30T14:45")
|
||||
unschedule(post_dir)
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["schedule_draft_for"] == "2026-03-30T14:45"
|
||||
|
||||
|
||||
def test_scheduled_publish_failure_keeps_the_schedule_and_records_backoff(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Retry scheduled")
|
||||
post_dir = tmp_path / directory_name
|
||||
mark_scheduled(post_dir, "2026-03-30T14:45:00+02:00")
|
||||
|
||||
mark_scheduled_publish_failure(post_dir, "Temporary Telegram connection failure")
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["status"] == "scheduled"
|
||||
assert metadata["scheduled_for"] == "2026-03-30T14:45:00+02:00"
|
||||
assert metadata["scheduled_retry_count"] == 1
|
||||
assert metadata["scheduled_next_attempt_at"] is not None
|
||||
assert metadata["last_result"] == "scheduled_publish_retry"
|
||||
assert "Temporary Telegram connection failure" in metadata["last_output"]
|
||||
|
||||
|
||||
def test_scheduled_retry_backoff_caps_at_thirty_minutes() -> None:
|
||||
assert scheduled_retry_delay(1) == timedelta(minutes=1)
|
||||
assert scheduled_retry_delay(2) == timedelta(minutes=5)
|
||||
assert scheduled_retry_delay(3) == timedelta(minutes=15)
|
||||
assert scheduled_retry_delay(4) == timedelta(minutes=30)
|
||||
assert scheduled_retry_delay(99) == timedelta(minutes=30)
|
||||
|
||||
|
||||
def test_telegram_target_ids_are_saved_for_unlinked_broadcast(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Telegram target test")
|
||||
post_dir = tmp_path / directory_name
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"telegram_bot_id": {
|
||||
"base_revision": 0,
|
||||
"value": "123456",
|
||||
},
|
||||
"telegram_chat_id": {
|
||||
"base_revision": 0,
|
||||
"value": "-100123",
|
||||
},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["telegram_bot_id"]["status"] == "saved"
|
||||
assert result["results"]["telegram_chat_id"]["status"] == "saved"
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["telegram_bot_id"] == "123456"
|
||||
assert metadata["telegram_chat_id"] == "-100123"
|
||||
|
||||
|
||||
def test_linked_broadcast_rejects_telegram_target_id_change(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Target lock")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
)
|
||||
},
|
||||
discussion_message_ids={},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"telegram_bot_id": {"base_revision": 0, "value": "999999"},
|
||||
"telegram_chat_id": {"base_revision": 0, "value": "-100999"},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["telegram_bot_id"]["status"] == "rejected"
|
||||
assert result["results"]["telegram_chat_id"]["status"] == "rejected"
|
||||
|
||||
|
||||
def test_scheduled_broadcast_rejects_telegram_target_id_change(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Scheduled target lock")
|
||||
post_dir = tmp_path / directory_name
|
||||
mark_scheduled(post_dir, "2026-03-02T12:00:00+02:00")
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"telegram_bot_id": {"base_revision": 0, "value": "999999"},
|
||||
"telegram_chat_id": {"base_revision": 0, "value": "-100999"},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["telegram_bot_id"]["status"] == "rejected"
|
||||
assert result["results"]["telegram_chat_id"]["status"] == "rejected"
|
||||
|
||||
|
||||
def test_publish_success_sets_telegram_published_at_and_delete_deadline(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Delete window")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
)
|
||||
},
|
||||
discussion_message_ids={},
|
||||
)
|
||||
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["telegram_published_at"] is not None
|
||||
deadline = telegram_delete_deadline(metadata)
|
||||
assert deadline is not None
|
||||
assert deadline > datetime.fromisoformat(metadata["telegram_published_at"])
|
||||
|
||||
|
||||
def test_delete_expiry_uses_original_publish_time_not_last_update(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Delete expiry")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
)
|
||||
},
|
||||
discussion_message_ids={},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
metadata = load_metadata(post_dir)
|
||||
original_publish_time = metadata["telegram_published_at"]
|
||||
|
||||
save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"texts": {
|
||||
"fi": {"base_revision": 0, "value": "Updated"},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["telegram_published_at"] == original_publish_time
|
||||
assert is_telegram_delete_expired(
|
||||
metadata,
|
||||
now=datetime.fromisoformat(original_publish_time) + timedelta(hours=48, seconds=1),
|
||||
)
|
||||
|
||||
|
||||
def test_clear_status_history_keeps_status_but_removes_attempt_log(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "History clear")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
)
|
||||
},
|
||||
discussion_message_ids={},
|
||||
)
|
||||
|
||||
|
||||
def test_linked_announcement_pinning_change_marks_modified_and_unschedule_preserves_it(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Pinning update")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
discussion_message_id=201,
|
||||
)
|
||||
},
|
||||
discussion_message_ids={101: 201},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"discussion_pinning_mode": {"base_revision": 0, "value": "all"},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["discussion_pinning_mode"]["status"] == "saved"
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["status"] == "modified"
|
||||
assert metadata["dirty_discussion_pinning"] is True
|
||||
|
||||
mark_scheduled(post_dir, "2026-03-02T12:00:00+02:00")
|
||||
unschedule(post_dir)
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["status"] == "modified"
|
||||
mark_publish_success(post_dir, binding, "posted output")
|
||||
|
||||
clear_status_history(post_dir)
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["status"] == "published"
|
||||
assert metadata["last_attempt_at"] is None
|
||||
assert metadata["last_output"] == ""
|
||||
assert metadata["last_result"] is None
|
||||
|
||||
|
||||
def test_linked_announcement_pinning_mode_none_is_saved(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Pinning none")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
discussion_message_id=201,
|
||||
)
|
||||
},
|
||||
discussion_message_ids={101: 201},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"discussion_pinning_mode": {"base_revision": 0, "value": "none"},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["discussion_pinning_mode"]["status"] == "saved"
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["discussion_pinning_mode"] == "none"
|
||||
assert metadata["status"] == "modified"
|
||||
assert metadata["dirty_discussion_pinning"] is True
|
||||
|
||||
|
||||
def test_linked_announcement_delete_service_message_toggle_marks_modified(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Service cleanup update")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
discussion_message_id=201,
|
||||
)
|
||||
},
|
||||
discussion_message_ids={101: 201},
|
||||
)
|
||||
mark_publish_success(post_dir, binding, "ok")
|
||||
|
||||
result = save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"delete_pin_service_messages": {"base_revision": 0, "value": False},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert result["results"]["delete_pin_service_messages"]["status"] == "saved"
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["delete_pin_service_messages"] is False
|
||||
assert metadata["status"] == "modified"
|
||||
assert metadata["dirty_discussion_pinning"] is True
|
||||
|
||||
|
||||
def test_publish_success_can_persist_effective_pinning_fallback(tmp_path: Path) -> None:
|
||||
directory_name = create_broadcast(tmp_path, "Fallback pin mode")
|
||||
post_dir = tmp_path / directory_name
|
||||
binding = PublishBinding(
|
||||
global_message_ids=[],
|
||||
global_attached_to_language=None,
|
||||
languages={
|
||||
Language.FI: LanguageBinding(
|
||||
language=Language.FI,
|
||||
image_message_ids=[],
|
||||
text_segments=[TextSegmentBinding(message_id=101, mode="message")],
|
||||
primary_message_id=101,
|
||||
primary_message_url="https://t.me/c/123/101",
|
||||
discussion_message_id=201,
|
||||
)
|
||||
},
|
||||
discussion_message_ids={101: 201},
|
||||
)
|
||||
|
||||
save_broadcast_changes(
|
||||
post_dir,
|
||||
{
|
||||
"discussion_pinning_mode": {"base_revision": 0, "value": "all"},
|
||||
"texts": {},
|
||||
"images": {},
|
||||
},
|
||||
)
|
||||
mark_publish_success_with_result(
|
||||
post_dir,
|
||||
binding,
|
||||
"ok",
|
||||
"updated",
|
||||
effective_discussion_pinning_mode="last",
|
||||
)
|
||||
|
||||
metadata = load_metadata(post_dir)
|
||||
assert metadata["discussion_pinning_mode"] == "last"
|
||||
assert metadata["dirty_discussion_pinning"] is False
|
||||
|
||||
|
||||
def test_list_broadcasts_uses_manual_sort_order_instead_of_updated_time(tmp_path: Path) -> None:
|
||||
first = create_broadcast(tmp_path, "First")
|
||||
second = create_broadcast(tmp_path, "Second")
|
||||
|
||||
save_broadcast_changes(
|
||||
tmp_path / first,
|
||||
{
|
||||
"texts": {
|
||||
"fi": {"base_revision": 0, "value": "Updated later"},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
ordered = list_broadcasts(tmp_path)
|
||||
|
||||
assert [item["directory"] for item in ordered] == [second, first]
|
||||
|
||||
|
||||
def test_new_broadcasts_are_inserted_at_top_of_sidebar_order(tmp_path: Path) -> None:
|
||||
first = create_broadcast(tmp_path, "First")
|
||||
second = create_broadcast(tmp_path, "Second")
|
||||
third = create_broadcast(tmp_path, "Third")
|
||||
|
||||
ordered = list_broadcasts(tmp_path)
|
||||
|
||||
assert [item["directory"] for item in ordered] == [third, second, first]
|
||||
|
||||
|
||||
def test_move_broadcast_swaps_manual_order(tmp_path: Path) -> None:
|
||||
first = create_broadcast(tmp_path, "First")
|
||||
second = create_broadcast(tmp_path, "Second")
|
||||
third = create_broadcast(tmp_path, "Third")
|
||||
|
||||
move_broadcast(tmp_path, first, "up")
|
||||
ordered = list_broadcasts(tmp_path)
|
||||
|
||||
assert [item["directory"] for item in ordered] == [third, first, second]
|
||||
Reference in New Issue
Block a user