@@ -126,3 +126,124 @@ fn handle_event_rejects_empty_name() {
126126 "Error should mention empty name, got: {err}"
127127 ) ;
128128}
129+
130+ // ── Auto-export heuristic tests (issue #39) ──────────────────────────
131+ // The auto-export logic must only detect actual ES export statements,
132+ // not the word "export" inside string literals, comments, or identifiers.
133+
134+ #[ test]
135+ fn handler_with_export_in_string_literal ( ) {
136+ // "export" appears inside a string — auto-export should still fire
137+ let handler = Script :: from_content (
138+ r#"
139+ function handler(event) {
140+ const xml = '<config mode="export">value</config>';
141+ return { result: xml };
142+ }
143+ "# ,
144+ ) ;
145+
146+ let proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
147+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
148+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
149+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
150+
151+ let res = loaded
152+ . handle_event ( "handler" , "{}" . to_string ( ) , None )
153+ . unwrap ( ) ;
154+ assert_eq ! (
155+ res,
156+ r#"{"result":"<config mode=\"export\">value</config>"}"#
157+ ) ;
158+ }
159+
160+ #[ test]
161+ fn handler_with_export_in_comment ( ) {
162+ // "export" appears in a comment — auto-export should still fire
163+ let handler = Script :: from_content (
164+ r#"
165+ function handler(event) {
166+ // TODO: export this data to CSV
167+ return { result: 42 };
168+ }
169+ "# ,
170+ ) ;
171+
172+ let proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
173+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
174+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
175+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
176+
177+ let res = loaded
178+ . handle_event ( "handler" , "{}" . to_string ( ) , None )
179+ . unwrap ( ) ;
180+ assert_eq ! ( res, r#"{"result":42}"# ) ;
181+ }
182+
183+ #[ test]
184+ fn handler_with_export_in_identifier ( ) {
185+ // "export" is part of an identifier — auto-export should still fire
186+ let handler = Script :: from_content (
187+ r#"
188+ function handler(event) {
189+ const exportPath = "/tmp/out.csv";
190+ return { result: exportPath };
191+ }
192+ "# ,
193+ ) ;
194+
195+ let proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
196+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
197+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
198+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
199+
200+ let res = loaded
201+ . handle_event ( "handler" , "{}" . to_string ( ) , None )
202+ . unwrap ( ) ;
203+ assert_eq ! ( res, r#"{"result":"/tmp/out.csv"}"# ) ;
204+ }
205+
206+ #[ test]
207+ fn handler_with_explicit_export_is_not_doubled ( ) {
208+ // Script already has an export statement — auto-export should be skipped
209+ let handler = Script :: from_content (
210+ r#"
211+ function handler(event) {
212+ return { result: "explicit" };
213+ }
214+ export { handler };
215+ "# ,
216+ ) ;
217+
218+ let proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
219+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
220+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
221+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
222+
223+ let res = loaded
224+ . handle_event ( "handler" , "{}" . to_string ( ) , None )
225+ . unwrap ( ) ;
226+ assert_eq ! ( res, r#"{"result":"explicit"}"# ) ;
227+ }
228+
229+ #[ test]
230+ fn handler_with_export_default_function ( ) {
231+ // `export function` — auto-export should be skipped
232+ let handler = Script :: from_content (
233+ r#"
234+ export function handler(event) {
235+ return { result: "inline-export" };
236+ }
237+ "# ,
238+ ) ;
239+
240+ let proto = SandboxBuilder :: new ( ) . build ( ) . unwrap ( ) ;
241+ let mut sandbox = proto. load_runtime ( ) . unwrap ( ) ;
242+ sandbox. add_handler ( "handler" , handler) . unwrap ( ) ;
243+ let mut loaded = sandbox. get_loaded_sandbox ( ) . unwrap ( ) ;
244+
245+ let res = loaded
246+ . handle_event ( "handler" , "{}" . to_string ( ) , None )
247+ . unwrap ( ) ;
248+ assert_eq ! ( res, r#"{"result":"inline-export"}"# ) ;
249+ }
0 commit comments