tercul-backend/test/e2e/e2e_test.go

139 lines
3.7 KiB
Go

package e2e
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
graph "tercul/internal/adapters/graphql"
"tercul/internal/domain"
"tercul/internal/observability"
platform_auth "tercul/internal/platform/auth"
platform_config "tercul/internal/platform/config"
"tercul/internal/testutil"
"tercul/test/fixtures"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/suite"
"gorm.io/gorm/logger"
)
// E2ETestSuite provides end-to-end testing infrastructure.
type E2ETestSuite struct {
testutil.IntegrationTestSuite
Server *httptest.Server
Client *http.Client
JWTManager *platform_auth.JWTManager
Fixtures *fixtures.Loader
}
func (s *E2ETestSuite) SetupSuite() {
// Use a file-based SQLite DB for stability across multiple connections.
// In-memory SQLite (":memory:") can create isolated DBs per connection, which breaks HTTP handler requests.
s.IntegrationTestSuite.SetupSuite(&testutil.TestConfig{UseInMemoryDB: false, DBPath: "e2e_test.db", LogLevel: logger.Silent})
cfg, err := platform_config.LoadConfig()
s.Require().NoError(err)
s.JWTManager = platform_auth.NewJWTManager(cfg)
s.Fixtures = fixtures.NewLoader(s.DB)
resolver := &graph.Resolver{App: s.App}
c := graph.Config{Resolvers: resolver}
c.Directives.Binding = graph.Binding
//nolint:staticcheck // Required here for custom error presenter
srv := handler.NewDefaultServer(graph.NewExecutableSchema(c))
srv.SetErrorPresenter(graph.NewErrorPresenter())
reg := prometheus.NewRegistry()
metrics := observability.NewMetrics(reg)
var chain http.Handler
chain = srv
chain = platform_auth.GraphQLAuthMiddleware(s.JWTManager)(chain)
chain = metrics.PrometheusMiddleware(chain)
chain = observability.TracingMiddleware(chain)
chain = observability.RequestIDMiddleware(chain)
s.Server = httptest.NewServer(chain)
s.Client = s.Server.Client()
}
func (s *E2ETestSuite) TearDownSuite() {
if s.Server != nil {
s.Server.Close()
}
s.IntegrationTestSuite.TearDownSuite()
}
func (s *E2ETestSuite) SetupTest() {
ctx := context.Background()
s.Require().NoError(s.Fixtures.Clear(ctx))
s.Require().NoError(s.Fixtures.LoadAll(ctx))
}
func (s *E2ETestSuite) TearDownTest() {
ctx := context.Background()
_ = s.Fixtures.Clear(ctx)
}
func (s *E2ETestSuite) GenerateToken(username string) string {
var user domain.User
err := s.DB.Where("username = ?", username).First(&user).Error
s.Require().NoError(err)
token, err := s.JWTManager.GenerateToken(&user)
s.Require().NoError(err)
return token
}
func (s *E2ETestSuite) executeGraphQL(query string, variables map[string]interface{}, token string) map[string]interface{} {
payload := map[string]interface{}{"query": query}
if variables != nil {
payload["variables"] = variables
}
body, err := json.Marshal(payload)
s.Require().NoError(err)
req, err := http.NewRequest("POST", s.Server.URL, bytes.NewBuffer(body))
s.Require().NoError(err)
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := s.Client.Do(req)
s.Require().NoError(err)
defer resp.Body.Close()
var out map[string]interface{}
s.Require().NoError(json.NewDecoder(resp.Body).Decode(&out))
return out
}
func (s *E2ETestSuite) Eventually(condition func() bool, timeout time.Duration, message string) {
start := time.Now()
for {
if condition() {
return
}
if time.Since(start) > timeout {
s.Fail(message)
return
}
time.Sleep(25 * time.Millisecond)
}
}
func TestE2ETestSuite(t *testing.T) {
if testing.Short() {
t.Skip("Skipping E2E tests in short mode")
}
suite.Run(t, new(E2ETestSuite))
}