ops design and app
add more comments feature, more tags feature, and other frontend updates
This commit is contained in:
@@ -384,17 +384,36 @@ func passwordResetTokenFromEmail(t *testing.T, body string) string {
|
||||
|
||||
func (s *apiTestServer) token(t *testing.T, scopes ...auth.Scope) string {
|
||||
t.Helper()
|
||||
return s.tokenForUser(t, s.userID, scopes...)
|
||||
}
|
||||
|
||||
created, err := s.auth.CreateAPIKey(context.Background(), s.userID, "test token", scopes, nil)
|
||||
func (s *apiTestServer) tokenForUser(t *testing.T, userID string, scopes ...auth.Scope) string {
|
||||
t.Helper()
|
||||
|
||||
created, err := s.auth.CreateAPIKey(context.Background(), userID, "test token", scopes, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("create api token: %v", err)
|
||||
}
|
||||
return created.Token
|
||||
}
|
||||
|
||||
func (s *apiTestServer) viewerUser(t *testing.T) auth.User {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
admin := auth.Principal{UserID: s.userID, Role: auth.RoleAdmin}
|
||||
if _, err := s.auth.UpdateSignupsEnabled(ctx, admin, true); err != nil {
|
||||
t.Fatalf("enable signups: %v", err)
|
||||
}
|
||||
user, err := s.auth.RegisterPasswordUser(ctx, "viewer@example.com", "Viewer", "correct horse battery staple", "viewer")
|
||||
if err != nil {
|
||||
t.Fatalf("create viewer: %v", err)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func TestAPIUploadStoresAndServesAttachment(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeDocsWrite)
|
||||
token := server.token(t, auth.ScopeDocsRead, auth.ScopeDocsWrite)
|
||||
|
||||
body, contentType := multipartBody(t, "file", `unsafe"name.pdf`, []byte("%PDF-1.4\n"))
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/uploads", body)
|
||||
@@ -446,6 +465,41 @@ func TestAPIUploadStoresAndServesAttachment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIDocumentsArePrivateUntilResourceGrant(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
viewer := server.viewerUser(t)
|
||||
token := server.tokenForUser(t, viewer.ID, auth.ScopeDocsRead)
|
||||
|
||||
listRequest := httptest.NewRequest(http.MethodGet, "/api/documents", nil)
|
||||
listRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
listRecorder := httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(listRecorder, listRequest)
|
||||
if listRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("documents status = %d, want %d; body=%s", listRecorder.Code, http.StatusOK, listRecorder.Body.String())
|
||||
}
|
||||
if strings.Contains(listRecorder.Body.String(), "hello.md") {
|
||||
t.Fatalf("private document leaked before grant: %s", listRecorder.Body.String())
|
||||
}
|
||||
|
||||
admin := auth.Principal{UserID: server.userID, Role: auth.RoleAdmin}
|
||||
if _, err := server.auth.UpdateResourcePermissions(context.Background(), admin, auth.ResourceFolder, "", []auth.ResourcePermissionUpdate{
|
||||
{UserID: viewer.ID, Permission: auth.PermissionRead},
|
||||
}); err != nil {
|
||||
t.Fatalf("grant root folder read: %v", err)
|
||||
}
|
||||
|
||||
listRequest = httptest.NewRequest(http.MethodGet, "/api/documents", nil)
|
||||
listRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
listRecorder = httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(listRecorder, listRequest)
|
||||
if listRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("documents after grant status = %d, want %d; body=%s", listRecorder.Code, http.StatusOK, listRecorder.Body.String())
|
||||
}
|
||||
if !strings.Contains(listRecorder.Body.String(), "hello.md") {
|
||||
t.Fatalf("document missing after folder grant: %s", listRecorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIUploadRejectsTokenWithoutDocsWriteScope(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeSyncRead, auth.ScopeSyncWrite)
|
||||
@@ -463,6 +517,41 @@ func TestAPIUploadRejectsTokenWithoutDocsWriteScope(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPISyncSnapshotInheritsResourcePermissions(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
viewer := server.viewerUser(t)
|
||||
token := server.tokenForUser(t, viewer.ID, auth.ScopeSyncRead, auth.ScopeSyncWrite)
|
||||
|
||||
initRequest := jsonRequest(http.MethodPost, "/api/sync/init", map[string]string{"deviceId": "device-1"})
|
||||
initRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
initRecorder := httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(initRecorder, initRequest)
|
||||
if initRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("sync init status = %d, want %d; body=%s", initRecorder.Code, http.StatusOK, initRecorder.Body.String())
|
||||
}
|
||||
if strings.Contains(initRecorder.Body.String(), "hello.md") {
|
||||
t.Fatalf("private sync snapshot leaked before grant: %s", initRecorder.Body.String())
|
||||
}
|
||||
|
||||
admin := auth.Principal{UserID: server.userID, Role: auth.RoleAdmin}
|
||||
if _, err := server.auth.UpdateResourcePermissions(context.Background(), admin, auth.ResourceFolder, "", []auth.ResourcePermissionUpdate{
|
||||
{UserID: viewer.ID, Permission: auth.PermissionRead},
|
||||
}); err != nil {
|
||||
t.Fatalf("grant root folder read: %v", err)
|
||||
}
|
||||
|
||||
initRequest = jsonRequest(http.MethodPost, "/api/sync/init", map[string]string{"deviceId": "device-2"})
|
||||
initRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
initRecorder = httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(initRecorder, initRequest)
|
||||
if initRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("sync init after grant status = %d, want %d; body=%s", initRecorder.Code, http.StatusOK, initRecorder.Body.String())
|
||||
}
|
||||
if !strings.Contains(initRecorder.Body.String(), "hello.md") {
|
||||
t.Fatalf("sync snapshot missing document after folder grant: %s", initRecorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPISyncInitAndContentFetchUseBearerScopes(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeSyncRead, auth.ScopeSyncWrite)
|
||||
@@ -575,10 +664,12 @@ func TestAPISyncDeltaReturnsServerChanges(t *testing.T) {
|
||||
|
||||
func TestAPIDocumentSaveReturnsConflictForStaleBaseHash(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeDocsWrite)
|
||||
token := server.token(t, auth.ScopeDocsRead, auth.ScopeDocsWrite)
|
||||
|
||||
documentsRequest := httptest.NewRequest(http.MethodGet, "/api/documents", nil)
|
||||
documentsRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
documents := httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(documents, httptest.NewRequest(http.MethodGet, "/api/documents", nil))
|
||||
server.handler.ServeHTTP(documents, documentsRequest)
|
||||
if documents.Code != http.StatusOK {
|
||||
t.Fatalf("documents status = %d, want %d", documents.Code, http.StatusOK)
|
||||
}
|
||||
@@ -637,9 +728,44 @@ func TestAPIDocumentSaveReturnsConflictForStaleBaseHash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIDocumentCreateGrantsCreatorAdminPermission(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeDocsRead, auth.ScopeDocsWrite)
|
||||
|
||||
save := jsonRequest(http.MethodPost, "/api/documents/inbox/new-note", map[string]string{
|
||||
"content": "# New Note\n\nCreated in browser",
|
||||
})
|
||||
save.Header.Set("Authorization", "Bearer "+token)
|
||||
saveRecorder := httptest.NewRecorder()
|
||||
|
||||
server.handler.ServeHTTP(saveRecorder, save)
|
||||
|
||||
if saveRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("save status = %d, want %d; body=%s", saveRecorder.Code, http.StatusOK, saveRecorder.Body.String())
|
||||
}
|
||||
|
||||
permissions := httptest.NewRequest(http.MethodGet, "/api/permissions?resourceType=document&resourceId=inbox/new-note.md", nil)
|
||||
permissions.Header.Set("Authorization", "Bearer "+token)
|
||||
permissionsRecorder := httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(permissionsRecorder, permissions)
|
||||
|
||||
if permissionsRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("permissions status = %d, want %d; body=%s", permissionsRecorder.Code, http.StatusOK, permissionsRecorder.Body.String())
|
||||
}
|
||||
var payload struct {
|
||||
Permissions []auth.ResourcePermission `json:"permissions"`
|
||||
}
|
||||
if err := json.Unmarshal(permissionsRecorder.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode permissions: %v", err)
|
||||
}
|
||||
if len(payload.Permissions) != 1 || payload.Permissions[0].UserID != server.userID || payload.Permissions[0].Permission != auth.PermissionAdmin {
|
||||
t.Fatalf("permissions = %#v, want creator admin grant", payload.Permissions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIDocumentArchiveSupportsNestedPaths(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeDocsWrite)
|
||||
token := server.token(t, auth.ScopeDocsRead, auth.ScopeDocsWrite)
|
||||
ctx := context.Background()
|
||||
|
||||
nestedDir := filepath.Join(server.root, "content", "guide")
|
||||
@@ -663,8 +789,10 @@ func TestAPIDocumentArchiveSupportsNestedPaths(t *testing.T) {
|
||||
t.Fatalf("archive status = %d, want %d; body=%s", recorder.Code, http.StatusOK, recorder.Body.String())
|
||||
}
|
||||
|
||||
documentsRequest := httptest.NewRequest(http.MethodGet, "/api/documents", nil)
|
||||
documentsRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
documents := httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(documents, httptest.NewRequest(http.MethodGet, "/api/documents", nil))
|
||||
server.handler.ServeHTTP(documents, documentsRequest)
|
||||
if documents.Code != http.StatusOK {
|
||||
t.Fatalf("documents status = %d, want %d", documents.Code, http.StatusOK)
|
||||
}
|
||||
@@ -675,7 +803,7 @@ func TestAPIDocumentArchiveSupportsNestedPaths(t *testing.T) {
|
||||
|
||||
func TestAPIDocumentArchiveUnescapesSpecialCharacters(t *testing.T) {
|
||||
server := newAPITestServer(t)
|
||||
token := server.token(t, auth.ScopeDocsWrite)
|
||||
token := server.token(t, auth.ScopeDocsRead, auth.ScopeDocsWrite)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := os.WriteFile(filepath.Join(server.root, "content", "@dani.md"), []byte("# @dani\n\nProfile"), 0o644); err != nil {
|
||||
@@ -695,8 +823,10 @@ func TestAPIDocumentArchiveUnescapesSpecialCharacters(t *testing.T) {
|
||||
t.Fatalf("archive status = %d, want %d; body=%s", recorder.Code, http.StatusOK, recorder.Body.String())
|
||||
}
|
||||
|
||||
documentsRequest := httptest.NewRequest(http.MethodGet, "/api/documents", nil)
|
||||
documentsRequest.Header.Set("Authorization", "Bearer "+token)
|
||||
documents := httptest.NewRecorder()
|
||||
server.handler.ServeHTTP(documents, httptest.NewRequest(http.MethodGet, "/api/documents", nil))
|
||||
server.handler.ServeHTTP(documents, documentsRequest)
|
||||
if documents.Code != http.StatusOK {
|
||||
t.Fatalf("documents status = %d, want %d", documents.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user