Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78cf615ad1 |
Executable
+201
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
In-Memory 项目仓储 + 小仓储测试.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.adapters.in_memory.project_repository import InMemoryProjectRepository
|
||||
from packages.adapters.in_memory.classification_job_repository import InMemoryClassificationJobRepository
|
||||
from packages.adapters.in_memory.ingest_job_repository import InMemoryIngestJobRepository
|
||||
from packages.domain import Project, ClassificationJob, IngestJob
|
||||
|
||||
|
||||
# ── Project Repository ──────────────────────────────────────────────────────
|
||||
|
||||
@pytest.fixture
|
||||
def project_repo():
|
||||
return InMemoryProjectRepository()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_project():
|
||||
counter = 0
|
||||
|
||||
def _make(owner_id: str = "user_1", name: str = "测试项目"):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return Project(
|
||||
id=f"proj_{counter}",
|
||||
owner_user_id=owner_id,
|
||||
name=name,
|
||||
)
|
||||
|
||||
return _make
|
||||
|
||||
|
||||
class TestInMemoryProjectRepository:
|
||||
"""项目仓储."""
|
||||
|
||||
def test_save_and_find_by_id(self, project_repo, make_project):
|
||||
proj = make_project()
|
||||
project_repo.save(proj)
|
||||
found = project_repo.find_by_id(proj.id)
|
||||
assert found is not None
|
||||
assert found.id == proj.id
|
||||
assert found.name == "测试项目"
|
||||
|
||||
def test_find_by_id_not_found(self, project_repo):
|
||||
assert project_repo.find_by_id("nonexistent") is None
|
||||
|
||||
def test_find_by_owner(self, project_repo, make_project):
|
||||
for i in range(3):
|
||||
project_repo.save(make_project(owner_id="user_1", name=f"p{i}"))
|
||||
project_repo.save(make_project(owner_id="user_2", name="other"))
|
||||
|
||||
result = project_repo.find_by_owner_user_id("user_1")
|
||||
assert len(result) == 3
|
||||
assert all(p.owner_user_id == "user_1" for p in result)
|
||||
|
||||
def test_count_by_owner(self, project_repo, make_project):
|
||||
for i in range(5):
|
||||
project_repo.save(make_project(owner_id="user_1", name=f"p{i}"))
|
||||
project_repo.save(make_project(owner_id="user_2", name="other"))
|
||||
assert project_repo.count_by_owner("user_1") == 5
|
||||
assert project_repo.count_by_owner("user_2") == 1
|
||||
assert project_repo.count_by_owner("user_3") == 0
|
||||
|
||||
def test_delete_existing(self, project_repo, make_project):
|
||||
proj = make_project()
|
||||
project_repo.save(proj)
|
||||
result = project_repo.delete(proj.id)
|
||||
assert result is True
|
||||
assert project_repo.find_by_id(proj.id) is None
|
||||
|
||||
def test_delete_nonexistent(self, project_repo):
|
||||
assert project_repo.delete("nonexistent") is False
|
||||
|
||||
def test_save_updates(self, project_repo, make_project):
|
||||
proj = make_project(name="v1")
|
||||
project_repo.save(proj)
|
||||
proj.name = "v2"
|
||||
project_repo.save(proj)
|
||||
found = project_repo.find_by_id(proj.id)
|
||||
assert found.name == "v2"
|
||||
|
||||
def test_find_accessible_projects(self, project_repo, make_project):
|
||||
p1 = make_project(owner_id="user_1", name="owned")
|
||||
project_repo.save(p1)
|
||||
# 自己拥有的项目可访问
|
||||
result = project_repo.find_accessible_projects("user_1")
|
||||
assert len(result) >= 1
|
||||
assert any(p.id == p1.id for p in result)
|
||||
|
||||
|
||||
# ── Classification Job Repository ──────────────────────────────────────────
|
||||
|
||||
@pytest.fixture
|
||||
def classif_repo():
|
||||
return InMemoryClassificationJobRepository()
|
||||
|
||||
|
||||
class TestInMemoryClassificationJobRepository:
|
||||
"""分类任务仓储."""
|
||||
|
||||
def test_create_and_get(self, classif_repo):
|
||||
job = ClassificationJob(
|
||||
id="job_1",
|
||||
project_id="p1",
|
||||
asset_id="a1",
|
||||
status="pending",
|
||||
)
|
||||
classif_repo.create(job)
|
||||
found = classif_repo.get("job_1")
|
||||
assert found is not None
|
||||
assert found.id == "job_1"
|
||||
assert found.status == "pending"
|
||||
|
||||
def test_get_not_found(self, classif_repo):
|
||||
assert classif_repo.get("nonexistent") is None
|
||||
|
||||
def test_update(self, classif_repo):
|
||||
job = ClassificationJob(
|
||||
id="job_1",
|
||||
project_id="p1",
|
||||
asset_id="a1",
|
||||
status="pending",
|
||||
)
|
||||
classif_repo.create(job)
|
||||
job.status = "completed"
|
||||
classif_repo.update(job)
|
||||
found = classif_repo.get("job_1")
|
||||
assert found.status == "completed"
|
||||
|
||||
def test_update_nonexistent_creates(self, classif_repo):
|
||||
"""update 对不存在的也会写入(dict 赋值)"""
|
||||
job = ClassificationJob(
|
||||
id="new_job",
|
||||
project_id="p1",
|
||||
asset_id="a1",
|
||||
status="running",
|
||||
)
|
||||
classif_repo.update(job)
|
||||
found = classif_repo.get("new_job")
|
||||
assert found is not None
|
||||
|
||||
|
||||
# ── Ingest Job Repository ──────────────────────────────────────────────────
|
||||
|
||||
@pytest.fixture
|
||||
def ingest_repo():
|
||||
return InMemoryIngestJobRepository()
|
||||
|
||||
|
||||
class TestInMemoryIngestJobRepository:
|
||||
"""导入任务仓储."""
|
||||
|
||||
def test_create_and_get(self, ingest_repo):
|
||||
job = IngestJob(
|
||||
id="ingest_1",
|
||||
project_id="p1",
|
||||
library_id="lib1",
|
||||
storage_key="key1",
|
||||
status="pending",
|
||||
)
|
||||
ingest_repo.create(job)
|
||||
found = ingest_repo.get("ingest_1")
|
||||
assert found is not None
|
||||
assert found.id == "ingest_1"
|
||||
assert found.status == "pending"
|
||||
|
||||
def test_get_not_found(self, ingest_repo):
|
||||
assert ingest_repo.get("nonexistent") is None
|
||||
|
||||
def test_update(self, ingest_repo):
|
||||
job = IngestJob(
|
||||
id="ingest_1",
|
||||
project_id="p1",
|
||||
library_id="lib1",
|
||||
storage_key="key1",
|
||||
status="pending",
|
||||
)
|
||||
ingest_repo.create(job)
|
||||
job.status = "completed"
|
||||
ingest_repo.update(job)
|
||||
found = ingest_repo.get("ingest_1")
|
||||
assert found.status == "completed"
|
||||
|
||||
def test_update_nonexistent_creates(self, ingest_repo):
|
||||
job = IngestJob(
|
||||
id="new_ingest",
|
||||
project_id="p1",
|
||||
library_id="lib1",
|
||||
storage_key="key1",
|
||||
status="running",
|
||||
)
|
||||
ingest_repo.update(job)
|
||||
found = ingest_repo.get("new_ingest")
|
||||
assert found is not None
|
||||
Executable
+174
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
In-Memory 标签仓储测试.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.adapters.in_memory.tag_repository import InMemoryTagRepository
|
||||
from packages.domain import Tag
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo():
|
||||
return InMemoryTagRepository()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def make_tag():
|
||||
counter = 0
|
||||
|
||||
def _make(user_id: str = "user_1", name: str = "默认标签"):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
return Tag(
|
||||
id=f"tag_{counter}",
|
||||
user_id=user_id,
|
||||
name=name,
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
|
||||
return _make
|
||||
|
||||
|
||||
class TestInMemoryTagRepositoryCreate:
|
||||
"""创建标签."""
|
||||
|
||||
def test_create_tag(self, repo, make_tag):
|
||||
tag = make_tag()
|
||||
result = repo.create(tag)
|
||||
assert result.id == tag.id
|
||||
assert result.name == tag.name
|
||||
|
||||
def test_create_same_id_overwrites(self, repo, make_tag):
|
||||
tag = make_tag(name="first")
|
||||
repo.create(tag)
|
||||
tag.name = "second"
|
||||
repo.create(tag)
|
||||
found = repo.get(tag.id)
|
||||
assert found.name == "second"
|
||||
|
||||
|
||||
class TestInMemoryTagRepositoryGet:
|
||||
"""获取标签."""
|
||||
|
||||
def test_get_existing(self, repo, make_tag):
|
||||
tag = make_tag()
|
||||
repo.create(tag)
|
||||
found = repo.get(tag.id)
|
||||
assert found is not None
|
||||
assert found.id == tag.id
|
||||
|
||||
def test_get_nonexistent(self, repo):
|
||||
assert repo.get("nonexistent") is None
|
||||
|
||||
|
||||
class TestInMemoryTagRepositoryFindByName:
|
||||
"""按名称查找."""
|
||||
|
||||
def test_find_by_name_found(self, repo, make_tag):
|
||||
tag = make_tag(name="风景")
|
||||
repo.create(tag)
|
||||
found = repo.find_by_name("user_1", "风景")
|
||||
assert found is not None
|
||||
assert found.id == tag.id
|
||||
|
||||
def test_find_by_name_not_found(self, repo, make_tag):
|
||||
tag = make_tag(name="风景")
|
||||
repo.create(tag)
|
||||
assert repo.find_by_name("user_1", "美食") is None
|
||||
assert repo.find_by_name("user_2", "风景") is None
|
||||
|
||||
def test_find_by_name_different_user(self, repo, make_tag):
|
||||
tag = make_tag(user_id="user_1", name="风景")
|
||||
repo.create(tag)
|
||||
assert repo.find_by_name("user_2", "风景") is None
|
||||
|
||||
|
||||
class TestInMemoryTagRepositoryListByUser:
|
||||
"""用户标签列表."""
|
||||
|
||||
def test_list_by_user_empty(self, repo):
|
||||
result = repo.list_by_user("user_1")
|
||||
assert result == []
|
||||
|
||||
def test_list_by_user_filters_correctly(self, repo, make_tag):
|
||||
for i in range(5):
|
||||
make_tag(user_id="user_1", name=f"标签{i}")
|
||||
repo.create(make_tag(user_id="user_1", name=f"标签{i}"))
|
||||
repo.create(make_tag(user_id="user_2", name="其他标签"))
|
||||
|
||||
result = repo.list_by_user("user_1")
|
||||
assert len(result) == 5
|
||||
assert all(t.user_id == "user_1" for t in result)
|
||||
|
||||
def test_list_sorted_by_created_desc(self, repo, make_tag):
|
||||
tags = []
|
||||
for i in range(3):
|
||||
tag = make_tag(name=f"t{i}")
|
||||
tag.created_at = datetime.fromtimestamp(1000 + i * 100)
|
||||
repo.create(tag)
|
||||
tags.append(tag)
|
||||
|
||||
result = repo.list_by_user("user_1")
|
||||
assert len(result) == 3
|
||||
# 最新的排在前面
|
||||
assert result[0].created_at > result[2].created_at
|
||||
|
||||
def test_list_pagination(self, repo, make_tag):
|
||||
for i in range(10):
|
||||
repo.create(make_tag(name=f"tag{i}"))
|
||||
|
||||
page1 = repo.list_by_user("user_1", skip=0, limit=3)
|
||||
page2 = repo.list_by_user("user_1", skip=3, limit=3)
|
||||
assert len(page1) == 3
|
||||
assert len(page2) == 3
|
||||
assert page1[0].id != page2[0].id
|
||||
|
||||
def test_list_limit_exceeds_total(self, repo, make_tag):
|
||||
for i in range(3):
|
||||
repo.create(make_tag(name=f"t{i}"))
|
||||
result = repo.list_by_user("user_1", skip=0, limit=100)
|
||||
assert len(result) == 3
|
||||
|
||||
|
||||
class TestInMemoryTagRepositoryCount:
|
||||
"""统计用户标签数."""
|
||||
|
||||
def test_count_zero(self, repo):
|
||||
assert repo.count_by_user("user_1") == 0
|
||||
|
||||
def test_count_correct(self, repo, make_tag):
|
||||
for i in range(7):
|
||||
repo.create(make_tag(user_id="user_1", name=f"t{i}"))
|
||||
repo.create(make_tag(user_id="user_2", name="other"))
|
||||
assert repo.count_by_user("user_1") == 7
|
||||
assert repo.count_by_user("user_2") == 1
|
||||
|
||||
|
||||
class TestInMemoryTagRepositoryDelete:
|
||||
"""删除标签."""
|
||||
|
||||
def test_delete_existing(self, repo, make_tag):
|
||||
tag = make_tag()
|
||||
repo.create(tag)
|
||||
result = repo.delete(tag.id)
|
||||
assert result is True
|
||||
assert repo.get(tag.id) is None
|
||||
|
||||
def test_delete_nonexistent(self, repo):
|
||||
result = repo.delete("nonexistent")
|
||||
assert result is False
|
||||
|
||||
def test_delete_does_not_affect_others(self, repo, make_tag):
|
||||
t1 = make_tag(name="a")
|
||||
t2 = make_tag(name="b")
|
||||
repo.create(t1)
|
||||
repo.create(t2)
|
||||
repo.delete(t1.id)
|
||||
assert repo.get(t2.id) is not None
|
||||
assert repo.count_by_user("user_1") == 1
|
||||
Executable
+189
@@ -0,0 +1,189 @@
|
||||
"""
|
||||
In-Memory 用户仓储测试.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from packages.adapters.in_memory.user_repository import InMemoryUserRepository
|
||||
from packages.domain.entities import User
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo():
|
||||
return InMemoryUserRepository()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_user():
|
||||
return User(
|
||||
id="user_1",
|
||||
email="test@example.com",
|
||||
display_name="Test User",
|
||||
username="testuser",
|
||||
password_hash="hashed",
|
||||
email_verified=True,
|
||||
email_verification_token="verify_token",
|
||||
password_reset_token="reset_token",
|
||||
wechat_openid="openid_123",
|
||||
wechat_unionid="unionid_123",
|
||||
phone="13800138000",
|
||||
)
|
||||
|
||||
|
||||
class TestInMemoryUserRepositorySave:
|
||||
"""保存用户."""
|
||||
|
||||
def test_save_new_user(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_id("user_1")
|
||||
assert found is not None
|
||||
assert found.email == "test@example.com"
|
||||
|
||||
def test_save_updates_existing(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
sample_user.display_name = "Updated"
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_id("user_1")
|
||||
assert found.display_name == "Updated"
|
||||
|
||||
def test_email_index_case_insensitive(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_email("TEST@EXAMPLE.COM")
|
||||
assert found is not None
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_username_index_case_insensitive(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_username("TESTUSER")
|
||||
assert found is not None
|
||||
assert found.id == "user_1"
|
||||
|
||||
|
||||
class TestInMemoryUserRepositoryFind:
|
||||
"""各种查找方式."""
|
||||
|
||||
def test_find_by_id_found(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_id("user_1")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_id_not_found(self, repo):
|
||||
assert repo.find_by_id("nonexistent") is None
|
||||
|
||||
def test_find_by_email_found(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_email("test@example.com")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_email_not_found(self, repo):
|
||||
assert repo.find_by_email("no@example.com") is None
|
||||
|
||||
def test_find_by_username_found(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_username("testuser")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_username_not_found(self, repo):
|
||||
assert repo.find_by_username("nobody") is None
|
||||
|
||||
def test_find_by_verification_token(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_verification_token("verify_token")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_verification_token_not_found(self, repo):
|
||||
assert repo.find_by_verification_token("bad_token") is None
|
||||
|
||||
def test_find_by_password_reset_token(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_password_reset_token("reset_token")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_password_reset_token_not_found(self, repo):
|
||||
assert repo.find_by_password_reset_token("bad_token") is None
|
||||
|
||||
def test_find_by_wechat_openid(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_wechat_openid("openid_123")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_wechat_openid_not_found(self, repo):
|
||||
assert repo.find_by_wechat_openid("bad_openid") is None
|
||||
|
||||
def test_find_by_wechat_unionid(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_wechat_unionid("unionid_123")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_wechat_unionid_empty_returns_none(self, repo):
|
||||
"""空 unionid 直接返回 None."""
|
||||
assert repo.find_by_wechat_unionid("") is None
|
||||
assert repo.find_by_wechat_unionid(None) is None # type: ignore
|
||||
|
||||
def test_find_by_wechat_unionid_not_found(self, repo):
|
||||
assert repo.find_by_wechat_unionid("bad_unionid") is None
|
||||
|
||||
def test_find_by_phone(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
found = repo.find_by_phone("13800138000")
|
||||
assert found.id == "user_1"
|
||||
|
||||
def test_find_by_phone_empty_returns_none(self, repo):
|
||||
assert repo.find_by_phone("") is None
|
||||
assert repo.find_by_phone(None) is None # type: ignore
|
||||
|
||||
def test_find_by_phone_not_found(self, repo):
|
||||
assert repo.find_by_phone("13900139000") is None
|
||||
|
||||
def test_user_without_username_not_in_username_index(self, repo):
|
||||
user = User(id="u2", email="no_user@example.com", display_name="No Username")
|
||||
repo.save(user)
|
||||
assert repo.find_by_username("") is None
|
||||
|
||||
|
||||
class TestInMemoryUserRepositoryDelete:
|
||||
"""删除用户."""
|
||||
|
||||
def test_delete_existing(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
result = repo.delete("user_1")
|
||||
assert result is True
|
||||
assert repo.find_by_id("user_1") is None
|
||||
assert repo.find_by_email("test@example.com") is None
|
||||
assert repo.find_by_username("testuser") is None
|
||||
assert repo.find_by_verification_token("verify_token") is None
|
||||
assert repo.find_by_password_reset_token("reset_token") is None
|
||||
|
||||
def test_delete_nonexistent(self, repo):
|
||||
result = repo.delete("nonexistent")
|
||||
assert result is False
|
||||
|
||||
def test_delete_cleans_wechat_and_phone_indexes(self, repo, sample_user):
|
||||
repo.save(sample_user)
|
||||
repo.delete("user_1")
|
||||
assert repo.find_by_wechat_openid("openid_123") is None
|
||||
assert repo.find_by_wechat_unionid("unionid_123") is None
|
||||
assert repo.find_by_phone("13800138000") is None
|
||||
|
||||
|
||||
class TestInMemoryUserRepositoryMultipleUsers:
|
||||
"""多用户场景."""
|
||||
|
||||
def test_multiple_users(self, repo):
|
||||
for i in range(5):
|
||||
user = User(
|
||||
id=f"user_{i}",
|
||||
email=f"user{i}@example.com",
|
||||
display_name=f"User {i}",
|
||||
username=f"user{i}",
|
||||
)
|
||||
repo.save(user)
|
||||
|
||||
for i in range(5):
|
||||
assert repo.find_by_id(f"user_{i}") is not None
|
||||
assert repo.find_by_email(f"user{i}@example.com") is not None
|
||||
assert repo.find_by_username(f"user{i}") is not None
|
||||
Reference in New Issue
Block a user