opencode-pilot 0.1.0 → 0.2.1

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.
@@ -1,952 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # Tests for opencode-ntfy plugin
4
- #
5
- # These tests verify plugin file structure and JavaScript syntax.
6
- # Pure function tests will be added as modules are implemented.
7
- #
8
-
9
- set -euo pipefail
10
-
11
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
- source "$SCRIPT_DIR/test_helper.bash"
13
-
14
- PLUGIN_DIR="$(dirname "$SCRIPT_DIR")/plugin"
15
-
16
- echo "Testing opencode-ntfy plugin..."
17
- echo ""
18
-
19
- # =============================================================================
20
- # Plugin File Structure Tests
21
- # =============================================================================
22
-
23
- test_plugin_index_exists() {
24
- assert_file_exists "$PLUGIN_DIR/index.js"
25
- }
26
-
27
- test_plugin_notifier_exists() {
28
- assert_file_exists "$PLUGIN_DIR/notifier.js"
29
- }
30
-
31
- # =============================================================================
32
- # JavaScript Syntax Validation Tests
33
- # =============================================================================
34
-
35
- test_index_js_syntax() {
36
- if ! command -v node &>/dev/null; then
37
- echo "SKIP: node not available"
38
- return 0
39
- fi
40
- node --check "$PLUGIN_DIR/index.js" 2>&1 || {
41
- echo "index.js has syntax errors"
42
- return 1
43
- }
44
- }
45
-
46
- test_notifier_js_syntax() {
47
- if ! command -v node &>/dev/null; then
48
- echo "SKIP: node not available"
49
- return 0
50
- fi
51
- node --check "$PLUGIN_DIR/notifier.js" 2>&1 || {
52
- echo "notifier.js has syntax errors"
53
- return 1
54
- }
55
- }
56
-
57
- # =============================================================================
58
- # Configuration Tests
59
- # =============================================================================
60
-
61
- test_index_imports_config() {
62
- # Verify index.js imports config from config.js
63
- grep -q "import.*loadConfig.*from.*config" "$PLUGIN_DIR/index.js" || {
64
- echo "loadConfig import not found in index.js"
65
- return 1
66
- }
67
- }
68
-
69
- test_index_uses_load_config() {
70
- # Verify index.js calls loadConfig()
71
- grep -q "loadConfig()" "$PLUGIN_DIR/index.js" || {
72
- echo "loadConfig() call not found in index.js"
73
- return 1
74
- }
75
- }
76
-
77
- # =============================================================================
78
- # Idle Notification Behavior Tests
79
- # =============================================================================
80
-
81
- test_handles_session_status_events() {
82
- # Verify the event handler checks for session.status events
83
- grep -q "session.status" "$PLUGIN_DIR/index.js" || {
84
- echo "session.status event handling not found"
85
- return 1
86
- }
87
- }
88
-
89
- test_handles_idle_status() {
90
- # Verify idle status triggers timer
91
- grep -q "idle" "$PLUGIN_DIR/index.js" || {
92
- echo "idle status handling not found"
93
- return 1
94
- }
95
- }
96
-
97
- test_handles_busy_status() {
98
- # Verify busy status cancels timer
99
- grep -q "busy" "$PLUGIN_DIR/index.js" || {
100
- echo "busy status handling not found"
101
- return 1
102
- }
103
- }
104
-
105
- test_uses_settimeout_for_idle() {
106
- # Verify setTimeout is used for idle delay
107
- grep -q "setTimeout" "$PLUGIN_DIR/index.js" || {
108
- echo "setTimeout not found for idle delay"
109
- return 1
110
- }
111
- }
112
-
113
- test_uses_cleartimeout_for_busy() {
114
- # Verify clearTimeout is used when busy
115
- grep -q "clearTimeout" "$PLUGIN_DIR/index.js" || {
116
- echo "clearTimeout not found for busy cancel"
117
- return 1
118
- }
119
- }
120
-
121
- test_imports_send_notification_from_notifier() {
122
- # Verify index.js imports sendNotification from notifier.js
123
- grep -q "sendNotification" "$PLUGIN_DIR/index.js" || {
124
- echo "sendNotification not imported/used in index.js"
125
- return 1
126
- }
127
- }
128
-
129
- test_uses_configured_server_and_topic() {
130
- # Verify notification uses config.server and config.topic
131
- grep -q 'config\.server' "$PLUGIN_DIR/index.js" && \
132
- grep -q 'config\.topic' "$PLUGIN_DIR/index.js" || {
133
- echo "Notification should use config.server and config.topic"
134
- return 1
135
- }
136
- }
137
-
138
- test_uses_configured_idle_delay() {
139
- # Verify timeout uses config.idleDelayMs
140
- grep -q 'config\.idleDelayMs' "$PLUGIN_DIR/index.js" || {
141
- echo "Timer should use config.idleDelayMs"
142
- return 1
143
- }
144
- }
145
-
146
- test_uses_directory_param_not_process_cwd() {
147
- # Should use the directory parameter from OpenCode, not process.cwd()
148
- # This ensures devcontainer temp dirs don't show up in notifications
149
- grep -q 'directory' "$PLUGIN_DIR/index.js" || {
150
- echo "directory parameter not used in index.js"
151
- return 1
152
- }
153
- # Should NOT use process.cwd() for directory name in code (comments are ok)
154
- # Look for actual usage like: basename(process.cwd()) or = process.cwd()
155
- if grep -E 'basename\(process\.cwd\(\)\)|=\s*process\.cwd\(\)' "$PLUGIN_DIR/index.js"; then
156
- echo "Should not use process.cwd() for directory - use directory param instead"
157
- return 1
158
- fi
159
- }
160
-
161
- test_idle_notification_shows_repo_context() {
162
- # Idle notification should include repo name for context
163
- # Title should be "Idle (repo)" not just "OpenCode"
164
- grep -q 'Idle' "$PLUGIN_DIR/index.js" || {
165
- echo "Idle notification title should include 'Idle'"
166
- return 1
167
- }
168
- }
169
-
170
- # =============================================================================
171
- # Logging Tests
172
- # =============================================================================
173
- # NOTE: Logging tests removed - all console output is now suppressed to avoid TUI interference
174
-
175
- # =============================================================================
176
- # Plugin Export Structure Tests
177
- # =============================================================================
178
-
179
- test_index_exports_notify() {
180
- # Plugin should only use default export to prevent double-loading by OpenCode
181
- # (Issue #34: Having both named and default export caused plugin to load twice)
182
- grep -q "export default Notify" "$PLUGIN_DIR/index.js" || {
183
- echo "Default Notify export not found in index.js"
184
- return 1
185
- }
186
- # Ensure named export is NOT present (would cause double-loading)
187
- if grep -q "export const Notify" "$PLUGIN_DIR/index.js"; then
188
- echo "Named export 'export const Notify' found - should only use default export"
189
- return 1
190
- fi
191
- }
192
-
193
- test_index_has_default_export() {
194
- grep -q "export default" "$PLUGIN_DIR/index.js" || {
195
- echo "Default export not found in index.js"
196
- return 1
197
- }
198
- }
199
-
200
- test_notifier_exports_send_notification() {
201
- grep -q "export.*sendNotification" "$PLUGIN_DIR/notifier.js" || {
202
- echo "sendNotification export not found in notifier.js"
203
- return 1
204
- }
205
- }
206
-
207
- # =============================================================================
208
- # Retry Event Handling Tests (Issue #7)
209
- # =============================================================================
210
-
211
- test_handles_retry_status() {
212
- # Verify retry status is handled within session.status events
213
- grep -q "retry" "$PLUGIN_DIR/index.js" || {
214
- echo "retry status handling not found"
215
- return 1
216
- }
217
- }
218
-
219
- test_tracks_retry_count() {
220
- # Verify retry counter is tracked
221
- grep -q "retryCount" "$PLUGIN_DIR/index.js" || {
222
- echo "retryCount variable not found"
223
- return 1
224
- }
225
- }
226
-
227
- test_uses_retry_notify_first_config() {
228
- # Verify retryNotifyFirst config is used
229
- grep -q "retryNotifyFirst" "$PLUGIN_DIR/index.js" || {
230
- echo "retryNotifyFirst config not used"
231
- return 1
232
- }
233
- }
234
-
235
- test_uses_retry_notify_after_config() {
236
- # Verify retryNotifyAfter config is used
237
- grep -q "retryNotifyAfter" "$PLUGIN_DIR/index.js" || {
238
- echo "retryNotifyAfter config not used"
239
- return 1
240
- }
241
- }
242
-
243
- test_sends_retry_notification_with_priority() {
244
- # Verify retry notifications use high priority (4)
245
- grep -q "priority.*4\|4.*priority" "$PLUGIN_DIR/index.js" || {
246
- echo "High priority (4) not found for retry notifications"
247
- return 1
248
- }
249
- }
250
-
251
- # =============================================================================
252
- # Error Event Handling Tests (Issue #7)
253
- # =============================================================================
254
-
255
- test_handles_session_error() {
256
- # Verify session.error events are handled
257
- grep -q "session\.error" "$PLUGIN_DIR/index.js" || {
258
- echo "session.error event handling not found"
259
- return 1
260
- }
261
- }
262
-
263
- test_uses_error_notify_config() {
264
- # Verify errorNotify config is used
265
- grep -q "errorNotify" "$PLUGIN_DIR/index.js" || {
266
- echo "errorNotify config not used"
267
- return 1
268
- }
269
- }
270
-
271
- test_uses_error_debounce_config() {
272
- # Verify errorDebounceMs config is used
273
- grep -q "errorDebounceMs" "$PLUGIN_DIR/index.js" || {
274
- echo "errorDebounceMs config not used"
275
- return 1
276
- }
277
- }
278
-
279
- test_tracks_last_error_time() {
280
- # Verify last error timestamp is tracked for debouncing
281
- grep -q "lastErrorTime" "$PLUGIN_DIR/index.js" || {
282
- echo "lastErrorTime variable not found"
283
- return 1
284
- }
285
- }
286
-
287
- test_sends_error_notification_with_urgent_priority() {
288
- # Verify error notifications use urgent priority (5)
289
- grep -q "priority.*5\|5.*priority" "$PLUGIN_DIR/index.js" || {
290
- echo "Urgent priority (5) not found for error notifications"
291
- return 1
292
- }
293
- }
294
-
295
-
296
-
297
- # =============================================================================
298
- # Counter Reset Tests (Issue #7)
299
- # =============================================================================
300
-
301
- test_resets_retry_counter_on_status_change() {
302
- # Verify retry counter is reset when status changes
303
- grep -q "retryCount.*=.*0\|retryCount = 0" "$PLUGIN_DIR/index.js" || {
304
- echo "Retry counter reset not found"
305
- return 1
306
- }
307
- }
308
-
309
- # =============================================================================
310
- # Cancellation Handling Tests
311
- # =============================================================================
312
-
313
- test_handles_canceled_status() {
314
- # Verify canceled status is handled (no notification on cancel)
315
- grep -q "canceled" "$PLUGIN_DIR/index.js" || {
316
- echo "canceled status handling not found"
317
- return 1
318
- }
319
- }
320
-
321
- test_sets_canceled_flag_on_canceled_status() {
322
- # Verify a flag is set when session is canceled
323
- grep -q "wasCanceled\|sessionCanceled\|canceled.*true\|isCanceled" "$PLUGIN_DIR/index.js" || {
324
- echo "canceled flag not found"
325
- return 1
326
- }
327
- }
328
-
329
- test_shutdown_checks_canceled_before_notification() {
330
- # Verify shutdown handler respects canceled state
331
- # The idle timer should be cancelled without sending notification
332
- grep -q "shutdown" "$PLUGIN_DIR/index.js" || {
333
- echo "shutdown handler not found"
334
- return 1
335
- }
336
- }
337
-
338
- # =============================================================================
339
- # Console Output Suppression Tests
340
- # =============================================================================
341
-
342
- test_no_console_log_calls() {
343
- # Plugin should not use console.log (interferes with TUI)
344
- if grep -q 'console\.log' "$PLUGIN_DIR/index.js"; then
345
- echo "console.log found - should use silent or file-based logging"
346
- return 1
347
- fi
348
- }
349
-
350
- test_no_console_warn_calls() {
351
- # Plugin should not use console.warn (interferes with TUI)
352
- if grep -q 'console\.warn' "$PLUGIN_DIR/index.js"; then
353
- echo "console.warn found - should use silent or file-based logging"
354
- return 1
355
- fi
356
- }
357
-
358
- test_no_console_error_calls() {
359
- # Plugin should not use console.error (interferes with TUI)
360
- if grep -q 'console\.error' "$PLUGIN_DIR/index.js"; then
361
- echo "console.error found - should use silent or file-based logging"
362
- return 1
363
- fi
364
- }
365
-
366
- # =============================================================================
367
- # Debug Logging Tests
368
- # =============================================================================
369
-
370
- test_index_imports_logger() {
371
- grep -q "import.*logger\|from.*logger" "$PLUGIN_DIR/index.js" || {
372
- echo "logger import not found in index.js"
373
- return 1
374
- }
375
- }
376
-
377
- test_index_initializes_logger() {
378
- grep -q "initLogger" "$PLUGIN_DIR/index.js" || {
379
- echo "initLogger call not found in index.js"
380
- return 1
381
- }
382
- }
383
-
384
- test_index_uses_debug_for_events() {
385
- # Plugin should log events received
386
- grep -q "debug.*[Ee]vent" "$PLUGIN_DIR/index.js" || {
387
- echo "debug logging for events not found in index.js"
388
- return 1
389
- }
390
- }
391
-
392
- test_index_uses_debug_for_idle_timer() {
393
- # Plugin should log idle timer start/cancel
394
- grep -q "debug.*[Ii]dle\|debug.*timer" "$PLUGIN_DIR/index.js" || {
395
- echo "debug logging for idle timer not found in index.js"
396
- return 1
397
- }
398
- }
399
-
400
- test_index_uses_debug_for_initialization() {
401
- # Plugin should log initialization with key config
402
- grep -q "debug.*[Ii]nitial\|debug.*[Pp]lugin" "$PLUGIN_DIR/index.js" || {
403
- echo "debug logging for initialization not found in index.js"
404
- return 1
405
- }
406
- }
407
-
408
- # =============================================================================
409
- # Notification Suppression Logging Tests (Issue #7)
410
- # =============================================================================
411
- # NOTE: Console output suppression tests removed - all logging disabled to avoid TUI interference
412
-
413
- # =============================================================================
414
- # OpenCode Runtime Integration Tests
415
- # =============================================================================
416
- # These tests verify the plugin doesn't hang opencode on startup and
417
- # works correctly in the real OpenCode runtime.
418
- #
419
- # Tests run if opencode is installed and plugin is configured.
420
- # Skipped in CI unless explicitly enabled.
421
-
422
- # Helper to check if we can run integration tests
423
- can_run_integration_tests() {
424
- # Check opencode is installed
425
- if ! command -v opencode &>/dev/null; then
426
- return 1
427
- fi
428
-
429
- # Check plugin is installed (use REAL_HOME since we may have changed HOME)
430
- local plugin_path="${REAL_HOME:-$HOME}/.config/opencode/plugins/opencode-ntfy/index.js"
431
- if [[ ! -f "$plugin_path" ]]; then
432
- return 1
433
- fi
434
-
435
- return 0
436
- }
437
-
438
- # Setup isolated environment for integration tests
439
- # Uses temp directory for opencode data while symlinking config from real HOME
440
- setup_integration_env() {
441
- REAL_HOME="$HOME"
442
- INTEGRATION_TEST_DIR=$(mktemp -d)
443
- export HOME="$INTEGRATION_TEST_DIR"
444
-
445
- # Create necessary directories
446
- mkdir -p "$HOME/.config"
447
- mkdir -p "$HOME/.local/share"
448
-
449
- # Symlink opencode config (contains plugin registrations and auth)
450
- ln -s "$REAL_HOME/.config/opencode" "$HOME/.config/opencode"
451
-
452
- # Symlink opencode-ntfy config
453
- if [[ -d "$REAL_HOME/.config/opencode-ntfy" ]]; then
454
- ln -s "$REAL_HOME/.config/opencode-ntfy" "$HOME/.config/opencode-ntfy"
455
- fi
456
- }
457
-
458
- # Cleanup isolated environment after integration tests
459
- cleanup_integration_env() {
460
- if [[ -n "${INTEGRATION_TEST_DIR:-}" ]] && [[ -d "$INTEGRATION_TEST_DIR" ]]; then
461
- rm -rf "$INTEGRATION_TEST_DIR"
462
- fi
463
- if [[ -n "${REAL_HOME:-}" ]]; then
464
- export HOME="$REAL_HOME"
465
- fi
466
- }
467
-
468
- # Cross-platform timeout wrapper
469
- # Uses perl alarm which works on macOS and Linux
470
- run_with_timeout() {
471
- local timeout_secs="$1"
472
- shift
473
- perl -e "alarm $timeout_secs; exec @ARGV" "$@" 2>&1
474
- }
475
-
476
- # Run opencode and capture output (with timeout)
477
- run_opencode() {
478
- local prompt="$1"
479
- local timeout="${2:-60}"
480
-
481
- run_with_timeout "$timeout" opencode run --format json "$prompt"
482
- }
483
-
484
- test_opencode_starts_within_timeout() {
485
- if ! can_run_integration_tests; then
486
- echo "SKIP: opencode not installed or plugin not configured"
487
- return 0
488
- fi
489
-
490
- setup_integration_env
491
-
492
- # CRITICAL: Verify opencode starts within 10 seconds
493
- # This catches plugin initialization hangs that would block startup indefinitely.
494
-
495
- local start_time end_time elapsed output
496
- start_time=$(date +%s)
497
-
498
- # Use a short timeout - if plugin hangs, this will fail
499
- output=$(run_with_timeout 10 opencode run --format json "Say hi" 2>&1)
500
- local exit_code=$?
501
-
502
- end_time=$(date +%s)
503
- elapsed=$((end_time - start_time))
504
-
505
- cleanup_integration_env
506
-
507
- if [[ $exit_code -ne 0 ]]; then
508
- # Check if it was a timeout (exit code 142 = SIGALRM)
509
- if [[ $exit_code -eq 142 ]] || [[ "$output" == *"Alarm clock"* ]]; then
510
- echo "FAIL: opencode startup timed out after ${elapsed}s (plugin may be hanging)"
511
- echo "Output: $output"
512
- return 1
513
- fi
514
- # Skip on model configuration errors (not a plugin issue)
515
- if [[ "$output" == *"ModelNotFoundError"* ]] || [[ "$output" == *"ProviderModelNotFoundError"* ]]; then
516
- echo "SKIP: model configuration error (not a plugin issue)"
517
- return 0
518
- fi
519
- echo "opencode run failed (exit $exit_code): $output"
520
- return 1
521
- fi
522
-
523
- # Verify we got a response
524
- if ! echo "$output" | grep -q '"type"'; then
525
- # Skip on model configuration errors (not a plugin issue)
526
- if [[ "$output" == *"ModelNotFoundError"* ]] || [[ "$output" == *"ProviderModelNotFoundError"* ]]; then
527
- echo "SKIP: model configuration error (not a plugin issue)"
528
- return 0
529
- fi
530
- echo "No valid JSON output from opencode"
531
- echo "Output: $output"
532
- return 1
533
- fi
534
-
535
- return 0
536
- }
537
-
538
- test_opencode_plugin_loads() {
539
- if ! can_run_integration_tests; then
540
- echo "SKIP: opencode not installed or plugin not configured"
541
- return 0
542
- fi
543
-
544
- setup_integration_env
545
-
546
- # Plugin should load without errors - check opencode doesn't report plugin failures
547
- local output
548
- output=$(run_opencode "Say hello" 30) || {
549
- cleanup_integration_env
550
- echo "opencode run failed: $output"
551
- return 1
552
- }
553
-
554
- cleanup_integration_env
555
-
556
- # Check that output doesn't contain plugin error messages
557
- if echo "$output" | grep -qi "plugin.*error\|failed to load"; then
558
- echo "Plugin load error detected"
559
- echo "Output: $output"
560
- return 1
561
- fi
562
-
563
- return 0
564
- }
565
-
566
- test_opencode_ntfy_disabled_without_topic() {
567
- if ! can_run_integration_tests; then
568
- echo "SKIP: opencode not installed or plugin not configured"
569
- return 0
570
- fi
571
-
572
- setup_integration_env
573
-
574
- # Without NTFY_TOPIC, plugin should disable gracefully (not crash)
575
- # Unset NTFY_TOPIC temporarily
576
- local old_topic="${NTFY_TOPIC:-}"
577
- unset NTFY_TOPIC
578
-
579
- local output
580
- output=$(run_opencode "Say hi" 30)
581
- local exit_code=$?
582
-
583
- # Restore
584
- if [[ -n "$old_topic" ]]; then
585
- export NTFY_TOPIC="$old_topic"
586
- fi
587
-
588
- cleanup_integration_env
589
-
590
- if [[ $exit_code -ne 0 ]]; then
591
- echo "opencode failed without NTFY_TOPIC: $output"
592
- return 1
593
- fi
594
-
595
- return 0
596
- }
597
-
598
- # =============================================================================
599
- # Run Tests
600
- # =============================================================================
601
-
602
- echo "Plugin File Structure Tests:"
603
-
604
- for test_func in \
605
- test_plugin_index_exists \
606
- test_plugin_notifier_exists
607
- do
608
- run_test "${test_func#test_}" "$test_func"
609
- done
610
-
611
- echo ""
612
- echo "JavaScript Syntax Validation Tests:"
613
-
614
- for test_func in \
615
- test_index_js_syntax \
616
- test_notifier_js_syntax
617
- do
618
- run_test "${test_func#test_}" "$test_func"
619
- done
620
-
621
- echo ""
622
- echo "Configuration Tests:"
623
-
624
- for test_func in \
625
- test_index_imports_config \
626
- test_index_uses_load_config
627
- do
628
- run_test "${test_func#test_}" "$test_func"
629
- done
630
-
631
- echo ""
632
- echo "Idle Notification Behavior Tests:"
633
-
634
- for test_func in \
635
- test_handles_session_status_events \
636
- test_handles_idle_status \
637
- test_handles_busy_status \
638
- test_uses_settimeout_for_idle \
639
- test_uses_cleartimeout_for_busy \
640
- test_imports_send_notification_from_notifier \
641
- test_uses_configured_server_and_topic \
642
- test_uses_configured_idle_delay \
643
- test_uses_directory_param_not_process_cwd \
644
- test_idle_notification_shows_repo_context
645
- do
646
- run_test "${test_func#test_}" "$test_func"
647
- done
648
-
649
- # Logging tests removed - console output is now fully suppressed
650
-
651
- echo ""
652
- echo "Plugin Export Structure Tests:"
653
-
654
- for test_func in \
655
- test_index_exports_notify \
656
- test_index_has_default_export \
657
- test_notifier_exports_send_notification
658
- do
659
- run_test "${test_func#test_}" "$test_func"
660
- done
661
-
662
- echo ""
663
- echo "Retry Event Handling Tests (Issue #7):"
664
-
665
- for test_func in \
666
- test_handles_retry_status \
667
- test_tracks_retry_count \
668
- test_uses_retry_notify_first_config \
669
- test_uses_retry_notify_after_config \
670
- test_sends_retry_notification_with_priority
671
- do
672
- run_test "${test_func#test_}" "$test_func"
673
- done
674
-
675
- echo ""
676
- echo "Error Event Handling Tests (Issue #7):"
677
-
678
- for test_func in \
679
- test_handles_session_error \
680
- test_uses_error_notify_config \
681
- test_uses_error_debounce_config \
682
- test_tracks_last_error_time \
683
- test_sends_error_notification_with_urgent_priority
684
- do
685
- run_test "${test_func#test_}" "$test_func"
686
- done
687
-
688
- echo ""
689
- echo "Counter Reset Tests (Issue #7):"
690
-
691
- for test_func in \
692
- test_resets_retry_counter_on_status_change
693
- do
694
- run_test "${test_func#test_}" "$test_func"
695
- done
696
-
697
- echo ""
698
- echo "Cancellation Handling Tests:"
699
-
700
- for test_func in \
701
- test_handles_canceled_status \
702
- test_sets_canceled_flag_on_canceled_status \
703
- test_shutdown_checks_canceled_before_notification
704
- do
705
- run_test "${test_func#test_}" "$test_func"
706
- done
707
-
708
- echo ""
709
- echo "Console Output Suppression Tests:"
710
-
711
- for test_func in \
712
- test_no_console_log_calls \
713
- test_no_console_warn_calls \
714
- test_no_console_error_calls
715
- do
716
- run_test "${test_func#test_}" "$test_func"
717
- done
718
-
719
- echo ""
720
- echo "Debug Logging Tests:"
721
-
722
- for test_func in \
723
- test_index_imports_logger \
724
- test_index_initializes_logger \
725
- test_index_uses_debug_for_events \
726
- test_index_uses_debug_for_idle_timer \
727
- test_index_uses_debug_for_initialization
728
- do
729
- run_test "${test_func#test_}" "$test_func"
730
- done
731
-
732
- # Removed notification suppression logging tests - console output is now fully suppressed
733
-
734
- # =============================================================================
735
- # Per-Conversation State Tracking Tests (Issue #34)
736
- # =============================================================================
737
-
738
- test_uses_conversations_map_for_state() {
739
- # Plugin should track state per-conversation using a Map, not global variables
740
- grep -q "conversations\|Map()" "$PLUGIN_DIR/index.js" || {
741
- echo "Conversations Map not found in index.js"
742
- return 1
743
- }
744
- }
745
-
746
- test_extracts_session_id_from_event() {
747
- # Plugin should extract session ID from event properties for routing
748
- grep -q "event\.properties.*id\|properties.*info.*id" "$PLUGIN_DIR/index.js" || {
749
- echo "Session ID extraction from event not found"
750
- return 1
751
- }
752
- }
753
-
754
- test_stores_idle_timer_per_conversation() {
755
- # Each conversation should have its own idle timer
756
- # Look for pattern like conversations.get(sessionId).idleTimer or similar
757
- grep -q "idleTimer" "$PLUGIN_DIR/index.js" || {
758
- echo "idleTimer not found in index.js"
759
- return 1
760
- }
761
- }
762
-
763
- test_captures_session_id_in_idle_timer_closure() {
764
- # The sessionId should be captured when setting the timer, not read later
765
- # This ensures the notification URL points to the correct conversation
766
- # Pattern: use local variable in setTimeout callback, not external reference
767
- grep -A 30 "setTimeout" "$PLUGIN_DIR/index.js" | grep -q "session" || {
768
- echo "Session ID not captured in idle timer"
769
- return 1
770
- }
771
- }
772
-
773
- test_clears_conversation_state_on_cancel() {
774
- # When a conversation is canceled, its state should be cleaned up
775
- grep -q "canceled" "$PLUGIN_DIR/index.js" && \
776
- grep -q "delete\|clear" "$PLUGIN_DIR/index.js" || {
777
- echo "Conversation cleanup on cancel not found"
778
- return 1
779
- }
780
- }
781
-
782
- # =============================================================================
783
- # Session Tracking and Open Session Action Tests (Issue #27)
784
- # =============================================================================
785
-
786
- test_tracks_current_session_id() {
787
- # Plugin should track current session ID from session events
788
- grep -q "currentSessionId\|sessionId" "$PLUGIN_DIR/index.js" || {
789
- echo "session ID tracking not found in index.js"
790
- return 1
791
- }
792
- }
793
-
794
- test_extracts_session_id_from_status_event() {
795
- # Plugin should extract session ID from session.status events for per-conversation tracking
796
- grep -q "session\.status\|properties.*info.*id" "$PLUGIN_DIR/index.js" || {
797
- echo "Session ID extraction from status event not found"
798
- return 1
799
- }
800
- }
801
-
802
-
803
-
804
- # =============================================================================
805
- # Devcontainer Path Parsing Tests
806
- # =============================================================================
807
-
808
- test_parses_devcontainer_clone_path() {
809
- # When directory is a devcontainer clone, should extract repo and branch
810
- # Path format: /Users/foo/.cache/devcontainer-clones/{repo}/{branch}
811
- grep -q "devcontainer-clones" "$PLUGIN_DIR/index.js" || {
812
- echo "devcontainer-clones path parsing not found in index.js"
813
- return 1
814
- }
815
- }
816
-
817
- test_notification_shows_repo_and_branch() {
818
- # Notification title should show both repo name and branch when in devcontainer
819
- # e.g., "Idle (opencode-ntfy/fix-something)" instead of just "Idle (fix-something)"
820
- grep -q "parseRepoInfo\|getRepoName" "$PLUGIN_DIR/index.js" || {
821
- echo "Repo info parsing function not found in index.js"
822
- return 1
823
- }
824
- }
825
-
826
- test_regular_directory_shows_basename_only() {
827
- # For regular directories (not devcontainer clones), should show just basename
828
- # e.g., "/Users/foo/code/myrepo" -> "myrepo"
829
- grep -q "basename" "$PLUGIN_DIR/index.js" || {
830
- echo "basename fallback not found in index.js"
831
- return 1
832
- }
833
- }
834
-
835
- # =============================================================================
836
- # Session Ownership Verification Tests (Issue #50)
837
- # =============================================================================
838
- # These tests verify the plugin only processes events for sessions that
839
- # belong to its OpenCode instance, preventing duplicate notifications when
840
- # multiple OpenCode instances are running.
841
-
842
- test_verifies_session_ownership_before_processing() {
843
- # Plugin should verify session exists in its OpenCode instance before processing events
844
- # This prevents duplicate notifications from multiple plugin instances
845
- grep -q "verifySessionOwnership\|session\.get\|verifiedSessions" "$PLUGIN_DIR/index.js" || {
846
- echo "Session ownership verification not found in index.js"
847
- return 1
848
- }
849
- }
850
-
851
- test_caches_verified_sessions() {
852
- # Plugin should cache verified sessions to avoid repeated API calls
853
- grep -q "verifiedSessions\|ownedSessions\|sessionCache" "$PLUGIN_DIR/index.js" || {
854
- echo "Verified sessions cache not found in index.js"
855
- return 1
856
- }
857
- }
858
-
859
- test_tracks_rejected_sessions() {
860
- # Plugin should track sessions that don't belong to it to avoid repeated lookups
861
- grep -q "rejectedSessions\|foreignSessions\|notOurs" "$PLUGIN_DIR/index.js" || {
862
- echo "Rejected sessions tracking not found in index.js"
863
- return 1
864
- }
865
- }
866
-
867
- test_uses_client_session_get_for_verification() {
868
- # Plugin should use client.session.get() to verify session ownership
869
- grep -q "client\.session\.get\|session\.get" "$PLUGIN_DIR/index.js" || {
870
- echo "client.session.get() call not found in index.js"
871
- return 1
872
- }
873
- }
874
-
875
- test_skips_events_for_foreign_sessions() {
876
- # Events for sessions that don't belong to this instance should be skipped
877
- # Look for early return when session is not owned
878
- grep -q "rejectedSessions\|foreignSessions\|not.*ours\|skip.*event" "$PLUGIN_DIR/index.js" || {
879
- echo "Foreign session skip logic not found in index.js"
880
- return 1
881
- }
882
- }
883
-
884
- test_cleans_up_session_caches_on_shutdown() {
885
- # Shutdown handler should clear session ownership caches to prevent memory leaks
886
- grep -A 20 "shutdown:" "$PLUGIN_DIR/index.js" | grep -q "verifiedSessions.clear\|rejectedSessions.clear" || {
887
- echo "Session cache cleanup not found in shutdown handler"
888
- return 1
889
- }
890
- }
891
-
892
- echo ""
893
- echo "Session Ownership Verification Tests (Issue #50):"
894
-
895
- for test_func in \
896
- test_verifies_session_ownership_before_processing \
897
- test_caches_verified_sessions \
898
- test_tracks_rejected_sessions \
899
- test_uses_client_session_get_for_verification \
900
- test_skips_events_for_foreign_sessions \
901
- test_cleans_up_session_caches_on_shutdown
902
- do
903
- run_test "${test_func#test_}" "$test_func"
904
- done
905
-
906
- echo ""
907
- echo "Per-Conversation State Tracking Tests (Issue #34):"
908
-
909
- for test_func in \
910
- test_uses_conversations_map_for_state \
911
- test_extracts_session_id_from_event \
912
- test_stores_idle_timer_per_conversation \
913
- test_captures_session_id_in_idle_timer_closure \
914
- test_clears_conversation_state_on_cancel
915
- do
916
- run_test "${test_func#test_}" "$test_func"
917
- done
918
-
919
- echo ""
920
- echo "Session Tracking Tests:"
921
-
922
- for test_func in \
923
- test_tracks_current_session_id \
924
- test_extracts_session_id_from_status_event
925
- do
926
- run_test "${test_func#test_}" "$test_func"
927
- done
928
-
929
- echo ""
930
- echo "Devcontainer Path Parsing Tests:"
931
-
932
- for test_func in \
933
- test_parses_devcontainer_clone_path \
934
- test_notification_shows_repo_and_branch \
935
- test_regular_directory_shows_basename_only
936
- do
937
- run_test "${test_func#test_}" "$test_func"
938
- done
939
-
940
- echo ""
941
- echo "OpenCode Runtime Integration Tests:"
942
-
943
- for test_func in \
944
- test_opencode_starts_within_timeout \
945
- test_opencode_plugin_loads \
946
- test_opencode_ntfy_disabled_without_topic
947
- do
948
- # Don't use setup/teardown for integration tests - use real HOME
949
- run_test "${test_func#test_}" "$test_func"
950
- done
951
-
952
- print_summary