180 lines
5.6 KiB
Go
180 lines
5.6 KiB
Go
package rss
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v5"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/usememos/memos/internal/markdown"
|
|
storepb "github.com/usememos/memos/proto/gen/store"
|
|
"github.com/usememos/memos/store"
|
|
teststore "github.com/usememos/memos/store/test"
|
|
)
|
|
|
|
func TestPublicRSSExcludesComments(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := teststore.NewTestingStore(ctx, t)
|
|
defer stores.Close()
|
|
setInstanceAccessMode(ctx, t, stores, storepb.InstanceAccessMode_INSTANCE_ACCESS_MODE_PUBLIC)
|
|
|
|
user, err := stores.CreateUser(ctx, &store.User{
|
|
Username: "rss-comment-owner",
|
|
Role: store.RoleUser,
|
|
Email: "rss-comment-owner@example.com",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
parent, err := stores.CreateMemo(ctx, &store.Memo{
|
|
UID: "rss-public-parent",
|
|
CreatorID: user.ID,
|
|
Content: "public parent should stay in rss",
|
|
Visibility: store.Public,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = stores.CreateMemoComment(ctx, &store.Memo{
|
|
UID: "rss-public-comment",
|
|
CreatorID: user.ID,
|
|
Content: "public comment should not be in rss",
|
|
Visibility: store.Private,
|
|
}, parent.ID, user.ID)
|
|
require.NoError(t, err)
|
|
|
|
service := NewRSSService(stores, markdown.NewService())
|
|
|
|
exploreRSS := renderRSS(t, service, "/explore/rss.xml", "")
|
|
require.Contains(t, exploreRSS, "public parent should stay in rss")
|
|
require.NotContains(t, exploreRSS, "public comment should not be in rss")
|
|
|
|
userRSS := renderRSS(t, service, "/u/rss-comment-owner/rss.xml", user.Username)
|
|
require.Contains(t, userRSS, "public parent should stay in rss")
|
|
require.NotContains(t, userRSS, "public comment should not be in rss")
|
|
}
|
|
|
|
func TestPrivateInstanceDisablesRSS(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := teststore.NewTestingStore(ctx, t)
|
|
defer stores.Close()
|
|
setInstanceAccessMode(ctx, t, stores, storepb.InstanceAccessMode_INSTANCE_ACCESS_MODE_PRIVATE)
|
|
service := NewRSSService(stores, nil)
|
|
|
|
for _, test := range []struct {
|
|
name string
|
|
target string
|
|
username string
|
|
}{
|
|
{name: "explore", target: "/explore/rss.xml"},
|
|
{name: "user", target: "/u/alice/rss.xml", username: "alice"},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
e := echo.New()
|
|
req := httptest.NewRequest(http.MethodGet, test.target, strings.NewReader(""))
|
|
rec := httptest.NewRecorder()
|
|
c := e.NewContext(req, rec)
|
|
if test.username != "" {
|
|
c.SetPathValues(echo.PathValues{{Name: "username", Value: test.username}})
|
|
}
|
|
|
|
var err error
|
|
if test.username == "" {
|
|
err = service.GetExploreRSS(c)
|
|
} else {
|
|
err = service.GetUserRSS(c)
|
|
}
|
|
|
|
var httpError *echo.HTTPError
|
|
require.ErrorAs(t, err, &httpError)
|
|
require.Equal(t, http.StatusNotFound, httpError.Code)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRSSIfNoneMatchRemainsStableAcrossSeconds(t *testing.T) {
|
|
ctx := context.Background()
|
|
stores := teststore.NewTestingStore(ctx, t)
|
|
defer stores.Close()
|
|
setInstanceAccessMode(ctx, t, stores, storepb.InstanceAccessMode_INSTANCE_ACCESS_MODE_PUBLIC)
|
|
|
|
user, err := stores.CreateUser(ctx, &store.User{
|
|
Username: "rss-etag-owner",
|
|
Role: store.RoleUser,
|
|
Email: "rss-etag-owner@example.com",
|
|
})
|
|
require.NoError(t, err)
|
|
_, err = stores.CreateMemo(ctx, &store.Memo{
|
|
UID: "rss-etag-stable",
|
|
CreatorID: user.ID,
|
|
Content: "stable feed content",
|
|
Visibility: store.Public,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
service := NewRSSService(stores, markdown.NewService())
|
|
explore := requestRSS(t, service, "/explore/rss.xml", "", "")
|
|
userFeed := requestRSS(t, service, "/u/rss-etag-owner/rss.xml", user.Username, "")
|
|
require.Equal(t, http.StatusOK, explore.Code)
|
|
require.Equal(t, http.StatusOK, userFeed.Code)
|
|
exploreETag := explore.Header().Get("ETag")
|
|
userETag := userFeed.Header().Get("ETag")
|
|
require.NotEmpty(t, exploreETag)
|
|
require.NotEmpty(t, userETag)
|
|
|
|
// The previous request-time channel timestamp changed once per second even
|
|
// when no feed data changed, causing these conditional reads to return 200.
|
|
time.Sleep(1100 * time.Millisecond)
|
|
explore = requestRSS(t, service, "/explore/rss.xml", "", exploreETag)
|
|
userFeed = requestRSS(t, service, "/u/rss-etag-owner/rss.xml", user.Username, userETag)
|
|
require.Equal(t, http.StatusNotModified, explore.Code)
|
|
require.Equal(t, http.StatusNotModified, userFeed.Code)
|
|
require.Equal(t, exploreETag, explore.Header().Get("ETag"))
|
|
require.Equal(t, userETag, userFeed.Header().Get("ETag"))
|
|
}
|
|
|
|
func setInstanceAccessMode(ctx context.Context, t *testing.T, stores *store.Store, mode storepb.InstanceAccessMode) {
|
|
t.Helper()
|
|
_, err := stores.UpsertInstanceSetting(ctx, &storepb.InstanceSetting{
|
|
Key: storepb.InstanceSettingKey_ACCESS,
|
|
Value: &storepb.InstanceSetting_AccessSetting{AccessSetting: &storepb.InstanceAccessSetting{
|
|
AccessMode: mode,
|
|
}},
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func renderRSS(t *testing.T, service *RSSService, target string, username string) string {
|
|
t.Helper()
|
|
rec := requestRSS(t, service, target, username, "")
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
return rec.Body.String()
|
|
}
|
|
|
|
func requestRSS(t *testing.T, service *RSSService, target, username, ifNoneMatch string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
|
|
e := echo.New()
|
|
req := httptest.NewRequest(http.MethodGet, target, strings.NewReader(""))
|
|
req.Host = "example.com"
|
|
if ifNoneMatch != "" {
|
|
req.Header.Set("If-None-Match", ifNoneMatch)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
c := e.NewContext(req, rec)
|
|
if username != "" {
|
|
c.SetPathValues(echo.PathValues{{Name: "username", Value: username}})
|
|
}
|
|
|
|
var err error
|
|
if username == "" {
|
|
err = service.GetExploreRSS(c)
|
|
} else {
|
|
err = service.GetUserRSS(c)
|
|
}
|
|
require.NoError(t, err)
|
|
return rec
|
|
}
|