概要
パッケージ本体(リポジトリルートの test/)にライブラリの振る舞いを検証するテストが 1 件も存在せず、リグレッションを検知する手段がない。
現状
ルートに test/ ディレクトリ自体が存在しない。
$ find . -name '*_test.dart' -not -path './.git/*'
./example/test/widget_test.dart
ヒットするのは example 側の flutter create テンプレート由来のカウンターアプリ用テストのみで、ライブラリの動作は一切検証していない。
また .github/workflows/ci.yaml の Test ステップは以下の条件付きになっている。
- name: Test
if: ${{ hashFiles('test/**/*_test.dart') != '' }}
run: flutter test
test/ が存在しない現在のツリーでは、この条件は常に false となる。そのため、CI がグリーンでもライブラリの振る舞いを検証する flutter test は実行されていない。
なお、履歴上は当初ルートに flutter create テンプレート由来の test/widget_test.dart があり、旧 package-ci で flutter test が実行されていた。478c405(2025-08-16)でこのテンプレートテストを削除して以降、ルートのテストはスキップされている。したがって、正確には次の状態である。
- 過去にテンプレートのカウンターテストが CI で実行されたことはある
- ライブラリ固有のテストは過去にも存在しない
478c405 以降、パッケージ本体の flutter test は常にスキップされている
CI には format / analyze / publish dry-run に加えて example の Android / iOS ビルドもあるが、これらは以下のライブラリ内部ロジックの振る舞いを保証するものではない。
影響
lib/ 配下 約 1,400 行がノーガード。PKCE の生成ロジック、リトライの判定、トークンの JSON 直列化、マルチアカウントのインデックス管理など、壊れても検知できない。
- 0.1.3-beta で「保存責務をクライアントから
TokenStore へ移す」という破壊的変更を入れているが、移行後の挙動を保証するテストが存在しない。
修正方針
プラットフォームチャネル(flutter_web_auth_2 / flutter_secure_storage の実機呼び出し)に依存しない部分から着手する。
1. 純粋ロジック(プラットフォームチャネル依存なし)
| 対象 |
検証内容 |
MisskeyOAuthClient.generateCodeVerifier() |
長さが 128 / RFC 7636 の unreserved 文字のみで構成される / 複数回の生成結果が同一にならない |
MisskeyOAuthClient.generateCodeChallenge() |
既知の verifier に対する S256 の期待値と一致する / = パディングが除去されている / base64url であること |
MisskeyOAuthClient.generateState() |
空でない / 複数回の生成結果が同一にならない |
MisskeyMiAuthClient.generateSessionId() |
既定長 32 / length 指定が反映される / 英数字のみ |
MisskeyMiAuthConfig.callbackUrl |
callbackScheme から <scheme>:// を正しく組み立てる |
2. RetryPolicy / retry()(lib/src/net/retry.dart)
shouldRetry()
retryOnTypes に含まれる DioExceptionType(connectionTimeout / sendTimeout / receiveTimeout / connectionError / unknown)で true
retryOnStatusCodes(429, 500, 502, 503, 504)で true
- 400 / 401 / 403 など、リトライすべきでないステータスで
false
nextDelay()
- 試行回数に応じて指数的に増加する
maxDelay を超えない(ジッター加算後も上限を超えないこと)
retry()
- リトライ対象外の例外は即 rethrow され、
action の呼び出し回数が 1 回で終わる
maxAttempts に達したら最後の例外を投げる(呼び出し回数が maxAttempts と一致する)
- 途中で成功したらそこで打ち切る
- 待機を発生させないテストでは、
initialDelay: Duration.zero と maxDelay: Duration.zero を指定する。initialDelay だけを小さくしても 0〜99ms のジッターが残るため、実時間待機はなくならない
3. モデルの JSON 往復
StoredToken.toJson() / fromJson()
scope / user / createdAt が null のとき、出力 JSON にキー自体が含まれないこと
createdAt の ISO8601 往復で値が保たれること
createdAt が不正な文字列のとき DateTime.tryParse により null になり、例外にならないこと
AccountKey.toJson() / fromJson() / == / hashCode / storageKey()
- 同じ
host + accountId の 2 インスタンスが等価で、Set / List.contains で重複判定できること(SecureTokenStore のインデックス重複排除がこれに依存している)
OAuthServerInfo.fromJson() / OAuthTokenResponse.fromJson() / MiAuthCheckResponse.fromJson()
- optional フィールドが欠落したレスポンスで例外にならないこと
- required フィールド(例:
authorization_endpoint / token_endpoint / access_token / token_type)の欠落や型不一致は、正常値として受理されないこと
4. HTTP 層(Dio をモックして)
MisskeyOAuthClient / MisskeyMiAuthClient / MisskeyAuthManager はいずれもコンストラクタで Dio を受け取れるため、http_mock_adapter 等でスタブ化してテストできる(dev_dependencies への追加が必要)。
MisskeyOAuthClient.getOAuthServerInfo()
- 200 →
OAuthServerInfo を返す
- 404 / 501 →
null(OAuth 非対応と判定)を返す
- 404 / 501 以外の非 2xx → 現在の Dio 既定動作では
DioException を経由して NetworkException になること
- 通信不能 →
NetworkException
- 404 / 501 以外を
ServerInfoException に統一する仕様を希望する場合は、テスト追加に先立って本体の例外マッピングを変更する必要がある
MisskeyOAuthClient.exchangeCodeForToken()
- 200 →
OAuthTokenResponse を返す
- エラーレスポンスの
error / error_description が TokenExchangeException のメッセージに含まれる
MisskeyAuthManager の /api/i 呼び出し
- リクエストボディが
{"i": "<token>"} の形式で送られていること
user.id が存在しないレスポンスで ResponseParseException になること
loginWithOAuth() / loginWithMiAuth() 自体は flutter_web_auth_2 を踏むため、まずは /api/i 部分をテスト可能な単位に切り出すか、クライアントをモックする形を検討する
5. SecureTokenStore
FlutterSecureStorage をコンストラクタで注入できるため、mockito でモックするか、Map を backing store にしたフェイク実装を用意して以下を検証する。
upsert() → read() の往復で StoredToken が復元できること
list()
- インデックスに登録したアカウントが返ること
- トークン本体が消えているキーが混ざっていても例外にならないこと
- 表示名のフォールバック順(
user.name → user.username → user.userName)が期待どおりであること
delete()
- トークン本体とインデックスの両方から削除されること
- 削除対象がアクティブアカウントだった場合にアクティブ設定が解除されること
clearAll()
- 全トークン +
misskey_accounts_index + misskey_active_account がすべて削除されること
_addToIndex() 経由で同一 AccountKey を重複登録しないこと(upsert() を 2 回呼んでも list() が 1 件であること)
完了条件
- リポジトリルートに
test/ を作成し、少なくとも上記セクション 1〜3(プラットフォーム非依存の範囲)をカバーする
flutter test がローカルおよび CI で実際に実行され、パスする
test/ に *_test.dart が置かれれば ci.yaml の hashFiles 条件は自動的に true になるため、ワークフロー側の変更は不要
概要
パッケージ本体(リポジトリルートの
test/)にライブラリの振る舞いを検証するテストが 1 件も存在せず、リグレッションを検知する手段がない。現状
ルートに
test/ディレクトリ自体が存在しない。ヒットするのは example 側の
flutter createテンプレート由来のカウンターアプリ用テストのみで、ライブラリの動作は一切検証していない。また
.github/workflows/ci.yamlの Test ステップは以下の条件付きになっている。test/が存在しない現在のツリーでは、この条件は常に false となる。そのため、CI がグリーンでもライブラリの振る舞いを検証するflutter testは実行されていない。なお、履歴上は当初ルートに
flutter createテンプレート由来のtest/widget_test.dartがあり、旧package-ciでflutter testが実行されていた。478c405(2025-08-16)でこのテンプレートテストを削除して以降、ルートのテストはスキップされている。したがって、正確には次の状態である。478c405以降、パッケージ本体のflutter testは常にスキップされているCI には format / analyze / publish dry-run に加えて example の Android / iOS ビルドもあるが、これらは以下のライブラリ内部ロジックの振る舞いを保証するものではない。
影響
lib/配下 約 1,400 行がノーガード。PKCE の生成ロジック、リトライの判定、トークンの JSON 直列化、マルチアカウントのインデックス管理など、壊れても検知できない。TokenStoreへ移す」という破壊的変更を入れているが、移行後の挙動を保証するテストが存在しない。修正方針
プラットフォームチャネル(
flutter_web_auth_2/flutter_secure_storageの実機呼び出し)に依存しない部分から着手する。1. 純粋ロジック(プラットフォームチャネル依存なし)
MisskeyOAuthClient.generateCodeVerifier()MisskeyOAuthClient.generateCodeChallenge()=パディングが除去されている / base64url であることMisskeyOAuthClient.generateState()MisskeyMiAuthClient.generateSessionId()length指定が反映される / 英数字のみMisskeyMiAuthConfig.callbackUrlcallbackSchemeから<scheme>://を正しく組み立てる2.
RetryPolicy/retry()(lib/src/net/retry.dart)shouldRetry()retryOnTypesに含まれるDioExceptionType(connectionTimeout / sendTimeout / receiveTimeout / connectionError / unknown)でtrueretryOnStatusCodes(429, 500, 502, 503, 504)でtruefalsenextDelay()maxDelayを超えない(ジッター加算後も上限を超えないこと)retry()actionの呼び出し回数が 1 回で終わるmaxAttemptsに達したら最後の例外を投げる(呼び出し回数がmaxAttemptsと一致する)initialDelay: Duration.zeroとmaxDelay: Duration.zeroを指定する。initialDelayだけを小さくしても 0〜99ms のジッターが残るため、実時間待機はなくならない3. モデルの JSON 往復
StoredToken.toJson()/fromJson()scope/user/createdAtが null のとき、出力 JSON にキー自体が含まれないことcreatedAtの ISO8601 往復で値が保たれることcreatedAtが不正な文字列のときDateTime.tryParseにより null になり、例外にならないことAccountKey.toJson()/fromJson()/==/hashCode/storageKey()host+accountIdの 2 インスタンスが等価で、Set/List.containsで重複判定できること(SecureTokenStoreのインデックス重複排除がこれに依存している)OAuthServerInfo.fromJson()/OAuthTokenResponse.fromJson()/MiAuthCheckResponse.fromJson()authorization_endpoint/token_endpoint/access_token/token_type)の欠落や型不一致は、正常値として受理されないこと4. HTTP 層(
Dioをモックして)MisskeyOAuthClient/MisskeyMiAuthClient/MisskeyAuthManagerはいずれもコンストラクタでDioを受け取れるため、http_mock_adapter等でスタブ化してテストできる(dev_dependenciesへの追加が必要)。MisskeyOAuthClient.getOAuthServerInfo()OAuthServerInfoを返すnull(OAuth 非対応と判定)を返すDioExceptionを経由してNetworkExceptionになることNetworkExceptionServerInfoExceptionに統一する仕様を希望する場合は、テスト追加に先立って本体の例外マッピングを変更する必要があるMisskeyOAuthClient.exchangeCodeForToken()OAuthTokenResponseを返すerror/error_descriptionがTokenExchangeExceptionのメッセージに含まれるMisskeyAuthManagerの/api/i呼び出し{"i": "<token>"}の形式で送られていることuser.idが存在しないレスポンスでResponseParseExceptionになることloginWithOAuth()/loginWithMiAuth()自体はflutter_web_auth_2を踏むため、まずは/api/i部分をテスト可能な単位に切り出すか、クライアントをモックする形を検討する5.
SecureTokenStoreFlutterSecureStorageをコンストラクタで注入できるため、mockitoでモックするか、Mapを backing store にしたフェイク実装を用意して以下を検証する。upsert()→read()の往復でStoredTokenが復元できることlist()user.name→user.username→user.userName)が期待どおりであることdelete()clearAll()misskey_accounts_index+misskey_active_accountがすべて削除されること_addToIndex()経由で同一AccountKeyを重複登録しないこと(upsert()を 2 回呼んでもlist()が 1 件であること)完了条件
test/を作成し、少なくとも上記セクション 1〜3(プラットフォーム非依存の範囲)をカバーするflutter testがローカルおよび CI で実際に実行され、パスするtest/に*_test.dartが置かれればci.yamlのhashFiles条件は自動的に true になるため、ワークフロー側の変更は不要