package main import ( "log/slog" "me-fit/service" "me-fit/types" "me-fit/utils" "context" "database/sql" "net/http" "net/url" "strings" "testing" "time" "github.com/google/uuid" ) func TestHandleSignIn(t *testing.T) { t.Parallel() httpClient := http.Client{ // Disable redirect following CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } t.Run("should signin and return session cookie", func(t *testing.T) { t.Parallel() db, ctx := setupIntegrationTest(t, "8080") 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) } 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") resp, err := httpClient.Do(req) if err != nil { t.Fatalf("Error making request: %v", err) } if resp.StatusCode != http.StatusSeeOther { t.Fatalf("Expected status code 303, got %d", resp.StatusCode) } var cookie *http.Cookie for _, c := range resp.Cookies() { if c.Name == "id" { cookie = c break } } if cookie == nil { t.Fatalf("No session cookie found") } else if cookie.SameSite != http.SameSiteStrictMode || cookie.HttpOnly != true || cookie.Secure != true { t.Fatalf("Cookie is not secure") } }) } func setupIntegrationTest(t *testing.T, port string) (*sql.DB, context.Context) { 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) } go run(ctx, db, getEnv(port)) err = waitForReady(ctx, 5*time.Second, "http://localhost:8080") if err != nil { t.Fatalf("Failed to start server: %v", err) } return db, ctx } func getEnv(port string) func(string) string { return func(key string) string { if key == "PORT" { return port } else if key == "SMTP_ENABLED" { return "false" } else if key == "PROMETHEUS_ENABLED" { return "false" } else if key == "BASE_URL" { return "http://localhost:" + port } else if key == "ENVIRONMENT" { return "test" } else { return "" } } } // 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 { slog.Error("failed to create request: " + err.Error()) return err } resp, err := client.Do(req) if err != nil { slog.Info("Error making request: " + err.Error()) continue } if resp.StatusCode == http.StatusOK { slog.Info("Endpoint is ready!") resp.Body.Close() return nil } resp.Body.Close() select { case <-ctx.Done(): return ctx.Err() default: if time.Since(startTime) >= timeout { slog.Error("timeout reached while waiting for endpoint") return types.ErrInternal } // wait a little while between checks time.Sleep(250 * time.Millisecond) } } }