-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(server): project import export external url #1205
Changes from all commits
5ebd099
64a2858
6abf2d6
46765a3
3ec99a8
1080e20
6d82b48
557dc49
634644b
ed0a37e
cbead29
000d27d
31d38f6
25c10c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,130 @@ | ||||||||||||||||||||
package e2e | ||||||||||||||||||||
|
||||||||||||||||||||
import ( | ||||||||||||||||||||
"fmt" | ||||||||||||||||||||
"net/http" | ||||||||||||||||||||
"os" | ||||||||||||||||||||
"testing" | ||||||||||||||||||||
|
||||||||||||||||||||
"github.com/gavv/httpexpect/v2" | ||||||||||||||||||||
"github.com/reearth/reearth/server/internal/app/config" | ||||||||||||||||||||
"github.com/stretchr/testify/assert" | ||||||||||||||||||||
) | ||||||||||||||||||||
|
||||||||||||||||||||
// export REEARTH_DB=mongodb://localhost | ||||||||||||||||||||
// go test -v -run TestCallExportProject ./e2e/... | ||||||||||||||||||||
|
||||||||||||||||||||
func TestCallExportProject(t *testing.T) { | ||||||||||||||||||||
|
||||||||||||||||||||
e := StartServer(t, &config.Config{ | ||||||||||||||||||||
Origins: []string{"https://example.com"}, | ||||||||||||||||||||
AuthSrv: config.AuthSrvConfig{ | ||||||||||||||||||||
Disabled: true, | ||||||||||||||||||||
}, | ||||||||||||||||||||
}, true, baseSeeder) | ||||||||||||||||||||
|
||||||||||||||||||||
pID := createProjectWithExternalImage(e, "test") | ||||||||||||||||||||
|
||||||||||||||||||||
_, _, sID := createScene(e, pID) | ||||||||||||||||||||
|
||||||||||||||||||||
_, _, storyID := createStory(e, sID, "test", 0) | ||||||||||||||||||||
|
||||||||||||||||||||
_, _, pageID := createPage(e, sID, storyID, "test", true) | ||||||||||||||||||||
|
||||||||||||||||||||
_, _, _ = createBlock(e, sID, storyID, pageID, "reearth", "imageStoryBlock", nil) | ||||||||||||||||||||
_, _, _ = createBlock(e, sID, storyID, pageID, "reearth", "imageStoryBlock", nil) | ||||||||||||||||||||
_, _, _ = createBlock(e, sID, storyID, pageID, "reearth", "imageStoryBlock", nil) | ||||||||||||||||||||
|
||||||||||||||||||||
_, res := fetchSceneForStories(e, sID) | ||||||||||||||||||||
|
||||||||||||||||||||
blocks := res.Object().Value("data").Object(). | ||||||||||||||||||||
Value("node").Object(). | ||||||||||||||||||||
Value("stories").Array().First().Object(). | ||||||||||||||||||||
Value("pages").Array().First().Object(). | ||||||||||||||||||||
Value("blocks").Array().Iter() | ||||||||||||||||||||
|
||||||||||||||||||||
propID1 := blocks[0].Object().Value("propertyId").Raw().(string) | ||||||||||||||||||||
propID2 := blocks[1].Object().Value("propertyId").Raw().(string) | ||||||||||||||||||||
propID3 := blocks[2].Object().Value("propertyId").Raw().(string) | ||||||||||||||||||||
|
||||||||||||||||||||
_, res = updatePropertyValue(e, propID1, "default", "", "src", "http://localhost:8080/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png", "URL") | ||||||||||||||||||||
res.Path("$.data.updatePropertyValue.propertyField.value").Equal("http://localhost:8080/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png") | ||||||||||||||||||||
|
||||||||||||||||||||
_, res = updatePropertyValue(e, propID2, "default", wID.String(), "src", "https://test.com/project.jpg", "URL") | ||||||||||||||||||||
res.Path("$.data.updatePropertyValue.propertyField.value").Equal("https://test.com/project.jpg") | ||||||||||||||||||||
|
||||||||||||||||||||
_, res = updatePropertyValue(e, propID3, "default", wID.String(), "src", "https://api.visualizer.test.reearth.dev/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png", "URL") | ||||||||||||||||||||
res.Path("$.data.updatePropertyValue.propertyField.value").Equal("https://api.visualizer.test.reearth.dev/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png") | ||||||||||||||||||||
Comment on lines
+50
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Extract test URLs to constants and validate URL patterns. Hard-coded URLs make tests brittle and harder to maintain. Consider extracting them to constants and adding validation for URL patterns. +const (
+ testLocalAssetURL = "http://localhost:8080/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png"
+ testExternalURL = "https://test.com/project.jpg"
+ testAPIAssetURL = "https://api.visualizer.test.reearth.dev/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png"
+)
+
+func isValidAssetURL(url string) bool {
+ // Add URL validation logic here
+ return true
+}
- _, res = updatePropertyValue(e, propID1, "default", "", "src", "http://localhost:8080/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png", "URL")
+ _, res = updatePropertyValue(e, propID1, "default", "", "src", testLocalAssetURL, "URL")
+ assert.True(t, isValidAssetURL(testLocalAssetURL))
res.Path("$.data.updatePropertyValue.propertyField.value").Equal("http://localhost:8080/assets/01jbbhhtq2jq7mx39dhyq1cfr2.png")
|
||||||||||||||||||||
|
||||||||||||||||||||
fileName := exporProject(t, e, pID) | ||||||||||||||||||||
|
||||||||||||||||||||
defer func() { | ||||||||||||||||||||
err := os.Remove(fileName) | ||||||||||||||||||||
assert.Nil(t, err) | ||||||||||||||||||||
}() | ||||||||||||||||||||
Comment on lines
+61
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improve error handling in deferred cleanup. The deferred cleanup could fail silently. Consider logging the error or using a test cleanup function. - defer func() {
- err := os.Remove(fileName)
- assert.Nil(t, err)
- }()
+ t.Cleanup(func() {
+ if err := os.Remove(fileName); err != nil {
+ t.Errorf("Failed to cleanup test file %s: %v", fileName, err)
+ }
+ }) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
func createProjectWithExternalImage(e *httpexpect.Expect, name string) string { | ||||||||||||||||||||
requestBody := GraphQLRequest{ | ||||||||||||||||||||
OperationName: "CreateProject", | ||||||||||||||||||||
Query: `mutation CreateProject($teamId: ID!, $visualizer: Visualizer!, $name: String!, $description: String!, $imageUrl: URL, $coreSupport: Boolean) { | ||||||||||||||||||||
createProject( input: {teamId: $teamId, visualizer: $visualizer, name: $name, description: $description, imageUrl: $imageUrl, coreSupport: $coreSupport} ) { | ||||||||||||||||||||
project { | ||||||||||||||||||||
id | ||||||||||||||||||||
__typename | ||||||||||||||||||||
} | ||||||||||||||||||||
__typename | ||||||||||||||||||||
} | ||||||||||||||||||||
}`, | ||||||||||||||||||||
Variables: map[string]any{ | ||||||||||||||||||||
"name": name, | ||||||||||||||||||||
"description": "abc", | ||||||||||||||||||||
"imageUrl": "https://test.com/project.jpg", | ||||||||||||||||||||
"teamId": wID.String(), | ||||||||||||||||||||
"visualizer": "CESIUM", | ||||||||||||||||||||
"coreSupport": true, | ||||||||||||||||||||
}, | ||||||||||||||||||||
} | ||||||||||||||||||||
res := e.POST("/api/graphql"). | ||||||||||||||||||||
WithHeader("Origin", "https://example.com"). | ||||||||||||||||||||
WithHeader("X-Reearth-Debug-User", uID.String()). | ||||||||||||||||||||
WithHeader("Content-Type", "application/json"). | ||||||||||||||||||||
WithJSON(requestBody). | ||||||||||||||||||||
Expect(). | ||||||||||||||||||||
Status(http.StatusOK). | ||||||||||||||||||||
JSON() | ||||||||||||||||||||
return res.Path("$.data.createProject.project.id").Raw().(string) | ||||||||||||||||||||
} | ||||||||||||||||||||
Comment on lines
+68
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for project creation response. The project creation function should validate the response structure and ensure the project was created successfully. func createProjectWithExternalImage(e *httpexpect.Expect, name string) string {
+ const expectedTypeName = "Project"
+
requestBody := GraphQLRequest{
OperationName: "CreateProject",
@@ -94,5 +98,11 @@
Expect().
Status(http.StatusOK).
JSON()
- return res.Path("$.data.createProject.project.id").Raw().(string)
+
+ projectID := res.Path("$.data.createProject.project.id").Raw().(string)
+ assert.NotEmpty(t, projectID, "Project ID should not be empty")
+
+ typeName := res.Path("$.data.createProject.project.__typename").String().Raw()
+ assert.Equal(t, expectedTypeName, typeName, "Unexpected typename in response")
+
+ return projectID
}
|
||||||||||||||||||||
|
||||||||||||||||||||
func exporProject(t *testing.T, e *httpexpect.Expect, p string) string { | ||||||||||||||||||||
requestBody := GraphQLRequest{ | ||||||||||||||||||||
OperationName: "ExportProject", | ||||||||||||||||||||
Query: "mutation ExportProject($projectId: ID!) { exportProject(input: {projectId: $projectId}) { projectDataPath __typename } }", | ||||||||||||||||||||
Variables: map[string]any{ | ||||||||||||||||||||
"projectId": p, | ||||||||||||||||||||
}, | ||||||||||||||||||||
} | ||||||||||||||||||||
r := e.POST("/api/graphql"). | ||||||||||||||||||||
WithHeader("Origin", "https://example.com"). | ||||||||||||||||||||
WithHeader("authorization", "Bearer test"). | ||||||||||||||||||||
WithHeader("X-Reearth-Debug-User", uID.String()). | ||||||||||||||||||||
WithHeader("Content-Type", "application/json"). | ||||||||||||||||||||
WithJSON(requestBody). | ||||||||||||||||||||
Expect(). | ||||||||||||||||||||
Status(http.StatusOK). | ||||||||||||||||||||
JSON(). | ||||||||||||||||||||
Object() | ||||||||||||||||||||
downloadPath := r. | ||||||||||||||||||||
Value("data").Object(). | ||||||||||||||||||||
Value("exportProject").Object(). | ||||||||||||||||||||
Value("projectDataPath").String().Raw() | ||||||||||||||||||||
downloadResponse := e.GET(fmt.Sprintf("http://localhost:8080%s", downloadPath)). | ||||||||||||||||||||
Expect(). | ||||||||||||||||||||
Status(http.StatusOK). | ||||||||||||||||||||
Body().Raw() | ||||||||||||||||||||
fileName := "project_data.zip" | ||||||||||||||||||||
err := os.WriteFile(fileName, []byte(downloadResponse), os.ModePerm) | ||||||||||||||||||||
assert.Nil(t, err) | ||||||||||||||||||||
return fileName | ||||||||||||||||||||
} | ||||||||||||||||||||
Comment on lines
+100
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enhance export functionality with additional validations. The export function needs improvements in several areas:
func exporProject(t *testing.T, e *httpexpect.Expect, p string) string {
+ const timeout = 30 * time.Second
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+
requestBody := GraphQLRequest{
OperationName: "ExportProject",
Query: "mutation ExportProject($projectId: ID!) { exportProject(input: {projectId: $projectId}) { projectDataPath __typename } }",
@@ -122,9 +126,19 @@
downloadResponse := e.GET(fmt.Sprintf("http://localhost:8080%s", downloadPath)).
Expect().
Status(http.StatusOK).
+ ContentType("application/zip").
Body().Raw()
+
fileName := "project_data.zip"
- err := os.WriteFile(fileName, []byte(downloadResponse), os.ModePerm)
+ err := os.WriteFile(fileName, []byte(downloadResponse), 0600)
assert.Nil(t, err)
+
+ // Validate zip file contents
+ reader, err := zip.OpenReader(fileName)
+ assert.Nil(t, err)
+ defer reader.Close()
+
+ // Add validation for expected files in the zip
+ assert.Greater(t, len(reader.File), 0, "Zip file should not be empty")
+
return fileName
}
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add assertions for block creation responses.
The block creation responses are currently ignored. Consider adding assertions to verify successful creation of blocks.
📝 Committable suggestion