Skip to content

Commit f816285

Browse files
committed
feat: add --data-dir option and CLI integration test framework
Add global --data-dir flag to override the default downloads directory, enabling CLI testing against fixtures. Include fixture generator script that creates realistic book directories with tiny MP3 files, and an integration test script that validates all non-interactive CLI flows. - Add --data-dir global option threaded through all commands - Add scripts/generate-fixtures.sh to generate test book directories - Add scripts/test-cli.sh with 38 assertions across 11 test groups - Add npm scripts: fixtures, fixtures:clean, test:cli - Resolve relative paths in merge and metadata services (bug fix) - Update CLAUDE.md with new commands and testing docs
1 parent 5de7ae5 commit f816285

11 files changed

Lines changed: 550 additions & 29 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ Thumbs.db
4949
coverage/
5050
.nyc_output/
5151
.claudelint-cache/
52+
53+
# Generated fixtures
54+
fixtures/

CLAUDE.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ npm run dev # Run interactive CLI
1010
npm run dev -- list # List downloaded books
1111
npm run dev -- tag # Tag MP3 files
1212
npm run dev -- merge # Merge chapters into M4B audiobook
13+
npm run dev -- --data-dir ./fixtures # Use test fixtures instead of real downloads
1314

1415
# Testing & Validation
15-
npm test # Run Jest tests
16+
npm test # Run Jest unit tests
17+
npm run test:cli # Run CLI integration tests against fixtures
1618
npm run test:coverage # Run tests with coverage report
1719
npm run check-all # Full validation: typecheck + lint + format + test
1820

