@@ -6,6 +6,15 @@ defmodule ElixirScript.ScriptRunnerTest do
66 alias ElixirScript.ScriptRunner
77 alias Test.Fixtures.GitHubWorkflowRun
88
9+ # Create a temporary file with automatic cleanup
10+ # Returns the full path to the created file
11+ defp create_temp_file ( filename , content ) do
12+ path = Path . join ( System . tmp_dir ( ) , filename )
13+ File . write! ( path , content )
14+ on_exit ( fn -> File . rm! ( path ) end )
15+ path
16+ end
17+
918 setup do
1019 stub ( SystemEnvMock , :get_env , fn varname , default ->
1120 GitHubWorkflowRun . env ( ) [ varname ] || default
@@ -34,6 +43,116 @@ defmodule ElixirScript.ScriptRunnerTest do
3443 end
3544 end
3645
46+ describe "file-based scripts" do
47+ test "can execute scripts from file paths" do
48+ file_path = create_temp_file ( "test_script.exs" , "\" Hello from test file\" " )
49+ assert ScriptRunner . run ( file_path ) == "Hello from test file"
50+ end
51+
52+ test "file scripts have access to context bindings" do
53+ file_path = create_temp_file ( "context_test.exs" , ~S[ "Actor is: #{context.actor}"] )
54+ assert ScriptRunner . run ( file_path ) == "Actor is: gaggle"
55+ end
56+
57+ test "file scripts have access to client bindings" do
58+ file_path = create_temp_file ( "client_test.exs" , "inspect(client)" )
59+ result = ScriptRunner . run ( file_path )
60+ assert result =~ "auth: nil"
61+ end
62+
63+ test "auto-detects file paths vs inline scripts" do
64+ # Test detection by behavior - file paths cause File.Error
65+ assert_raise File.Error , fn ->
66+ ScriptRunner . run ( "./non_existent.exs" )
67+ end
68+
69+ assert_raise File.Error , fn ->
70+ ScriptRunner . run ( "../non_existent.exs" )
71+ end
72+
73+ assert_raise File.Error , fn ->
74+ ScriptRunner . run ( "/non_existent.exs" )
75+ end
76+
77+ # Inline scripts should be evaluated as code
78+ assert ScriptRunner . run ( "1 + 1" ) == 2
79+ assert ScriptRunner . run ( "\" hello\" " ) == "hello"
80+ end
81+
82+ test "raises clear error when file doesn't exist" do
83+ assert_raise File.Error , ~r/ no such file or directory/ , fn ->
84+ ScriptRunner . run ( "./non_existent_file.exs" )
85+ end
86+ end
87+
88+ test "handles empty file gracefully" do
89+ file_path = create_temp_file ( "empty.exs" , "" )
90+ # Elixir naturally returns nil for empty files
91+ assert ScriptRunner . run ( file_path ) == nil
92+ end
93+
94+ test "handles files returning nil" do
95+ file_path = create_temp_file ( "nil_return.exs" , "IO.puts(\" side effect\" )\n nil" )
96+ assert ScriptRunner . run ( file_path ) == nil
97+ end
98+
99+ test "works with any file extension containing valid Elixir code" do
100+ # .ex files work
101+ ex_file = create_temp_file ( "test.ex" , "\" from .ex\" " )
102+ assert ScriptRunner . run ( ex_file ) == "from .ex"
103+
104+ # .txt files work if they contain Elixir
105+ txt_file = create_temp_file ( "test.txt" , "\" from .txt\" " )
106+ assert ScriptRunner . run ( txt_file ) == "from .txt"
107+
108+ # No extension works too
109+ no_ext = create_temp_file ( "testfile" , "\" no extension\" " )
110+ assert ScriptRunner . run ( no_ext ) == "no extension"
111+ end
112+
113+ test "gives helpful error for non-Elixir content" do
114+ file_path = create_temp_file ( "not_elixir.txt" , "This is just plain text, not Elixir code" )
115+ # Should get a syntax error with the filename in the message
116+ exception =
117+ assert_raise SyntaxError , fn ->
118+ ScriptRunner . run ( file_path )
119+ end
120+
121+ # The error includes the file path, helping users identify the problem
122+ assert exception . file =~ "not_elixir.txt"
123+ end
124+
125+ test "preserves proper file semantics like __DIR__ and __ENV__" do
126+ file_path = create_temp_file ( "file_semantics.exs" , "{__DIR__, __ENV__.file}" )
127+ { dir , file } = ScriptRunner . run ( file_path )
128+
129+ # __DIR__ should be the directory containing the file
130+ assert dir == Path . dirname ( Path . expand ( file_path ) )
131+
132+ # __ENV__.file should be the absolute path to the file
133+ assert file == Path . expand ( file_path )
134+ end
135+
136+ test "supports relative requires within files" do
137+ dir = Path . join ( System . tmp_dir ( ) , "require_test_#{ :rand . uniform ( 1000 ) } " )
138+ File . mkdir! ( dir )
139+
140+ helper_path = Path . join ( dir , "my_helper.exs" )
141+ File . write! ( helper_path , "defmodule MyHelper do\n def value, do: :from_helper\n end" )
142+
143+ main_path = Path . join ( dir , "main.exs" )
144+ File . write! ( main_path , "Code.require_file(\" ./my_helper.exs\" , __DIR__)\n MyHelper.value()" )
145+
146+ on_exit ( fn ->
147+ File . rm! ( main_path )
148+ File . rm! ( helper_path )
149+ File . rmdir! ( dir )
150+ end )
151+
152+ assert ScriptRunner . run ( main_path ) == :from_helper
153+ end
154+ end
155+
37156 describe "github access" do
38157 test "executes with an un-authenticated Tentacat GitHub client by default" do
39158 expect ( TentacatMock.ClientMock , :new , fn -> % { auth: nil , endpoint: "github" } end )
0 commit comments