@aiquants/duckdb-helper 1.5.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +164 -5
- package/dist/index.d.ts +164 -5
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/docs/specs/2026.04.11 [AI] 00-overview.md +32 -11
- package/docs/specs/2026.04.11 [AI] 02-service.md +114 -240
- package/docs/specs/2026.04.11 [AI] 03-hooks.md +10 -21
- package/docs/specs/2026.04.11 [AI] 05-logger.md +3 -5
- package/docs/specs/2026.04.11 [AI] 06-build-config.md +22 -4
- package/docs/specs/2026.07.05 [AI] 07-testing.md +184 -0
- package/package.json +6 -1
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# テスト仕様書
|
|
2
|
+
|
|
3
|
+
> 対象: `@aiquants/duckdb-helper` v1.6.0
|
|
4
|
+
> テストソース: `tests/**`
|
|
5
|
+
> 作成: 2026-07-05
|
|
6
|
+
|
|
7
|
+
## 1. 目的とスコープ
|
|
8
|
+
|
|
9
|
+
本パッケージのロジック整合性を担保するための**単体テスト仕様**。全ランタイムモジュール(`src/**`、型定義 `src/types` を除く)に対して **カバレッジ 100%(lines / functions / branches / statements)** を目標とし、v1.6.0 で修正した各不具合が再発しないことを保証する回帰テストを含む。
|
|
10
|
+
|
|
11
|
+
## 2. テスト環境・ツール
|
|
12
|
+
|
|
13
|
+
| 項目 | 内容 |
|
|
14
|
+
|------|------|
|
|
15
|
+
| ランナー | Vitest 3.2.6(`vitest.config.ts`) |
|
|
16
|
+
| 実行環境 | `jsdom`(`window` / `URL` / `Blob` / React フックのレンダリングに必要) |
|
|
17
|
+
| カバレッジ | `@vitest/coverage-v8`。対象 `src/**`、除外 `src/types/**`。閾値 100% |
|
|
18
|
+
| React テスト | `@testing-library/react`(`renderHook` / `act` / `waitFor`) |
|
|
19
|
+
| 型チェック | `tsconfig.test.json`(`tests/**` を含む)を `tsc --noEmit` で検証 |
|
|
20
|
+
|
|
21
|
+
### 実行コマンド
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm run test # 全テスト実行
|
|
25
|
+
pnpm run test:coverage # カバレッジ付き(閾値 100% を満たさなければ失敗)
|
|
26
|
+
pnpm run typecheck:test # tests を含む型チェック
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 3. テスト方針(モック戦略)
|
|
30
|
+
|
|
31
|
+
DuckDB-WASM は実際の Worker と WASM ダウンロードを伴うため、単体テストでは**外部依存を完全にフェイク化**して全経路を決定論的に検証する。
|
|
32
|
+
|
|
33
|
+
| 対象 | 手法 |
|
|
34
|
+
|------|------|
|
|
35
|
+
| `@duckdb/duckdb-wasm` | `vi.mock` で `getJsDelivrBundles` / `selectBundle` / `ConsoleLogger` / `AsyncDuckDB` を差し替え。`vi.hoisted` の共有状態で「バンドル選択」「instantiate」「connect」「query」「close」「terminate」の振る舞いをテストごとに制御し、発行 SQL を記録する |
|
|
36
|
+
| `Worker` | `vi.stubGlobal` でフェイク(`terminate` 呼び出しを記録) |
|
|
37
|
+
| `URL.createObjectURL` / `revokeObjectURL` | スタブして呼び出し回数を記録(Blob URL リーク検証用) |
|
|
38
|
+
| `Blob` | 空のフェイククラス |
|
|
39
|
+
| タイマー(120s/60s タイムアウト) | `vi.useFakeTimers()` + `advanceTimersByTimeAsync()` |
|
|
40
|
+
| `DuckDBService`(フックのテスト) | `useDuckDB` のテストでは `../services/duckdb` を丸ごとモックし、`getStatus` とリスナー通知を制御 |
|
|
41
|
+
|
|
42
|
+
## 4. テストファイル構成
|
|
43
|
+
|
|
44
|
+
| ファイル | 対象モジュール | 主眼 |
|
|
45
|
+
|---------|--------------|------|
|
|
46
|
+
| `tests/services/duckdb.test.ts` | `services/duckdb.ts` | ライフサイクル・並行性・SQL 生成・型推定・トランザクション |
|
|
47
|
+
| `tests/hooks/useDuckDB.test.tsx` | `hooks/useDuckDB.ts` | ステータス同期・操作ガード・`useDuckDBQuery` |
|
|
48
|
+
| `tests/utils/duckdb-helpers.test.ts` | `utils/duckdb-helpers.ts` | 結果変換ヘルパーの境界値 |
|
|
49
|
+
| `tests/utils/simple-logger.test.ts` | `utils/simple-logger.ts` | レベルフィルタ・整形・静的/インスタンス API |
|
|
50
|
+
| `tests/index.test.ts` | `index.ts` | 公開エクスポートの存在確認 |
|
|
51
|
+
| `tests/integration/dummy.test.ts` | 公開 API(結果ハンドリング) | ユーティリティを利用側の流れで組み合わせるスモークテスト |
|
|
52
|
+
|
|
53
|
+
## 5. テストケース一覧
|
|
54
|
+
|
|
55
|
+
### 5.1 `DuckDBService`(`tests/services/duckdb.test.ts`)
|
|
56
|
+
|
|
57
|
+
#### initialize()
|
|
58
|
+
|
|
59
|
+
| ケース | 検証内容 | 関連修正 |
|
|
60
|
+
|--------|---------|---------|
|
|
61
|
+
| 正常初期化 | `ready` に遷移、Blob URL を revoke、splink 一時コネクションを close | |
|
|
62
|
+
| 高速パス | 初期化済みなら Worker を作り直さない | |
|
|
63
|
+
| 同時呼び出しの重複排除 | 2 回同時呼び出しでも Worker は 1 つ | |
|
|
64
|
+
| splink 失敗時の続行 | 拡張ロード失敗でも `ready`、一時コネクションは close | |
|
|
65
|
+
| **instantiate 失敗 → 再試行可能** | `error` に遷移し `isReady() === false`、Worker terminate + Blob URL revoke、その後 `initialize()` で復旧 | 初期化復旧不能(High) |
|
|
66
|
+
| 非 Error の reject | 文字列 reject でも `error` に遷移 | |
|
|
67
|
+
| Worker 生成前の失敗 | `selectBundle` reject で Worker/URL 未生成のまま `error` | |
|
|
68
|
+
| **タイムアウト(pending worker 無し)** | 120s 経過で reject、`error`、再試行可能 | タイマーリーク / 復旧 |
|
|
69
|
+
| **タイムアウト(instantiate ハング)** | pending worker を terminate、Blob URL を revoke、遅延解決を無視 | Worker/Blob リーク・遅延結果 |
|
|
70
|
+
| 破棄処理自体の例外 | terminate/revoke が例外を投げても握りつぶす | |
|
|
71
|
+
|
|
72
|
+
#### getStatus()
|
|
73
|
+
|
|
74
|
+
- 初期は `not-initialized`
|
|
75
|
+
- 初期化中は `initializing`
|
|
76
|
+
|
|
77
|
+
#### executeQuery / executeQueries
|
|
78
|
+
|
|
79
|
+
| ケース | 検証内容 | 関連修正 |
|
|
80
|
+
|--------|---------|---------|
|
|
81
|
+
| 実行と単一コネクション再利用 | 2 回実行で二重 connect しない | getConnection 競合 |
|
|
82
|
+
| クエリエラー伝播 | 失敗後も次クエリが実行可能(キューが詰まらない) | 操作直列化 |
|
|
83
|
+
| クエリタイムアウト | 60s 経過で reject | タイマーリーク |
|
|
84
|
+
| **タイムアウト時のコネクション破棄** | 詰まったコネクションを close し、次クエリは新しいコネクションで実行 | タイムアウト詰まり |
|
|
85
|
+
| **コネクション取得ハング時のタイムアウト** | getConnection がハングしても 60s で reject(破棄対象なし) | 同上 |
|
|
86
|
+
| 逐次実行 | 複数クエリを順に実行し結果配列を返す(単一キュー単位) | executeQueries 割り込み防止 |
|
|
87
|
+
|
|
88
|
+
#### getConnection()
|
|
89
|
+
|
|
90
|
+
- 同時アクセスで二重 connect しない(同一コネクションを返す)
|
|
91
|
+
- connect 失敗時にリセットしてリトライ可能
|
|
92
|
+
|
|
93
|
+
#### createTableFromData — バリデーション
|
|
94
|
+
|
|
95
|
+
- `data` が null / 空配列 → `Data array is empty`
|
|
96
|
+
- 列が無い(`[{}]`)→ `no columns`
|
|
97
|
+
- 非文字列 / 空文字列のテーブル名 → `Invalid SQL identifier`
|
|
98
|
+
|
|
99
|
+
#### createTableFromData — 型推定
|
|
100
|
+
|
|
101
|
+
- `BIGINT`(int32 超含む)/ `DOUBLE` / `BOOLEAN` / `TIMESTAMP`(Date・厳密日付文字列)/ `VARCHAR`(非日付ハイフン文字列・無効日付・NaN・全 null)を正しく推定 → **BIGINT 化・厳密 TIMESTAMP** の回帰テスト
|
|
102
|
+
|
|
103
|
+
#### createTableFromData — 値フォーマット
|
|
104
|
+
|
|
105
|
+
- 文字列エスケープ(`O'Brien` → `'O''Brien'`)、Date、`TRUE`/`FALSE`、bigint、整数・小数、**NaN/Infinity → NULL**、**配列/オブジェクト → JSON**、**関数/循環参照 → NULL** → 値フォーマットの回帰テスト
|
|
106
|
+
|
|
107
|
+
#### createTableFromData — スキーマ/行
|
|
108
|
+
|
|
109
|
+
| ケース | 検証内容 | 関連修正 |
|
|
110
|
+
|--------|---------|---------|
|
|
111
|
+
| **列名の和集合** | 先頭行に無い列も CREATE/INSERT に含む、null 行は全 NULL | 列取りこぼし |
|
|
112
|
+
| **トランザクション順序** | `BEGIN → DROP → CREATE → INSERT → COMMIT` の順序 | 原子性 |
|
|
113
|
+
| primaryKey / DROP スキップ | `PRIMARY KEY ("id")` を付与、DROP なし | |
|
|
114
|
+
| **識別子の安全なクォート** | ダブルクォートを含む名前を `""` にエスケープ | SQL 識別子 |
|
|
115
|
+
| **INSERT 失敗でロールバック** | `ROLLBACK` を発行し reject | 原子性 |
|
|
116
|
+
| ロールバック自体の失敗 | warn を出しつつ元エラーで reject | |
|
|
117
|
+
|
|
118
|
+
#### createTableFromData — 重複排除 / verbose
|
|
119
|
+
|
|
120
|
+
- **同名テーブルの同時呼び出しで構築は 1 回だけ**(verbose 有効/無効の両経路)→ dedup 回帰テスト
|
|
121
|
+
|
|
122
|
+
#### getTableInfo / listTables
|
|
123
|
+
|
|
124
|
+
- `DESCRIBE "a""b"`(クォート)、`SHOW TABLES`
|
|
125
|
+
|
|
126
|
+
#### ステータスリスナー
|
|
127
|
+
|
|
128
|
+
- 追加後に通知、削除後は通知されない
|
|
129
|
+
- 例外を投げるリスナーが他のリスナーを妨げない
|
|
130
|
+
|
|
131
|
+
#### cleanup / resetInstance
|
|
132
|
+
|
|
133
|
+
- コネクション close + Worker terminate + `not-initialized` へ復帰
|
|
134
|
+
- 未初期化での cleanup は安全
|
|
135
|
+
- teardown 中の例外を握りつぶす
|
|
136
|
+
- `resetInstance` はシングルトンを差し替え、`null` 状態での再呼び出しも安全
|
|
137
|
+
|
|
138
|
+
### 5.2 React フック(`tests/hooks/useDuckDB.test.tsx`)
|
|
139
|
+
|
|
140
|
+
| グループ | ケース |
|
|
141
|
+
|---------|--------|
|
|
142
|
+
| 初期化 | `autoInitialize` 既定でマウント時に `initialize()` を呼ぶ / `false` で呼ばない / リスナーをマウントで登録・アンマウントで解除 |
|
|
143
|
+
| ステータス同期 | `ready` で `error` クリア / `error` でメッセージ設定(push リスナー経由) |
|
|
144
|
+
| 操作ガード | 未 ready で全操作(`executeQuery`/`executeQueries`/`createTableFromData`/`getTableInfo`/`listTables`)が throw / ready で各サービスメソッドに委譲 |
|
|
145
|
+
| 手動 initialize | Error reject でメッセージ設定 / 非 Error reject で汎用メッセージ / 成功時は静かに解決 |
|
|
146
|
+
| `useDuckDBQuery` | 未 ready で fetch しない / 空 SQL で fetch しない / ready で data 取得 / エラー(Error・非 Error)で error 設定・data クリア / 手動 refetch / 依存配列変更で再 fetch / **依存配列の長さ変更でもエラーにならず再 fetch** |
|
|
147
|
+
|
|
148
|
+
> 1 秒ポーリングの撤去により、フックは push リスナーのみで状態同期する。上記「ステータス同期」ケースがリスナー経由で全遷移を受け取れることを担保する。
|
|
149
|
+
|
|
150
|
+
### 5.3 ユーティリティ(`tests/utils/duckdb-helpers.test.ts`)
|
|
151
|
+
|
|
152
|
+
`duckdbTableToArray` / `getDuckDBRowCount` / `getDuckDBColumnCount` / `isDuckDBTable` について、非オブジェクト・メソッド/プロパティ欠如・型不一致・正常値の各分岐を網羅。
|
|
153
|
+
|
|
154
|
+
### 5.4 ロガー(`tests/utils/simple-logger.test.ts`)
|
|
155
|
+
|
|
156
|
+
- `LogLevel` の数値
|
|
157
|
+
- コンストラクタ既定(WARN / `[duckdb-helper]` / console)
|
|
158
|
+
- DEBUG で全レベル出力 / NONE で全抑制
|
|
159
|
+
- 文字列メッセージのプレフィックス結合 / 非文字列は別引数
|
|
160
|
+
- インスタンス/静的の `setLevel` / `setPrefix` / `setImplementation`
|
|
161
|
+
|
|
162
|
+
### 5.5 公開 API(`tests/index.test.ts`)
|
|
163
|
+
|
|
164
|
+
`useDuckDB` / `useDuckDBQuery` / `DuckDBService` / `duckDBService` / 各ユーティリティ / **`Logger` / `LogLevel`**(v1.6.0 で追加)がエクスポートされていることを確認。
|
|
165
|
+
|
|
166
|
+
## 6. カバレッジ結果
|
|
167
|
+
|
|
168
|
+
`pnpm run test:coverage` の結果(v1.6.0 時点、88 テスト):
|
|
169
|
+
|
|
170
|
+
| File | % Stmts | % Branch | % Funcs | % Lines |
|
|
171
|
+
|------|---------|----------|---------|---------|
|
|
172
|
+
| All files | 100 | 100 | 100 | 100 |
|
|
173
|
+
| `index.ts` | 100 | 100 | 100 | 100 |
|
|
174
|
+
| `hooks/useDuckDB.ts` | 100 | 100 | 100 | 100 |
|
|
175
|
+
| `services/duckdb.ts` | 100 | 100 | 100 | 100 |
|
|
176
|
+
| `utils/duckdb-helpers.ts` | 100 | 100 | 100 | 100 |
|
|
177
|
+
| `utils/simple-logger.ts` | 100 | 100 | 100 | 100 |
|
|
178
|
+
|
|
179
|
+
> カバレッジ閾値は `vitest.config.ts` で 100% に設定。下回るとテストは失敗する。
|
|
180
|
+
|
|
181
|
+
## 7. 既知の制約
|
|
182
|
+
|
|
183
|
+
- 本テストは**単体テスト**であり、実 DuckDB-WASM / 実 Worker を用いた結合は対象外(実 SQL の意味的正しさ、例えば `BEGIN/COMMIT` のトランザクション意味論は DuckDB 側の責務)。
|
|
184
|
+
- `types/duckdb.ts` は型のみでランタイムコードを持たないためカバレッジ対象から除外。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/duckdb-helper",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "DuckDB helper utilities with React hooks, worker initialization, and typed query helpers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -36,8 +36,12 @@
|
|
|
36
36
|
"@duckdb/duckdb-wasm": "^1.30.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@testing-library/dom": "^10.4.1",
|
|
40
|
+
"@testing-library/react": "^16.3.2",
|
|
39
41
|
"@types/react": "^19.2.7",
|
|
40
42
|
"@types/react-dom": "^19.2.3",
|
|
43
|
+
"@vitest/coverage-v8": "3.2.6",
|
|
44
|
+
"jsdom": "^26.1.0",
|
|
41
45
|
"react": "^19.2.7",
|
|
42
46
|
"react-dom": "^19.2.7",
|
|
43
47
|
"rimraf": "^6.1.2",
|
|
@@ -70,6 +74,7 @@
|
|
|
70
74
|
"dev": "tsup --watch",
|
|
71
75
|
"watch": "tsup --watch",
|
|
72
76
|
"typecheck": "tsc --noEmit",
|
|
77
|
+
"typecheck:test": "tsc --noEmit -p tsconfig.test.json",
|
|
73
78
|
"clean": "rimraf dist",
|
|
74
79
|
"publish:patch": "npm version patch && pnpm publish --no-git-checks",
|
|
75
80
|
"publish:minor": "npm version minor && pnpm publish --no-git-checks",
|