feat(workspace): decouple folder sharing with independent mount points & real-time sync
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
This commit is contained in:
1 parent
1776e87029
commit
6c1898d489
5 files changed
+225
-13
No files matched your search
@@ -989,5 +989,167 @@ func TestSSEGlobalEndpoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecoupledFolderSharingAPI(t *testing.T) {
|
||||
srv, store, tokenA := setupTestServer(t)
|
||||
router := srv.Router()
|
||||
|
||||
userB, err := store.CreateUser("user_b@example.com", "hash", "User B", "member")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create user B: %v", err)
|
||||
}
|
||||
tokenB, err := auth.GenerateToken(srv.cfg.JWTSecret, userB.ID, userB.Email, userB.DisplayName)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate token B: %v", err)
|
||||
}
|
||||
|
||||
// 1. User A creates a shared folder "TeamProjekt"
|
||||
shPayload := map[string]string{"name": "TeamProjekt", "mountPath": "TeamProjekt"}
|
||||
b, _ := json.Marshal(shPayload)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/workspace/folders/shared", bytes.NewReader(b))
|
||||
req.Header.Set("Authorization", "Bearer "+tokenA)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create shared folder failed: %s", rec.Body.String())
|
||||
}
|
||||
var shFolder domain.SharedFolder
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &shFolder)
|
||||
|
||||
// 2. User A creates a document inside "TeamProjekt"
|
||||
docPayload := map[string]interface{}{
|
||||
"title": "Spezifikation",
|
||||
"folderPath": "TeamProjekt",
|
||||
"blocks": []domain.DocumentBlock{
|
||||
{ID: "blk_1", Type: "text", Content: "Projekt Details", Level: 0, Position: 1000},
|
||||
},
|
||||
}
|
||||
b, _ = json.Marshal(docPayload)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/documents", bytes.NewReader(b))
|
||||
req.Header.Set("Authorization", "Bearer "+tokenA)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create doc failed: %s", rec.Body.String())
|
||||
}
|
||||
var createdDoc domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &createdDoc)
|
||||
|
||||
// 3. User A shares the folder with User B (write permission)
|
||||
sharePayload := map[string]interface{}{
|
||||
"userId": userB.ID,
|
||||
"targetType": "user",
|
||||
"permission": "write",
|
||||
}
|
||||
b, _ = json.Marshal(sharePayload)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/workspace/folders/shared/"+shFolder.ID+"/shares", bytes.NewReader(b))
|
||||
req.Header.Set("Authorization", "Bearer "+tokenA)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("share folder failed: %s", rec.Body.String())
|
||||
}
|
||||
|
||||
// 4. User B gets their documents list -> must see the document in "TeamProjekt"
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/documents", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+tokenB)
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("get docs userB failed: %s", rec.Body.String())
|
||||
}
|
||||
var docsB []domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &docsB)
|
||||
var foundDocB *domain.Document
|
||||
for i := range docsB {
|
||||
if docsB[i].ID == createdDoc.ID {
|
||||
foundDocB = &docsB[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundDocB == nil {
|
||||
t.Fatalf("User B did not see shared document!")
|
||||
}
|
||||
if foundDocB.FolderPath != "TeamProjekt" {
|
||||
t.Fatalf("expected User B initial folderPath 'TeamProjekt', got %q", foundDocB.FolderPath)
|
||||
}
|
||||
|
||||
// 5. User B moves their mount point to "MeineOrdner/TeamProjekt"
|
||||
movePayload := map[string]string{
|
||||
"sourcePath": "TeamProjekt",
|
||||
"targetParentPath": "MeineOrdner",
|
||||
}
|
||||
b, _ = json.Marshal(movePayload)
|
||||
req = httptest.NewRequest(http.MethodPost, "/api/v1/folders/move", bytes.NewReader(b))
|
||||
req.Header.Set("Authorization", "Bearer "+tokenB)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User B move folder failed: %s", rec.Body.String())
|
||||
}
|
||||
|
||||
// 6. DECOUPLED CHECK:
|
||||
// User B's document path must now be "MeineOrdner/TeamProjekt"
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+createdDoc.ID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+tokenB)
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User B get doc failed: %s", rec.Body.String())
|
||||
}
|
||||
var docBAfterMove domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &docBAfterMove)
|
||||
if docBAfterMove.FolderPath != "MeineOrdner/TeamProjekt" {
|
||||
t.Fatalf("expected User B folderPath 'MeineOrdner/TeamProjekt', got %q", docBAfterMove.FolderPath)
|
||||
}
|
||||
|
||||
// User A's document path must STILL be "TeamProjekt" (completely decoupled!)
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+createdDoc.ID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+tokenA)
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User A get doc failed: %s", rec.Body.String())
|
||||
}
|
||||
var docAAfterMove domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &docAAfterMove)
|
||||
if docAAfterMove.FolderPath != "TeamProjekt" {
|
||||
t.Fatalf("User A's folder hierarchy was altered! Expected 'TeamProjekt', got %q", docAAfterMove.FolderPath)
|
||||
}
|
||||
|
||||
// 7. Synchronous contents check:
|
||||
// User B moves the document into a subfolder: "MeineOrdner/TeamProjekt/Archiv"
|
||||
moveDocPayload := map[string]string{
|
||||
"folderPath": "MeineOrdner/TeamProjekt/Archiv",
|
||||
}
|
||||
b, _ = json.Marshal(moveDocPayload)
|
||||
req = httptest.NewRequest(http.MethodPut, "/api/v1/documents/"+createdDoc.ID+"/move", bytes.NewReader(b))
|
||||
req.Header.Set("Authorization", "Bearer "+tokenB)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User B move doc into subfolder failed: %s", rec.Body.String())
|
||||
}
|
||||
|
||||
// User A must now see the document synchronized at "TeamProjekt/Archiv"!
|
||||
req = httptest.NewRequest(http.MethodGet, "/api/v1/documents/"+createdDoc.ID, nil)
|
||||
req.Header.Set("Authorization", "Bearer "+tokenA)
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("User A get doc failed: %s", rec.Body.String())
|
||||
}
|
||||
var docASynced domain.Document
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &docASynced)
|
||||
if docASynced.FolderPath != "TeamProjekt/Archiv" {
|
||||
t.Fatalf("expected User A to see subfolder sync at 'TeamProjekt/Archiv', got %q", docASynced.FolderPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user