first commit
This commit is contained in:
41 files changed
+4964
No files matched your search
@@ -0,0 +1,155 @@
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
from articles import inspect_article, short_title
|
||||
from store import Store
|
||||
|
||||
|
||||
class QueueTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self.tmp.name)
|
||||
self.article = self.root / 'article'
|
||||
self.article.mkdir()
|
||||
self.md = self.article / 'a.md'
|
||||
self.md.write_text('# 测试新闻\n\n正文内容。\n\n来源:测试来源,如有侵权,请联系删除。\n')
|
||||
self.store = Store(self.root / 'queue.sqlite3')
|
||||
self.store.configure(dict(fid=125, minimum=1, maximum=1, interval=0, history_checked=True))
|
||||
self.owner = dict(owner='tab-a', account=dict(uid='123', name='测试账号'), extension={'idle': True})
|
||||
|
||||
def tearDown(self):
|
||||
self.tmp.cleanup()
|
||||
|
||||
def begin(self):
|
||||
self.store.enqueue(self.article)
|
||||
self.store.control('start')
|
||||
return self.store.next(self.owner)['job']
|
||||
|
||||
def transition(self, job, state, **extra):
|
||||
return self.store.event(dict(owner='tab-a', id=job['id'], state=state, epoch=self.store.run['epoch'], **extra))
|
||||
|
||||
def submit(self, job):
|
||||
for state in ('importing', 'formatting', 'awaiting_user_review'):
|
||||
self.transition(job, state)
|
||||
self.transition(job, 'user_submitting', snapshot={'title': job['title']})
|
||||
|
||||
def accept(self, job):
|
||||
return self.store.accept(dict(owner='tab-a', id=job['id'], post_url='https://www.dww11.com/thread/12345.html',
|
||||
author_uid='123', fid=125, title=job['title'], state='accepted_pending_moderation', evidence={'test': 'fixture only'}))
|
||||
|
||||
def test_stable_identity_and_original_preserved(self):
|
||||
before = self.md.read_bytes()
|
||||
item = inspect_article(self.article)
|
||||
copy = self.root / 'renamed'
|
||||
shutil.copytree(self.article, copy)
|
||||
(copy / 'a.md').write_text(self.md.read_text().replace('测试新闻', '改标题'))
|
||||
self.assertEqual(inspect_article(copy)['article_key'], item['article_key'])
|
||||
self.assertEqual(self.md.read_bytes(), before)
|
||||
self.assertEqual(item['source'], '测试来源')
|
||||
|
||||
def test_title_does_not_break_emoji(self):
|
||||
short = short_title('😀' * 50)
|
||||
self.assertTrue(short.endswith('...'))
|
||||
self.assertLessEqual(len(short.encode('utf-16-le')) // 2, 80)
|
||||
self.assertEqual(short_title('短标题'), '短标题')
|
||||
|
||||
def test_unsupported_images_stop_before_queue(self):
|
||||
(self.article / 'a.webp').write_bytes(b'webp')
|
||||
with self.assertRaisesRegex(ValueError, '暂不支持'):
|
||||
self.store.enqueue(self.article)
|
||||
self.assertEqual(self.store.state()['jobs'], [])
|
||||
|
||||
def test_missing_and_outside_image_rejected(self):
|
||||
for image in ('missing.png', '../outside.png', 'https://example.com/a.png'):
|
||||
self.md.write_text('# 标题\n\n正文 \n\n来源:测试,如有侵权,请联系删除。')
|
||||
with self.assertRaises(ValueError):
|
||||
self.store.enqueue(self.article)
|
||||
|
||||
def test_no_duplicate_queue_or_cross_tab_claim(self):
|
||||
job = self.begin()
|
||||
self.assertIn('已在队列', self.store.enqueue(self.article))
|
||||
self.assertEqual(self.store.next({**self.owner, 'owner': 'tab-b'})['action'], 'other_tab')
|
||||
self.assertEqual(self.store.next(self.owner)['job']['id'], job['id'])
|
||||
|
||||
def test_stop_invalidates_old_continuation(self):
|
||||
job = self.begin()
|
||||
epoch = self.store.run['epoch']
|
||||
self.store.control('stop')
|
||||
self.store.control('start')
|
||||
with self.assertRaisesRegex(ValueError, '过期'):
|
||||
self.store.event(dict(owner='tab-a', id=job['id'], state='importing', epoch=epoch))
|
||||
|
||||
def test_restart_pauses_and_preserves_claim(self):
|
||||
job = self.begin()
|
||||
restarted = Store(self.root / 'queue.sqlite3')
|
||||
self.assertFalse(restarted.run['running'])
|
||||
self.assertEqual(restarted.run['active'], job['id'])
|
||||
self.assertEqual(restarted.run['batch']['target'], 1)
|
||||
|
||||
def test_accept_requires_manual_submission(self):
|
||||
job = self.begin()
|
||||
with self.assertRaisesRegex(ValueError, '人工提交'):
|
||||
self.accept(job)
|
||||
|
||||
def test_accept_is_atomic_and_idempotent(self):
|
||||
job = self.begin()
|
||||
self.submit(job)
|
||||
self.accept(job)
|
||||
self.accept(job)
|
||||
self.assertEqual(self.store.run['batch']['count'], 1)
|
||||
self.assertIn('已受理', self.store.enqueue(self.article))
|
||||
with self.assertRaisesRegex(ValueError, '已受理'):
|
||||
self.store.resolve(dict(id=job['id'], confirmed_not_accepted=True, retry=True))
|
||||
|
||||
def test_unknown_never_requeues_automatically(self):
|
||||
job = self.begin()
|
||||
self.submit(job)
|
||||
self.transition(job, 'submit_unknown')
|
||||
self.assertEqual(self.store.next(self.owner)['job']['state'], 'submit_unknown')
|
||||
self.assertEqual(self.store.run['batch']['count'], 0)
|
||||
self.assertIsNone(self.store.run['switch'])
|
||||
|
||||
def test_migrate_real_ledger_shape(self):
|
||||
record = dict(markdown_sha256=hashlib.sha256(self.md.read_bytes()).hexdigest(), block_repost=True,
|
||||
markdown_path=str(self.md), status='accepted_pending_moderation', post_id='12345')
|
||||
self.store.import_history([record])
|
||||
self.assertIn('已受理', self.store.enqueue(self.article))
|
||||
|
||||
def test_switch_only_after_target_and_with_more_work(self):
|
||||
job = self.begin()
|
||||
self.submit(job)
|
||||
self.accept(job)
|
||||
other = self.root / 'other';shutil.copytree(self.article, other)
|
||||
(other / 'a.md').write_text(self.md.read_text().replace('正文内容', '另一篇内容'))
|
||||
self.store.enqueue(other)
|
||||
self.assertEqual(self.store.next(self.owner)['action'], 'switch')
|
||||
self.assertEqual(self.store.next(self.owner)['action'], 'switch')
|
||||
self.store.switch_begin({'owner': 'tab-a'})
|
||||
with self.assertRaisesRegex(ValueError, '禁止重复'):
|
||||
self.store.switch_begin({'owner': 'tab-a'})
|
||||
self.assertEqual(self.store.next(self.owner)['action'], 'switch_wait')
|
||||
next_job = self.store.next({**self.owner, 'account': {'uid': '456', 'name': '新测试账号'}})
|
||||
self.assertEqual(next_job['action'], 'prepare')
|
||||
self.assertEqual(self.store.run['batch']['count'], 0)
|
||||
|
||||
def test_no_switch_when_queue_done(self):
|
||||
job = self.begin();self.submit(job);self.accept(job)
|
||||
self.assertEqual(self.store.next(self.owner)['action'], 'idle')
|
||||
self.assertIsNone(self.store.run['switch'])
|
||||
|
||||
def test_bad_result_does_not_increment(self):
|
||||
job = self.begin();self.submit(job)
|
||||
with self.assertRaises(ValueError):
|
||||
self.store.accept(dict(owner='tab-a', id=job['id'], post_url='https://example.com/thread/12345.html'))
|
||||
self.assertEqual(self.store.run['batch']['count'], 0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user