mirror of
https://github.com/Dvorinka/excalidraw-full.git
synced 2026-06-03 22:02:57 +00:00
feat: full project sync - CI fixes, frontend, workspace API, and all changes
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInviteAcceptAddsEditorMembership(t *testing.T) {
|
||||
api, cleanup := newTestAPI(t)
|
||||
defer cleanup()
|
||||
|
||||
aliceCookie, _, aliceTeam := signup(t, api, "alice@example.com")
|
||||
bobCookie, _, _ := signup(t, api, "bob@example.com")
|
||||
|
||||
inviteRR := doJSON(t, api, http.MethodPost, "/teams/"+aliceTeam.ID+"/invites", map[string]any{
|
||||
"email": "bob@example.com",
|
||||
"role": "editor",
|
||||
}, aliceCookie)
|
||||
if inviteRR.Code != http.StatusCreated {
|
||||
t.Fatalf("invite status = %d body = %s", inviteRR.Code, inviteRR.Body.String())
|
||||
}
|
||||
var invite struct {
|
||||
Invite TeamInvite `json:"invite"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := json.Unmarshal(inviteRR.Body.Bytes(), &invite); err != nil {
|
||||
t.Fatalf("invite decode error = %v", err)
|
||||
}
|
||||
if invite.Token == "" || invite.Invite.Email != "bob@example.com" {
|
||||
t.Fatalf("unexpected invite: %#v", invite)
|
||||
}
|
||||
|
||||
beforeRR := doJSON(t, api, http.MethodGet, "/teams/"+aliceTeam.ID+"/members", nil, bobCookie)
|
||||
if beforeRR.Code != http.StatusForbidden {
|
||||
t.Fatalf("bob members before accept status = %d body = %s", beforeRR.Code, beforeRR.Body.String())
|
||||
}
|
||||
|
||||
acceptRR := doJSON(t, api, http.MethodPost, "/invites/accept", map[string]string{"token": invite.Token}, bobCookie)
|
||||
if acceptRR.Code != http.StatusOK {
|
||||
t.Fatalf("accept status = %d body = %s", acceptRR.Code, acceptRR.Body.String())
|
||||
}
|
||||
|
||||
afterRR := doJSON(t, api, http.MethodGet, "/teams/"+aliceTeam.ID+"/members", nil, bobCookie)
|
||||
if afterRR.Code != http.StatusOK {
|
||||
t.Fatalf("bob members after accept status = %d body = %s", afterRR.Code, afterRR.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestrictedDrawingGrantAllowsViewNotEdit(t *testing.T) {
|
||||
api, cleanup := newTestAPI(t)
|
||||
defer cleanup()
|
||||
|
||||
aliceCookie, _, aliceTeam := signup(t, api, "alice@example.com")
|
||||
bobCookie, _, _ := signup(t, api, "bob@example.com")
|
||||
|
||||
createRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
|
||||
"team_id": aliceTeam.ID,
|
||||
"title": "Restricted map",
|
||||
"visibility": "restricted",
|
||||
}, aliceCookie)
|
||||
if createRR.Code != http.StatusCreated {
|
||||
t.Fatalf("create drawing status = %d body = %s", createRR.Code, createRR.Body.String())
|
||||
}
|
||||
var drawing Drawing
|
||||
if err := json.Unmarshal(createRR.Body.Bytes(), &drawing); err != nil {
|
||||
t.Fatalf("drawing decode error = %v", err)
|
||||
}
|
||||
|
||||
noAccessRR := doJSON(t, api, http.MethodGet, "/drawings/"+drawing.ID, nil, bobCookie)
|
||||
if noAccessRR.Code != http.StatusForbidden {
|
||||
t.Fatalf("bob get before grant status = %d body = %s", noAccessRR.Code, noAccessRR.Body.String())
|
||||
}
|
||||
|
||||
grantRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/permissions", map[string]any{
|
||||
"subject_type": "user",
|
||||
"email": "bob@example.com",
|
||||
"permission": "view",
|
||||
}, aliceCookie)
|
||||
if grantRR.Code != http.StatusCreated {
|
||||
t.Fatalf("grant status = %d body = %s", grantRR.Code, grantRR.Body.String())
|
||||
}
|
||||
|
||||
getRR := doJSON(t, api, http.MethodGet, "/drawings/"+drawing.ID, nil, bobCookie)
|
||||
if getRR.Code != http.StatusOK {
|
||||
t.Fatalf("bob get after grant status = %d body = %s", getRR.Code, getRR.Body.String())
|
||||
}
|
||||
|
||||
editRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/revisions", map[string]any{
|
||||
"snapshot": map[string]any{"type": "excalidraw", "elements": []any{}},
|
||||
}, bobCookie)
|
||||
if editRR.Code != http.StatusForbidden {
|
||||
t.Fatalf("bob edit with view grant status = %d body = %s", editRR.Code, editRR.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestShareLinkAllowsUnauthenticatedDrawingRead(t *testing.T) {
|
||||
api, cleanup := newTestAPI(t)
|
||||
defer cleanup()
|
||||
|
||||
aliceCookie, _, aliceTeam := signup(t, api, "alice@example.com")
|
||||
createRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
|
||||
"team_id": aliceTeam.ID,
|
||||
"title": "Shared map",
|
||||
}, aliceCookie)
|
||||
if createRR.Code != http.StatusCreated {
|
||||
t.Fatalf("create drawing status = %d body = %s", createRR.Code, createRR.Body.String())
|
||||
}
|
||||
var drawing Drawing
|
||||
if err := json.Unmarshal(createRR.Body.Bytes(), &drawing); err != nil {
|
||||
t.Fatalf("drawing decode error = %v", err)
|
||||
}
|
||||
|
||||
shareRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/share-links", map[string]any{
|
||||
"permission": "view",
|
||||
}, aliceCookie)
|
||||
if shareRR.Code != http.StatusCreated {
|
||||
t.Fatalf("share status = %d body = %s", shareRR.Code, shareRR.Body.String())
|
||||
}
|
||||
var share struct {
|
||||
ShareLink ShareLink `json:"share_link"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
if err := json.Unmarshal(shareRR.Body.Bytes(), &share); err != nil {
|
||||
t.Fatalf("share decode error = %v", err)
|
||||
}
|
||||
if share.Token == "" || share.ShareLink.TokenHash != "" {
|
||||
t.Fatalf("unexpected share response: %#v", share)
|
||||
}
|
||||
|
||||
publicRR := doJSON(t, api, http.MethodGet, "/shared/"+share.Token, nil)
|
||||
if publicRR.Code != http.StatusOK {
|
||||
t.Fatalf("public shared status = %d body = %s", publicRR.Code, publicRR.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmbedsRejectUnsafeURLs(t *testing.T) {
|
||||
api, cleanup := newTestAPI(t)
|
||||
defer cleanup()
|
||||
|
||||
cookie, _, team := signup(t, api, "alice@example.com")
|
||||
createRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
|
||||
"team_id": team.ID,
|
||||
"title": "Embed map",
|
||||
}, cookie)
|
||||
if createRR.Code != http.StatusCreated {
|
||||
t.Fatalf("create drawing status = %d body = %s", createRR.Code, createRR.Body.String())
|
||||
}
|
||||
var drawing Drawing
|
||||
if err := json.Unmarshal(createRR.Body.Bytes(), &drawing); err != nil {
|
||||
t.Fatalf("drawing decode error = %v", err)
|
||||
}
|
||||
|
||||
for _, unsafeURL := range []string{"javascript:alert(1)", "http://127.0.0.1/admin", "http://localhost:3002"} {
|
||||
rr := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/embeds", map[string]any{
|
||||
"source_url": unsafeURL,
|
||||
"embed_type": "link",
|
||||
}, cookie)
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("unsafe url %q status = %d body = %s", unsafeURL, rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
safeRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/embeds", map[string]any{
|
||||
"source_url": "https://example.com/roadmap",
|
||||
"embed_type": "link",
|
||||
}, cookie)
|
||||
if safeRR.Code != http.StatusCreated {
|
||||
t.Fatalf("safe embed status = %d body = %s", safeRR.Code, safeRR.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetsAndLinkReferences(t *testing.T) {
|
||||
api, cleanup := newTestAPI(t)
|
||||
defer cleanup()
|
||||
|
||||
cookie, _, team := signup(t, api, "alice@example.com")
|
||||
projectRR := doJSON(t, api, http.MethodPost, "/projects", map[string]any{
|
||||
"team_id": team.ID,
|
||||
"name": "Roadmap",
|
||||
}, cookie)
|
||||
if projectRR.Code != http.StatusCreated {
|
||||
t.Fatalf("project status = %d body = %s", projectRR.Code, projectRR.Body.String())
|
||||
}
|
||||
var project Project
|
||||
if err := json.Unmarshal(projectRR.Body.Bytes(), &project); err != nil {
|
||||
t.Fatalf("project decode error = %v", err)
|
||||
}
|
||||
drawingRR := doJSON(t, api, http.MethodPost, "/drawings", map[string]any{
|
||||
"team_id": team.ID,
|
||||
"title": "Linked map",
|
||||
}, cookie)
|
||||
if drawingRR.Code != http.StatusCreated {
|
||||
t.Fatalf("drawing status = %d body = %s", drawingRR.Code, drawingRR.Body.String())
|
||||
}
|
||||
var drawing Drawing
|
||||
if err := json.Unmarshal(drawingRR.Body.Bytes(), &drawing); err != nil {
|
||||
t.Fatalf("drawing decode error = %v", err)
|
||||
}
|
||||
|
||||
assetRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/assets", map[string]any{
|
||||
"kind": "attachment",
|
||||
"mime_type": "image/png",
|
||||
"size": 2048,
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
}, cookie)
|
||||
if assetRR.Code != http.StatusCreated {
|
||||
t.Fatalf("asset status = %d body = %s", assetRR.Code, assetRR.Body.String())
|
||||
}
|
||||
|
||||
linkRR := doJSON(t, api, http.MethodPost, "/drawings/"+drawing.ID+"/links", map[string]any{
|
||||
"target_resource_type": "project",
|
||||
"target_resource_id": project.ID,
|
||||
"label": "Roadmap project",
|
||||
}, cookie)
|
||||
if linkRR.Code != http.StatusCreated {
|
||||
t.Fatalf("link status = %d body = %s", linkRR.Code, linkRR.Body.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user