test: 补充知识处理、检索与 Agent 回归用例
覆盖切块、向量检索、工具调用和历史预算;模型相关用例使用桩。
This commit is contained in:
1 parent
362d79a5c2
commit
f4d8019829
123 files changed
+29005
No files matched your search
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Copyright 2024-2026 ChatterMate
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from uuid import uuid4
|
||||
|
||||
from app.models.fcm_token import FCMToken
|
||||
from app.models.user import User
|
||||
from app.repositories.fcm_token import FCMTokenRepository
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo(db):
|
||||
return FCMTokenRepository(db)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def other_user(db, test_organization, test_role) -> User:
|
||||
user = User(
|
||||
email="other@example.com",
|
||||
hashed_password="x",
|
||||
full_name="Other User",
|
||||
organization_id=test_organization.id,
|
||||
role_id=test_role.id,
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
class TestRegister:
|
||||
def test_stores_token(self, repo, test_user):
|
||||
assert repo.register(test_user.id, "phone_token") is True
|
||||
assert repo.get_tokens(test_user.id) == ["phone_token"]
|
||||
|
||||
def test_is_idempotent(self, repo, test_user):
|
||||
"""The client posts on every app load — that must not create duplicates"""
|
||||
for _ in range(3):
|
||||
repo.register(test_user.id, "phone_token")
|
||||
assert repo.get_tokens(test_user.id) == ["phone_token"]
|
||||
|
||||
def test_keeps_other_devices(self, repo, test_user):
|
||||
repo.register(test_user.id, "phone_token")
|
||||
repo.register(test_user.id, "laptop_token")
|
||||
assert set(repo.get_tokens(test_user.id)) == {"phone_token", "laptop_token"}
|
||||
|
||||
def test_survives_a_lost_insert_race(self, repo, test_user, db, monkeypatch):
|
||||
"""Two tabs post the same token at once; the loser must still succeed.
|
||||
|
||||
The client posts on every app load, so a concurrent duplicate insert is
|
||||
routine — failing it would leave that browser unregistered.
|
||||
"""
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
real_commit = db.commit
|
||||
calls = {"n": 0}
|
||||
|
||||
def commit_once_conflicting():
|
||||
calls["n"] += 1
|
||||
if calls["n"] == 1:
|
||||
# Simulate the other tab having committed the row first.
|
||||
raise IntegrityError("duplicate", None, Exception("conflict"))
|
||||
return real_commit()
|
||||
|
||||
monkeypatch.setattr(db, "commit", commit_once_conflicting)
|
||||
assert repo.register(test_user.id, "raced_token") is True
|
||||
monkeypatch.undo()
|
||||
|
||||
assert repo.get_tokens(test_user.id) == ["raced_token"]
|
||||
|
||||
def test_reassigns_shared_browser(self, repo, test_user, other_user):
|
||||
"""FCM hands the same token to a browser whichever account signs in, so
|
||||
the row moves rather than delivering to the previous owner."""
|
||||
repo.register(test_user.id, "shared_browser_token")
|
||||
repo.register(other_user.id, "shared_browser_token")
|
||||
|
||||
assert repo.get_tokens(test_user.id) == []
|
||||
assert repo.get_tokens(other_user.id) == ["shared_browser_token"]
|
||||
|
||||
|
||||
class TestGetTokens:
|
||||
def test_empty_for_unknown_user(self, repo):
|
||||
assert repo.get_tokens(uuid4()) == []
|
||||
|
||||
|
||||
class TestRemove:
|
||||
def test_removes_only_that_device(self, repo, test_user):
|
||||
repo.register(test_user.id, "phone_token")
|
||||
repo.register(test_user.id, "laptop_token")
|
||||
|
||||
assert repo.remove(test_user.id, "laptop_token") is True
|
||||
assert repo.get_tokens(test_user.id) == ["phone_token"]
|
||||
|
||||
def test_unknown_token_is_not_an_error(self, repo, test_user):
|
||||
assert repo.remove(test_user.id, "never_registered") is False
|
||||
|
||||
def test_cannot_remove_another_users_token(self, repo, test_user, other_user):
|
||||
repo.register(other_user.id, "their_token")
|
||||
|
||||
assert repo.remove(test_user.id, "their_token") is False
|
||||
assert repo.get_tokens(other_user.id) == ["their_token"]
|
||||
|
||||
|
||||
class TestRemoveTokens:
|
||||
def test_prunes_listed_tokens(self, repo, test_user):
|
||||
repo.register(test_user.id, "live")
|
||||
repo.register(test_user.id, "dead_one")
|
||||
repo.register(test_user.id, "dead_two")
|
||||
|
||||
assert repo.remove_tokens(["dead_one", "dead_two"]) == 2
|
||||
assert repo.get_tokens(test_user.id) == ["live"]
|
||||
|
||||
def test_empty_list_is_a_no_op(self, repo, test_user):
|
||||
repo.register(test_user.id, "live")
|
||||
assert repo.remove_tokens([]) == 0
|
||||
assert repo.get_tokens(test_user.id) == ["live"]
|
||||
|
||||
|
||||
class TestCascade:
|
||||
def test_tokens_die_with_the_user(self, db, repo, other_user):
|
||||
repo.register(other_user.id, "doomed_token")
|
||||
|
||||
db.delete(other_user)
|
||||
db.commit()
|
||||
|
||||
assert db.query(FCMToken).filter(
|
||||
FCMToken.token == "doomed_token").first() is None
|
||||
Reference in new issue
Block a user