344 рядки
10 KiB
Go
344 рядки
10 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"git.entityseven.com/rilaxik/pllist/internal/domain"
|
|
)
|
|
|
|
type fakeUserLookup struct {
|
|
users map[int64]domain.User
|
|
}
|
|
|
|
func (f *fakeUserLookup) GetByTelegramID(
|
|
_ context.Context, telegramID int64,
|
|
) (domain.User, bool, error) {
|
|
user, ok := f.users[telegramID]
|
|
return user, ok, nil
|
|
}
|
|
|
|
type fakeShareRepository struct {
|
|
shares []domain.Share
|
|
|
|
userUpserts []shareUserUpsert
|
|
pendingIDUpserts []sharePendingIDUpsert
|
|
pendingUsernameUpserts []sharePendingUsernameUpsert
|
|
setUserRoles []shareRoleUpdate
|
|
setPendingRoles []shareRoleUpdate
|
|
revokedUserShares []shareRef
|
|
revokedPendingShares []shareRef
|
|
resolveCalls []resolveCall
|
|
}
|
|
|
|
type shareUserUpsert struct {
|
|
listID int64
|
|
userID int64
|
|
role domain.Role
|
|
}
|
|
|
|
type sharePendingIDUpsert struct {
|
|
listID int64
|
|
createdBy int64
|
|
telegramID int64
|
|
role domain.Role
|
|
}
|
|
|
|
type sharePendingUsernameUpsert struct {
|
|
listID int64
|
|
createdBy int64
|
|
username string
|
|
role domain.Role
|
|
}
|
|
|
|
type shareRoleUpdate struct {
|
|
listID int64
|
|
shareID int64
|
|
role domain.Role
|
|
}
|
|
|
|
type shareRef struct {
|
|
listID int64
|
|
shareID int64
|
|
}
|
|
|
|
type resolveCall struct {
|
|
userID int64
|
|
telegramID int64
|
|
username string
|
|
}
|
|
|
|
func (f *fakeShareRepository) ListByList(
|
|
_ context.Context, _ int64,
|
|
) ([]domain.Share, error) {
|
|
return f.shares, nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) UpsertUser(
|
|
_ context.Context, listID, userID int64, role domain.Role,
|
|
) error {
|
|
f.userUpserts = append(f.userUpserts, shareUserUpsert{listID, userID, role})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) UpsertPendingTelegramID(
|
|
_ context.Context, listID, createdByID, telegramID int64, role domain.Role,
|
|
) error {
|
|
f.pendingIDUpserts = append(f.pendingIDUpserts,
|
|
sharePendingIDUpsert{listID, createdByID, telegramID, role})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) UpsertPendingUsername(
|
|
_ context.Context, listID, createdByID int64, username string, role domain.Role,
|
|
) error {
|
|
f.pendingUsernameUpserts = append(f.pendingUsernameUpserts,
|
|
sharePendingUsernameUpsert{listID, createdByID, username, role})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) SetUserRole(
|
|
_ context.Context, listID, shareID int64, role domain.Role,
|
|
) error {
|
|
f.setUserRoles = append(f.setUserRoles, shareRoleUpdate{listID, shareID, role})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) SetPendingRole(
|
|
_ context.Context, listID, shareID int64, role domain.Role,
|
|
) error {
|
|
f.setPendingRoles = append(f.setPendingRoles, shareRoleUpdate{listID, shareID, role})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) RevokeUser(_ context.Context, listID, shareID int64) error {
|
|
f.revokedUserShares = append(f.revokedUserShares, shareRef{listID, shareID})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) RevokePending(_ context.Context, listID, shareID int64) error {
|
|
f.revokedPendingShares = append(f.revokedPendingShares, shareRef{listID, shareID})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) ResolvePending(
|
|
_ context.Context, userID, telegramID int64, username string,
|
|
) error {
|
|
f.resolveCalls = append(f.resolveCalls, resolveCall{userID, telegramID, username})
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeShareRepository) RemoveSharedAccess(
|
|
_ context.Context, _ int64, _ string,
|
|
) error {
|
|
return nil
|
|
}
|
|
|
|
func newTestShareService(
|
|
shares *fakeShareRepository,
|
|
users *fakeUserLookup,
|
|
bot *fakeBotAccessRepository,
|
|
lists *fakeAccessRepository,
|
|
) *ShareService {
|
|
return NewShareService(
|
|
shares, users, NewAccessService(lists),
|
|
NewBotAccessService(bot, 100, nil),
|
|
)
|
|
}
|
|
|
|
func TestShareServiceShare(t *testing.T) {
|
|
owner := domain.User{ID: 1, TelegramID: 100}
|
|
lists := &fakeAccessRepository{
|
|
lists: map[int64]domain.List{
|
|
10: {ID: 10, OwnerID: 1, Title: "Groceries"},
|
|
},
|
|
}
|
|
|
|
t.Run("resolved user by numeric id", func(t *testing.T) {
|
|
shares := &fakeShareRepository{}
|
|
users := &fakeUserLookup{users: map[int64]domain.User{
|
|
200: {ID: 2, TelegramID: 200},
|
|
}}
|
|
bot := &fakeBotAccessRepository{allowedIDs: map[int64]bool{200: true}}
|
|
service := newTestShareService(shares, users, bot, lists)
|
|
|
|
err := service.Share(context.Background(), owner, 10, "200", domain.RoleEditor)
|
|
if err != nil {
|
|
t.Fatalf("Share() error = %v", err)
|
|
}
|
|
want := shareUserUpsert{listID: 10, userID: 2, role: domain.RoleEditor}
|
|
if len(shares.userUpserts) != 1 || shares.userUpserts[0] != want {
|
|
t.Fatalf("user upserts = %v, want %v", shares.userUpserts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("pending username", func(t *testing.T) {
|
|
shares := &fakeShareRepository{}
|
|
bot := &fakeBotAccessRepository{usernameIDs: map[string]int64{"alice_1": 0}}
|
|
service := newTestShareService(shares, &fakeUserLookup{}, bot, lists)
|
|
|
|
err := service.Share(context.Background(), owner, 10, "@Alice_1", domain.RoleReader)
|
|
if err != nil {
|
|
t.Fatalf("Share() error = %v", err)
|
|
}
|
|
want := sharePendingUsernameUpsert{
|
|
listID: 10, createdBy: 1, username: "alice_1", role: domain.RoleReader,
|
|
}
|
|
if len(shares.pendingUsernameUpserts) != 1 ||
|
|
shares.pendingUsernameUpserts[0] != want {
|
|
t.Fatalf("pending username upserts = %v, want %v",
|
|
shares.pendingUsernameUpserts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects user without bot access", func(t *testing.T) {
|
|
service := newTestShareService(
|
|
&fakeShareRepository{}, &fakeUserLookup{}, &fakeBotAccessRepository{}, lists,
|
|
)
|
|
|
|
err := service.Share(context.Background(), owner, 10, "@alice_1", domain.RoleEditor)
|
|
if !errors.Is(err, domain.ErrInvalidInput) {
|
|
t.Fatalf("Share() error = %v, want ErrInvalidInput", err)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects sharing with the owner", func(t *testing.T) {
|
|
bot := &fakeBotAccessRepository{allowedIDs: map[int64]bool{100: true}}
|
|
users := &fakeUserLookup{users: map[int64]domain.User{
|
|
100: {ID: 1, TelegramID: 100},
|
|
}}
|
|
service := newTestShareService(&fakeShareRepository{}, users, bot, lists)
|
|
|
|
err := service.Share(context.Background(), owner, 10, "100", domain.RoleEditor)
|
|
if !errors.Is(err, domain.ErrInvalidInput) {
|
|
t.Fatalf("Share() error = %v, want ErrInvalidInput", err)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects non-owner", func(t *testing.T) {
|
|
readerLists := &fakeAccessRepository{
|
|
lists: map[int64]domain.List{
|
|
10: {ID: 10, OwnerID: 1, Title: "Groceries"},
|
|
},
|
|
roles: map[[2]int64]domain.Role{{10, 2}: domain.RoleReader},
|
|
}
|
|
service := newTestShareService(
|
|
&fakeShareRepository{}, &fakeUserLookup{}, &fakeBotAccessRepository{}, readerLists,
|
|
)
|
|
|
|
err := service.Share(context.Background(),
|
|
domain.User{ID: 2, TelegramID: 200}, 10, "300", domain.RoleEditor)
|
|
if !errors.Is(err, domain.ErrForbidden) {
|
|
t.Fatalf("Share() error = %v, want ErrForbidden", err)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects invalid role", func(t *testing.T) {
|
|
service := newTestShareService(
|
|
&fakeShareRepository{}, &fakeUserLookup{}, &fakeBotAccessRepository{}, lists,
|
|
)
|
|
|
|
err := service.Share(context.Background(), owner, 10, "200", domain.RoleOwner)
|
|
if !errors.Is(err, domain.ErrInvalidInput) {
|
|
t.Fatalf("Share() error = %v, want ErrInvalidInput", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestShareServicePendingTargets(t *testing.T) {
|
|
owner := domain.User{ID: 1, TelegramID: 100}
|
|
lists := &fakeAccessRepository{
|
|
lists: map[int64]domain.List{
|
|
10: {ID: 10, OwnerID: 1, Title: "Groceries"},
|
|
},
|
|
}
|
|
|
|
t.Run("pending telegram id", func(t *testing.T) {
|
|
shares := &fakeShareRepository{}
|
|
bot := &fakeBotAccessRepository{allowedIDs: map[int64]bool{200: true}}
|
|
service := newTestShareService(shares, &fakeUserLookup{}, bot, lists)
|
|
|
|
err := service.Share(context.Background(), owner, 10, "200", domain.RoleReader)
|
|
if err != nil {
|
|
t.Fatalf("Share() error = %v", err)
|
|
}
|
|
want := sharePendingIDUpsert{
|
|
listID: 10, createdBy: 1, telegramID: 200, role: domain.RoleReader,
|
|
}
|
|
if len(shares.pendingIDUpserts) != 1 || shares.pendingIDUpserts[0] != want {
|
|
t.Fatalf("pending id upserts = %v, want %v", shares.pendingIDUpserts, want)
|
|
}
|
|
})
|
|
|
|
t.Run("pending username resolves to a known id", func(t *testing.T) {
|
|
shares := &fakeShareRepository{}
|
|
bot := &fakeBotAccessRepository{usernameIDs: map[string]int64{"alice_1": 200}}
|
|
service := newTestShareService(shares, &fakeUserLookup{}, bot, lists)
|
|
|
|
if _, err := service.Prepare(context.Background(), owner, 10, "alice_1"); err != nil {
|
|
t.Fatalf("Prepare() error = %v", err)
|
|
}
|
|
err := service.Share(context.Background(), owner, 10, "alice_1", domain.RoleEditor)
|
|
if err != nil {
|
|
t.Fatalf("Share() error = %v", err)
|
|
}
|
|
want := sharePendingIDUpsert{
|
|
listID: 10, createdBy: 1, telegramID: 200, role: domain.RoleEditor,
|
|
}
|
|
if len(shares.pendingIDUpserts) != 1 || shares.pendingIDUpserts[0] != want {
|
|
t.Fatalf("pending id upserts = %v, want %v", shares.pendingIDUpserts, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestShareServiceSetRoleAndRevoke(t *testing.T) {
|
|
lists := &fakeAccessRepository{
|
|
lists: map[int64]domain.List{
|
|
10: {ID: 10, OwnerID: 1, Title: "Groceries"},
|
|
},
|
|
}
|
|
shares := &fakeShareRepository{}
|
|
service := newTestShareService(shares, &fakeUserLookup{}, &fakeBotAccessRepository{}, lists)
|
|
|
|
if err := service.SetRole(context.Background(), 1, 10, 55, false, domain.RoleReader); err != nil {
|
|
t.Fatalf("SetRole() error = %v", err)
|
|
}
|
|
if len(shares.setUserRoles) != 1 ||
|
|
shares.setUserRoles[0] != (shareRoleUpdate{10, 55, domain.RoleReader}) {
|
|
t.Fatalf("set user roles = %v", shares.setUserRoles)
|
|
}
|
|
|
|
if err := service.SetRole(context.Background(), 1, 10, 56, true, domain.RoleEditor); err != nil {
|
|
t.Fatalf("SetRole() error = %v", err)
|
|
}
|
|
if len(shares.setPendingRoles) != 1 ||
|
|
shares.setPendingRoles[0] != (shareRoleUpdate{10, 56, domain.RoleEditor}) {
|
|
t.Fatalf("set pending roles = %v", shares.setPendingRoles)
|
|
}
|
|
|
|
if err := service.Revoke(context.Background(), 1, 10, 57, false); err != nil {
|
|
t.Fatalf("Revoke() error = %v", err)
|
|
}
|
|
if len(shares.revokedUserShares) != 1 ||
|
|
shares.revokedUserShares[0] != (shareRef{10, 57}) {
|
|
t.Fatalf("revoked user shares = %v", shares.revokedUserShares)
|
|
}
|
|
}
|
|
|
|
func TestShareServiceResolvePending(t *testing.T) {
|
|
shares := &fakeShareRepository{}
|
|
service := newTestShareService(
|
|
shares, &fakeUserLookup{}, &fakeBotAccessRepository{},
|
|
&fakeAccessRepository{},
|
|
)
|
|
|
|
if err := service.ResolvePending(context.Background(), 2, 200, "@Alice_1"); err != nil {
|
|
t.Fatalf("ResolvePending() error = %v", err)
|
|
}
|
|
want := resolveCall{userID: 2, telegramID: 200, username: "alice_1"}
|
|
if len(shares.resolveCalls) != 1 || shares.resolveCalls[0] != want {
|
|
t.Fatalf("resolve calls = %v, want %v", shares.resolveCalls, want)
|
|
}
|
|
}
|