Skip to content

Commit

Permalink
add test code
Browse files Browse the repository at this point in the history
  • Loading branch information
soneda-yuya committed Jan 11, 2025
1 parent bf2d0c1 commit e62cd20
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 55 deletions.
3 changes: 3 additions & 0 deletions server/internal/adapter/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ func LangByTag(ctx context.Context, lang *language.Tag) string {
func Lang(ctx context.Context) string {
if v := ctx.Value(contextLang); v != nil {
if lang, ok := v.(string); ok {
if lang == "" {
return defaultLang.String()
}
return lang
}
}
Expand Down
16 changes: 1 addition & 15 deletions server/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,7 @@ func initEcho(ctx context.Context, cfg *ServerConfig) *echo.Echo {
AuthSrvUIDomain: cfg.Config.Host_Web,
}))

e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// browser language
lang := c.Request().Header.Get("lang")
// user language
// if user language is not "und", use user language
// if user language is "und", use browser language
u := adapter.User(c.Request().Context())
if u != nil && u.Lang().String() != "und" {
lang = u.Lang().String()
}
c.SetRequest(c.Request().WithContext(adapter.AttachLang(c.Request().Context(), lang)))
return next(c)
}
})
e.Use(AttachLanguageMiddleware)

// auth srv
authServer(ctx, e, &cfg.Config.AuthSrv, cfg.Repos)
Expand Down
91 changes: 51 additions & 40 deletions server/internal/app/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/reearth/reearth/server/internal/adapter/gql"
"github.com/reearth/reearth/server/internal/app/config"
"github.com/reearth/reearth/server/pkg/apperror"
"github.com/reearth/reearthx/log"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/gqlerror"
)

Expand Down Expand Up @@ -58,46 +60,9 @@ func GraphqlAPI(conf config.GraphQLConfig, dev bool) echo.HandlerFunc {
// tracing
srv.Use(otelgqlgen.Middleware())

srv.SetErrorPresenter(
// show more detailed error messgage in debug mode
func(ctx context.Context, e error) *gqlerror.Error {
var graphqlErr *gqlerror.Error
var appErr *apperror.AppError
lang := adapter.Lang(ctx)
if ok := errors.As(e, &appErr); ok {
localesErr := appErr.LocalesError[lang]
graphqlErr = &gqlerror.Error{
Message: localesErr.Message,
Extensions: map[string]interface{}{
"code": localesErr.Code,
"description": localesErr.Description,
},
}
} else {
graphqlErr = graphql.DefaultErrorPresenter(ctx, e)
}

if dev {
graphqlErr.Path = graphql.GetFieldContext(ctx).Path()
}

// FixMe: system error should not be shown to user
systemError := ""
if graphqlErr.Err != nil {
systemError = graphqlErr.Err.Error()
} else {
systemError = e.Error()
}

if graphqlErr.Extensions == nil {
graphqlErr.Extensions = make(map[string]interface{})
}

graphqlErr.Extensions["system_error"] = systemError

return graphqlErr
},
)
srv.SetErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error {
return customErrorPresenter(ctx, e, dev)
})

// only enable middlewares in dev mode
if dev {
Expand All @@ -116,3 +81,49 @@ func GraphqlAPI(conf config.GraphQLConfig, dev bool) echo.HandlerFunc {
return nil
}
}

// customErrorPresenter handles custom GraphQL error presentation.
func customErrorPresenter(ctx context.Context, e error, devMode bool) *gqlerror.Error {
var graphqlErr *gqlerror.Error
var appErr *apperror.AppError
lang := adapter.Lang(ctx)

log.Infof("lang: %s", lang)
// Handle application-specific errors
systemError := ""
if errors.As(e, &appErr) {
localesErr := appErr.LocalesError[lang]
graphqlErr = &gqlerror.Error{
Err: appErr.SystemError,
Message: localesErr.Message,
Extensions: map[string]interface{}{
"code": localesErr.Code,
"description": localesErr.Description,
},
}
if graphqlErr.Err != nil {
systemError = graphqlErr.Err.Error()
}
} else {
// Fallback to default GraphQL error presenter
graphqlErr = graphql.DefaultErrorPresenter(ctx, e)
systemError = e.Error()
}

// Add debugging information in development mode
if devMode {
if fieldCtx := graphql.GetFieldContext(ctx); fieldCtx != nil {
graphqlErr.Path = fieldCtx.Path()
} else {
graphqlErr.Path = ast.Path{}
}
}

// Ensure Extensions map exists
if graphqlErr.Extensions == nil {
graphqlErr.Extensions = make(map[string]interface{})
}
graphqlErr.Extensions["system_error"] = systemError

return graphqlErr
}
61 changes: 61 additions & 0 deletions server/internal/app/graphql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package app