21+
# Fixtures
22+
npm run fixtures # Generate test fixtures (tiny MP3s + metadata)
23+
npm run fixtures:clean # Remove generated fixtures
24+
1925
# Building
2026
npm run build # Compile CLI TypeScript to dist/
2127
npm run build:extension # Build extension for production (Vite)
@@ -31,6 +37,10 @@ npm run format:check # Check if code is formatted
3137
# Chrome Extension Validation
3238
npm run extension:validate # Lint extension manifest and code
3339
npm run extension:lint # Lint with warnings-as-errors
40+
41+
# Releases
42+
npm run release # Interactive release (bumps version, tags, pushes)
43+
npm run release:dry # Preview release without changes
3444
```
3545

3646
## Entry Points
@@ -60,7 +70,7 @@ npm run extension:lint # Lint with warnings-as-errors
6070
- Saves metadata.json alongside MP3 files
6171

6272
2. **CLI Tags (Optional):**
63-
- Auto-discovers books in ~/Downloads/libby-downloads/
73+
- Auto-discovers books in ~/Downloads/libby-downloads/ (override with `--data-dir`)
6474
- Reads metadata.json, embeds ID3 tags into MP3 files
6575

6676
3. **CLI Merges (Optional):**
@@ -85,10 +95,12 @@ Extension handles ALL downloading. CLI is for tagging, merging, and listing only
8595

8696
## Testing
8797

88-
- Test structure: `src/__tests__/` and `src/utils/__tests__/`
89-
- Environment: Jest with jsdom
98+
- **Unit tests:** `src/__tests__/` and `src/utils/__tests__/` (Jest with jsdom)
99+
- **Integration tests:** `npm run test:cli` runs CLI against generated fixtures
100+
- **Fixtures:** `npm run fixtures` generates tiny MP3s in `fixtures/` (gitignored)
90101
- Chrome API mocks in `src/__tests__/mocks/`
91102
- Coverage thresholds: 50% branches, 60% statements
103+
- Conventional commits enforced via commitlint (commit-msg hook)
92104

93105
## Chrome Extension Build
94106

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"extension:validate": "node scripts/validate-extension.js",
3030
"lint:claude": "claudelint check-all",
3131
"lint:claude:fix": "claudelint format --fix",
32+
"fixtures": "bash scripts/generate-fixtures.sh",
33+
"fixtures:clean": "bash scripts/generate-fixtures.sh --clean",
34+
"test:cli": "bash scripts/test-cli.sh",
3235
"release": "release-it",
3336
"release:dry": "release-it --dry-run",
3437
"release:patch": "release-it patch",

scripts/generate-fixtures.sh

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Generate test fixtures for CLI integration testing.
4+
# Creates realistic book directories with tiny valid MP3 files.
5+
#
6+
# Usage: ./scripts/generate-fixtures.sh [--clean]
7+
8+
set -euo pipefail
9+
10+
FIXTURES_DIR="$(cd "$(dirname "$0")/.." && pwd)/fixtures"
11+
12+
if [[ "${1:-}" == "--clean" ]]; then
13+
echo "[INFO] Removing existing fixtures..."
14+
rm -rf "$FIXTURES_DIR"
15+
echo "[INFO] Done."
16+
exit 0
17+
fi
18+
19+
if [[ -d "$FIXTURES_DIR" ]]; then
20+
echo "[INFO] Fixtures directory already exists. Use --clean to regenerate."
21+
exit 0
22+
fi
23+
24+
echo "[INFO] Generating test fixtures in $FIXTURES_DIR"
25+
26+
# Check for ffmpeg
27+
if ! command -v ffmpeg &>/dev/null; then
28+
echo "[ERROR] ffmpeg is required to generate fixtures"
29+
exit 1
30+
fi
31+
32+
mkdir -p "$FIXTURES_DIR"
33+
34+
# Helper: generate a silent MP3 file with a specific duration
35+
generate_mp3() {
36+
local output="$1"
37+
local duration="$2"
38+
ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=mono -t "$duration" -q:a 9 "$output" 2>/dev/null
39+
}
40+
41+
# Helper: generate a tagged MP3 file
42+
generate_tagged_mp3() {
43+
local output="$1"
44+
local duration="$2"
45+
local title="$3"
46+
local artist="$4"
47+
local album="$5"
48+
local track="$6"
49+
ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=mono -t "$duration" -q:a 9 \
50+
-metadata title="$title" \
51+
-metadata artist="$artist" \
52+
-metadata album="$album" \
53+
-metadata track="$track" \
54+
-metadata genre="Audiobook" \
55+
"$output" 2>/dev/null
56+
}
57+
58+
# ──────────────────────────────────────────────────────────────
59+
# 1. complete-book — Has metadata + chapters, not tagged, not merged
60+
# ──────────────────────────────────────────────────────────────
61+
echo "[INFO] Creating complete-book fixture..."
62+
BOOK_DIR="$FIXTURES_DIR/The Great Adventure"
63+
mkdir -p "$BOOK_DIR"
64+
65+
generate_mp3 "$BOOK_DIR/chapter-1.mp3" 1
66+
generate_mp3 "$BOOK_DIR/chapter-2.mp3" 1
67+
generate_mp3 "$BOOK_DIR/chapter-3.mp3" 1
68+
69+
cat > "$BOOK_DIR/metadata.json" << 'METADATA'
70+
{
71+
"metadata": {
72+
"title": "The Great Adventure",
73+
"authors": ["Jane Smith"],
74+
"narrator": "Bob Johnson",
75+
"coverUrl": "https://example.com/cover.jpg",
76+
"description": "A thrilling tale of adventure and discovery."
77+
},
78+
"chapters": [
79+
{ "index": 0, "title": "Chapter 1: The Beginning", "duration": 1 },
80+
{ "index": 1, "title": "Chapter 2: The Journey", "duration": 1 },
81+
{ "index": 2, "title": "Chapter 3: The End", "duration": 1 }
82+
]
83+
}
84+
METADATA
85+
86+
# ──────────────────────────────────────────────────────────────
87+
# 2. tagged-book — Has metadata + chapters, already tagged
88+
# ──────────────────────────────────────────────────────────────
89+
echo "[INFO] Creating tagged-book fixture..."
90+
BOOK_DIR="$FIXTURES_DIR/Mystery at Midnight"
91+
mkdir -p "$BOOK_DIR"
92+
93+
generate_tagged_mp3 "$BOOK_DIR/chapter-1.mp3" 1 "Chapter 1: The Discovery" "Alice Walker" "Mystery at Midnight" "1/2"
94+
generate_tagged_mp3 "$BOOK_DIR/chapter-2.mp3" 1 "Chapter 2: The Reveal" "Alice Walker" "Mystery at Midnight" "2/2"
95+
96+
cat > "$BOOK_DIR/metadata.json" << 'METADATA'
97+
{
98+
"metadata": {
99+
"title": "Mystery at Midnight",
100+
"authors": ["Alice Walker"],
101+
"narrator": "Sarah Chen",
102+
"coverUrl": "https://example.com/mystery-cover.jpg",
103+
"description": "A gripping mystery that keeps you guessing until the very end."
104+
},
105+
"chapters": [
106+
{ "index": 0, "title": "Chapter 1: The Discovery", "duration": 1 },
107+
{ "index": 1, "title": "Chapter 2: The Reveal", "duration": 1 }
108+
]
109+
}
110+
METADATA
111+
112+
# ──────────────────────────────────────────────────────────────
113+
# 3. merged-book — Has metadata + chapters + .m4b, already merged
114+
# ──────────────────────────────────────────────────────────────
115+
echo "[INFO] Creating merged-book fixture..."
116+
BOOK_DIR="$FIXTURES_DIR/Science of Everything"
117+
mkdir -p "$BOOK_DIR"
118+
119+
generate_tagged_mp3 "$BOOK_DIR/chapter-1.mp3" 1 "Chapter 1: Physics" "David Lee" "Science of Everything" "1/2"
120+
generate_tagged_mp3 "$BOOK_DIR/chapter-2.mp3" 1 "Chapter 2: Chemistry" "David Lee" "Science of Everything" "2/2"
121+
122+
# Create a tiny M4B (just an AAC in MP4 container)
123+
ffmpeg -y -f lavfi -i anullsrc=r=44100:cl=mono -t 2 -c:a aac -b:a 64k \
124+
-metadata title="Science of Everything" \
125+
-metadata artist="David Lee" \
126+
"$BOOK_DIR/Science of Everything.m4b" 2>/dev/null
127+
128+
cat > "$BOOK_DIR/metadata.json" << 'METADATA'
129+
{
130+
"metadata": {
131+
"title": "Science of Everything",
132+
"authors": ["David Lee"],
133+
"narrator": "Emma Wilson",
134+
"description": "An exploration of the fundamental sciences."
135+
},
136+
"chapters": [
137+
{ "index": 0, "title": "Chapter 1: Physics", "duration": 1 },
138+
{ "index": 1, "title": "Chapter 2: Chemistry", "duration": 1 }
139+
]
140+
}
141+
METADATA
142+
143+
# ──────────────────────────────────────────────────────────────
144+
# 4. no-metadata — Has chapters but no metadata.json
145+
# ──────────────────────────────────────────────────────────────
146+
echo "[INFO] Creating no-metadata fixture..."
147+
BOOK_DIR="$FIXTURES_DIR/Unknown Book"
148+
mkdir -p "$BOOK_DIR"
149+
150+
generate_mp3 "$BOOK_DIR/chapter-1.mp3" 1
151+
generate_mp3 "$BOOK_DIR/chapter-2.mp3" 1
152+
153+
# ──────────────────────────────────────────────────────────────
154+
# 5. no-chapters — Has metadata.json but no MP3 files
155+
# ──────────────────────────────────────────────────────────────
156+
echo "[INFO] Creating no-chapters fixture..."
157+
BOOK_DIR="$FIXTURES_DIR/Empty Promises"
158+
mkdir -p "$BOOK_DIR"
159+
160+
cat > "$BOOK_DIR/metadata.json" << 'METADATA'
161+
{
162+
"metadata": {
163+
"title": "Empty Promises",
164+
"authors": ["No One"],
165+
"description": "A book with no chapters."
166+
},
167+
"chapters": []
168+
}
169+
METADATA
170+
171+
# ──────────────────────────────────────────────────────────────
172+
# 6. multi-author — Multiple authors for display testing
173+
# ──────────────────────────────────────────────────────────────
174+
echo "[INFO] Creating multi-author fixture..."
175+
BOOK_DIR="$FIXTURES_DIR/Collaborative Work"
176+
mkdir -p "$BOOK_DIR"
177+
178+
generate_mp3 "$BOOK_DIR/chapter-1.mp3" 1
179+
generate_mp3 "$BOOK_DIR/chapter-2.mp3" 1
180+
181+
cat > "$BOOK_DIR/metadata.json" << 'METADATA'
182+
{
183+
"metadata": {
184+
"title": "Collaborative Work",
185+
"authors": ["Author One", "Author Two", "Author Three"],
186+
"narrator": "Group Narrator",
187+
"description": {
188+
"full": "A collaborative work by three brilliant minds exploring the nature of teamwork.",
189+
"short": "A book about teamwork."
190+
}
191+
},
192+
"chapters": [
193+
{ "index": 0, "title": "Part 1: Foundations", "duration": 1 },
194+
{ "index": 1, "title": "Part 2: Applications", "duration": 1 }
195+
]
196+
}
197+
METADATA
198+
199+
echo ""
200+
echo "[SUCCESS] Fixtures generated in $FIXTURES_DIR"
201+
echo ""
202+
echo " The Great Adventure - complete book (untagged, unmerged)"
203+
echo " Mystery at Midnight - tagged book"
204+
echo " Science of Everything - tagged and merged book"
205+
echo " Unknown Book - no metadata.json"
206+
echo " Empty Promises - metadata but no chapter files"
207+
echo " Collaborative Work - multiple authors, object description"
208+
echo ""
209+
echo "Usage:"
210+
echo " npm run dev -- list --data-dir ./fixtures"
211+
echo " npm run dev -- tag ./fixtures/The\\ Great\\ Adventure"
212+
echo " npm run dev -- --data-dir ./fixtures"

0 commit comments

Comments
 (0)