From fd072d39e2566c98f8748da390d1dcd89c634289 Mon Sep 17 00:00:00 2001 From: David Christofas Date: Tue, 14 Sep 2021 17:00:52 +0200 Subject: [PATCH] add filter-files to the dav REPORT API I added filter-files to the dav REPORT API. This enables the listing of favorites. --- changelog/unreleased/list-favorites.md | 7 + .../http/services/owncloud/ocdav/ocdav.go | 11 +- .../http/services/owncloud/ocdav/propfind.go | 4 +- .../http/services/owncloud/ocdav/proppatch.go | 28 ++++ .../services/owncloud/ocdav/publicfile.go | 2 +- .../http/services/owncloud/ocdav/report.go | 99 ++++++++++++- .../services/owncloud/ocdav/report_test.go | 63 +++++++++ .../http/services/owncloud/ocdav/versions.go | 2 +- pkg/storage/favorite/favorite.go | 71 ++++++++++ pkg/storage/favorite/favorite_test.go | 130 ++++++++++++++++++ .../expected-failures-on-OCIS-storage.md | 6 - .../expected-failures-on-S3NG-storage.md | 6 - 12 files changed, 408 insertions(+), 21 deletions(-) create mode 100644 changelog/unreleased/list-favorites.md create mode 100644 internal/http/services/owncloud/ocdav/report_test.go create mode 100644 pkg/storage/favorite/favorite.go create mode 100644 pkg/storage/favorite/favorite_test.go diff --git a/changelog/unreleased/list-favorites.md b/changelog/unreleased/list-favorites.md new file mode 100644 index 00000000000..859b2271d1a --- /dev/null +++ b/changelog/unreleased/list-favorites.md @@ -0,0 +1,7 @@ +Enhancement: Implement listing favorites via the dav report API + +Added filter-files to the dav REPORT API. This enables the listing of +favorites. + +https://github.com/cs3org/reva/pull/2071 +https://github.com/cs3org/reva/pull/2086 diff --git a/internal/http/services/owncloud/ocdav/ocdav.go b/internal/http/services/owncloud/ocdav/ocdav.go index 812040b6b44..3fa1eadb5d7 100644 --- a/internal/http/services/owncloud/ocdav/ocdav.go +++ b/internal/http/services/owncloud/ocdav/ocdav.go @@ -40,6 +40,7 @@ import ( "github.com/cs3org/reva/pkg/rhttp/global" "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/sharedconf" + "github.com/cs3org/reva/pkg/storage/favorite" "github.com/cs3org/reva/pkg/storage/utils/templates" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -110,10 +111,11 @@ func (c *Config) init() { } type svc struct { - c *Config - webDavHandler *WebDavHandler - davHandler *DavHandler - client *http.Client + c *Config + webDavHandler *WebDavHandler + davHandler *DavHandler + favoritesManager favorite.Manager + client *http.Client } // New returns a new ocdav @@ -133,6 +135,7 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) rhttp.Timeout(time.Duration(conf.Timeout*int64(time.Second))), rhttp.Insecure(conf.Insecure), ), + favoritesManager: favorite.NewInMemoryManager(), } // initialize handlers and set default configs if err := s.webDavHandler.init(conf.WebdavNamespace, true); err != nil { diff --git a/internal/http/services/owncloud/ocdav/propfind.go b/internal/http/services/owncloud/ocdav/propfind.go index 0e9c99be888..e95007abc90 100644 --- a/internal/http/services/owncloud/ocdav/propfind.go +++ b/internal/http/services/owncloud/ocdav/propfind.go @@ -134,7 +134,7 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space } func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) { - propRes, err := s.formatPropfind(ctx, &pf, resourceInfos, namespace) + propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace) if err != nil { log.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) @@ -342,7 +342,7 @@ func readPropfind(r io.Reader) (pf propfindXML, status int, err error) { return pf, 0, nil } -func (s *svc) formatPropfind(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) { +func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) { responses := make([]*responseXML, 0, len(mds)) for i := range mds { res, err := s.mdToPropResponse(ctx, pf, mds[i], ns) diff --git a/internal/http/services/owncloud/ocdav/proppatch.go b/internal/http/services/owncloud/ocdav/proppatch.go index 2fb3a0f6a39..d7d5ce4a429 100644 --- a/internal/http/services/owncloud/ocdav/proppatch.go +++ b/internal/http/services/owncloud/ocdav/proppatch.go @@ -30,6 +30,7 @@ import ( rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" rtrace "github.com/cs3org/reva/pkg/trace" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -232,6 +233,19 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt HandleErrorStatus(&log, w, res.Status) return nil, nil, false } + if key == "http://owncloud.org/ns/favorite" { + statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + currentUser := ctxpkg.ContextMustGetUser(ctx) + err = s.favoritesManager.UnsetFavorite(ctx, currentUser.Id, statRes.Info.Id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + } removedProps = append(removedProps, propNameXML) } else { sreq.ArbitraryMetadata.Metadata[key] = value @@ -259,6 +273,20 @@ func (s *svc) handleProppatch(ctx context.Context, w http.ResponseWriter, r *htt acceptedProps = append(acceptedProps, propNameXML) delete(sreq.ArbitraryMetadata.Metadata, key) + + if key == "http://owncloud.org/ns/favorite" { + statRes, err := c.Stat(ctx, &provider.StatRequest{Ref: ref}) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + currentUser := ctxpkg.ContextMustGetUser(ctx) + err = s.favoritesManager.SetFavorite(ctx, currentUser.Id, statRes.Info.Id) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return nil, nil, false + } + } } } // FIXME: in case of error, need to set all properties back to the original state, diff --git a/internal/http/services/owncloud/ocdav/publicfile.go b/internal/http/services/owncloud/ocdav/publicfile.go index 797f1e4d530..f2315e90cfd 100644 --- a/internal/http/services/owncloud/ocdav/publicfile.go +++ b/internal/http/services/owncloud/ocdav/publicfile.go @@ -186,7 +186,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo) - propRes, err := s.formatPropfind(ctx, &pf, infos, ns) + propRes, err := s.multistatusResponse(ctx, &pf, infos, ns) if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/internal/http/services/owncloud/ocdav/report.go b/internal/http/services/owncloud/ocdav/report.go index 5807d5094df..1ae453480ba 100644 --- a/internal/http/services/owncloud/ocdav/report.go +++ b/internal/http/services/owncloud/ocdav/report.go @@ -22,8 +22,18 @@ import ( "encoding/xml" "io" "net/http" + "strings" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/pkg/appctx" + ctxpkg "github.com/cs3org/reva/pkg/ctx" +) + +const ( + elementNameSearchFiles = "search-files" + elementNameFilterFiles = "filter-files" ) func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) { @@ -42,6 +52,11 @@ func (s *svc) handleReport(w http.ResponseWriter, r *http.Request, ns string) { return } + if rep.FilterFiles != nil { + s.doFilterFiles(w, r, rep.FilterFiles, ns) + return + } + // TODO(jfd): implement report w.WriteHeader(http.StatusNotImplemented) @@ -59,9 +74,72 @@ func (s *svc) doSearchFiles(w http.ResponseWriter, r *http.Request, sf *reportSe w.WriteHeader(http.StatusNotImplemented) } +func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFilterFiles, namespace string) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + if ff.Rules.Favorite { + // List the users favorite resources. + currentUser := ctxpkg.ContextMustGetUser(ctx) + favorites, err := s.favoritesManager.ListFavorites(ctx, currentUser.Id) + if err != nil { + log.Error().Err(err).Msg("error getting favorites") + w.WriteHeader(http.StatusInternalServerError) + return + } + + client, err := s.getClient() + if err != nil { + log.Error().Err(err).Msg("error getting gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + infos := make([]*provider.ResourceInfo, 0, len(favorites)) + for i := range favorites { + statRes, err := client.Stat(ctx, &providerv1beta1.StatRequest{Ref: &providerv1beta1.Reference{ResourceId: favorites[i]}}) + if err != nil { + log.Error().Err(err).Msg("error getting resource info") + w.WriteHeader(http.StatusInternalServerError) + return + } + if statRes.Status.Code != rpcv1beta1.Code_CODE_OK { + HandleErrorStatus(log, w, statRes.Status) + return + } + + // The paths we receive have the format /user// + // We only want the `` part. Thus we remove the /user// part. + parts := strings.SplitN(statRes.Info.Path, "/", 4) + if len(parts) != 4 { + log.Error().Str("path", statRes.Info.Path).Msg("path doesn't have the expected format") + w.WriteHeader(http.StatusInternalServerError) + return + } + statRes.Info.Path = parts[3] + + infos = append(infos, statRes.Info) + } + + responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace) + if err != nil { + log.Error().Err(err).Msg("error formatting propfind") + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set(HeaderDav, "1, 3, extended-mkcol") + w.Header().Set(HeaderContentType, "application/xml; charset=utf-8") + w.WriteHeader(http.StatusMultiStatus) + if _, err := w.Write([]byte(responsesXML)); err != nil { + log.Err(err).Msg("error writing response") + } + } +} + type report struct { SearchFiles *reportSearchFiles // FilterFiles TODO add this for tag based search + FilterFiles *reportFilterFiles `xml:"filter-files"` } type reportSearchFiles struct { XMLName xml.Name `xml:"search-files"` @@ -75,6 +153,18 @@ type reportSearchFilesSearch struct { Offset int `xml:"offset"` } +type reportFilterFiles struct { + XMLName xml.Name `xml:"filter-files"` + Lang string `xml:"xml:lang,attr,omitempty"` + Prop propfindProps `xml:"DAV: prop"` + Rules reportFilterFilesRules `xml:"filter-rules"` +} + +type reportFilterFilesRules struct { + Favorite bool `xml:"favorite"` + SystemTag int `xml:"systemtag"` +} + func readReport(r io.Reader) (rep *report, status int, err error) { decoder := xml.NewDecoder(r) rep = &report{} @@ -89,13 +179,20 @@ func readReport(r io.Reader) (rep *report, status int, err error) { } if v, ok := t.(xml.StartElement); ok { - if v.Name.Local == "search-files" { + if v.Name.Local == elementNameSearchFiles { var repSF reportSearchFiles err = decoder.DecodeElement(&repSF, &v) if err != nil { return nil, http.StatusBadRequest, err } rep.SearchFiles = &repSF + } else if v.Name.Local == elementNameFilterFiles { + var repFF reportFilterFiles + err = decoder.DecodeElement(&repFF, &v) + if err != nil { + return nil, http.StatusBadRequest, err + } + rep.FilterFiles = &repFF } } } diff --git a/internal/http/services/owncloud/ocdav/report_test.go b/internal/http/services/owncloud/ocdav/report_test.go new file mode 100644 index 00000000000..42b5d64a90e --- /dev/null +++ b/internal/http/services/owncloud/ocdav/report_test.go @@ -0,0 +1,63 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package ocdav + +import ( + "strings" + "testing" +) + +func TestUnmarshallReportFilterFiles(t *testing.T) { + ffXML := ` + + + + + + + + + + + + + + + + + + 1 + +` + + reader := strings.NewReader(ffXML) + + report, status, err := readReport(reader) + if status != 0 || err != nil { + t.Error("Failed to unmarshal filter-files xml") + } + + if report.FilterFiles == nil { + t.Error("Failed to unmarshal filter-files xml. FilterFiles is nil") + } + + if report.FilterFiles.Rules.Favorite == false { + t.Error("Failed to correctly unmarshal filter-rules. Favorite is expected to be true.") + } +} diff --git a/internal/http/services/owncloud/ocdav/versions.go b/internal/http/services/owncloud/ocdav/versions.go index b9af216c47f..459e995620c 100644 --- a/internal/http/services/owncloud/ocdav/versions.go +++ b/internal/http/services/owncloud/ocdav/versions.go @@ -164,7 +164,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request, infos = append(infos, vi) } - propRes, err := s.formatPropfind(ctx, &pf, infos, "") + propRes, err := s.multistatusResponse(ctx, &pf, infos, "") if err != nil { sublog.Error().Err(err).Msg("error formatting propfind") w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/storage/favorite/favorite.go b/pkg/storage/favorite/favorite.go new file mode 100644 index 00000000000..7e441d36870 --- /dev/null +++ b/pkg/storage/favorite/favorite.go @@ -0,0 +1,71 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package favorite + +import ( + "context" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// Manager defines an interface for a favorites manager. +type Manager interface { + // ListFavorites returns all resources that were favorited by a user. + ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceId, error) + // SetFavorite marks a resource as favorited by a user. + SetFavorite(ctx context.Context, userID *user.UserId, resourceID *provider.ResourceId) error + // UnsetFavorite unmarks a resource as favorited by a user. + UnsetFavorite(ctx context.Context, userID *user.UserId, resourceID *provider.ResourceId) error +} + +// NewInMemoryManager returns an instance of the in-memory favorites manager. +func NewInMemoryManager() Manager { + return InMemoryManager{favorites: make(map[string]map[string]*provider.ResourceId)} +} + +// InMemoryManager implements the Manager interface to manage favorites using an in-memory storage. +// This should not be used in production but can be used for tests. +type InMemoryManager struct { + favorites map[string]map[string]*provider.ResourceId +} + +// ListFavorites returns all resources that were favorited by a user. +func (m InMemoryManager) ListFavorites(ctx context.Context, userID *user.UserId) ([]*provider.ResourceId, error) { + favorites := make([]*provider.ResourceId, 0, len(m.favorites[userID.OpaqueId])) + for _, id := range m.favorites[userID.OpaqueId] { + favorites = append(favorites, id) + } + return favorites, nil +} + +// SetFavorite marks a resource as favorited by a user. +func (m InMemoryManager) SetFavorite(_ context.Context, userID *user.UserId, resourceID *provider.ResourceId) error { + if m.favorites[userID.OpaqueId] == nil { + m.favorites[userID.OpaqueId] = make(map[string]*provider.ResourceId) + } + m.favorites[userID.OpaqueId][resourceID.OpaqueId] = resourceID + return nil +} + +// UnsetFavorite unmarks a resource as favorited by a user. +func (m InMemoryManager) UnsetFavorite(_ context.Context, userID *user.UserId, resourceID *provider.ResourceId) error { + delete(m.favorites[userID.OpaqueId], resourceID.OpaqueId) + return nil +} diff --git a/pkg/storage/favorite/favorite_test.go b/pkg/storage/favorite/favorite_test.go new file mode 100644 index 00000000000..61da0cfe9fb --- /dev/null +++ b/pkg/storage/favorite/favorite_test.go @@ -0,0 +1,130 @@ +// Copyright 2018-2021 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package favorite + +import ( + "context" + "testing" + + user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" +) + +type environment struct { + userOne *user.User + userOneCtx context.Context + + userTwo *user.User + userTwoCtx context.Context + + userThree *user.User + userThreeCtx context.Context + + resourceInfoOne *provider.ResourceInfo + resourceInfoTwo *provider.ResourceInfo +} + +func createEnvironment() environment { + userOne := &user.User{Id: &user.UserId{OpaqueId: "userOne"}} + userTwo := &user.User{Id: &user.UserId{OpaqueId: "userTwo"}} + userThree := &user.User{Id: &user.UserId{OpaqueId: "userThree"}} + + resourceInfoOne := &provider.ResourceInfo{Id: &provider.ResourceId{OpaqueId: "resourceInfoOne"}} + resourceInfoTwo := &provider.ResourceInfo{Id: &provider.ResourceId{OpaqueId: "resourceInfoTwo"}} + + return environment{ + userOne: userOne, + userOneCtx: ctxpkg.ContextSetUser(context.Background(), userOne), + userTwo: userTwo, + userTwoCtx: ctxpkg.ContextSetUser(context.Background(), userTwo), + userThree: userThree, + userThreeCtx: ctxpkg.ContextSetUser(context.Background(), userThree), + + resourceInfoOne: resourceInfoOne, + resourceInfoTwo: resourceInfoTwo, + } +} + +func TestListFavorite(t *testing.T) { + env := createEnvironment() + sut := NewInMemoryManager() + + favorites, _ := sut.ListFavorites(env.userOneCtx, env.userOne.Id) + if len(favorites) != 0 { + t.Error("ListFavorites should not return anything when a user hasn't set a favorite") + } + + _ = sut.SetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + _ = sut.SetFavorite(env.userTwoCtx, env.userTwo.Id, env.resourceInfoOne.Id) + _ = sut.SetFavorite(env.userTwoCtx, env.userTwo.Id, env.resourceInfoTwo.Id) + + favorites, _ = sut.ListFavorites(env.userOneCtx, env.userOne.Id) + if len(favorites) != 1 { + t.Errorf("Expected %d favorites got %d", 1, len(favorites)) + } + + favorites, _ = sut.ListFavorites(env.userTwoCtx, env.userTwo.Id) + if len(favorites) != 2 { + t.Errorf("Expected %d favorites got %d", 2, len(favorites)) + } + + favorites, _ = sut.ListFavorites(env.userThreeCtx, env.userThree.Id) + if len(favorites) != 0 { + t.Errorf("Expected %d favorites got %d", 0, len(favorites)) + } +} + +func TestSetFavorite(t *testing.T) { + env := createEnvironment() + + sut := NewInMemoryManager() + + favorites, _ := sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenBefore := len(favorites) + + _ = sut.SetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + + favorites, _ = sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenAfter := len(favorites) + + if lenAfter-lenBefore != 1 { + t.Errorf("Setting a favorite should add 1 favorite but actually added %d", lenAfter-lenBefore) + } +} + +func TestUnsetFavorite(t *testing.T) { + env := createEnvironment() + + sut := NewInMemoryManager() + + _ = sut.SetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + favorites, _ := sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenBefore := len(favorites) + + _ = sut.UnsetFavorite(env.userOneCtx, env.userOne.Id, env.resourceInfoOne.Id) + + favorites, _ = sut.ListFavorites(env.userOneCtx, env.userOne.Id) + lenAfter := len(favorites) + + if lenAfter-lenBefore != -1 { + t.Errorf("Setting a favorite should remove 1 favorite but actually removed %d", lenAfter-lenBefore) + } +} diff --git a/tests/acceptance/expected-failures-on-OCIS-storage.md b/tests/acceptance/expected-failures-on-OCIS-storage.md index cabfea6de7e..1f068f189cc 100644 --- a/tests/acceptance/expected-failures-on-OCIS-storage.md +++ b/tests/acceptance/expected-failures-on-OCIS-storage.md @@ -989,18 +989,12 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L91) -- [apiFavorites/favorites.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L92) -- [apiFavorites/favorites.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L112) -- [apiFavorites/favorites.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L113) - [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) - [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) - [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) - [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) - [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) - [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) - [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) - [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35) diff --git a/tests/acceptance/expected-failures-on-S3NG-storage.md b/tests/acceptance/expected-failures-on-S3NG-storage.md index 527c5e90061..4b6f55a7003 100644 --- a/tests/acceptance/expected-failures-on-S3NG-storage.md +++ b/tests/acceptance/expected-failures-on-S3NG-storage.md @@ -991,18 +991,12 @@ Scenario Outline: search for entry with emoji by pattern - [apiWebdavOperations/search.feature:255](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiWebdavOperations/search.feature#L255) Scenario: search for entries across various folders by tags using REPORT method And other missing implementation of favorites -- [apiFavorites/favorites.feature:91](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L91) -- [apiFavorites/favorites.feature:92](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L92) -- [apiFavorites/favorites.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L112) -- [apiFavorites/favorites.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L113) - [apiFavorites/favorites.feature:128](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L128) - [apiFavorites/favorites.feature:129](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L129) - [apiFavorites/favorites.feature:148](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L148) - [apiFavorites/favorites.feature:149](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L149) - [apiFavorites/favorites.feature:176](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L176) - [apiFavorites/favorites.feature:177](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L177) -- [apiFavorites/favorites.feature:217](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L217) -- [apiFavorites/favorites.feature:218](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favorites.feature#L218) - [apiFavorites/favoritesSharingToShares.feature:21](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L21) - [apiFavorites/favoritesSharingToShares.feature:22](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L22) - [apiFavorites/favoritesSharingToShares.feature:35](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiFavorites/favoritesSharingToShares.feature#L35)