import (
"context"
"errors"
"testing"

"github.com/reearth/reearth/server/internal/adapter"
"github.com/reearth/reearth/server/pkg/apperror"
"github.com/stretchr/testify/assert"
"github.com/vektah/gqlparser/v2/ast"
)

func TestCustomErrorPresenter(t *testing.T) {
// モック用のコンテキストとロケール
ctx := context.Background()
ctx = adapter.AttachLang(ctx, "en")

// アプリケーション固有のエラー
appErr := &apperror.AppError{
LocalesError: map[string]*apperror.LocalesError{
"en": {
Code: "test_code",
Message: "Test message",
Description: "Test description",
},
},
SystemError: errors.New("system error"),
}

t.Run("AppError with English language", func(t *testing.T) {
graphqlErr := customErrorPresenter(ctx, appErr, false)

// アサーション: GraphQL エラーの内容を検証
assert.NotNil(t, graphqlErr)
assert.Equal(t, "Test message", graphqlErr.Message)
assert.Equal(t, "test_code", graphqlErr.Extensions["code"])
assert.Equal(t, "Test description", graphqlErr.Extensions["description"])
assert.Equal(t, "system error", graphqlErr.Extensions["system_error"])
})

t.Run("Fallback to default GraphQL error", func(t *testing.T) {
defaultErr := errors.New("default error")
graphqlErr := customErrorPresenter(ctx, defaultErr, false)

// アサーション: デフォルトのエラーハンドリングを検証
assert.NotNil(t, graphqlErr)
assert.Equal(t, "default error", graphqlErr.Message)
assert.Equal(t, "default error", graphqlErr.Extensions["system_error"])
})

t.Run("Development mode with debugging information(Path)", func(t *testing.T) {
graphqlErr := customErrorPresenter(ctx, appErr, true)

assert.NotNil(t, graphqlErr)
assert.Equal(t, ast.Path{}, graphqlErr.Path)
assert.Equal(t, "Test message", graphqlErr.Message)
assert.Equal(t, "test_code", graphqlErr.Extensions["code"])
assert.Equal(t, "Test description", graphqlErr.Extensions["description"])
})
}
34 changes: 34 additions & 0 deletions server/internal/app/lang.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package app

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/reearth/reearth/server/internal/adapter"
)

// LanguageExtractor extracts the appropriate language from the request.
func LanguageExtractor(req *http.Request) string {
// Extract browser language from header
lang := req.Header.Get("lang")

// Extract user language from the adapter
// if user language is not "und", use user language
// if user language is "und", use browser language
u := adapter.User(req.Context())
if u != nil && u.Lang().String() != "und" {
lang = u.Lang().String()
}

return lang
}

// AttachLanguageMiddleware attaches the detected language to the request context.
func AttachLanguageMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
lang := LanguageExtractor(c.Request())
ctx := adapter.AttachLang(c.Request().Context(), lang)
c.SetRequest(c.Request().WithContext(ctx))
return next(c)
}
}
83 changes: 83 additions & 0 deletions server/internal/app/lang_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package app_test

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/labstack/echo/v4"
"github.com/reearth/reearth/server/internal/adapter"
"github.com/reearth/reearth/server/internal/app"
"github.com/reearth/reearthx/account/accountdomain/user"
"golang.org/x/text/language"

"github.com/stretchr/testify/assert"
)

func TestLanguageExtractor(t *testing.T) {
// Mock user with a language
tests := []struct {
name string
headerLang string
userLang language.Tag
expected string
}{
{
name: "User language overrides browser language",
headerLang: "fr",
userLang: language.English,
expected: "en",
},
{
name: "No user language, use browser language",
headerLang: "fr",
userLang: language.Und,
expected: "fr",
},
{
name: "No browser language or user language is und, fallback to default",
headerLang: "",
userLang: language.Und,
expected: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Mock request and context
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set("lang", tt.headerLang)

u := &user.User{}
u.UpdateLang(tt.userLang)
ctx := adapter.AttachUser(context.Background(), u)
req = req.WithContext(ctx)

lang := app.LanguageExtractor(req)
assert.Equal(t, tt.expected, lang)
})
}
}

func TestAttachLanguageMiddleware(t *testing.T) {
e := echo.New()
e.Use(app.AttachLanguageMiddleware)

e.GET("/", func(c echo.Context) error {
// get lang from context
lang := adapter.Lang(c.Request().Context())
// include lang in response
return c.String(http.StatusOK, lang)
})

t.Run("Middleware attaches correct language", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.Header.Set("lang", "fr")
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
// check lang in response
assert.Equal(t, "fr", rec.Body.String())
})
}

0 comments on commit e62cd20

Please sign in to comment.