chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
313
internal/app/stories/service_test.go
Normal file
313
internal/app/stories/service_test.go
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
package stories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
func TestReadStoriesClampsFutureMaxIDToVisibleActiveStory(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := memory.NewStoryStore()
|
||||
service := NewService(store)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
viewerID := int64(2001)
|
||||
for _, id := range []int{1, 2} {
|
||||
if _, err := store.UpsertStory(ctx, domain.UpsertStoryRequest{Story: domain.Story{
|
||||
Owner: owner,
|
||||
ID: id,
|
||||
Date: 100 + id,
|
||||
ExpireDate: 1000,
|
||||
Public: true,
|
||||
}}); err != nil {
|
||||
t.Fatalf("upsert story %d: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
read, err := service.ReadStories(ctx, viewerID, owner, 99, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("read stories: %v", err)
|
||||
}
|
||||
if !read.Advanced || read.MaxReadID != 2 {
|
||||
t.Fatalf("read = %+v, want advanced max 2", read)
|
||||
}
|
||||
|
||||
if _, err := store.UpsertStory(ctx, domain.UpsertStoryRequest{Story: domain.Story{
|
||||
Owner: owner,
|
||||
ID: 3,
|
||||
Date: 300,
|
||||
ExpireDate: 1000,
|
||||
Public: true,
|
||||
}}); err != nil {
|
||||
t.Fatalf("upsert future story: %v", err)
|
||||
}
|
||||
peerStories, err := store.GetPeerStories(ctx, viewerID, owner, 400)
|
||||
if err != nil {
|
||||
t.Fatalf("get peer stories: %v", err)
|
||||
}
|
||||
if peerStories.MaxReadID != 2 || len(peerStories.Stories) != 3 {
|
||||
t.Fatalf("peer stories = %+v, want max read 2 and three active stories", peerStories)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadStoriesWithoutVisibleActiveStoryDoesNotWriteFutureBoundary(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := memory.NewStoryStore()
|
||||
service := NewService(store)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
viewerID := int64(2001)
|
||||
|
||||
read, err := service.ReadStories(ctx, viewerID, owner, 99, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("read stories: %v", err)
|
||||
}
|
||||
if read.Advanced || read.MaxReadID != 0 {
|
||||
t.Fatalf("read = %+v, want no-op zero boundary", read)
|
||||
}
|
||||
states, err := store.ListReadStates(ctx, viewerID)
|
||||
if err != nil {
|
||||
t.Fatalf("list read states: %v", err)
|
||||
}
|
||||
if len(states) != 0 {
|
||||
t.Fatalf("read states = %+v, want none", states)
|
||||
}
|
||||
|
||||
if _, err := store.UpsertStory(ctx, domain.UpsertStoryRequest{Story: domain.Story{
|
||||
Owner: owner,
|
||||
ID: 1,
|
||||
Date: 300,
|
||||
ExpireDate: 1000,
|
||||
Public: true,
|
||||
}}); err != nil {
|
||||
t.Fatalf("upsert story after no-op read: %v", err)
|
||||
}
|
||||
peerStories, err := store.GetPeerStories(ctx, viewerID, owner, 400)
|
||||
if err != nil {
|
||||
t.Fatalf("get peer stories: %v", err)
|
||||
}
|
||||
if peerStories.MaxReadID != 0 || len(peerStories.Stories) != 1 {
|
||||
t.Fatalf("peer stories = %+v, want unread first story", peerStories)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadStoriesWithoutStoreIsNoop(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
service := NewService(nil)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
|
||||
read, err := service.ReadStories(ctx, 2001, owner, 99, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("read stories without store: %v", err)
|
||||
}
|
||||
if read.Advanced || read.MaxReadID != 0 || read.Peer != owner || read.ViewerID != 2001 {
|
||||
t.Fatalf("read without store = %+v, want no-op zero boundary for viewer/peer", read)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStoryReactionsListRequiresChannelInteractionGrant(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := memory.NewStoryStore()
|
||||
service := NewService(store)
|
||||
owner := domain.Peer{Type: domain.PeerTypeChannel, ID: 1001}
|
||||
if _, err := store.UpsertStory(ctx, domain.UpsertStoryRequest{Story: domain.Story{
|
||||
Owner: owner,
|
||||
ID: 1,
|
||||
Date: 100,
|
||||
ExpireDate: 1000,
|
||||
Public: true,
|
||||
}}); err != nil {
|
||||
t.Fatalf("upsert story: %v", err)
|
||||
}
|
||||
if _, err := store.SetReaction(ctx, 2001, owner, 1, &domain.MessageReaction{
|
||||
Type: domain.MessageReactionEmoji,
|
||||
Emoticon: "🔥",
|
||||
}, 110); err != nil {
|
||||
t.Fatalf("set reaction: %v", err)
|
||||
}
|
||||
|
||||
_, err := service.GetStoryReactionsList(ctx, 3001, domain.StoryReactionListRequest{
|
||||
Owner: owner,
|
||||
StoryID: 1,
|
||||
Limit: 10,
|
||||
})
|
||||
if !errors.Is(err, domain.ErrStoryPeerInvalid) {
|
||||
t.Fatalf("ungranted channel reaction list err = %v, want ErrStoryPeerInvalid", err)
|
||||
}
|
||||
|
||||
list, err := service.GetStoryReactionsList(ctx, 3001, domain.StoryReactionListRequest{
|
||||
Owner: owner,
|
||||
StoryID: 1,
|
||||
Limit: 10,
|
||||
CanViewOwnerInteractions: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("granted channel reaction list: %v", err)
|
||||
}
|
||||
if list.Count != 1 || len(list.Reactions) != 1 || list.Reactions[0].ViewerID != 2001 {
|
||||
t.Fatalf("reaction list = %+v, want viewer 2001", list)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStoryReactionsListRejectsUserPeer(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
service := NewService(memory.NewStoryStore())
|
||||
_, err := service.GetStoryReactionsList(ctx, 1001, domain.StoryReactionListRequest{
|
||||
Owner: domain.Peer{Type: domain.PeerTypeUser, ID: 1001},
|
||||
StoryID: 1,
|
||||
Limit: 10,
|
||||
CanViewOwnerInteractions: true,
|
||||
})
|
||||
if !errors.Is(err, domain.ErrStoryPeerInvalid) {
|
||||
t.Fatalf("user story reaction list err = %v, want ErrStoryPeerInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoryInteractionListsRejectMalformedOffsets(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
service := NewService(nil)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
if _, err := service.GetStoryViewsList(ctx, owner.ID, domain.StoryViewListRequest{
|
||||
Owner: owner,
|
||||
StoryID: 1,
|
||||
Offset: "bad",
|
||||
Limit: 10,
|
||||
}); !errors.Is(err, domain.ErrStoryOffsetInvalid) {
|
||||
t.Fatalf("bad views offset err = %v, want ErrStoryOffsetInvalid", err)
|
||||
}
|
||||
if _, err := service.GetStoryViewsList(ctx, owner.ID, domain.StoryViewListRequest{
|
||||
Owner: owner,
|
||||
StoryID: 1,
|
||||
Offset: strings.Repeat("9", domain.MaxStoryInteractionOffsetLength+1),
|
||||
Limit: 10,
|
||||
}); !errors.Is(err, domain.ErrStoryOffsetInvalid) {
|
||||
t.Fatalf("oversized views offset err = %v, want ErrStoryOffsetInvalid", err)
|
||||
}
|
||||
if _, err := service.GetStoryReactionsList(ctx, 2001, domain.StoryReactionListRequest{
|
||||
Owner: domain.Peer{Type: domain.PeerTypeChannel, ID: 3001},
|
||||
StoryID: 1,
|
||||
Offset: "1:1700000000:2001",
|
||||
Limit: 10,
|
||||
CanViewOwnerInteractions: true,
|
||||
}); !errors.Is(err, domain.ErrStoryOffsetInvalid) {
|
||||
t.Fatalf("reaction group offset err = %v, want ErrStoryOffsetInvalid", err)
|
||||
}
|
||||
if _, err := service.GetStoryReactionsList(ctx, 2001, domain.StoryReactionListRequest{
|
||||
Owner: domain.Peer{Type: domain.PeerTypeChannel, ID: 3001},
|
||||
StoryID: 1,
|
||||
Offset: "1:1700000000:2001",
|
||||
Limit: 10,
|
||||
ForwardsFirst: true,
|
||||
CanViewOwnerInteractions: true,
|
||||
}); err != nil {
|
||||
t.Fatalf("reaction forwards-first group offset err = %v, want nil", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStoriesByIDDedupesAndRejectsEmptyIDs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := memory.NewStoryStore()
|
||||
service := NewService(store)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
if _, err := store.UpsertStory(ctx, domain.UpsertStoryRequest{Story: domain.Story{
|
||||
Owner: owner,
|
||||
ID: 3,
|
||||
Date: 100,
|
||||
ExpireDate: 1000,
|
||||
Public: true,
|
||||
}}); err != nil {
|
||||
t.Fatalf("upsert story: %v", err)
|
||||
}
|
||||
|
||||
list, err := service.GetStoriesByID(ctx, owner.ID, owner, []int{3, 3}, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("get stories by duplicate ids: %v", err)
|
||||
}
|
||||
if len(list.Stories) != 1 || list.Stories[0].ID != 3 {
|
||||
t.Fatalf("stories = %+v, want one deduped story", list.Stories)
|
||||
}
|
||||
|
||||
if _, err := service.GetStoriesByID(ctx, owner.ID, owner, nil, 200); !errors.Is(err, domain.ErrStoryIDInvalid) {
|
||||
t.Fatalf("empty get stories by id err = %v, want ErrStoryIDInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrementViewsDedupeAndRejectsEmptyIDs(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
store := memory.NewStoryStore()
|
||||
service := NewService(store)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
if _, err := store.UpsertStory(ctx, domain.UpsertStoryRequest{Story: domain.Story{
|
||||
Owner: owner,
|
||||
ID: 7,
|
||||
Date: 100,
|
||||
ExpireDate: 1000,
|
||||
Public: true,
|
||||
}}); err != nil {
|
||||
t.Fatalf("upsert story: %v", err)
|
||||
}
|
||||
|
||||
created, err := service.IncrementViews(ctx, 2001, owner, []int{7, 7}, 200)
|
||||
if err != nil {
|
||||
t.Fatalf("increment views duplicate ids: %v", err)
|
||||
}
|
||||
if created != 1 {
|
||||
t.Fatalf("created views = %d, want one deduped view", created)
|
||||
}
|
||||
|
||||
if _, err := service.IncrementViews(ctx, 2001, owner, nil, 200); !errors.Is(err, domain.ErrStoryIDInvalid) {
|
||||
t.Fatalf("empty increment views err = %v, want ErrStoryIDInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoryMutationsDedupeIDsBeforeStore(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
service := NewService(nil)
|
||||
owner := domain.Peer{Type: domain.PeerTypeUser, ID: 1001}
|
||||
|
||||
pinned, err := service.TogglePinned(ctx, owner.ID, owner, []int{3, 3, 4}, true, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("toggle pinned: %v", err)
|
||||
}
|
||||
if got, want := pinned.IDs, []int{3, 4}; !intSlicesEqual(got, want) {
|
||||
t.Fatalf("pinned ids = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
emptyPinned, err := service.TogglePinned(ctx, owner.ID, owner, nil, true, 100)
|
||||
if err != nil {
|
||||
t.Fatalf("empty toggle pinned: %v", err)
|
||||
}
|
||||
if len(emptyPinned.IDs) != 0 || len(emptyPinned.Stories) != 0 {
|
||||
t.Fatalf("empty pinned = %+v, want no-op", emptyPinned)
|
||||
}
|
||||
|
||||
deleted, err := service.DeleteStories(ctx, owner.ID, owner, []int{4, 3, 4}, 101)
|
||||
if err != nil {
|
||||
t.Fatalf("delete stories: %v", err)
|
||||
}
|
||||
if got, want := deleted.IDs, []int{4, 3}; !intSlicesEqual(got, want) {
|
||||
t.Fatalf("deleted ids = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
if err := service.TogglePinnedToTop(ctx, owner.ID, owner, []int{4, 3, 4}); err != nil {
|
||||
t.Fatalf("toggle pinned to top deduped ids: %v", err)
|
||||
}
|
||||
if err := service.TogglePinnedToTop(ctx, owner.ID, owner, []int{1, 2, 3, 4}); !errors.Is(err, domain.ErrStoryIDInvalid) {
|
||||
t.Fatalf("oversized pinned-to-top err = %v, want ErrStoryIDInvalid", err)
|
||||
}
|
||||
}
|
||||
|
||||
func intSlicesEqual(a, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue