Ship native iOS app, Wiki/TTS backend, and skeleton loading UX.
Replace the WebView shell with SwiftUI screens, add account-scoped Wiki and TTS APIs with adaptive review and photo scan support, and keep web/iOS pages usable while data loads asynchronously. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import unittest
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from database import Base
|
||||
from models import QuizRecord, User, UserSettings, WikiPage, WikiSource, Word
|
||||
from services.wiki_service import wiki_service
|
||||
|
||||
|
||||
class WikiServiceTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
Base.metadata.create_all(engine)
|
||||
self.db = sessionmaker(bind=engine)()
|
||||
|
||||
def tearDown(self):
|
||||
self.db.close()
|
||||
|
||||
def create_user(self, username: str) -> User:
|
||||
user = User(
|
||||
username=username,
|
||||
password_hash="test",
|
||||
created_at="2026-06-06T00:00:00Z",
|
||||
)
|
||||
self.db.add(user)
|
||||
self.db.flush()
|
||||
self.db.add(
|
||||
UserSettings(
|
||||
user_id=user.id,
|
||||
ai_wiki_enabled=1,
|
||||
ai_wiki_auto_organize=1,
|
||||
)
|
||||
)
|
||||
self.db.commit()
|
||||
return user
|
||||
|
||||
def create_word(self, user: User, text: str) -> Word:
|
||||
word = Word(
|
||||
user_id=user.id,
|
||||
book_id=0,
|
||||
source_text=text,
|
||||
target_text="测试",
|
||||
source_lang="en",
|
||||
target_lang="zh",
|
||||
status="learning",
|
||||
correct_count=1,
|
||||
wrong_count=0,
|
||||
consecutive_correct_count=1,
|
||||
mastery_score=100,
|
||||
train_count=1,
|
||||
total_train_seconds=3,
|
||||
difficulty=5.0,
|
||||
stability_hours=24.0,
|
||||
created_at="2026-06-06T00:00:00Z",
|
||||
)
|
||||
self.db.add(word)
|
||||
self.db.commit()
|
||||
return word
|
||||
|
||||
def test_ingest_builds_private_markdown_page(self):
|
||||
user = self.create_user("alice")
|
||||
word = self.create_word(user, "resilient")
|
||||
wiki_service.ingest_word(self.db, user, word)
|
||||
|
||||
page = (
|
||||
self.db.query(WikiPage)
|
||||
.filter(WikiPage.user_id == user.id, WikiPage.category == "word")
|
||||
.one()
|
||||
)
|
||||
self.assertIn("# resilient", page.content)
|
||||
self.assertIn("WordLoop 原始学习事件 1 条", page.content)
|
||||
self.assertEqual(
|
||||
self.db.query(WikiSource).filter(WikiSource.user_id == user.id).count(),
|
||||
1,
|
||||
)
|
||||
|
||||
def test_quiz_ingest_updates_only_own_wiki(self):
|
||||
alice = self.create_user("alice")
|
||||
bob = self.create_user("bob")
|
||||
word = self.create_word(alice, "resilient")
|
||||
wiki_service.ingest_word(self.db, alice, word)
|
||||
record = QuizRecord(
|
||||
user_id=alice.id,
|
||||
word_id=word.id,
|
||||
question_type="spell",
|
||||
user_answer="resilient",
|
||||
correct_answer="resilient",
|
||||
is_correct=1,
|
||||
duration_seconds=2,
|
||||
created_at="2026-06-06T00:01:00Z",
|
||||
)
|
||||
self.db.add(record)
|
||||
self.db.commit()
|
||||
wiki_service.ingest_quiz(self.db, alice, word, record)
|
||||
|
||||
alice_status = wiki_service.status(self.db, alice)
|
||||
bob_status = wiki_service.status(self.db, bob)
|
||||
self.assertGreater(alice_status["page_count"], 0)
|
||||
self.assertEqual(alice_status["latest_page"]["source_count"], 2)
|
||||
self.assertEqual(bob_status["page_count"], 0)
|
||||
self.assertIsNone(bob_status["latest_page"])
|
||||
|
||||
def test_recompile_prunes_orphan_status_collection(self):
|
||||
user = self.create_user("alice")
|
||||
word = self.create_word(user, "resilient")
|
||||
word.status = "new"
|
||||
self.db.commit()
|
||||
wiki_service.ingest_word(self.db, user, word)
|
||||
|
||||
word.status = "learning"
|
||||
self.db.commit()
|
||||
wiki_service.rebuild(self.db, user)
|
||||
|
||||
slugs = {
|
||||
page.slug
|
||||
for page in self.db.query(WikiPage).filter(WikiPage.user_id == user.id).all()
|
||||
}
|
||||
self.assertNotIn("learning/new", slugs)
|
||||
self.assertIn("learning/learning", slugs)
|
||||
|
||||
def test_graph_returns_wiki_pages_and_links(self):
|
||||
user = self.create_user("alice")
|
||||
word = self.create_word(user, "resilient")
|
||||
wiki_service.ingest_word(self.db, user, word)
|
||||
|
||||
graph = wiki_service.graph(self.db, user)
|
||||
|
||||
node_slugs = {node["slug"] for node in graph["nodes"]}
|
||||
relations = {link["relation"] for link in graph["links"]}
|
||||
self.assertIn("index", node_slugs)
|
||||
self.assertIn(f"vocabulary/{word.id}-resilient", node_slugs)
|
||||
self.assertIn("learning_status", relations)
|
||||
self.assertIn("collection", relations)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user