diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml new file mode 100644 index 0000000..baf3663 --- /dev/null +++ b/.github/workflows/integration_test.yml @@ -0,0 +1,30 @@ +name: Integration Tests + +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + integration-test: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + + - name: Set up Python + run: uv python install 3.12 + + - name: Run integration tests + env: + INTEGRATION_TEST: '1' + WIKIDOT_USERNAME: ${{ secrets.WIKIDOT_USERNAME }} + WIKIDOT_PASSWORD: ${{ secrets.WIKIDOT_PASSWORD }} + run: make test-integration diff --git a/Makefile b/Makefile index 45dbb60..f2941aa 100644 --- a/Makefile +++ b/Makefile @@ -47,21 +47,29 @@ lint-fix: uv sync --extra lint uv run ruff check $(FORMAT_DIR) --fix -# テスト関連のコマンド +# テスト関連のコマンド(デフォルトはユニットテストのみ) test: uv sync --extra test - uv run pytest tests/ -v + uv run pytest tests/unit/ -v test-cov: uv sync --extra test - uv run pytest tests/ -v --cov=src/wikidot --cov-report=term-missing --cov-report=html + uv run pytest tests/unit/ -v --cov=src/wikidot --cov-report=term-missing --cov-report=html --cov-fail-under=80 test-unit: uv sync --extra test uv run pytest tests/unit/ -v +test-unit-cov: + uv sync --extra test + uv run pytest tests/unit/ -v --cov=src/wikidot --cov-report=term-missing --cov-report=html --cov-fail-under=80 + test-integration: uv sync --extra test uv run pytest tests/integration/ -v -.PHONY: build release release_from-develop update-version format format-check commit lint lint-fix test test-cov test-unit test-integration +test-integration-cov: + uv sync --extra test + uv run pytest tests/integration/ -v --cov=src/wikidot --cov-report=term-missing --cov-report=html --cov-fail-under=50 + +.PHONY: build release release_from-develop update-version format format-check commit lint lint-fix test test-cov test-unit test-unit-cov test-integration test-integration-cov diff --git a/src/wikidot/connector/ajax.py b/src/wikidot/connector/ajax.py index ac262c5..0b3288a 100644 --- a/src/wikidot/connector/ajax.py +++ b/src/wikidot/connector/ajax.py @@ -131,7 +131,7 @@ class AjaxModuleConnectorConfig: """ request_timeout: int = 20 - attempt_limit: int = 3 + attempt_limit: int = 5 retry_interval: float = 1.0 max_backoff: float = 60.0 backoff_factor: float = 2.0 diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000..4c31e46 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,86 @@ +# 統合テスト + +## 概要 + +このディレクトリには、実際のWikidotサーバー(ukwhatn-ci.wikidot.com)に対する統合テストが含まれています。 + +## 環境設定 + +### 必要な環境変数 + +```bash +export WIKIDOT_USERNAME=your_username +export WIKIDOT_PASSWORD=your_password +``` + +### テストサイト + +- サイト名: `ukwhatn-ci.wikidot.com` +- 要件: テストアカウントがサイトのメンバーであること + +## テスト実行 + +```bash +# 統合テストのみ実行 +cd /Users/yuki.c.watanabe/dev/scp/libs/wikidot.py +make test-integration + +# または直接pytest +pytest tests/integration/ -v + +# 特定のテストファイルを実行 +pytest tests/integration/test_page_lifecycle.py -v +``` + +## テストカバー範囲 + +| テストファイル | カバー機能 | +|--------------|----------| +| test_site.py | サイト取得、ページ取得 | +| test_page_lifecycle.py | ページ作成、取得、編集、削除 | +| test_page_tags.py | タグ追加、変更、削除 | +| test_page_meta.py | メタ設定、取得、更新、削除 | +| test_page_revision.py | リビジョン履歴取得、最新リビジョン取得 | +| test_page_votes.py | 投票情報取得 | +| test_page_discussion.py | ディスカッション取得、投稿作成 | +| test_forum_category.py | フォーラムカテゴリ一覧、スレッド取得 | +| test_user.py | ユーザー検索、一括取得 | +| test_pm.py | 受信箱/送信箱取得、メッセージ確認 | + +## スキップ対象機能 + +以下の機能は統合テストからスキップされています: + +### 1. サイト参加申請 +- 理由: テストサイトへの参加申請は手動承認が必要 +- 該当API: `site.application.*` + +### 2. プライベートメッセージ送信 +- 理由: 実ユーザーへのメッセージ送信を避けるため +- 該当API: `client.private_message.send()` +- 備考: 取得のみテスト。事前にInbox/Outboxにメッセージを入れておくこと + +### 3. フォーラムカテゴリ/スレッド作成 +- 理由: フォーラム構造への永続的な変更を避けるため +- 該当API: `site.forum.create_thread()` +- 備考: ページディスカッションへの投稿のみテスト + +### 4. メンバー招待 +- 理由: 実ユーザーへの招待を避けるため +- 該当API: `site.member.invite()` + +## クリーンアップ戦略 + +1. 各テストクラスの`setup`フィクスチャでテスト用ページを作成 +2. `yield`後のクリーンアップ処理で作成したページを削除 +3. 削除失敗時はログ出力して続行 +4. ページ命名: `{prefix}-{timestamp}-{random6chars}` 形式で衝突を回避 + +## 注意事項 + +- 環境変数が未設定の場合、統合テストは自動的にスキップされます +- テストは各ファイル内で順次実行されます(テスト間に依存関係がある場合があるため) +- APIレート制限に注意してください +- テスト実行後、クリーンアップに失敗したページが残る場合があります + - ページ名プレフィックス(`test-`)で識別可能 + - 必要に応じて手動削除してください diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 0000000..0601264 --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,134 @@ +"""統合テスト用フィクスチャ""" + +from __future__ import annotations + +import os +import random +import string +import time +from collections.abc import Callable, Generator +from typing import TypeVar + +import pytest + +T = TypeVar("T") + +# 統合テストは環境変数が設定されている場合のみ実行 +WIKIDOT_USERNAME = os.environ.get("WIKIDOT_USERNAME") +WIKIDOT_PASSWORD = os.environ.get("WIKIDOT_PASSWORD") +TEST_SITE_UNIX_NAME = "ukwhatn-ci" + +# 認証情報が未設定の場合はスキップ +pytestmark = pytest.mark.skipif( + not WIKIDOT_USERNAME or not WIKIDOT_PASSWORD, + reason="WIKIDOT_USERNAME and WIKIDOT_PASSWORD environment variables are required", +) + + +def generate_page_name(prefix: str = "test") -> str: + """テスト用ランダムページ名を生成 + + フォーマット: {prefix}-{timestamp}-{random6chars} + 例: test-1703404800-abc123 + """ + timestamp = int(time.time()) + random_suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) + return f"{prefix}-{timestamp}-{random_suffix}" + + +@pytest.fixture(scope="session") +def credentials() -> dict[str, str]: + """テスト用認証情報""" + assert WIKIDOT_USERNAME is not None + assert WIKIDOT_PASSWORD is not None + return { + "username": WIKIDOT_USERNAME, + "password": WIKIDOT_PASSWORD, + } + + +@pytest.fixture(scope="session") +def client(credentials: dict[str, str]): + """認証済みクライアント(セッション全体で共有)""" + from wikidot import Client + + _client = Client( + username=credentials["username"], + password=credentials["password"], + ) + yield _client + # セッション終了時にクリーンアップ + + +@pytest.fixture(scope="session") +def site(client): + """テストサイト(セッション全体で共有)""" + return client.site.get(TEST_SITE_UNIX_NAME) + + +@pytest.fixture +def page_name_generator() -> Callable[[str], str]: + """ページ名生成ヘルパー""" + return generate_page_name + + +@pytest.fixture +def cleanup_pages(site) -> Generator[list[str], None, None]: + """テスト終了時にページをクリーンアップ + + 使用方法: + def test_something(site, cleanup_pages): + page_name = "test-page" + cleanup_pages.append(page_name) + # ... ページ作成 + """ + pages_to_cleanup: list[str] = [] + yield pages_to_cleanup + + for fullname in pages_to_cleanup: + try: + page = site.page.get(fullname, raise_when_not_found=False) + if page is not None: + page.destroy() + except Exception as e: + print(f"Warning: Failed to cleanup page {fullname}: {e}") + + +def wait_for_condition( + fn: Callable[[], T], + predicate: Callable[[T], bool], + max_retries: int = 5, + interval: float = 1.0, +) -> T: + """条件が満たされるまでリトライする + + Wikidot APIのeventual consistencyを考慮し、 + 期待する条件が満たされるまでリトライを行う。 + + Parameters + ---------- + fn : Callable[[], T] + 値を取得する関数 + predicate : Callable[[T], bool] + 条件を判定する関数 + max_retries : int, default 5 + 最大リトライ回数 + interval : float, default 1.0 + リトライ間隔(秒) + + Returns + ------- + T + 条件を満たした値 + + Raises + ------ + AssertionError + 条件を満たさないままリトライ上限に達した場合 + """ + for _ in range(max_retries): + time.sleep(interval) + value = fn() + if predicate(value): + return value + raise AssertionError(f"Condition not met after {max_retries} retries") diff --git a/tests/integration/test_forum_category.py b/tests/integration/test_forum_category.py new file mode 100644 index 0000000..9190e28 --- /dev/null +++ b/tests/integration/test_forum_category.py @@ -0,0 +1,36 @@ +"""フォーラムカテゴリの統合テスト""" + +from __future__ import annotations + + +class TestForumCategory: + """フォーラムカテゴリ操作テスト""" + + def test_1_get_forum_categories(self, site): + """1. フォーラムカテゴリ一覧取得""" + categories = site.forum.categories + assert categories is not None + # カテゴリがなくても空のコレクションが返る + assert isinstance(categories, list) + + def test_2_category_properties(self, site): + """2. カテゴリプロパティ確認""" + categories = site.forum.categories + + # カテゴリがある場合はプロパティを確認 + if len(categories) > 0: + category = categories[0] + assert category.id is not None + assert category.title is not None + assert category.site is not None + + def test_3_category_threads(self, site): + """3. カテゴリのスレッド一覧取得""" + categories = site.forum.categories + + # カテゴリがある場合はスレッドを取得 + if len(categories) > 0: + category = categories[0] + threads = category.threads + assert threads is not None + assert isinstance(threads, list) diff --git a/tests/integration/test_page_lifecycle.py b/tests/integration/test_page_lifecycle.py new file mode 100644 index 0000000..ac08599 --- /dev/null +++ b/tests/integration/test_page_lifecycle.py @@ -0,0 +1,121 @@ +"""ページライフサイクル(作成→取得→編集→削除)の統合テスト""" + +from __future__ import annotations + +import contextlib + +import pytest + +from wikidot.common.exceptions import NotFoundException + +from .conftest import generate_page_name + + +class TestPageLifecycle: + """ページライフサイクルテスト + + テストは順番に実行され、前のテストで作成したページを使用する。 + """ + + @pytest.fixture(autouse=True) + def setup(self, site): + """テストセットアップ""" + self.site = site + self.page_name = generate_page_name("lifecycle") + self.page = None + yield + # クリーンアップ + if self.page is not None: + with contextlib.suppress(Exception): + self.page.destroy() + else: + # ページオブジェクトがない場合は名前で削除を試行 + with contextlib.suppress(Exception): + page = self.site.page.get(self.page_name, raise_when_not_found=False) + if page is not None: + page.destroy() + + def test_1_page_create(self): + """1. ページ作成""" + self.site.page.create( + fullname=self.page_name, + title="Test Page", + source="This is test content.", + comment="Initial creation", + ) + # 作成確認 + self.page = self.site.page.get(self.page_name) + assert self.page is not None + assert self.page.fullname == self.page_name + + def test_2_page_get(self): + """2. 作成ページ取得""" + # まずページを作成 + self.site.page.create( + fullname=self.page_name, + title="Test Page", + source="This is test content.", + ) + self.page = self.site.page.get(self.page_name) + + assert self.page is not None + assert self.page.fullname == self.page_name + assert self.page.title == "Test Page" + + def test_3_page_source(self): + """3. ページソース取得""" + # まずページを作成 + self.site.page.create( + fullname=self.page_name, + title="Test Page", + source="This is test content.", + ) + self.page = self.site.page.get(self.page_name) + + assert self.page is not None + source = self.page.source + assert source is not None + assert source.wiki_text == "This is test content." + + def test_4_page_edit(self): + """4. ページ編集""" + # まずページを作成 + self.site.page.create( + fullname=self.page_name, + title="Test Page", + source="Original content.", + ) + self.page = self.site.page.get(self.page_name) + assert self.page is not None + + # 編集 + self.page.edit( + title="Updated Test Page", + source="Updated content.", + comment="Test edit", + ) + + # 再取得して確認 + updated_page = self.site.page.get(self.page_name) + assert updated_page is not None + assert updated_page.title == "Updated Test Page" + assert updated_page.source.wiki_text == "Updated content." + + def test_5_page_delete(self): + """5. ページ削除""" + # まずページを作成 + self.site.page.create( + fullname=self.page_name, + title="Test Page", + source="Content to be deleted.", + ) + self.page = self.site.page.get(self.page_name) + assert self.page is not None + + # 削除 + self.page.destroy() + self.page = None # クリーンアップ不要 + + # 削除確認 + with pytest.raises(NotFoundException): + self.site.page.get(self.page_name) diff --git a/tests/integration/test_page_meta.py b/tests/integration/test_page_meta.py new file mode 100644 index 0000000..42bbe98 --- /dev/null +++ b/tests/integration/test_page_meta.py @@ -0,0 +1,111 @@ +"""ページメタ操作の統合テスト""" + +from __future__ import annotations + +import pytest + +from .conftest import generate_page_name + + +class TestPageMeta: + """ページメタ操作テスト""" + + @pytest.fixture(autouse=True) + def setup(self, site): + """テストセットアップ - ページを作成""" + self.site = site + self.page_name = generate_page_name("meta") + + # テスト用ページを作成 + self.site.page.create( + fullname=self.page_name, + title="Meta Test Page", + source="Content for meta test.", + ) + self.page = self.site.page.get(self.page_name) + assert self.page is not None + + yield + + # クリーンアップ + try: + page = self.site.page.get(self.page_name, raise_when_not_found=False) + if page is not None: + page.destroy() + except Exception: + pass + + def test_1_set_meta(self): + """1. メタ設定""" + self.page.metas = {"description": "Test description"} + + # 再取得して確認 + updated_page = self.site.page.get(self.page_name) + assert updated_page is not None + assert "description" in updated_page.metas + assert updated_page.metas["description"] == "Test description" + + def test_2_get_meta(self): + """2. メタ取得""" + # まずメタを設定 + self.page.metas = {"keywords": "test, integration"} + + # 再取得 + updated_page = self.site.page.get(self.page_name) + assert updated_page is not None + + # メタ取得 + metas = updated_page.metas + assert "keywords" in metas + assert metas["keywords"] == "test, integration" + + def test_3_update_meta(self): + """3. メタ更新""" + # まずメタを設定 + self.page.metas = {"description": "Original description"} + + # 再取得 + self.page = self.site.page.get(self.page_name) + assert self.page is not None + assert self.page.metas["description"] == "Original description" + + # メタを更新 + self.page.metas = {"description": "Updated description"} + + # 確認 + updated_page = self.site.page.get(self.page_name) + assert updated_page is not None + assert updated_page.metas["description"] == "Updated description" + + def test_4_delete_meta(self): + """4. メタ削除""" + # まずメタを設定 + self.page.metas = {"description": "To be deleted"} + + # 再取得 + self.page = self.site.page.get(self.page_name) + assert self.page is not None + assert "description" in self.page.metas + + # メタを削除(空の辞書を設定) + self.page.metas = {} + + # 確認 + updated_page = self.site.page.get(self.page_name) + assert updated_page is not None + assert "description" not in updated_page.metas + + def test_5_multiple_metas(self): + """5. 複数メタの設定""" + self.page.metas = { + "description": "Page description", + "keywords": "keyword1, keyword2", + "author": "Test Author", + } + + # 確認 + updated_page = self.site.page.get(self.page_name) + assert updated_page is not None + assert updated_page.metas["description"] == "Page description" + assert updated_page.metas["keywords"] == "keyword1, keyword2" + assert updated_page.metas["author"] == "Test Author" diff --git a/tests/integration/test_page_revision.py b/tests/integration/test_page_revision.py new file mode 100644 index 0000000..b077ddf --- /dev/null +++ b/tests/integration/test_page_revision.py @@ -0,0 +1,90 @@ +"""ページリビジョン(編集履歴)の統合テスト""" + +from __future__ import annotations + +import pytest + +from .conftest import generate_page_name + + +class TestPageRevision: + """ページリビジョン操作テスト""" + + @pytest.fixture(autouse=True) + def setup(self, site): + """テストセットアップ - ページを作成して編集""" + self.site = site + self.page_name = generate_page_name("revision") + + # テスト用ページを作成 + self.site.page.create( + fullname=self.page_name, + title="Revision Test Page", + source="Initial content.", + ) + self.page = self.site.page.get(self.page_name) + assert self.page is not None + + # 編集してリビジョンを作成 + self.page.edit( + source="Updated content.", + comment="First edit", + ) + + yield + + # クリーンアップ + try: + page = self.site.page.get(self.page_name, raise_when_not_found=False) + if page is not None: + page.destroy() + except Exception: + pass + + def test_1_get_revisions(self): + """1. リビジョン一覧取得""" + # 再取得 + page = self.site.page.get(self.page_name) + assert page is not None + + revisions = page.revisions + assert revisions is not None + # 作成 + 編集で少なくとも1つ以上のリビジョンがある + assert len(revisions) >= 1 + + def test_2_revision_properties(self): + """2. リビジョンプロパティ確認""" + page = self.site.page.get(self.page_name) + assert page is not None + + revisions = page.revisions + assert len(revisions) >= 1 + + latest_rev = revisions[-1] # 最新リビジョン + assert latest_rev.id is not None + assert latest_rev.rev_no is not None + assert latest_rev.created_by is not None + assert latest_rev.created_at is not None + + def test_3_get_latest_revision(self): + """3. 最新リビジョン取得""" + page = self.site.page.get(self.page_name) + assert page is not None + + latest = page.latest_revision + assert latest is not None + assert latest.rev_no == page.revisions_count + + def test_4_revision_source(self): + """4. リビジョンソース取得""" + page = self.site.page.get(self.page_name) + assert page is not None + + revisions = page.revisions + assert len(revisions) >= 1 + + # 最新リビジョンのソースを取得 + latest_rev = revisions[-1] + source = latest_rev.source + assert source is not None + assert source.wiki_text is not None diff --git a/tests/integration/test_page_votes.py b/tests/integration/test_page_votes.py new file mode 100644 index 0000000..08e0cff --- /dev/null +++ b/tests/integration/test_page_votes.py @@ -0,0 +1,31 @@ +"""ページ投票の統合テスト""" + +from __future__ import annotations + + +class TestPageVotes: + """ページ投票操作テスト""" + + def test_1_get_votes_from_existing_page(self, site): + """1. 既存ページの投票情報取得""" + # startページの投票情報を取得 + page = site.page.get("start") + assert page is not None + + votes = page.votes + assert votes is not None + # 投票がなくても空のコレクションが返る + assert isinstance(votes, list) + + def test_2_votes_properties(self, site): + """2. 投票プロパティ確認""" + page = site.page.get("start") + assert page is not None + + votes = page.votes + # 投票がある場合はプロパティを確認 + if len(votes) > 0: + vote = votes[0] + assert vote.page is not None + assert vote.user is not None + assert vote.value is not None diff --git a/tests/integration/test_pm.py b/tests/integration/test_pm.py new file mode 100644 index 0000000..bcfe9f2 --- /dev/null +++ b/tests/integration/test_pm.py @@ -0,0 +1,81 @@ +"""プライベートメッセージ取得の統合テスト + +NOTE: メッセージ送信はスキップ。取得のみテスト。 +事前にInbox/Outboxにメッセージが入っていることを前提とする。 +""" + +from __future__ import annotations + +import pytest + + +class TestPrivateMessage: + """プライベートメッセージ取得テスト""" + + def test_1_get_inbox(self, client): + """1. 受信箱取得""" + inbox = client.private_message.inbox + assert inbox is not None + + # メッセージ一覧を取得 + messages = list(inbox) + # メッセージがあることを期待(事前にテスト用メッセージを入れておく) + assert len(messages) >= 0 # 空でもテストは通す + + def test_2_get_sentbox(self, client): + """2. 送信箱取得""" + sentbox = client.private_message.sentbox + assert sentbox is not None + + # メッセージ一覧を取得 + messages = list(sentbox) + assert len(messages) >= 0 + + def test_3_inbox_message_properties(self, client): + """3. 受信メッセージのプロパティ確認""" + inbox = client.private_message.inbox + messages = list(inbox) + + if len(messages) == 0: + pytest.skip("No messages in inbox") + + message = messages[0] + + # 基本プロパティ + assert message.id is not None + assert message.id > 0 + assert message.subject is not None + assert message.sender is not None + assert message.created_at is not None + + def test_4_sentbox_message_properties(self, client): + """4. 送信メッセージのプロパティ確認""" + sentbox = client.private_message.sentbox + messages = list(sentbox) + + if len(messages) == 0: + pytest.skip("No messages in sentbox") + + message = messages[0] + + # 基本プロパティ + assert message.id is not None + assert message.id > 0 + assert message.subject is not None + assert message.recipient is not None + assert message.created_at is not None + + def test_5_get_message_by_id(self, client): + """5. IDでメッセージ取得""" + inbox = client.private_message.inbox + messages = list(inbox) + + if len(messages) == 0: + pytest.skip("No messages in inbox") + + # 最初のメッセージのIDで再取得 + message_id = messages[0].id + message = client.private_message.get_message(message_id) + + assert message is not None + assert message.id == message_id diff --git a/tests/integration/test_site.py b/tests/integration/test_site.py new file mode 100644 index 0000000..f483893 --- /dev/null +++ b/tests/integration/test_site.py @@ -0,0 +1,52 @@ +"""サイト取得・ページ取得の統合テスト""" + +from __future__ import annotations + +import pytest + +from wikidot.common.exceptions import NotFoundException + + +class TestSiteGet: + """サイト取得テスト""" + + def test_site_get(self, site): + """サイト取得""" + assert site is not None + assert site.unix_name == "ukwhatn-ci" + assert site.id > 0 + + def test_site_has_title(self, site): + """サイトにタイトルがある""" + assert site.title is not None + assert len(site.title) > 0 + + +class TestPageGet: + """ページ取得テスト""" + + def test_get_existing_page(self, site): + """既存ページ取得(startページ)""" + page = site.page.get("start") + assert page is not None + assert page.fullname == "start" + + def test_get_nonexistent_page(self, site): + """存在しないページ取得""" + with pytest.raises(NotFoundException): + site.page.get("nonexistent-page-12345678") + + def test_page_has_properties(self, site): + """ページにプロパティがある""" + page = site.page.get("start") + assert page is not None + + # 基本プロパティ + assert page.fullname is not None + assert page.title is not None + assert page.created_at is not None + + # 数値プロパティ + assert page.rating is not None + # NOTE: 初期ページはrevisions_countが0になる場合がある + assert page.revisions_count >= 0 diff --git a/tests/integration/test_user.py b/tests/integration/test_user.py new file mode 100644 index 0000000..c81dcb0 --- /dev/null +++ b/tests/integration/test_user.py @@ -0,0 +1,33 @@ +"""ユーザー検索の統合テスト""" + +from __future__ import annotations + + +class TestUserSearch: + """ユーザー検索テスト""" + + def test_1_get_user_by_name(self, client): + """1. ユーザー名でユーザー取得""" + # 既知のユーザー名で取得 + user = client.user.get("ukwhatn") + assert user is not None + assert user.name.lower() == "ukwhatn" + + def test_2_get_nonexistent_user(self, client): + """2. 存在しないユーザー取得""" + user = client.user.get("nonexistent-user-12345678") + assert user is None + + def test_3_user_properties(self, client): + """3. ユーザープロパティ確認""" + user = client.user.get("ukwhatn") + assert user is not None + assert user.id is not None + assert user.name is not None + assert user.unix_name is not None + + def test_4_get_bulk_users(self, client): + """4. 複数ユーザー一括取得""" + users = client.user.get_bulk(["ukwhatn"]) + assert users is not None + assert len(users) >= 1 diff --git a/tests/unit/test_amc_client.py b/tests/unit/test_amc_client.py index 56fdce0..575f3a4 100644 --- a/tests/unit/test_amc_client.py +++ b/tests/unit/test_amc_client.py @@ -78,7 +78,7 @@ def test_default_values(self) -> None: config = AjaxModuleConnectorConfig() assert config.request_timeout == 20 - assert config.attempt_limit == 3 + assert config.attempt_limit == 5 assert config.retry_interval == 1.0 assert config.max_backoff == 60.0 assert config.backoff_factor == 2.0 diff --git a/uv.lock b/uv.lock index 58d0339..8b04ae0 100644 --- a/uv.lock +++ b/uv.lock @@ -1367,7 +1367,7 @@ wheels = [ [[package]] name = "wikidot" -version = "4.0.1" +version = "4.0.2" source = { editable = "." } dependencies = [ { name = "beautifulsoup4" },