fix: fist integration test
All checks were successful
Build Docker Image / Explore-Gitea-Actions (push) Successful in 51s

This commit is contained in:
2024-10-02 23:13:31 +02:00
parent 33380e2124
commit 0ebe02619a
9 changed files with 181 additions and 46 deletions

136
auth_test.go Normal file
View File

@@ -0,0 +1,136 @@
package main
import (
"me-fit/service"
"me-fit/utils"
"context"
"database/sql"
"fmt"
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/google/uuid"
)
func TestHandleSignIn(t *testing.T) {
t.Parallel()
t.Run("should signIn and return session cookie", func(t *testing.T) {
t.Parallel()
ctx, done := context.WithCancel(context.Background())
t.Cleanup(done)
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("Could not open Database data.db: %v", err)
}
t.Cleanup(func() {
db.Close()
})
err = utils.RunMigrations(db, "")
if err != nil {
t.Fatalf("Could not run migrations: %v", err)
}
pass := service.GetHashPassword("password", []byte("salt"))
_, err = db.Exec(`
INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at)
VALUES (?, "mail@mail.de", FALSE, FALSE, ?, ?, datetime())`, uuid.New(), pass, []byte("salt"))
if err != nil {
t.Fatalf("Error inserting user: %v", err)
}
go run(ctx, db, func(key string) string {
if key == "PORT" {
return "8080"
} else if key == "SMTP_ENABLED" {
return "false"
} else if key == "PROMETHEUS_ENABLED" {
return "false"
} else if key == "BASE_URL" {
return "https://localhost:8080"
} else if key == "ENVIRONMENT" {
return "test"
} else {
return ""
}
})
err = waitForReady(ctx, 5*time.Second, "http://localhost:8080")
if err != nil {
t.Fatalf("Failed to start server: %v", err)
}
formData := url.Values{
"email": {"mail@mail.de"},
"password": {"password"},
}
req, err := http.NewRequestWithContext(ctx, "POST", "http://localhost:8080/api/auth/signin", strings.NewReader(formData.Encode()))
if err != nil {
t.Fatalf("Error creating request: %v", err)
}
// Set the content type to application/x-www-form-urlencoded
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
t.Fatalf("Error making request: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("Expected status code 200, got %d", resp.StatusCode)
}
})
}
// waitForReady calls the specified endpoint until it gets a 200
// response or until the context is cancelled or the timeout is
// reached.
func waitForReady(
ctx context.Context,
timeout time.Duration,
endpoint string,
) error {
client := http.Client{}
startTime := time.Now()
for {
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
endpoint,
nil,
)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Error making request: %s\n", err.Error())
continue
}
if resp.StatusCode == http.StatusOK {
fmt.Println("Endpoint is ready!")
resp.Body.Close()
return nil
}
resp.Body.Close()
select {
case <-ctx.Done():
return ctx.Err()
default:
if time.Since(startTime) >= timeout {
return fmt.Errorf("timeout reached while waiting for endpoint")
}
// wait a little while between checks
time.Sleep(250 * time.Millisecond)
}
}
}

View File

