@@ -2,99 +2,161 @@ import { spawn } from "node:child_process";
22import { resolve } from "node:path" ;
33import { describe , expect , it } from "vitest" ;
44
5- describe ( "bundle execution" , ( ) => {
6- const binPath = resolve ( import . meta. dirname , "../../../bin/obs-hook-handler" ) ;
5+ /** Execute a bin script with JSON input via stdin. */
6+ async function runBin (
7+ binScript : string ,
8+ input : string ,
9+ ) : Promise < { stdout : string ; stderr : string } > {
10+ const binPath = resolve ( import . meta. dirname , "../../../bin" , binScript ) ;
11+ return new Promise ( ( res , reject ) => {
12+ const proc = spawn ( "node" , [ binPath ] , {
13+ stdio : [ "pipe" , "pipe" , "pipe" ] ,
14+ } ) ;
715
8- /** Execute hook handler bin script with JSON input via stdin. */
9- async function runHookHandler ( input : string ) : Promise < { stdout : string ; stderr : string } > {
10- return new Promise ( ( resolve , reject ) => {
11- const proc = spawn ( "node" , [ binPath ] , {
12- stdio : [ "pipe" , "pipe" , "pipe" ] ,
13- } ) ;
16+ let stdout = "" ;
17+ let stderr = "" ;
1418
15- let stdout = "" ;
16- let stderr = "" ;
19+ proc . stdout . on ( "data" , ( chunk : Buffer ) => {
20+ stdout += chunk . toString ( ) ;
21+ } ) ;
1722
18- proc . stdout . on ( "data" , ( chunk : Buffer ) => {
19- stdout += chunk . toString ( ) ;
20- } ) ;
23+ proc . stderr . on ( "data" , ( chunk : Buffer ) => {
24+ stderr += chunk . toString ( ) ;
25+ } ) ;
2126
22- proc . stderr . on ( "data " , ( chunk : Buffer ) => {
23- stderr += chunk . toString ( ) ;
24- } ) ;
27+ proc . on ( "close " , ( ) => {
28+ res ( { stdout , stderr } ) ;
29+ } ) ;
2530
26- proc . on ( "close" , ( ) => {
27- resolve ( { stdout, stderr } ) ;
28- } ) ;
31+ proc . on ( "error" , ( err ) => {
32+ reject ( err ) ;
33+ } ) ;
34+
35+ proc . stdin . write ( input ) ;
36+ proc . stdin . end ( ) ;
37+ } ) ;
38+ }
2939
30- proc . on ( "error" , ( err ) => {
31- reject ( err ) ;
40+ describe ( "bundle execution" , ( ) => {
41+ describe ( "obs-hook-handler" , ( ) => {
42+ it ( "returns empty output for no mentions" , async ( ) => {
43+ const input = JSON . stringify ( {
44+ hook_event_name : "UserPromptSubmit" ,
45+ session_id : "test" ,
46+ transcript_path : "/tmp" ,
47+ cwd : "/tmp" ,
48+ prompt : "hello world" ,
3249 } ) ;
3350
34- proc . stdin . write ( input ) ;
35- proc . stdin . end ( ) ;
51+ const result = await runBin ( "obs-hook-handler" , input ) ;
52+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
53+ expect ( result . stderr ) . toBe ( "" ) ;
3654 } ) ;
37- }
38-
39- it ( "bundle executes via bin script and returns empty output for no mentions" , async ( ) => {
40- const input = JSON . stringify ( {
41- hook_event_name : "UserPromptSubmit" ,
42- session_id : "test" ,
43- transcript_path : "/tmp" ,
44- cwd : "/tmp" ,
45- prompt : "hello world" ,
55+
56+ it ( "returns empty output for invalid JSON input" , async ( ) => {
57+ const result = await runBin ( "obs-hook-handler" , "not json" ) ;
58+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
4659 } ) ;
4760
48- const result = await runHookHandler ( input ) ;
49- expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
50- expect ( result . stderr ) . toBe ( "" ) ;
51- } ) ;
61+ it ( "returns empty output for wrong hook event" , async ( ) => {
62+ const input = JSON . stringify ( {
63+ hook_event_name : "PreToolUse" ,
64+ session_id : "test" ,
65+ transcript_path : "/tmp" ,
66+ cwd : "/tmp" ,
67+ prompt : "@obs:test" ,
68+ } ) ;
5269
53- it ( "bundle executes and returns empty output for invalid JSON input" , async ( ) => {
54- const result = await runHookHandler ( "not json" ) ;
55- expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
56- } ) ;
70+ const result = await runBin ( "obs-hook-handler" , input ) ;
71+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
72+ } ) ;
73+
74+ it ( "returns empty output for missing required fields" , async ( ) => {
75+ const input = JSON . stringify ( {
76+ hook_event_name : "UserPromptSubmit" ,
77+ prompt : "@obs:test" ,
78+ } ) ;
5779
58- it ( "bundle executes and returns empty output for wrong hook event" , async ( ) => {
59- const input = JSON . stringify ( {
60- hook_event_name : "PreToolUse" ,
61- session_id : "test" ,
62- transcript_path : "/tmp" ,
63- cwd : "/tmp" ,
64- prompt : "@obs:test" ,
80+ const result = await runBin ( "obs-hook-handler" , input ) ;
81+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
6582 } ) ;
6683
67- const result = await runHookHandler ( input ) ;
68- expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
84+ it ( "handles @obs: mention (bootstrap failure expected)" , async ( ) => {
85+ const input = JSON . stringify ( {
86+ hook_event_name : "UserPromptSubmit" ,
87+ session_id : "test" ,
88+ transcript_path : "/tmp" ,
89+ cwd : "/tmp" ,
90+ prompt : "Check @obs:architect" ,
91+ } ) ;
92+
93+ const result = await runBin ( "obs-hook-handler" , input ) ;
94+ const output = JSON . parse ( result . stdout . trim ( ) ) as {
95+ hookSpecificOutput ?: { additionalContext ?: string } ;
96+ } ;
97+
98+ expect ( output ) . toHaveProperty ( "hookSpecificOutput" ) ;
99+ expect ( output . hookSpecificOutput ) . toHaveProperty ( "additionalContext" ) ;
100+ expect ( output . hookSpecificOutput ?. additionalContext ) . toContain ( "@obs:architect" ) ;
101+ expect ( output . hookSpecificOutput ?. additionalContext ) . toContain ( "Error:" ) ;
102+ } ) ;
69103 } ) ;
70104
71- it ( "bundle executes and returns empty output for missing required fields" , async ( ) => {
72- const input = JSON . stringify ( {
73- hook_event_name : "UserPromptSubmit" ,
74- prompt : "@obs:test" ,
105+ describe ( "obs-expansion-handler" , ( ) => {
106+ it ( "returns empty output for non-proxy skill" , async ( ) => {
107+ const input = JSON . stringify ( {
108+ hook_event_name : "UserPromptExpansion" ,
109+ session_id : "test" ,
110+ transcript_path : "/tmp" ,
111+ cwd : "/tmp" ,
112+ command_name : "nonexistent-skill" ,
113+ expansion_type : "skill" ,
114+ command_source : "project" ,
115+ } ) ;
116+
117+ const result = await runBin ( "obs-expansion-handler" , input ) ;
118+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
75119 } ) ;
76120
77- const result = await runHookHandler ( input ) ;
78- expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
121+ it ( "returns empty output for wrong hook event" , async ( ) => {
122+ const input = JSON . stringify ( {
123+ hook_event_name : "UserPromptSubmit" ,
124+ session_id : "test" ,
125+ transcript_path : "/tmp" ,
126+ cwd : "/tmp" ,
127+ command_name : "test" ,
128+ } ) ;
129+
130+ const result = await runBin ( "obs-expansion-handler" , input ) ;
131+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
132+ } ) ;
79133 } ) ;
80134
81- it ( "bundle executes and handles @obs: mention (bootstrap failure expected)" , async ( ) => {
82- const input = JSON . stringify ( {
83- hook_event_name : "UserPromptSubmit" ,
84- session_id : "test" ,
85- transcript_path : "/tmp" ,
86- cwd : "/tmp" ,
87- prompt : "Check @obs:architect" ,
135+ describe ( "obs-subagent-handler" , ( ) => {
136+ it ( "returns empty output for nonexistent agent" , async ( ) => {
137+ const input = JSON . stringify ( {
138+ hook_event_name : "SubagentStart" ,
139+ session_id : "test" ,
140+ transcript_path : "/tmp" ,
141+ cwd : "/tmp" ,
142+ agent_type : "nonexistent-agent" ,
143+ } ) ;
144+
145+ const result = await runBin ( "obs-subagent-handler" , input ) ;
146+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
88147 } ) ;
89148
90- const result = await runHookHandler ( input ) ;
91- const output = JSON . parse ( result . stdout . trim ( ) ) as {
92- hookSpecificOutput ?: { additionalContext ?: string } ;
93- } ;
149+ it ( "returns empty output for wrong hook event" , async ( ) => {
150+ const input = JSON . stringify ( {
151+ hook_event_name : "UserPromptSubmit" ,
152+ session_id : "test" ,
153+ transcript_path : "/tmp" ,
154+ cwd : "/tmp" ,
155+ agent_type : "test" ,
156+ } ) ;
94157
95- expect ( output ) . toHaveProperty ( "hookSpecificOutput" ) ;
96- expect ( output . hookSpecificOutput ) . toHaveProperty ( "additionalContext" ) ;
97- expect ( output . hookSpecificOutput ?. additionalContext ) . toContain ( "@obs:architect" ) ;
98- expect ( output . hookSpecificOutput ?. additionalContext ) . toContain ( "Error:" ) ;
158+ const result = await runBin ( "obs-subagent-handler" , input ) ;
159+ expect ( result . stdout . trim ( ) ) . toBe ( "{}" ) ;
160+ } ) ;
99161 } ) ;
100162} ) ;
0 commit comments