package assetloader import ( "encoding/base64" "os" "path/filepath" "strings" "testing" ) func TestProcessEmbedDirectives_TextLiteralEscapesQuotes(t *testing.T) { dir := t.TempDir() sqlPath := filepath.Join(dir, "1_001_seed.sql") if err := os.WriteFile(filepath.Join(dir, "body.txt"), []byte("Line 1\nIt's fine"), 0o644); err != nil { t.Fatal(err) } got, err := ProcessEmbedDirectives(sqlPath, ` -- @embed: path=body.txt var=:body mode=text INSERT INTO notes (body) VALUES (:body); `) if err != nil { t.Fatalf("ProcessEmbedDirectives failed: %v", err) } if !strings.Contains(got, "VALUES ('Line 1\nIt''s fine');") { t.Fatalf("embedded SQL did not contain escaped text literal:\n%s", got) } if strings.Contains(got, "VALUES (:body);") { t.Fatalf("placeholder was not replaced:\n%s", got) } } func TestProcessEmbedDirectives_Base64Literal(t *testing.T) { dir := t.TempDir() sqlPath := filepath.Join(dir, "1_001_seed.sql") binary := []byte{0x00, 0xff, 0x10, 0x20} if err := os.WriteFile(filepath.Join(dir, "blob.bin"), binary, 0o644); err != nil { t.Fatal(err) } got, err := ProcessEmbedDirectives(sqlPath, ` -- @embed: path=blob.bin var=:payload mode=base64 INSERT INTO files (payload) VALUES (decode(:payload, 'base64')::bytea); `) if err != nil { t.Fatalf("ProcessEmbedDirectives failed: %v", err) } want := "decode('" + base64.StdEncoding.EncodeToString(binary) + "', 'base64')::bytea" if !strings.Contains(got, want) { t.Fatalf("embedded SQL did not contain base64 literal %q:\n%s", want, got) } } func TestProcessEmbedDirectives_RelativeToSQLFile(t *testing.T) { root := t.TempDir() sqlDir := filepath.Join(root, "nested", "seed") if err := os.MkdirAll(filepath.Join(sqlDir, "assets"), 0o755); err != nil { t.Fatal(err) } sqlPath := filepath.Join(sqlDir, "1_001_seed.sql") if err := os.WriteFile(filepath.Join(sqlDir, "assets", "body.txt"), []byte("relative body"), 0o644); err != nil { t.Fatal(err) } got, err := ProcessEmbedDirectives(sqlPath, ` -- @embed: path=assets/body.txt var=:body mode=text SELECT :body; `) if err != nil { t.Fatalf("ProcessEmbedDirectives failed: %v", err) } if !strings.Contains(got, "SELECT 'relative body';") { t.Fatalf("path was not resolved relative to SQL file:\n%s", got) } } func TestProcessEmbedDirectives_InvalidDirectiveAndFiles(t *testing.T) { dir := t.TempDir() sqlPath := filepath.Join(dir, "1_001_seed.sql") if err := os.WriteFile(filepath.Join(dir, "body.txt"), []byte("ok"), 0o644); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "binary.txt"), []byte{0xff, 0xfe}, 0o644); err != nil { t.Fatal(err) } tests := []struct { name string sql string }{ { name: "missing mode", sql: "-- @embed: path=body.txt var=:body\nSELECT :body;", }, { name: "invalid var", sql: "-- @embed: path=body.txt var=body mode=text\nSELECT :body;", }, { name: "missing file", sql: "-- @embed: path=missing.txt var=:body mode=text\nSELECT :body;", }, { name: "invalid utf8 text", sql: "-- @embed: path=binary.txt var=:body mode=text\nSELECT :body;", }, { name: "placeholder not found", sql: "-- @embed: path=body.txt var=:body mode=text\nSELECT 1;", }, { name: "unknown attribute", sql: "-- @embed: path=body.txt var=:body mode=text extra=yes\nSELECT :body;", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if _, err := ProcessEmbedDirectives(sqlPath, tt.sql); err == nil { t.Fatal("expected error, got nil") } }) } } func TestProcessEmbedDirectives_DoesNotReplacePlaceholderPrefix(t *testing.T) { dir := t.TempDir() sqlPath := filepath.Join(dir, "1_001_seed.sql") if err := os.WriteFile(filepath.Join(dir, "body.txt"), []byte("ok"), 0o644); err != nil { t.Fatal(err) } got, err := ProcessEmbedDirectives(sqlPath, ` -- @embed: path=body.txt var=:body mode=text SELECT :body, :body_extra; `) if err != nil { t.Fatalf("ProcessEmbedDirectives failed: %v", err) } if !strings.Contains(got, "SELECT 'ok', :body_extra;") { t.Fatalf("placeholder boundary was not respected:\n%s", got) } }