Skip to content

Commit aa2cf0f

Browse files
committed
add TestExecuteFileScript
1 parent 688bd4b commit aa2cf0f

File tree

2 files changed

+302
-2
lines changed

2 files changed

+302
-2
lines changed

internal/pgengine/bootstrap_test.go

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

internal/pgengine/yaml.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,7 @@ func (pge *PgEngine) LoadYamlChains(ctx context.Context, filePath string, replac
7474
func (pge *PgEngine) createChainFromYaml(ctx context.Context, yamlChain *YamlChain) (int64, error) {
7575
// Insert chain
7676
var chainID int64
77-
err := pge.ConfigDb.QueryRow(ctx, `
78-
INSERT INTO timetable.chain (
77+
err := pge.ConfigDb.QueryRow(ctx, `INSERT INTO timetable.chain (
7978
chain_name, run_at, max_instances, timeout, live,
8079
self_destruct, exclusive_execution, client_name, on_error
8180
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)

0 commit comments

Comments
 (0)