diff --git a/.woodpecker/.build.yml b/.woodpecker/.build.yml index e2fe848..860c71f 100644 --- a/.woodpecker/.build.yml +++ b/.woodpecker/.build.yml @@ -2,16 +2,13 @@ when: event: [tag, push] steps: - build: - image: golang:1.23-alpine + - name: build + image: golang:1.24-alpine commands: - apk update - - apk add git - - go install github.com/goreleaser/goreleaser/v2@latest - - echo "$${SENTRY_DSN}" > cmd/api/sentry - - goreleaser build --snapshot - secrets: [sentry_dsn] - + - apk add bash + - bash scripts/build.sh + depends_on: - lint - tests \ No newline at end of file diff --git a/.woodpecker/.lint.yml b/.woodpecker/.lint.yml index 07e0cff..5ff810f 100644 --- a/.woodpecker/.lint.yml +++ b/.woodpecker/.lint.yml @@ -2,10 +2,10 @@ when: event: [tag, push] steps: - lint: - image: golang:1.23-alpine + - name: lint + image: golang:1.24-alpine commands: - apk update - apk add bash curl jq - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.60.1 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.64.5 - $(go env GOPATH)/bin/golangci-lint run --fast diff --git a/.woodpecker/.publish.yml b/.woodpecker/.publish.yml index 5759267..418ac4e 100644 --- a/.woodpecker/.publish.yml +++ b/.woodpecker/.publish.yml @@ -4,6 +4,11 @@ when: steps: - name: publish-docker image: docker:27-cli + environment: + DOCKER_USERNAME: + from_secret: docker_username + DOCKER_PASSWORD: + from_secret: docker_password commands: - tag="tintounn/trepa:$${CI_COMMIT_TAG}" - docker login -u="$${DOCKER_USERNAME}" -p="$${DOCKER_PASSWORD}" @@ -12,13 +17,6 @@ steps: - docker rmi $tag volumes: - /var/run/docker.sock:/var/run/docker.sock - secrets: [docker_username, docker_password] - - - name: publish-binaries - image: goreleaser/goreleaser - commands: - - goreleaser release - secrets: [ gitea_token ] depends_on: - build diff --git a/.woodpecker/.tests.yml b/.woodpecker/.tests.yml index e36c03d..61d8312 100644 --- a/.woodpecker/.tests.yml +++ b/.woodpecker/.tests.yml @@ -2,21 +2,22 @@ when: event: [tag, push] steps: - tests: - image: golang:1.23-alpine + - name: tests + image: golang:1.24-alpine environment: - TEST_DATABASE_URL: postgres://dev:dev@db/trepa?sslmode=disable + TEST_DB_HOST: db + TEST_DB_PORT: 5432 + TEST_DB_USER: dev + TEST_DB_PASSWORD: dev + TEST_DB_NAME: trepa_test commands: - sleep 30 - - wget https://github.com/golang-migrate/migrate/releases/download/v4.17.1/migrate.linux-amd64.tar.gz - - tar -xf migrate.linux-amd64.tar.gz - - ./migrate -source file://migrations/ -database "$${TEST_DATABASE_URL}" up - go test -cover ./internal/... -v services: - name: db image: postgres:16-alpine environment: - - POSTGRES_USER=dev - - POSTGRES_PASSWORD=dev - - POSTGRES_DB=trepa + POSTGRES_USER: dev + POSTGRES_PASSWORD: dev + POSTGRES_DB: trepa_test diff --git a/Dockerfile b/Dockerfile index 9e1465b..027d256 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,12 +12,12 @@ RUN chmod +x scripts/build.sh && \ ./scripts/build.sh -FROM debian:bookworm-slim AS runner +FROM debian:bookworm-slim WORKDIR /app RUN apt update &&\ - apt install bash curl -y &&\ + apt install bash curl wget -y &&\ wget https://github.com/golang-migrate/migrate/releases/download/v4.18.2/migrate.linux-amd64.deb &&\ dpkg -i migrate.linux-amd64.deb &&\ rm migrate.linux-amd64.deb &&\ diff --git a/cmd/api/main.go b/cmd/api/main.go index c29443d..c3545bc 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -22,7 +22,7 @@ var ascii string func main() { fmt.Println(ascii) - core.LoadEnvVars() + core.LoadEnvVars(".env") db := setupDB() router := setupRouter(db) diff --git a/go.mod b/go.mod index 7c5edac..4da147d 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,23 @@ module gitea.qpismont.fr/qpismont/trepa go 1.24.0 require ( + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 + github.com/magiconair/properties v1.8.9 + github.com/stretchr/testify v1.10.0 ) require ( github.com/cockroachdb/apd v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/text v0.22.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 239523b..dee274e 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,14 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= @@ -16,13 +20,23 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/accounts/const.go b/internal/accounts/const.go new file mode 100644 index 0000000..6e417dd --- /dev/null +++ b/internal/accounts/const.go @@ -0,0 +1,6 @@ +package accounts + +const ( + SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id" + SqlFetchOneByUsername = "SELECT * FROM accounts WHERE username = $1" +) diff --git a/internal/accounts/model.go b/internal/accounts/model.go new file mode 100644 index 0000000..fad9d86 --- /dev/null +++ b/internal/accounts/model.go @@ -0,0 +1,10 @@ +package accounts + +type Account struct { + Id int `db:"id" json:"id"` + Username string `db:"username" json:"username"` + Password string `db:"password" json:"-"` + RoleId int `db:"role_id" json:"role_id"` + CreatedAt string `db:"created_at" json:"created_at"` + UpdatedAt string `db:"updated_at" json:"updated_at"` +} diff --git a/internal/accounts/repository.go b/internal/accounts/repository.go index 11adc24..59da0f5 100644 --- a/internal/accounts/repository.go +++ b/internal/accounts/repository.go @@ -1,6 +1,8 @@ package accounts import ( + "database/sql" + "github.com/jmoiron/sqlx" ) @@ -11,3 +13,36 @@ type Repository struct { func NewRepository(db *sqlx.DB) Repository { return Repository{db: db} } + +func (r *Repository) Insert(account *Account) (int, error) { + var id int + + stmt, err := r.db.Prepare(SqlInsert) + if err != nil { + return id, err + } + + defer stmt.Close() + + err = stmt.QueryRow(account.Username, account.Password, account.RoleId).Scan(&id) + if err != nil { + return id, err + } + + return id, nil +} + +func (r *Repository) FetchOneByUsername(username string) (*Account, error) { + var account Account + + err := r.db.Get(&account, SqlFetchOneByUsername, username) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, err + } + } + + return &account, nil +} diff --git a/internal/accounts/repository_test.go b/internal/accounts/repository_test.go new file mode 100644 index 0000000..552f6b1 --- /dev/null +++ b/internal/accounts/repository_test.go @@ -0,0 +1,48 @@ +package accounts + +import ( + "testing" + + "gitea.qpismont.fr/qpismont/trepa/test" + "github.com/magiconair/properties/assert" +) + +func TestRepository_Insert(t *testing.T) { + db := test.SetupTestDB(t, "../..") + defer db.Close() + + repo := NewRepository(db) + + account := &Account{ + Username: "test", + Password: "test", + RoleId: 1, + } + + id, err := repo.Insert(account) + if err != nil { + t.Fatalf("Failed to insert account: %v", err) + } + + assert.Equal(t, id, 3) +} + +func TestRepository_FetchOneByUsername(t *testing.T) { + db := test.SetupTestDB(t, "../..") + defer db.Close() + + repo := NewRepository(db) + + account, err := repo.FetchOneByUsername("admin") + if err != nil { + t.Fatalf("Failed to fetch account: %v", err) + } + + if account == nil { + t.Fatalf("Account not found") + } + + assert.Equal(t, account.Username, "admin") + assert.Equal(t, account.Password, "LOLPASSWORD") + assert.Equal(t, account.RoleId, 1) +} diff --git a/internal/core/database_test.go b/internal/core/database_test.go new file mode 100644 index 0000000..90dfb17 --- /dev/null +++ b/internal/core/database_test.go @@ -0,0 +1,39 @@ +package core + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestComputeDBURL(t *testing.T) { + LoadEnvVars("../../.env") + + dbHost := MustGetEnvVar("TEST_DB_HOST") + dbPort := MustGetEnvVar("TEST_DB_PORT") + dbUser := MustGetEnvVar("TEST_DB_USER") + dbPassword := MustGetEnvVar("TEST_DB_PASSWORD") + dbName := MustGetEnvVar("TEST_DB_NAME") + + dbURL := ComputeDBURL(dbHost, dbPort, dbUser, dbPassword, dbName) + + assert.Equal(t, dbURL, fmt.Sprintf("postgres://%s:%s@%s:%s/%s", dbUser, dbPassword, dbHost, dbPort, dbName)) +} + +func TestSetupDB(t *testing.T) { + LoadEnvVars("../../.env") + + dbHost := MustGetEnvVar("TEST_DB_HOST") + dbPort := MustGetEnvVar("TEST_DB_PORT") + dbUser := MustGetEnvVar("TEST_DB_USER") + dbPassword := MustGetEnvVar("TEST_DB_PASSWORD") + dbName := MustGetEnvVar("TEST_DB_NAME") + + dbURL := ComputeDBURL(dbHost, dbPort, dbUser, dbPassword, dbName) + db, err := SetupDB(dbURL) + defer db.Close() + + assert.NoError(t, err) + assert.NotNil(t, db) +} diff --git a/internal/core/errors.go b/internal/core/errors.go index 3801bb0..c0ca822 100644 --- a/internal/core/errors.go +++ b/internal/core/errors.go @@ -4,6 +4,10 @@ import ( "net/http" ) +var ( + ErrInvalidToken = NewHTTPError(http.StatusUnauthorized, "Invalid token", nil) +) + type HTTPError struct { Code int `json:"code"` Message string `json:"message"` diff --git a/internal/core/errors_test.go b/internal/core/errors_test.go new file mode 100644 index 0000000..76ee86a --- /dev/null +++ b/internal/core/errors_test.go @@ -0,0 +1,23 @@ +package core + +import ( + "errors" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewError(t *testing.T) { + err := NewHTTPError(http.StatusBadRequest, "MyError", errors.New("test")) + assert.Equal(t, http.StatusBadRequest, err.Code) + assert.Equal(t, "MyError", err.Error()) + assert.Equal(t, errors.New("test"), err.Unwrap()) +} + +func TestNewInternalServerError(t *testing.T) { + err := NewInternalServerError(errors.New("test")) + assert.Equal(t, http.StatusInternalServerError, err.Code) + assert.Equal(t, "Internal server error", err.Error()) + assert.Equal(t, errors.New("test"), err.Unwrap()) +} diff --git a/internal/core/helpers.go b/internal/core/helpers.go index d09a742..f44fb17 100644 --- a/internal/core/helpers.go +++ b/internal/core/helpers.go @@ -7,10 +7,10 @@ import ( "github.com/joho/godotenv" ) -func LoadEnvVars() { - err := godotenv.Load() +func LoadEnvVars(path string) { + err := godotenv.Load(path) if err != nil { - slog.Warn("Error loading .env file") + slog.Warn("Error loading .env file", "path", path) } } diff --git a/internal/core/helpers_test.go b/internal/core/helpers_test.go new file mode 100644 index 0000000..2d6d8c0 --- /dev/null +++ b/internal/core/helpers_test.go @@ -0,0 +1,15 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMustGetEnvVar(t *testing.T) { + LoadEnvVars("../../.env") + + assert.NotPanics(t, func() { + MustGetEnvVar("TEST_DB_HOST") + }) +} diff --git a/internal/core/http.go b/internal/core/http.go index c3447a3..23fb068 100644 --- a/internal/core/http.go +++ b/internal/core/http.go @@ -39,6 +39,7 @@ func (r *Response) Write(b []byte) (int, error) { return n, err } +// Implement http.Handler type ServerMux struct { mux *http.ServeMux } diff --git a/internal/core/jwt.go b/internal/core/jwt.go new file mode 100644 index 0000000..4515334 --- /dev/null +++ b/internal/core/jwt.go @@ -0,0 +1,40 @@ +package core + +import ( + "github.com/golang-jwt/jwt/v5" +) + +type JWTClaims struct { + jwt.RegisteredClaims + AccountId int `json:"account_id"` + RoleId int `json:"role_id"` +} + +var jwtSecret string + +func InitJWT(secret string) { + jwtSecret = secret +} + +func SignJWT(claims JWTClaims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + + return token.SignedString([]byte(jwtSecret)) +} + +func VerifyJWT(token string) (JWTClaims, error) { + parsedClaims := JWTClaims{} + claims, err := jwt.ParseWithClaims(token, &parsedClaims, func(token *jwt.Token) (any, error) { + return []byte(jwtSecret), nil + }) + + if err != nil { + return JWTClaims{}, NewInternalServerError(err) + } + + if !claims.Valid { + return JWTClaims{}, ErrInvalidToken + } + + return parsedClaims, nil +} diff --git a/internal/core/jwt_test.go b/internal/core/jwt_test.go new file mode 100644 index 0000000..d9dce0f --- /dev/null +++ b/internal/core/jwt_test.go @@ -0,0 +1,29 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestJWT_GenerateToken(t *testing.T) { + InitJWT("secret") + + token, err := SignJWT(JWTClaims{ + AccountId: 1, + RoleId: 1, + }) + if err != nil { + t.Fatalf("Failed to generate token: %v", err) + } + + assert.NotEmpty(t, token) + + claims, err := VerifyJWT(token) + if err != nil { + t.Fatalf("Failed to verify token: %v", err) + } + + assert.Equal(t, claims.AccountId, 1) + assert.Equal(t, claims.RoleId, 1) +} diff --git a/test/fixtures/00-accounts.sql b/test/fixtures/00-accounts.sql new file mode 100644 index 0000000..bc38a90 --- /dev/null +++ b/test/fixtures/00-accounts.sql @@ -0,0 +1,2 @@ +INSERT INTO accounts (username, password, role_id) VALUES ('admin', 'LOLPASSWORD', 1); +INSERT INTO accounts (username, password, role_id) VALUES ('user', 'LOLPASSWORD', 2); diff --git a/test/helpers.go b/test/helpers.go new file mode 100644 index 0000000..7ac8be0 --- /dev/null +++ b/test/helpers.go @@ -0,0 +1,84 @@ +package test + +import ( + "os" + "path/filepath" + "testing" + + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "github.com/jmoiron/sqlx" +) + +func SetupTestDB(test *testing.T, rootPath string) *sqlx.DB { + core.LoadEnvVars(rootPath + "/.env") + + dbHost := core.MustGetEnvVar("TEST_DB_HOST") + dbPort := core.MustGetEnvVar("TEST_DB_PORT") + dbUser := core.MustGetEnvVar("TEST_DB_USER") + dbPassword := core.MustGetEnvVar("TEST_DB_PASSWORD") + dbName := core.MustGetEnvVar("TEST_DB_NAME") + + dbExecute := initTestDB(test, dbHost, dbPort, dbUser, dbPassword, "postgres") + resetTestDB(dbExecute, test, dbName) + dbExecute.Close() + + dbTest := initTestDB(test, dbHost, dbPort, dbUser, dbPassword, dbName) + executeMigrations(dbTest, test, rootPath) + executeFixtures(dbTest, test, rootPath) + + return dbTest +} + +func initTestDB(t *testing.T, dbHost, dbPort, dbUser, dbPassword, dbName string) *sqlx.DB { + dbURL := core.ComputeDBURL(dbHost, dbPort, dbUser, dbPassword, dbName) + db, err := core.SetupDB(dbURL) + if err != nil { + t.Fatalf("Failed to connect to test database: %v", err) + } + + return db +} + +func resetTestDB(db *sqlx.DB, t *testing.T, dbName string) { + _, err := db.Exec("DROP DATABASE IF EXISTS " + dbName + " WITH (FORCE);") + if err != nil { + t.Fatalf("Failed to drop test database: %v", err) + } + + _, err = db.Exec("CREATE DATABASE " + dbName + ";") + if err != nil { + t.Fatalf("Failed to create test database: %v", err) + } +} +func executeMigrations(db *sqlx.DB, t *testing.T, rootPath string) { + rootPath = filepath.Join(rootPath, "migrations") + + executeSqlFolder(db, t, rootPath) +} + +func executeFixtures(db *sqlx.DB, t *testing.T, rootPath string) { + rootPath = filepath.Join(rootPath, "test", "fixtures") + + executeSqlFolder(db, t, rootPath) +} + +func executeSqlFolder(db *sqlx.DB, t *testing.T, folder string) { + files, err := filepath.Glob(folder + "/*.sql") + if err != nil { + t.Fatalf("Failed to read sql folder: %v", err) + } + + for _, file := range files { + t.Log("Executing " + file) + + sql, err := os.ReadFile(file) + if err != nil { + t.Fatalf("Failed to read sql file: %v", err) + } + + _, err = db.Exec(string(sql)) + if err != nil { + t.Fatalf("Failed to execute sql file: %v", err) + } + } +}