@@ -4,10 +4,13 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "os"
8+ "path/filepath"
79 "reflect"
810 "testing"
911 "time"
1012
13+ "github.com/cybertec-postgresql/pg_timetable/internal/config"
1114 "github.com/cybertec-postgresql/pg_timetable/internal/pgengine"
1215 pgx "github.com/jackc/pgx/v5"
1316 "github.com/pashagolub/pgxmock/v4"
@@ -142,3 +145,301 @@ func TestTryLockClientName(t *testing.T) {
142145 assert .NoError (t , pge .TryLockClientName (context .Background (), m ))
143146 })
144147}
148+
149+ func TestExecuteFileScript (t * testing.T ) {
150+ initmockdb (t )
151+ defer mockPool .Close ()
152+ mockpge := pgengine .NewDB (mockPool , "pgengine_unit_test" )
153+
154+ // Create temporary directory for test files
155+ tmpDir := t .TempDir ()
156+
157+ t .Run ("SQL file execution" , func (t * testing.T ) {
158+ // Create temporary SQL file
159+ sqlFile := filepath .Join (tmpDir , "test.sql" )
160+ err := os .WriteFile (sqlFile , []byte ("SELECT 1;" ), 0644 )
161+ assert .NoError (t , err )
162+
163+ // Mock the SQL execution
164+ mockPool .ExpectExec ("SELECT 1;" ).WillReturnResult (pgxmock .NewResult ("SELECT" , 1 ))
165+
166+ cmdOpts := config.CmdOptions {}
167+ cmdOpts .Start .File = sqlFile
168+
169+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
170+ assert .NoError (t , err )
171+ })
172+
173+ t .Run ("SQL file execution error" , func (t * testing.T ) {
174+ // Create temporary SQL file
175+ sqlFile := filepath .Join (tmpDir , "test_error.sql" )
176+ err := os .WriteFile (sqlFile , []byte ("SELECT 1;" ), 0644 )
177+ assert .NoError (t , err )
178+
179+ // Mock the SQL execution with error
180+ mockPool .ExpectExec ("SELECT 1;" ).WillReturnError (errors .New ("SQL execution failed" ))
181+
182+ cmdOpts := config.CmdOptions {}
183+ cmdOpts .Start .File = sqlFile
184+
185+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
186+ assert .Error (t , err )
187+ })
188+
189+ t .Run ("YAML file validation mode - valid file" , func (t * testing.T ) {
190+ // Create temporary YAML file
191+ yamlFile := filepath .Join (tmpDir , "test.yaml" )
192+ yamlContent := `chains:
193+ - name: test_chain
194+ tasks:
195+ - name: test_task
196+ command: SELECT 1`
197+ err := os .WriteFile (yamlFile , []byte (yamlContent ), 0644 )
198+ assert .NoError (t , err )
199+
200+ cmdOpts := config.CmdOptions {}
201+ cmdOpts .Start .File = yamlFile
202+ cmdOpts .Start .Validate = true
203+
204+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
205+ assert .NoError (t , err )
206+ })
207+
208+ t .Run ("YAML file validation mode - invalid file" , func (t * testing.T ) {
209+ // Create temporary YAML file with invalid content
210+ yamlFile := filepath .Join (tmpDir , "invalid.yaml" )
211+ yamlContent := `chains:
212+ - name: test_chain
213+ invalid_field: value
214+ - malformed yaml`
215+ err := os .WriteFile (yamlFile , []byte (yamlContent ), 0644 )
216+ assert .NoError (t , err )
217+
218+ cmdOpts := config.CmdOptions {}
219+ cmdOpts .Start .File = yamlFile
220+ cmdOpts .Start .Validate = true
221+
222+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
223+ // Expect error due to invalid YAML structure
224+ assert .Error (t , err )
225+ })
226+
227+ t .Run ("YAML file import mode" , func (t * testing.T ) {
228+ // Create temporary YAML file
229+ yamlFile := filepath .Join (tmpDir , "test_import.yaml" )
230+ yamlContent := `chains:
231+ - name: test_chain
232+ tasks:
233+ - name: test_task
234+ command: SELECT 1`
235+ err := os .WriteFile (yamlFile , []byte (yamlContent ), 0644 )
236+ assert .NoError (t , err )
237+
238+ cmdOpts := config.CmdOptions {}
239+ cmdOpts .Start .File = yamlFile
240+ cmdOpts .Start .Validate = false
241+ cmdOpts .Start .Replace = false
242+
243+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
244+ assert .NoError (t , err )
245+ })
246+
247+ t .Run ("YML file extension" , func (t * testing.T ) {
248+ // Create temporary YML file
249+ ymlFile := filepath .Join (tmpDir , "test.yml" )
250+ yamlContent := `chains:
251+ - name: test_chain
252+ tasks:
253+ - name: test_task
254+ command: SELECT 1`
255+ err := os .WriteFile (ymlFile , []byte (yamlContent ), 0644 )
256+ assert .NoError (t , err )
257+
258+ cmdOpts := config.CmdOptions {}
259+ cmdOpts .Start .File = ymlFile
260+ cmdOpts .Start .Validate = true
261+
262+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
263+ assert .NoError (t , err )
264+ })
265+
266+ t .Run ("File without extension - YAML content" , func (t * testing.T ) {
267+ // Create file without extension with YAML content
268+ noExtFile := filepath .Join (tmpDir , "test_no_ext" )
269+ yamlContent := `chains:
270+ - name: test_chain
271+ tasks:
272+ - name: test_task
273+ command: SELECT 1`
274+ err := os .WriteFile (noExtFile , []byte (yamlContent ), 0644 )
275+ assert .NoError (t , err )
276+
277+ cmdOpts := config.CmdOptions {}
278+ cmdOpts .Start .File = noExtFile
279+
280+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
281+ assert .NoError (t , err )
282+ })
283+
284+ t .Run ("File without extension - SQL content" , func (t * testing.T ) {
285+ // Create file without extension with SQL content
286+ noExtFile := filepath .Join (tmpDir , "test_no_ext_sql" )
287+ sqlContent := "SELECT 1;"
288+ err := os .WriteFile (noExtFile , []byte (sqlContent ), 0644 )
289+ assert .NoError (t , err )
290+
291+ // Mock the SQL execution
292+ mockPool .ExpectExec ("SELECT 1;" ).WillReturnResult (pgxmock .NewResult ("SELECT" , 1 ))
293+
294+ cmdOpts := config.CmdOptions {}
295+ cmdOpts .Start .File = noExtFile
296+
297+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
298+ assert .NoError (t , err )
299+ })
300+
301+ t .Run ("File not found error - SQL file" , func (t * testing.T ) {
302+ cmdOpts := config.CmdOptions {}
303+ cmdOpts .Start .File = "/nonexistent/file.sql"
304+
305+ err := mockpge .ExecuteFileScript (context .Background (), cmdOpts )
306+ assert .Error (t , err )
307+ })
308+
309+ t .Run ("File not found error - unknown extension" , func (t * testing.T ) {
310+ cmdOpts := config.CmdOptions {}
311+ cmdOpts .Start .File = "/nonexistent/file.txt"
312+
313+ err := mockpge .ExecuteFileScript (context .Background (), cmdOpts )
314+ assert .Error (t , err )
315+ })
316+
317+ t .Run ("Unknown file extension defaults to content detection" , func (t * testing.T ) {
318+ // Create file with unknown extension containing SQL
319+ unknownFile := filepath .Join (tmpDir , "test.unknown" )
320+ sqlContent := "SELECT 2;"
321+ err := os .WriteFile (unknownFile , []byte (sqlContent ), 0644 )
322+ assert .NoError (t , err )
323+
324+ // Mock the SQL execution
325+ mockPool .ExpectExec ("SELECT 2;" ).WillReturnResult (pgxmock .NewResult ("SELECT" , 1 ))
326+
327+ cmdOpts := config.CmdOptions {}
328+ cmdOpts .Start .File = unknownFile
329+
330+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
331+ assert .NoError (t , err )
332+ })
333+
334+ t .Run ("Empty file" , func (t * testing.T ) {
335+ // Create empty file
336+ emptyFile := filepath .Join (tmpDir , "empty.txt" )
337+ err := os .WriteFile (emptyFile , []byte ("" ), 0644 )
338+ assert .NoError (t , err )
339+
340+ // Mock empty SQL execution
341+ mockPool .ExpectExec ("" ).WillReturnResult (pgxmock .NewResult ("SELECT" , 0 ))
342+
343+ cmdOpts := config.CmdOptions {}
344+ cmdOpts .Start .File = emptyFile
345+
346+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
347+ assert .NoError (t , err )
348+ })
349+
350+ t .Run ("YAML file with whitespace prefix" , func (t * testing.T ) {
351+ // Create file with leading whitespace before chains:
352+ whitespaceFile := filepath .Join (tmpDir , "whitespace" )
353+ yamlContent := `
354+
355+ chains:
356+ - name: test_chain`
357+ err := os .WriteFile (whitespaceFile , []byte (yamlContent ), 0644 )
358+ assert .NoError (t , err )
359+
360+ cmdOpts := config.CmdOptions {}
361+ cmdOpts .Start .File = whitespaceFile
362+
363+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
364+ assert .NoError (t , err )
365+ })
366+
367+ t .Run ("YAML import with replace flag" , func (t * testing.T ) {
368+ // Create temporary YAML file
369+ yamlFile := filepath .Join (tmpDir , "test_replace.yaml" )
370+ yamlContent := `chains:
371+ - name: test_chain_replace
372+ tasks:
373+ - name: test_task
374+ command: SELECT 1`
375+ err := os .WriteFile (yamlFile , []byte (yamlContent ), 0644 )
376+ assert .NoError (t , err )
377+
378+ cmdOpts := config.CmdOptions {}
379+ cmdOpts .Start .File = yamlFile
380+ cmdOpts .Start .Validate = false
381+ cmdOpts .Start .Replace = true
382+
383+ anyArgs := func (i int ) []any {
384+ args := make ([]any , i )
385+ for j := range i {
386+ args [j ] = pgxmock .AnyArg ()
387+ }
388+ return args
389+ }
390+
391+ mockPool .ExpectExec ("SELECT timetable\\ .delete_job" ).
392+ WithArgs ("test_chain_replace" ).
393+ WillReturnResult (pgxmock .NewResult ("DELETE" , 1 ))
394+ mockPool .ExpectQuery ("SELECT EXISTS" ).
395+ WithArgs ("test_chain_replace" ).
396+ WillReturnRows (pgxmock .NewRows ([]string {"exists" }).AddRow (false ))
397+ mockPool .ExpectQuery ("INSERT INTO timetable\\ .chain" ).
398+ WithArgs (anyArgs (9 )... ).
399+ WillReturnRows (pgxmock .NewRows ([]string {"chain_id" }).AddRow (1 ))
400+ mockPool .ExpectQuery ("INSERT INTO timetable\\ .task" ).
401+ WithArgs (anyArgs (10 )... ).
402+ WillReturnRows (pgxmock .NewRows ([]string {"task_id" }).AddRow (1 ))
403+
404+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
405+ assert .NoError (t , err )
406+ })
407+
408+ t .Run ("SQL file with multiple statements" , func (t * testing.T ) {
409+ // Create temporary SQL file with multiple statements
410+ sqlFile := filepath .Join (tmpDir , "multi.sql" )
411+ sqlContent := `SELECT 1;
412+ SELECT 2;
413+ INSERT INTO test VALUES (1);`
414+ err := os .WriteFile (sqlFile , []byte (sqlContent ), 0644 )
415+ assert .NoError (t , err )
416+
417+ // Mock the SQL execution - use regex pattern to match the content
418+ mockPool .ExpectExec (`SELECT 1;.*SELECT 2;.*INSERT INTO test VALUES \(1\);` ).WillReturnResult (pgxmock .NewResult ("SELECT" , 1 ))
419+
420+ cmdOpts := config.CmdOptions {}
421+ cmdOpts .Start .File = sqlFile
422+
423+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
424+ assert .NoError (t , err )
425+ })
426+
427+ t .Run ("Content detection with mixed content" , func (t * testing.T ) {
428+ // Create file with content that doesn't start with "chains:"
429+ mixedFile := filepath .Join (tmpDir , "mixed_content" )
430+ mixedContent := `# This is a comment
431+ # chains: this is just a comment, not actual YAML
432+ SELECT 1;`
433+ err := os .WriteFile (mixedFile , []byte (mixedContent ), 0644 )
434+ assert .NoError (t , err )
435+
436+ // Mock the SQL execution since it doesn't start with "chains:" - use regex pattern
437+ mockPool .ExpectExec (`# This is a comment.*# chains:.*SELECT 1;` ).WillReturnResult (pgxmock .NewResult ("SELECT" , 1 ))
438+
439+ cmdOpts := config.CmdOptions {}
440+ cmdOpts .Start .File = mixedFile
441+
442+ err = mockpge .ExecuteFileScript (context .Background (), cmdOpts )
443+ assert .NoError (t , err )
444+ })
445+ }
0 commit comments