@@ -18,14 +18,14 @@ type User struct {
Id uuid.UUID Id uuid.UUID
Email string Email string
EmailVerified bool EmailVerified bool
EmailVerifiedAt time.Time EmailVerifiedAt *time.Time
IsAdmin bool IsAdmin bool
Password []byte Password []byte
Salt []byte Salt []byte
CreateAt time.Time CreateAt time.Time
} }
func NewUser(id uuid.UUID, email string, emailVerified bool, emailVerifiedAt time.Time, isAdmin bool, password []byte, salt []byte, createAt time.Time) *User { func NewUser(id uuid.UUID, email string, emailVerified bool, emailVerifiedAt *time.Time, isAdmin bool, password []byte, salt []byte, createAt time.Time) *User {
return &User{ return &User{
Id: id, Id: id,
Email: email, Email: email,
@@ -54,7 +54,7 @@ func (db DbAuthSqlite) GetUser(email string) (*User, error) {
var ( var (
userId uuid.UUID userId uuid.UUID
emailVerified bool emailVerified bool
emailVerifiedAt time.Time emailVerifiedAt *time.Time
isAdmin bool isAdmin bool
password []byte password []byte
salt []byte salt []byte

View File

@@ -16,7 +16,10 @@ func setupDb(t *testing.T) *sql.DB {
t.Fatalf("Error opening database: %v", err) t.Fatalf("Error opening database: %v", err)
} }
utils.MustRunMigrations(db, "../") err = utils.RunMigrations(db, "../")
if err != nil {
t.Fatalf("Error running migrations: %v", err)
}
return db return db
} }
@@ -46,7 +49,7 @@ func TestGetUser(t *testing.T) {
verifiedAt := time.Date(2020, 1, 5, 13, 0, 0, 0, time.UTC) verifiedAt := time.Date(2020, 1, 5, 13, 0, 0, 0, time.UTC)
createAt := time.Date(2020, 1, 5, 12, 0, 0, 0, time.UTC) createAt := time.Date(2020, 1, 5, 12, 0, 0, 0, time.UTC)
user := NewUser(uuid.New(), "some@email.de", true, verifiedAt, false, []byte("somePass"), []byte("someSalt"), createAt) user := NewUser(uuid.New(), "some@email.de", true, &verifiedAt, false, []byte("somePass"), []byte("someSalt"), createAt)
_, err := db.Exec(` _, err := db.Exec(`
INSERT INTO user (user_uuid, email, email_verified, email_verified_at, is_admin, password, salt, created_at) INSERT INTO user (user_uuid, email, email_verified, email_verified_at, is_admin, password, salt, created_at)

View File

@@ -1,11 +0,0 @@
package handler
import (
"testing"
)
func TestHandleSignIn(t *testing.T) {
t.Parallel()
t.Run("should signIn and return session cookie", func(t *testing.T) {
})
}

18
main.go
View File

@@ -27,10 +27,16 @@ func main() {
log.Fatal("Error loading .env file") log.Fatal("Error loading .env file")
} }
run(context.Background(), os.Getenv) db, err := sql.Open("sqlite3", "./data.db")
if err != nil {
log.Fatal("Could not open Database data.db: ", err)
}
defer db.Close()
run(context.Background(), db, os.Getenv)
} }
func run(ctx context.Context, env func(string) string) { func run(ctx context.Context, db *sql.DB, env func(string) string) {
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer cancel() defer cancel()
@@ -40,15 +46,13 @@ func run(ctx context.Context, env func(string) string) {
serverSettings := types.NewServerSettingsFromEnv(env) serverSettings := types.NewServerSettingsFromEnv(env)
// init db // init db
db, err := sql.Open("sqlite3", serverSettings.DbPath) err := utils.RunMigrations(db, "")
if err != nil { if err != nil {
log.Fatal("Could not open Database data.db: ", err) slog.Error("Could not run migrations: " + err.Error())
os.Exit(1)
} }
defer db.Close()
utils.MustRunMigrations(db, "")
// init servers // init servers
var prometheusServer *http.Server var prometheusServer *http.Server
if serverSettings.PrometheusEnabled { if serverSettings.PrometheusEnabled {
prometheusServer := &http.Server{ prometheusServer := &http.Server{

View File

@@ -68,7 +68,7 @@ func (service ServiceAuthImpl) SignIn(email string, password string) (*User, err
} }
} }
hash := getHashPassword(password, user.Salt) hash := GetHashPassword(password, user.Salt)
if subtle.ConstantTimeCompare(hash, user.Password) == 0 { if subtle.ConstantTimeCompare(hash, user.Password) == 0 {
return nil, ErrInvaidCredentials return nil, ErrInvaidCredentials
@@ -279,7 +279,7 @@ func HandleSignUpComp(db *sql.DB, serverSettings *types.ServerSettings) http.Han
return return
} }
hash := getHashPassword(password, salt) hash := GetHashPassword(password, salt)
_, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", userId, email, hash, salt) _, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", userId, email, hash, salt)
if err != nil { if err != nil {
@@ -366,7 +366,7 @@ func HandleDeleteAccountComp(db *sql.DB, serverSettings *types.ServerSettings) h
return return
} }
currHash := getHashPassword(password, salt) currHash := GetHashPassword(password, salt)
if subtle.ConstantTimeCompare(currHash, storedHash) == 0 { if subtle.ConstantTimeCompare(currHash, storedHash) == 0 {
utils.TriggerToast(w, r, "error", "Password is not correct") utils.TriggerToast(w, r, "error", "Password is not correct")
return return
@@ -455,13 +455,13 @@ func HandleChangePasswordComp(db *sql.DB) http.HandlerFunc {
return return
} }
currHash := getHashPassword(currPass, salt) currHash := GetHashPassword(currPass, salt)
if subtle.ConstantTimeCompare(currHash, storedHash) == 0 { if subtle.ConstantTimeCompare(currHash, storedHash) == 0 {
utils.TriggerToast(w, r, "error", "Current Password is not correct") utils.TriggerToast(w, r, "error", "Current Password is not correct")
return return
} }
newHash := getHashPassword(newPass, salt) newHash := GetHashPassword(newPass, salt)
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", newHash, user.Id) _, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", newHash, user.Id)
if err != nil { if err != nil {
@@ -524,7 +524,7 @@ func HandleActualResetPasswordComp(db *sql.DB) http.HandlerFunc {
return return
} }
passHash := getHashPassword(newPass, salt) passHash := GetHashPassword(newPass, salt)
_, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", passHash, userId) _, err = db.Exec("UPDATE user SET password = ? WHERE user_uuid = ?", passHash, userId)
if err != nil { if err != nil {
@@ -653,7 +653,7 @@ func TryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sq
return nil return nil
} }
func getHashPassword(password string, salt []byte) []byte { func GetHashPassword(password string, salt []byte) []byte {
return argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16) return argon2.IDKey([]byte(password), salt, 1, 64*1024, 1, 16)
} }

View File

@@ -25,14 +25,15 @@ func TestSignIn(t *testing.T) {
t.Run("should return user if password is correct", func(t *testing.T) { t.Run("should return user if password is correct", func(t *testing.T) {
t.Parallel() t.Parallel()
salt := []byte("salt") salt := []byte("salt")
verifiedAt := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
stub := DbAuthStub{ stub := DbAuthStub{
user: db.NewUser( user: db.NewUser(
uuid.New(), uuid.New(),
"test@test.de", "test@test.de",
true, true,
time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC), &verifiedAt,
false, false,
getHashPassword("password", salt), GetHashPassword("password", salt),
salt, salt,
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
), ),
@@ -58,14 +59,15 @@ func TestSignIn(t *testing.T) {
t.Run("should return ErrInvalidCretentials if password is not correct", func(t *testing.T) { t.Run("should return ErrInvalidCretentials if password is not correct", func(t *testing.T) {
t.Parallel() t.Parallel()
salt := []byte("salt") salt := []byte("salt")
verifiedAt := time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC)
stub := DbAuthStub{ stub := DbAuthStub{
user: db.NewUser( user: db.NewUser(
uuid.New(), uuid.New(),
"test@test.de", "test@test.de",
true, true,
time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC), &verifiedAt,
false, false,
getHashPassword("password", salt), GetHashPassword("password", salt),
salt, salt,
time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
), ),

View File

@@ -11,7 +11,6 @@ type ServerSettings struct {
BaseUrl string BaseUrl string
Environment string Environment string
DbPath string
Smtp *SmtpSettings Smtp *SmtpSettings
} }
@@ -61,15 +60,10 @@ func NewServerSettingsFromEnv(env func(string) string) *ServerSettings {
Port: env("PORT"), Port: env("PORT"),
PrometheusEnabled: env("PROMETHEUS_ENABLED") == "true", PrometheusEnabled: env("PROMETHEUS_ENABLED") == "true",
BaseUrl: env("BASE_URL"), BaseUrl: env("BASE_URL"),
DbPath: env("DB_PATH"),
Environment: env("ENVIRONMENT"), Environment: env("ENVIRONMENT"),
Smtp: smtp, Smtp: smtp,
} }
if settings.DbPath == "" {
settings.DbPath = "./data.db"
}
if settings.BaseUrl == "" { if settings.BaseUrl == "" {
log.Fatal("BASE_URL must be set") log.Fatal("BASE_URL must be set")
} }

View File

@@ -2,17 +2,20 @@ package utils
import ( import (
"database/sql" "database/sql"
"log" "errors"
"log/slog"
"me-fit/types"
"github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/database/sqlite3" "github.com/golang-migrate/migrate/v4/database/sqlite3"
_ "github.com/golang-migrate/migrate/v4/source/file" _ "github.com/golang-migrate/migrate/v4/source/file"
) )
func MustRunMigrations(db *sql.DB, pathPrefix string) { func RunMigrations(db *sql.DB, pathPrefix string) error {
driver, err := sqlite3.WithInstance(db, &sqlite3.Config{}) driver, err := sqlite3.WithInstance(db, &sqlite3.Config{})
if err != nil { if err != nil {
log.Fatal(err) slog.Error("Could not create Migration instance: " + err.Error())
return types.ErrInternal
} }
m, err := migrate.NewWithDatabaseInstance( m, err := migrate.NewWithDatabaseInstance(
@@ -20,13 +23,17 @@ func MustRunMigrations(db *sql.DB, pathPrefix string) {
"", "",
driver) driver)
if err != nil { if err != nil {
log.Fatal("Could not create migrations instance: ", err) slog.Error("Could not create migrations instance: " + err.Error())
return types.ErrInternal
} }
err = m.Up() err = m.Up()
if err != nil { if err != nil {
if err.Error() != "no change" { if !errors.Is(err, migrate.ErrNoChange) {
log.Fatal("Could not run migrations: ", err) slog.Error("Could not run migrations: " + err.Error())
return types.ErrInternal
} }
} }
return nil
} }