package test import ( "database/sql" "fmt" "os" "pink_fox/inner/config" "pink_fox/packages/fw" "regexp" "strings" ) var ( dbTest *DBTest = nil ) type DBTest struct { db *sql.DB seedersPath string } func NewDBTest() *DBTest { if dbTest == nil { path := "/app/config-test.yml" dbConf, err := config.LoadTestConfig(path) if err != nil { panic(err.Add("ошибка загрузки конфига")) } db, err := fw.CreateConnection(dbConf.Host, dbConf.Port, dbConf.User, dbConf.Password, dbConf.Database) if err != nil { panic(err.Add("ошибка соединение с базой данной")) } err = refreshDatabase(db, dbConf.Migrations, dbConf.Database, true) if err != nil { panic(err.Add("ошибка обновление базы данных")) } dbTest = &DBTest{ db: db, seedersPath: "/app/database/seeders", } } return dbTest } func (it *DBTest) GetDB() *sql.DB { return it.db } func (it *DBTest) Seed(filename string) *DBTest { path := it.seedersPath + "/" + filename queryByte, err := os.ReadFile(path) if err != nil { panic(fw.ErrStr(fmt.Sprintf("ошибка загрузки файла для %s: %s", path, err))) } tx, err := it.db.Begin() if err != nil { panic(fw.ErrStr(fmt.Sprintf("ошибка запуска транзакции: %s", err))) } defer func() { _ = tx.Rollback() }() cleanedData := it.removeSQLComments(string(queryByte)) queries := it.splitSQLQueries(cleanedData) for _, query := range queries { query = strings.TrimSpace(query) if query == "" { continue } if _, err = tx.Exec(query); err != nil { panic(fmt.Errorf("ошибка в запросе `%s`: %s", query, err)) } } err = tx.Commit() if err != nil { panic(fmt.Sprintf("ошибка подтверждения транзакции: %s", err)) } return it } func refreshDatabase(db *sql.DB, migrationsPath, dbName string, disableLogger bool) fw.Error { err := fw.DropAppTable(db, dbName) if err != nil { return err.Tap() } return fw.MigrateUp(db, migrationsPath, disableLogger) } func (it *DBTest) removeSQLComments(sql string) string { // Удаляем однострочные комментарии (-- до конца строки) reSingleLine := regexp.MustCompile(`--.*`) sql = reSingleLine.ReplaceAllString(sql, "") // Удаляем многострочные комментарии (/* ... */) reMultiLine := regexp.MustCompile(`/\*.*?\*/`) sql = reMultiLine.ReplaceAllString(sql, "") return sql } // splitSQLQueries разбивает SQL-код на отдельные запросы func (it *DBTest) splitSQLQueries(sql string) []string { var queries []string var buffer strings.Builder inString := false quoteChar := byte(0) for i := 0; i < len(sql); i++ { char := sql[i] switch { case char == '\'' || char == '"': if !inString { inString = true quoteChar = char } else if char == quoteChar { // Проверяем, не экранирована ли кавычка if i > 0 && sql[i-1] == '\\' { continue } inString = false quoteChar = 0 } buffer.WriteByte(char) case char == ';' && !inString: queries = append(queries, buffer.String()) buffer.Reset() default: buffer.WriteByte(char) } } // Добавляем последний запрос (если есть) if buffer.Len() > 0 { queries = append(queries, buffer.String()) } return queries }