Skip to main content

max / makenotwork

23.9 KB · 582 lines History Blame Raw
1 #!/bin/bash
2 # MNW CLI — Internal API endpoint test suite
3 #
4 # Tests all TUI-backing endpoints against a live MNW server.
5 # Uses ffmpeg to generate test audio content for upload tests.
6 #
7 # Usage:
8 # ./tests/api_endpoints.sh # Run against localhost:3000
9 # MNW_URL=http://host:3000 ./tests/api_endpoints.sh # Custom server
10 #
11 # Prerequisites:
12 # - MNW server running with CLI_SERVICE_TOKEN set
13 # - ffmpeg (for generating test audio)
14 # - curl, jq
15 # - Seed data loaded (elena user + demo projects)
16
17 set -euo pipefail
18
19 # ── Configuration ──────────────────────────────────────────────────
20 MNW_URL="${MNW_URL:-http://localhost:3000}"
21 TOKEN="${MNW_SERVICE_TOKEN:-7f28a67c2e97a1b774aa482692c2a7d3e011da6de625a9a8b480d9156512e050}"
22 ELENA_ID="11111111-1111-1111-1111-111111111111"
23 PROJECT_NEWSLETTER="22222222-2222-2222-2222-222222222221"
24 PROJECT_PODCAST="22222222-2222-2222-2222-222222222222"
25 ITEM_ESSAY="33333333-3333-3333-3333-333333333331"
26 ITEM_PODCAST="33333333-3333-3333-3333-333333333341"
27
28 TMPDIR="$(mktemp -d)"
29 trap 'rm -rf "$TMPDIR"' EXIT
30
31 PASS=0
32 FAIL=0
33 SKIP=0
34 ERRORS=""
35
36 # ── Helpers ────────────────────────────────────────────────────────
37 AUTH="Authorization: Bearer $TOKEN"
38 CT="Content-Type: application/json"
39
40 api() {
41 local method="$1" path="$2"
42 shift 2
43 curl -sf -X "$method" -H "$AUTH" -H "$CT" "$@" "${MNW_URL}${path}"
44 }
45
46 api_status() {
47 local method="$1" path="$2"
48 shift 2
49 curl -s -o /dev/null -w '%{http_code}' -X "$method" -H "$AUTH" -H "$CT" "$@" "${MNW_URL}${path}"
50 }
51
52 pass() {
53 PASS=$((PASS + 1))
54 printf " [PASS] %s\n" "$1"
55 }
56
57 fail() {
58 FAIL=$((FAIL + 1))
59 printf " [FAIL] %s — %s\n" "$1" "$2"
60 ERRORS="${ERRORS}\n - $1: $2"
61 }
62
63 skip() {
64 SKIP=$((SKIP + 1))
65 printf " [SKIP] %s — %s\n" "$1" "$2"
66 }
67
68 assert_status() {
69 local name="$1" expected="$2" method="$3" path="$4"
70 shift 4
71 local status
72 status=$(curl -s -o /dev/null -w '%{http_code}' -X "$method" -H "$AUTH" -H "$CT" "$@" "${MNW_URL}${path}")
73 if [ "$status" = "$expected" ]; then
74 pass "$name (HTTP $status)"
75 else
76 fail "$name" "expected $expected, got $status"
77 fi
78 }
79
80 assert_json_field() {
81 local name="$1" json="$2" field="$3"
82 local val
83 val=$(echo "$json" | jq -r "$field" 2>/dev/null)
84 if [ -n "$val" ] && [ "$val" != "null" ]; then
85 pass "$name ($field=$val)"
86 else
87 fail "$name" "missing field $field"
88 fi
89 }
90
91 assert_json_eq() {
92 local name="$1" json="$2" field="$3" expected="$4"
93 local val
94 val=$(echo "$json" | jq -r "$field" 2>/dev/null)
95 if [ "$val" = "$expected" ]; then
96 pass "$name"
97 else
98 fail "$name" "expected $field=$expected, got $val"
99 fi
100 }
101
102 assert_json_count() {
103 local name="$1" json="$2" min="$3"
104 local count
105 count=$(echo "$json" | jq 'length' 2>/dev/null)
106 if [ "$count" -ge "$min" ]; then
107 pass "$name (count=$count)"
108 else
109 fail "$name" "expected >= $min items, got $count"
110 fi
111 }
112
113 # ── Check prerequisites ───────────────────────────────────────────
114 echo "=== MNW CLI API Endpoint Tests ==="
115 echo "Server: $MNW_URL"
116 echo ""
117
118 # Verify server is up
119 if ! curl -sf "${MNW_URL}/api/health" > /dev/null 2>&1; then
120 echo "ERROR: MNW server not reachable at $MNW_URL"
121 exit 1
122 fi
123 echo "Server reachable."
124
125 # Verify ffmpeg
126 if ! command -v ffmpeg > /dev/null 2>&1; then
127 echo "WARNING: ffmpeg not found — upload tests will be skipped"
128 HAS_FFMPEG=false
129 else
130 HAS_FFMPEG=true
131 fi
132
133 # Verify auth works
134 STATUS=$(api_status GET "/api/internal/creator/projects?user_id=$ELENA_ID")
135 if [ "$STATUS" != "200" ]; then
136 echo "ERROR: Auth failed (HTTP $STATUS). Check CLI_SERVICE_TOKEN."
137 exit 1
138 fi
139 echo "Auth OK."
140 echo ""
141
142 # ── Generate test content with ffmpeg ──────────────────────────────
143 if $HAS_FFMPEG; then
144 echo "Generating test audio files..."
145 # 5-second sine wave WAV (440 Hz)
146 ffmpeg -y -f lavfi -i "sine=frequency=440:duration=5" \
147 -ar 44100 -ac 2 "$TMPDIR/test_track.wav" 2>/dev/null
148 echo " Created test_track.wav ($(du -h "$TMPDIR/test_track.wav" | cut -f1))"
149
150 # 3-second MP3 (220 Hz)
151 ffmpeg -y -f lavfi -i "sine=frequency=220:duration=3" \
152 -ar 44100 -ac 1 -b:a 128k "$TMPDIR/test_audio.mp3" 2>/dev/null
153 echo " Created test_audio.mp3 ($(du -h "$TMPDIR/test_audio.mp3" | cut -f1))"
154
155 # Small ZIP file (placeholder download)
156 echo "test content for zip" > "$TMPDIR/readme.txt"
157 (cd "$TMPDIR" && zip -q test_download.zip readme.txt)
158 echo " Created test_download.zip ($(du -h "$TMPDIR/test_download.zip" | cut -f1))"
159 echo ""
160 fi
161
162 # ══════════════════════════════════════════════════════════════════
163 # Section 1: Creator Projects & Stats
164 # ══════════════════════════════════════════════════════════════════
165 echo "── 1. Projects & Stats ──"
166
167 # List projects
168 PROJECTS=$(api GET "/api/internal/creator/projects?user_id=$ELENA_ID")
169 assert_json_count "List projects" "$PROJECTS" 3
170
171 # Check project fields
172 FIRST=$(echo "$PROJECTS" | jq '.[0]')
173 assert_json_field "Project has id" "$FIRST" ".id"
174 assert_json_field "Project has slug" "$FIRST" ".slug"
175 assert_json_field "Project has title" "$FIRST" ".title"
176 assert_json_field "Project has project_type" "$FIRST" ".project_type"
177
178 # List items for a project
179 ITEMS=$(api GET "/api/internal/creator/projects/$PROJECT_NEWSLETTER/items?user_id=$ELENA_ID")
180 assert_json_count "List newsletter items" "$ITEMS" 3
181
182 # Stats
183 STATS=$(api GET "/api/internal/creator/stats?user_id=$ELENA_ID&range=30d")
184 assert_json_field "Stats has total_items" "$STATS" ".total_items"
185 assert_json_field "Stats has total_projects" "$STATS" ".total_projects"
186 assert_json_field "Stats has current_revenue_cents" "$STATS" ".current_revenue_cents"
187
188 # Stats ranges
189 for range in 7d 30d 90d all; do
190 assert_status "Stats range=$range" 200 GET "/api/internal/creator/stats?user_id=$ELENA_ID&range=$range"
191 done
192
193 echo ""
194
195 # ══════════════════════════════════════════════════════════════════
196 # Section 2: Storage Info
197 # ══════════════════════════════════════════════════════════════════
198 echo "── 2. Storage ──"
199
200 STORAGE=$(api GET "/api/internal/creator/storage?user_id=$ELENA_ID")
201 assert_json_field "Storage has storage_used_bytes" "$STORAGE" ".storage_used_bytes"
202 assert_json_field "Storage has max_storage_bytes" "$STORAGE" ".max_storage_bytes"
203 assert_json_field "Storage has allows_file_uploads" "$STORAGE" ".allows_file_uploads"
204
205 echo ""
206
207 # ══════════════════════════════════════════════════════════════════
208 # Section 3: Item Detail & Mutations
209 # ══════════════════════════════════════════════════════════════════
210 echo "── 3. Item Detail & Mutations ──"
211
212 # Get item detail
213 DETAIL=$(api GET "/api/internal/creator/items/$ITEM_ESSAY?user_id=$ELENA_ID")
214 assert_json_eq "Item detail title" "$DETAIL" ".title" "The Art of Doing Less"
215 assert_json_field "Item has price_cents" "$DETAIL" ".price_cents"
216 assert_json_field "Item has item_type" "$DETAIL" ".item_type"
217 assert_json_field "Item has is_public" "$DETAIL" ".is_public"
218 assert_json_field "Item has created_at" "$DETAIL" ".created_at"
219
220 # Get item versions
221 VERSIONS=$(api GET "/api/internal/creator/items/$ITEM_ESSAY/versions?user_id=$ELENA_ID")
222 # May be empty, just verify it returns an array
223 VCOUNT=$(echo "$VERSIONS" | jq 'length' 2>/dev/null)
224 if [ "$VCOUNT" -ge 0 ] 2>/dev/null; then
225 pass "Item versions returns array (count=$VCOUNT)"
226 else
227 fail "Item versions" "did not return array"
228 fi
229
230 # Create a new item
231 NEW_ITEM=$(api POST "/api/internal/creator/items" \
232 -d "{\"user_id\":\"$ELENA_ID\",\"project_id\":\"$PROJECT_NEWSLETTER\",\"title\":\"Test Item $(date +%s)\",\"item_type\":\"text\",\"price_cents\":999}")
233 NEW_ITEM_ID=$(echo "$NEW_ITEM" | jq -r '.item_id')
234 if [ -n "$NEW_ITEM_ID" ] && [ "$NEW_ITEM_ID" != "null" ]; then
235 pass "Create item (id=$NEW_ITEM_ID)"
236 else
237 fail "Create item" "no item_id in response"
238 NEW_ITEM_ID=""
239 fi
240
241 # Update the item
242 if [ -n "$NEW_ITEM_ID" ]; then
243 UPDATED=$(api PUT "/api/internal/creator/items/$NEW_ITEM_ID" \
244 -d "{\"user_id\":\"$ELENA_ID\",\"title\":\"Updated Title\",\"description\":\"A test description\",\"price_cents\":1299}")
245 assert_json_eq "Update item title" "$UPDATED" ".title" "Updated Title"
246 assert_json_eq "Update item price" "$UPDATED" ".price_cents" "1299"
247
248 # Unpublish / publish toggle
249 UNPUB=$(api POST "/api/internal/creator/items/$NEW_ITEM_ID/unpublish" \
250 -d "{\"user_id\":\"$ELENA_ID\"}")
251 assert_json_eq "Unpublish item" "$UNPUB" ".is_public" "false"
252
253 PUB=$(api POST "/api/internal/creator/items/$NEW_ITEM_ID/publish" \
254 -d "{\"user_id\":\"$ELENA_ID\"}")
255 assert_json_eq "Publish item" "$PUB" ".is_public" "true"
256 fi
257
258 echo ""
259
260 # ══════════════════════════════════════════════════════════════════
261 # Section 4: Blog Posts
262 # ══════════════════════════════════════════════════════════════════
263 echo "── 4. Blog Posts ──"
264
265 # List blog posts
266 BLOG=$(api GET "/api/internal/creator/projects/$PROJECT_NEWSLETTER/blog?user_id=$ELENA_ID")
267 BLOG_COUNT=$(echo "$BLOG" | jq 'length' 2>/dev/null)
268 if [ "$BLOG_COUNT" -ge 0 ] 2>/dev/null; then
269 pass "List blog posts (count=$BLOG_COUNT)"
270 else
271 fail "List blog posts" "did not return array"
272 fi
273
274 # Create blog post
275 TS=$(date +%s)
276 NEW_POST=$(api POST "/api/internal/creator/blog" \
277 -d "{\"user_id\":\"$ELENA_ID\",\"project_id\":\"$PROJECT_NEWSLETTER\",\"title\":\"Test Post $TS\",\"body_markdown\":\"Hello from the test suite.\",\"publish\":false}")
278 POST_ID=$(echo "$NEW_POST" | jq -r '.id')
279 if [ -n "$POST_ID" ] && [ "$POST_ID" != "null" ]; then
280 pass "Create blog post (id=$POST_ID)"
281 assert_json_eq "Blog post title" "$NEW_POST" ".title" "Test Post $TS"
282 assert_json_eq "Blog post unpublished" "$NEW_POST" ".is_published" "false"
283 else
284 fail "Create blog post" "no id in response"
285 POST_ID=""
286 fi
287
288 # Delete blog post
289 if [ -n "$POST_ID" ]; then
290 assert_status "Delete blog post" 204 DELETE "/api/internal/creator/blog/$POST_ID?user_id=$ELENA_ID"
291 fi
292
293 echo ""
294
295 # ══════════════════════════════════════════════════════════════════
296 # Section 5: Promo Codes
297 # ══════════════════════════════════════════════════════════════════
298 echo "── 5. Promo Codes ──"
299
300 # List promo codes
301 PROMOS=$(api GET "/api/internal/creator/promo-codes?user_id=$ELENA_ID")
302 PROMO_COUNT=$(echo "$PROMOS" | jq 'length' 2>/dev/null)
303 if [ "$PROMO_COUNT" -ge 0 ] 2>/dev/null; then
304 pass "List promo codes (count=$PROMO_COUNT)"
305 else
306 fail "List promo codes" "did not return array"
307 fi
308
309 # Create promo code
310 PROMO_CODE="TEST$(date +%s)"
311 NEW_PROMO=$(api POST "/api/internal/creator/promo-codes" \
312 -d "{\"user_id\":\"$ELENA_ID\",\"code\":\"$PROMO_CODE\",\"discount_type\":\"percentage\",\"discount_value\":25}")
313 PROMO_ID=$(echo "$NEW_PROMO" | jq -r '.id')
314 if [ -n "$PROMO_ID" ] && [ "$PROMO_ID" != "null" ]; then
315 pass "Create promo code (code=$PROMO_CODE)"
316 assert_json_eq "Promo discount" "$NEW_PROMO" ".discount_value" "25"
317 else
318 fail "Create promo code" "no id in response"
319 PROMO_ID=""
320 fi
321
322 # Delete promo code
323 if [ -n "$PROMO_ID" ]; then
324 assert_status "Delete promo code" 204 DELETE "/api/internal/creator/promo-codes/$PROMO_ID?user_id=$ELENA_ID"
325 fi
326
327 echo ""
328
329 # ══════════════════════════════════════════════════════════════════
330 # Section 6: License Keys
331 # ══════════════════════════════════════════════════════════════════
332 echo "── 6. License Keys ──"
333
334 # List license keys for an item
335 KEYS=$(api GET "/api/internal/creator/items/$ITEM_ESSAY/keys?user_id=$ELENA_ID")
336 KEY_COUNT=$(echo "$KEYS" | jq 'length' 2>/dev/null)
337 if [ "$KEY_COUNT" -ge 0 ] 2>/dev/null; then
338 pass "List license keys (count=$KEY_COUNT)"
339 else
340 fail "List license keys" "did not return array"
341 fi
342
343 # Generate a license key
344 NEW_KEY=$(api POST "/api/internal/creator/items/$ITEM_ESSAY/keys" \
345 -d "{\"user_id\":\"$ELENA_ID\"}")
346 KEY_ID=$(echo "$NEW_KEY" | jq -r '.id')
347 KEY_CODE=$(echo "$NEW_KEY" | jq -r '.key_code')
348 if [ -n "$KEY_ID" ] && [ "$KEY_ID" != "null" ]; then
349 pass "Generate license key (code=$KEY_CODE)"
350 else
351 fail "Generate license key" "no id in response"
352 KEY_ID=""
353 fi
354
355 # Revoke license key
356 if [ -n "$KEY_ID" ]; then
357 assert_status "Revoke license key" 204 POST "/api/internal/creator/keys/$KEY_ID/revoke" \
358 -d "{\"user_id\":\"$ELENA_ID\"}"
359 fi
360
361 echo ""
362
363 # ══════════════════════════════════════════════════════════════════
364 # Section 7: Analytics & Transactions
365 # ══════════════════════════════════════════════════════════════════
366 echo "── 7. Analytics & Transactions ──"
367
368 # Analytics
369 for range in 7d 30d 90d all; do
370 ANALYTICS=$(api GET "/api/internal/creator/analytics?user_id=$ELENA_ID&range=$range")
371 assert_json_field "Analytics ($range) has buckets" "$ANALYTICS" ".buckets"
372 assert_json_field "Analytics ($range) has current_revenue_cents" "$ANALYTICS" ".current_revenue_cents"
373 done
374
375 # Top projects in analytics
376 ANALYTICS_30=$(api GET "/api/internal/creator/analytics?user_id=$ELENA_ID&range=30d")
377 TOP=$(echo "$ANALYTICS_30" | jq '.top_projects' 2>/dev/null)
378 if [ "$(echo "$TOP" | jq 'type')" = '"array"' ]; then
379 pass "Analytics top_projects is array"
380 else
381 fail "Analytics top_projects" "not an array"
382 fi
383
384 # Transactions
385 TXS=$(api GET "/api/internal/creator/transactions?user_id=$ELENA_ID")
386 TX_COUNT=$(echo "$TXS" | jq 'length' 2>/dev/null)
387 if [ "$TX_COUNT" -ge 0 ] 2>/dev/null; then
388 pass "List transactions (count=$TX_COUNT)"
389 else
390 fail "List transactions" "did not return array"
391 fi
392
393 # Export sales CSV
394 EXPORT=$(api GET "/api/internal/creator/export/sales?user_id=$ELENA_ID")
395 assert_json_field "Export has csv" "$EXPORT" ".csv"
396 ROW_COUNT=$(echo "$EXPORT" | jq -r '.row_count' 2>/dev/null)
397 if [ "$ROW_COUNT" -ge 0 ] 2>/dev/null; then
398 pass "Export row_count=$ROW_COUNT"
399 else
400 fail "Export sales" "missing row_count"
401 fi
402
403 echo ""
404
405 # ══════════════════════════════════════════════════════════════════
406 # Section 8: SSH Keys
407 # ══════════════════════════════════════════════════════════════════
408 echo "── 8. SSH Keys ──"
409
410 SSH_KEYS=$(api GET "/api/internal/creator/ssh-keys?user_id=$ELENA_ID")
411 SSH_COUNT=$(echo "$SSH_KEYS" | jq 'length' 2>/dev/null)
412 if [ "$SSH_COUNT" -ge 0 ] 2>/dev/null; then
413 pass "List SSH keys (count=$SSH_COUNT)"
414 else
415 fail "List SSH keys" "did not return array"
416 fi
417
418 echo ""
419
420 # ══════════════════════════════════════════════════════════════════
421 # Section 9: SSH Key Lookup
422 # ══════════════════════════════════════════════════════════════════
423 echo "── 9. SSH Key Lookup ──"
424
425 # Test with a fake fingerprint (should 404)
426 LOOKUP_STATUS=$(api_status GET "/api/internal/ssh-key-lookup?fingerprint=SHA256:fakefingerprint123")
427 if [ "$LOOKUP_STATUS" = "404" ]; then
428 pass "SSH key lookup 404 for unknown key"
429 else
430 fail "SSH key lookup unknown" "expected 404, got $LOOKUP_STATUS"
431 fi
432
433 # Insert a test SSH key and look it up
434 TEST_FP="SHA256:test$(date +%s)"
435 DB_NAME="${MNW_DB:-makenotwork_staging}"
436 psql "$DB_NAME" -c "INSERT INTO ssh_keys (user_id, public_key, fingerprint, label) VALUES ('$ELENA_ID', 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAItest', '$TEST_FP', 'test-key') ON CONFLICT DO NOTHING;" 2>/dev/null
437
438 LOOKUP=$(api GET "/api/internal/ssh-key-lookup?fingerprint=$TEST_FP" 2>/dev/null || echo '{}')
439 LOOKUP_USER=$(echo "$LOOKUP" | jq -r '.username' 2>/dev/null)
440 if [ "$LOOKUP_USER" = "elena" ]; then
441 pass "SSH key lookup returns correct user"
442 else
443 fail "SSH key lookup" "expected elena, got $LOOKUP_USER"
444 fi
445
446 # Clean up test key
447 psql "$DB_NAME" -c "DELETE FROM ssh_keys WHERE fingerprint = '$TEST_FP';" 2>/dev/null
448
449 echo ""
450
451 # ══════════════════════════════════════════════════════════════════
452 # Section 10: Upload Pipeline (presign + confirm)
453 # ══════════════════════════════════════════════════════════════════
454 echo "── 10. Upload Pipeline ──"
455
456 if [ -z "$NEW_ITEM_ID" ]; then
457 skip "Upload presign" "no test item created"
458 skip "Upload confirm" "no test item created"
459 elif ! $HAS_FFMPEG; then
460 skip "Upload presign" "no ffmpeg"
461 skip "Upload confirm" "no ffmpeg"
462 else
463 # Presign for audio upload
464 PRESIGN=$(api POST "/api/internal/upload/presign" \
465 -d "{\"user_id\":\"$ELENA_ID\",\"item_id\":\"$NEW_ITEM_ID\",\"file_type\":\"audio\",\"file_name\":\"test_track.wav\",\"content_type\":\"audio/wav\"}" 2>/dev/null || echo '{}')
466 UPLOAD_URL=$(echo "$PRESIGN" | jq -r '.upload_url' 2>/dev/null)
467 S3_KEY=$(echo "$PRESIGN" | jq -r '.s3_key' 2>/dev/null)
468
469 if [ -n "$UPLOAD_URL" ] && [ "$UPLOAD_URL" != "null" ]; then
470 pass "Presign upload (s3_key=$S3_KEY)"
471 assert_json_field "Presign has expires_in" "$PRESIGN" ".expires_in"
472
473 # Upload to S3
474 S3_STATUS=$(curl -s -o /dev/null -w '%{http_code}' -X PUT \
475 -H "Content-Type: audio/wav" \
476 --data-binary "@$TMPDIR/test_track.wav" \
477 "$UPLOAD_URL")
478 if [ "$S3_STATUS" = "200" ]; then
479 pass "S3 upload (HTTP $S3_STATUS)"
480
481 # Confirm upload
482 CONFIRM=$(api POST "/api/internal/upload/confirm" \
483 -d "{\"user_id\":\"$ELENA_ID\",\"item_id\":\"$NEW_ITEM_ID\",\"file_type\":\"audio\",\"s3_key\":\"$S3_KEY\"}" 2>/dev/null || echo '{}')
484 CONFIRM_OK=$(echo "$CONFIRM" | jq -r '.success' 2>/dev/null)
485 if [ "$CONFIRM_OK" = "true" ]; then
486 pass "Confirm upload"
487 else
488 fail "Confirm upload" "success=$CONFIRM_OK (response: $CONFIRM)"
489 fi
490 else
491 fail "S3 upload" "HTTP $S3_STATUS"
492 skip "Confirm upload" "S3 upload failed"
493 fi
494 else
495 # Presign may fail if no S3 configured on staging — that's expected
496 skip "Presign upload" "no S3 configured (response: $(echo "$PRESIGN" | head -c 200))"
497 skip "S3 upload" "no presign URL"
498 skip "Confirm upload" "no presign URL"
499 fi
500 fi
501
502 echo ""
503
504 # ══════════════════════════════════════════════════════════════════
505 # Section 11: Error Cases
506 # ══════════════════════════════════════════════════════════════════
507 echo "── 11. Error Cases ──"
508
509 # Bad auth
510 BAD_AUTH=$(curl -s -o /dev/null -w '%{http_code}' -X GET \
511 -H "Authorization: Bearer wrong-token" \
512 "${MNW_URL}/api/internal/creator/projects?user_id=$ELENA_ID")
513 if [ "$BAD_AUTH" = "401" ]; then
514 pass "Bad auth returns 401"
515 else
516 fail "Bad auth" "expected 401, got $BAD_AUTH"
517 fi
518
519 # No auth header
520 NO_AUTH=$(curl -s -o /dev/null -w '%{http_code}' -X GET \
521 "${MNW_URL}/api/internal/creator/projects?user_id=$ELENA_ID")
522 if [ "$NO_AUTH" = "401" ]; then
523 pass "No auth returns 401"
524 else
525 fail "No auth" "expected 401, got $NO_AUTH"
526 fi
527
528 # Nonexistent user
529 FAKE_USER="99999999-9999-9999-9999-999999999999"
530 assert_status "Nonexistent user projects" 200 GET "/api/internal/creator/projects?user_id=$FAKE_USER"
531 FAKE_PROJECTS=$(api GET "/api/internal/creator/projects?user_id=$FAKE_USER")
532 assert_json_count "Nonexistent user empty projects" "$FAKE_PROJECTS" 0
533
534 # Nonexistent item detail
535 FAKE_ITEM="99999999-9999-9999-9999-999999999999"
536 FAKE_STATUS=$(api_status GET "/api/internal/creator/items/$FAKE_ITEM?user_id=$ELENA_ID")
537 if [ "$FAKE_STATUS" = "404" ] || [ "$FAKE_STATUS" = "403" ]; then
538 pass "Nonexistent item returns $FAKE_STATUS"
539 else
540 fail "Nonexistent item" "expected 404/403, got $FAKE_STATUS"
541 fi
542
543 # Wrong owner for item
544 FAKE_OWNER="99999999-9999-9999-9999-999999999998"
545 WRONG_STATUS=$(api_status GET "/api/internal/creator/items/$ITEM_ESSAY?user_id=$FAKE_OWNER")
546 if [ "$WRONG_STATUS" = "404" ] || [ "$WRONG_STATUS" = "403" ]; then
547 pass "Wrong owner returns $WRONG_STATUS"
548 else
549 fail "Wrong owner" "expected 404/403, got $WRONG_STATUS"
550 fi
551
552 echo ""
553
554 # ══════════════════════════════════════════════════════════════════
555 # Section 12: Cleanup
556 # ══════════════════════════════════════════════════════════════════
557 echo "── 12. Cleanup ──"
558
559 # Delete the test item we created
560 if [ -n "$NEW_ITEM_ID" ]; then
561 assert_status "Delete test item" 204 DELETE "/api/internal/creator/items/$NEW_ITEM_ID?user_id=$ELENA_ID"
562 fi
563
564 echo ""
565
566 # ══════════════════════════════════════════════════════════════════
567 # Summary
568 # ══════════════════════════════════════════════════════════════════
569 TOTAL=$((PASS + FAIL + SKIP))
570 echo "════════════════════════════════════════"
571 echo " Results: $PASS passed, $FAIL failed, $SKIP skipped ($TOTAL total)"
572 echo "════════════════════════════════════════"
573
574 if [ $FAIL -gt 0 ]; then
575 echo ""
576 echo "Failures:$ERRORS"
577 echo ""
578 exit 1
579 fi
580
581 exit 0
582