diff --git a/server/e2e/common.go b/server/e2e/common.go index 12d3c0fba..9d79f8b5c 100644 --- a/server/e2e/common.go +++ b/server/e2e/common.go @@ -2,8 +2,11 @@ package e2e import ( "context" + "encoding/json" "net" "net/http" + "regexp" + "strings" "testing" "github.com/gavv/httpexpect/v2" @@ -21,6 +24,8 @@ import ( "github.com/reearth/reearthx/mongox/mongotest" "github.com/samber/lo" "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "golang.org/x/text/language" ) type Seeder func(ctx context.Context, r *repo.Container) error @@ -170,3 +175,93 @@ func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Conta }) return httpexpect.New(t, "http://"+l.Addr().String()) } + +func Server(t *testing.T, seeder Seeder) *httpexpect.Expect { + c := &config.Config{ + Origins: []string{"https://example.com"}, + AuthSrv: config.AuthSrvConfig{ + Disabled: true, + }, + } + return StartServer(t, c, true, seeder) +} + +func ServerLanguage(t *testing.T, lang language.Tag) *httpexpect.Expect { + c := &config.Config{ + Origins: []string{"https://example.com"}, + AuthSrv: config.AuthSrvConfig{ + Disabled: true, + }, + } + return StartServer(t, c, true, + func(ctx context.Context, r *repo.Container) error { + return baseSeederWithLang(ctx, r, lang) + }, + ) +} + +func Request(e *httpexpect.Expect, user string, requestBody GraphQLRequest) *httpexpect.Value { + return e.POST("/api/graphql"). + WithHeader("Origin", "https://example.com"). + WithHeader("authorization", "Bearer test"). + WithHeader("X-Reearth-Debug-User", user). + WithHeader("Content-Type", "application/json"). + WithJSON(requestBody). + Expect(). + Status(http.StatusOK). + JSON() +} + +func RequestQuery(t *testing.T, e *httpexpect.Expect, query string, user string) *httpexpect.Value { + request := GraphQLRequest{ + Query: query, + } + jsonData, err := json.Marshal(request) + assert.Nil(t, err) + return e.POST("/api/graphql"). + WithHeader("authorization", "Bearer test"). + WithHeader("Content-Type", "application/json"). + WithHeader("X-Reearth-Debug-User", user). + WithBytes(jsonData). + Expect(). + Status(http.StatusOK). + JSON() +} + +func RequestWithMultipart(e *httpexpect.Expect, user string, requestBody map[string]interface{}, filePath string) *httpexpect.Value { + jsonData, _ := json.Marshal(requestBody) + return e.POST("/api/graphql"). + WithHeader("Origin", "https://example.com"). + WithHeader("authorization", "Bearer test"). + WithHeader("X-Reearth-Debug-User", user). + WithMultipart(). + WithFormField("operations", string(jsonData)). + WithFormField("map", `{"0": ["variables.file"]}`). + WithFile("0", filePath). + Expect(). + Status(http.StatusOK). + JSON() +} + +func JSONEqRegexp(t *testing.T, rx string, str string) bool { + + var rx1 map[string]interface{} + err := json.Unmarshal([]byte(rx), &rx1) + assert.Nil(t, err) + + rx2, err := json.Marshal(rx1) + assert.Nil(t, err) + + var str1 map[string]interface{} + err = json.Unmarshal([]byte(str), &str1) + assert.Nil(t, err) + + str2, err := json.Marshal(str1) + assert.Nil(t, err) + + return assert.Regexp( + t, + regexp.MustCompile(strings.ReplaceAll(string(rx2), "[", "\\[")), + string(str2), + ) +} diff --git a/server/e2e/gql_project_test.go b/server/e2e/gql_project_test.go index 12e32c18d..c218c7921 100644 --- a/server/e2e/gql_project_test.go +++ b/server/e2e/gql_project_test.go @@ -1,11 +1,19 @@ package e2e import ( + "context" + "fmt" "net/http" + "net/url" "testing" "github.com/gavv/httpexpect/v2" "github.com/reearth/reearth/server/internal/app/config" + "github.com/reearth/reearth/server/internal/usecase/repo" + "github.com/reearth/reearth/server/pkg/project" + "github.com/reearth/reearthx/account/accountdomain" + "github.com/reearth/reearthx/idx" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -256,6 +264,62 @@ func starProject(e *httpexpect.Expect, projectID string) { ValueEqual("starred", true) } +const GetProjectsQuery = ` +query GetProjects($teamId: ID!, $pagination: Pagination, $keyword: String, $sort: ProjectSort) { + projects( + teamId: $teamId + pagination: $pagination + keyword: $keyword + sort: $sort + ) { + edges { + node { + id + ...ProjectFragment + scene { + id + __typename + } + __typename + } + __typename + } + pageInfo { + endCursor + hasNextPage + hasPreviousPage + startCursor + __typename + } + totalCount + __typename + } +} +fragment ProjectFragment on Project { + id + teamId + name + description + imageUrl + isArchived + isBasicAuthActive + basicAuthUsername + basicAuthPassword + publicTitle + publicDescription + publicImage + alias + enableGa + trackingId + publishmentStatus + updatedAt + createdAt + coreSupport + starred + isDeleted + __typename +}` + func TestSortByUpdatedAt(t *testing.T) { e := StartServer(t, &config.Config{ @@ -298,68 +362,7 @@ func TestSortByUpdatedAt(t *testing.T) { requestBody := GraphQLRequest{ OperationName: "GetProjects", - Query: ` - query GetProjects($teamId: ID!, $pagination: Pagination, $keyword: String, $sort: ProjectSort) { - projects( - teamId: $teamId - pagination: $pagination - keyword: $keyword - sort: $sort - ) { - edges { - node { - id - ...ProjectFragment - scene { - id - __typename - } - __typename - } - __typename - } - nodes { - id - ...ProjectFragment - scene { - id - __typename - } - __typename - } - pageInfo { - endCursor - hasNextPage - hasPreviousPage - startCursor - __typename - } - totalCount - __typename - } - } - fragment ProjectFragment on Project { - id - name - description - imageUrl - isArchived - isBasicAuthActive - basicAuthUsername - basicAuthPassword - publicTitle - publicDescription - publicImage - alias - enableGa - trackingId - publishmentStatus - updatedAt - createdAt - coreSupport - starred - __typename - }`, + Query: GetProjectsQuery, Variables: map[string]any{ "pagination": map[string]any{ "first": 3, // Get first 3 itme @@ -551,3 +554,179 @@ func testData(e *httpexpect.Expect) { Value("id").Raw().(string) deleteProject(e, id) // delete } + +func projects(t *testing.T, ctx context.Context, r *repo.Container, count int, wID idx.ID[accountdomain.Workspace], name string, alias string) { + for i := range make([]int, count) { + p := project.New(). + ID(project.NewID()). + Name(fmt.Sprintf(name+" name%d", i+1)). + Description(fmt.Sprintf(name+" description%d", i+1)). + ImageURL(lo.Must(url.Parse("https://test.com"))). + Workspace(wID). + Alias(alias). + IsArchived(false). + Deleted(false). + CoreSupport(true). + MustBuild() + err := r.Project.Save(ctx, p) + assert.Nil(t, err) + } +} + +func TestGetProjectPagination(t *testing.T) { + c := &config.Config{ + Origins: []string{"https://example.com"}, + AuthSrv: config.AuthSrvConfig{ + Disabled: true, + }, + } + e, r, _ := StartServerAndRepos(t, c, true, baseSeeder) + ctx := context.Background() + + projects(t, ctx, r, 20, wID, "[wID]project", "ALIAS1") + projects(t, ctx, r, 20, wId1, "[wId1]project", "ALIAS2") + + // ===== First request + requestBody := GraphQLRequest{ + OperationName: "GetProjects", + Query: GetProjectsQuery, + Variables: map[string]any{ + "pagination": map[string]any{ + "first": 16, + }, + "teamId": wID.String(), + "sort": map[string]string{ + "field": "UPDATEDAT", + "direction": "DESC", + }, + // "keyword": "Project", + }, + } + + projects := Request(e, uID.String(), requestBody).Object().Value("data").Object().Value("projects").Object() + + projects.ValueEqual("totalCount", 20) + + edges := projects.Value("edges").Array().Iter() + assert.Equal(t, len(edges), 16) + for _, v := range edges { + //Only the same teamId + v.Object().Value("node").Object().ValueEqual("teamId", wID.String()) + } + + pageInfo := projects.Value("pageInfo").Object() + pageInfo.ValueEqual("hasNextPage", true) + + // ===== Second request + endCursor := pageInfo.Value("endCursor").Raw().(string) + + requestBody = GraphQLRequest{ + OperationName: "GetProjects", + Query: GetProjectsQuery, + Variables: map[string]any{ + "pagination": map[string]any{ + "after": endCursor, + "first": 16, + }, + "teamId": wID.String(), + "sort": map[string]string{ + "field": "UPDATEDAT", + "direction": "DESC", + }, + // "keyword": "Project", + }, + } + projects = Request(e, uID.String(), requestBody).Object().Value("data").Object().Value("projects").Object() + projects.ValueEqual("totalCount", 4) + + edges = projects.Value("edges").Array().Iter() + assert.Equal(t, len(edges), 4) + for _, v := range edges { + //Only the same teamId + v.Object().Value("node").Object().ValueEqual("teamId", wID.String()) + } + + pageInfo = projects.Value("pageInfo").Object() + pageInfo.ValueEqual("hasNextPage", false) + +} + +func TestGetProjectPaginationKeyword(t *testing.T) { + c := &config.Config{ + Origins: []string{"https://example.com"}, + AuthSrv: config.AuthSrvConfig{ + Disabled: true, + }, + } + e, r, _ := StartServerAndRepos(t, c, true, baseSeeder) + ctx := context.Background() + + projects(t, ctx, r, 10, wID, "AAAAAAA", "ALIAS1") + projects(t, ctx, r, 10, wID, "BBBBBBB", "ALIAS2") + projects(t, ctx, r, 10, wID, "AAAProjectAAAA", "ALIAS1") + projects(t, ctx, r, 10, wID, "BBBProjectBBBB", "ALIAS2") + + // ===== First request + requestBody := GraphQLRequest{ + OperationName: "GetProjects", + Query: GetProjectsQuery, + Variables: map[string]any{ + "pagination": map[string]any{ + "first": 16, + }, + "teamId": wID.String(), + "sort": map[string]string{ + "field": "UPDATEDAT", + "direction": "DESC", + }, + "keyword": "Project", + }, + } + + projects := Request(e, uID.String(), requestBody).Object().Value("data").Object().Value("projects").Object() + + projects.ValueEqual("totalCount", 20) + + edges := projects.Value("edges").Array().Iter() + assert.Equal(t, len(edges), 16) + for _, v := range edges { + //Only the same teamId + v.Object().Value("node").Object().ValueEqual("teamId", wID.String()) + } + + pageInfo := projects.Value("pageInfo").Object() + pageInfo.ValueEqual("hasNextPage", true) + + // ===== Second request + endCursor := pageInfo.Value("endCursor").Raw().(string) + + requestBody = GraphQLRequest{ + OperationName: "GetProjects", + Query: GetProjectsQuery, + Variables: map[string]any{ + "pagination": map[string]any{ + "after": endCursor, + "first": 16, + }, + "teamId": wID.String(), + "sort": map[string]string{ + "field": "UPDATEDAT", + "direction": "DESC", + }, + "keyword": "Project", + }, + } + projects = Request(e, uID.String(), requestBody).Object().Value("data").Object().Value("projects").Object() + projects.ValueEqual("totalCount", 4) + + edges = projects.Value("edges").Array().Iter() + assert.Equal(t, len(edges), 4) + for _, v := range edges { + //Only the same teamId + v.Object().Value("node").Object().ValueEqual("teamId", wID.String()) + } + + pageInfo = projects.Value("pageInfo").Object() + pageInfo.ValueEqual("hasNextPage", false) + +} diff --git a/server/go.mod b/server/go.mod index 060e41706..3a8029a79 100644 --- a/server/go.mod +++ b/server/go.mod @@ -27,7 +27,7 @@ require ( github.com/paulmach/go.geojson v1.4.0 github.com/pkg/errors v0.9.1 github.com/ravilushqa/otelgqlgen v0.15.0 - github.com/reearth/reearthx v0.0.0-20241127093551-4fcdd23fa824 + github.com/reearth/reearthx v0.0.0-20241128091912-9b8bc8adfdd7 github.com/samber/lo v1.39.0 github.com/spf13/afero v1.11.0 github.com/square/mongo-lock v0.0.0-20201208161834-4db518ed7fb2 diff --git a/server/go.sum b/server/go.sum index bd9d43f3a..7b3489e92 100644 --- a/server/go.sum +++ b/server/go.sum @@ -509,8 +509,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/ravilushqa/otelgqlgen v0.15.0 h1:U85nrlweMXTGaMChUViYM39/MXBZVeVVlpuHq+6eECQ= github.com/ravilushqa/otelgqlgen v0.15.0/go.mod h1:o+1Eju0VySmgq2BP8Vupz2YrN21Bj7D7imBqu3m2uB8= -github.com/reearth/reearthx v0.0.0-20241127093551-4fcdd23fa824 h1:Zy0dk/fXMIhJBKh5xBbFRyxDbw4PWU4ek0eEnH00GJg= -github.com/reearth/reearthx v0.0.0-20241127093551-4fcdd23fa824/go.mod h1:/ByvE9o0WANHL2nhOyZjOXWwY8cCgze0OmwyNzxcYoA= +github.com/reearth/reearthx v0.0.0-20241128091912-9b8bc8adfdd7 h1:WyDySGPgHtNXlOu3cPfuQyWM7xbpJc0Cm2a5Ti/2ZwM= +github.com/reearth/reearthx v0.0.0-20241128091912-9b8bc8adfdd7/go.mod h1:/ByvE9o0WANHL2nhOyZjOXWwY8cCgze0OmwyNzxcYoA= github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=