Files
llink/go/internal/network/service_test.go
T
Arjun Patel c1f5b6c8c1 Implement membership notifications and deep-link handling for Electron desktop app (#246)
* security: add cors for desktop app scheme

* Revert "security: add cors for desktop app scheme"

This reverts commit d450fced75.

* ignore tags

* add commands for windows dev

* cleanup unnecessary parts of main desktop process

* resolve lint errors

* add format command for go

* fix: send email on new member joining

Closes #228

* add proper deeplinking on desktop

The desktop app was merely focusing before, but now it will navigate to the proper route

* honor email notifications setting

* prevent sending email when no recipients
2026-06-09 08:04:18 -07:00

210 lines
6.5 KiB
Go

package network_test
import (
"context"
"os"
"testing"
pbaero "github.com/flowy-live/llink/genproto/aero"
mock_billing "github.com/flowy-live/llink/internal/billing/mocks"
"github.com/flowy-live/llink/internal/human"
mock_human "github.com/flowy-live/llink/internal/human/mocks"
mock_livestore "github.com/flowy-live/llink/internal/livestore/mocks"
"github.com/flowy-live/llink/internal/network"
"github.com/flowy-live/llink/internal/testhelper"
"github.com/flowy-live/llink/internal/testhelper/mocks/aero"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
)
var dbPool *pgxpool.Pool
func TestMain(m *testing.M) {
dbPool = testhelper.SetupTestDB()
defer testhelper.TeardownTestDB()
ret := m.Run()
os.Exit(ret)
}
func newTestService(t *testing.T) network.Service {
ctrl := gomock.NewController(t)
mockAero := aero.NewMockPrimaryClient(ctrl)
mockAero.EXPECT().
ShootEmail(gomock.Any(), gomock.Any()).
Return(&pbaero.ShootEmailResponse{}, nil).
AnyTimes()
mockBilling := mock_billing.NewMockService(ctrl)
mockBilling.EXPECT().SyncSeats(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockPub := mock_livestore.NewMockMembershipPublisher(ctrl)
mockPub.EXPECT().Add(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockPub.EXPECT().Remove(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
mockHuman := mock_human.NewMockService(ctrl)
mockHuman.EXPECT().GetByID(gomock.Any(), gomock.Any()).Return(&human.Human{
ID: "test_human",
Email: "test@gmail.com",
EmailPrefix: "test",
EmailNotificationsEnabled: false,
}, nil).AnyTimes()
return network.NewService(dbPool, mockAero, mockBilling, mockPub, mockHuman)
}
func TestNetworkService(t *testing.T) {
ctx := context.Background()
svc := newTestService(t)
adminHumanId := "human_admin123"
member1HumanId := "human_member1abc"
member2HumanId := "human_member2def"
strangerHumanId := "human_stranger789"
// Test Create
createdNetwork, err := svc.Create(ctx, "Test Network", adminHumanId)
assert.NoError(t, err)
assert.NotEmpty(t, createdNetwork.ID)
assert.Equal(t, "Test Network", createdNetwork.Name)
assert.Equal(t, adminHumanId, createdNetwork.AdminHumanId)
assert.NotZero(t, createdNetwork.CreatedAt)
// Test GetByID
foundNetwork, err := svc.GetByID(ctx, createdNetwork.ID)
assert.NoError(t, err)
assert.Equal(t, createdNetwork.ID, foundNetwork.ID)
assert.Equal(t, createdNetwork.Name, foundNetwork.Name)
assert.Equal(t, createdNetwork.AdminHumanId, foundNetwork.AdminHumanId)
// Test GetByID with non-existent id
_, err = svc.GetByID(ctx, "network_nonexistent")
assert.Error(t, err)
assert.ErrorIs(t, err, network.ErrNotFound)
// Test SetName
err = svc.SetName(ctx, createdNetwork.ID, "Updated Network Name")
assert.NoError(t, err)
// Verify name was updated
updatedNetwork, err := svc.GetByID(ctx, createdNetwork.ID)
assert.NoError(t, err)
assert.Equal(t, "Updated Network Name", updatedNetwork.Name)
// Test SetName with non-existent id
err = svc.SetName(ctx, "network_nonexistent", "New Name")
assert.Error(t, err)
assert.ErrorIs(t, err, network.ErrNotFound)
// Test AddMembers
err = svc.AddMembers(ctx, createdNetwork.ID, []string{member1HumanId, member2HumanId})
assert.NoError(t, err)
// Test ListForHuman - should find network for admin
networks, err := svc.ListForHuman(ctx, adminHumanId)
assert.NoError(t, err)
assert.Len(t, networks, 1)
assert.Equal(t, createdNetwork.ID, networks[0].ID)
// Test ListForHuman - should find network for member
networks, err = svc.ListForHuman(ctx, member1HumanId)
assert.NoError(t, err)
assert.Len(t, networks, 1)
assert.Equal(t, createdNetwork.ID, networks[0].ID)
// Test ListForHuman - should return empty for non-member
networks, err = svc.ListForHuman(ctx, strangerHumanId)
assert.NoError(t, err)
assert.Len(t, networks, 0)
// Test RemoveMember
err = svc.RemoveMember(ctx, createdNetwork.ID, member1HumanId)
assert.NoError(t, err)
// Verify member was removed
networks, err = svc.ListForHuman(ctx, member1HumanId)
assert.NoError(t, err)
assert.Len(t, networks, 0)
// member2 should still have access
networks, err = svc.ListForHuman(ctx, member2HumanId)
assert.NoError(t, err)
assert.Len(t, networks, 1)
// Create another network and verify ListForHuman returns multiple
network2, err := svc.Create(ctx, "Second Network", member2HumanId)
assert.NoError(t, err)
networks, err = svc.ListForHuman(ctx, member2HumanId)
assert.NoError(t, err)
assert.Len(t, networks, 2)
// Verify both networks are returned
networkIDs := []string{networks[0].ID, networks[1].ID}
assert.Contains(t, networkIDs, createdNetwork.ID)
assert.Contains(t, networkIDs, network2.ID)
}
func TestNetworkInvitations(t *testing.T) {
ctx := context.Background()
svc := newTestService(t)
adminHumanId := "human_invtest_admin"
inviteeEmail := "invitee@example.com"
inviteeHumanId := "human_invitee123"
// Create a network
net, err := svc.Create(ctx, "Invitation Test Network", adminHumanId)
assert.NoError(t, err)
// Invite by email
err = svc.InviteByEmail(ctx, net.ID, []string{inviteeEmail})
assert.NoError(t, err)
// List invitations for email
invitations, err := svc.ListInvitationsForEmail(ctx, inviteeEmail)
assert.NoError(t, err)
assert.Len(t, invitations, 1)
assert.Equal(t, net.ID, invitations[0].NetworkID)
assert.Equal(t, inviteeEmail, invitations[0].Email)
// List invitations for network
invitations, err = svc.ListInvitationsForNetwork(ctx, net.ID)
assert.NoError(t, err)
assert.Len(t, invitations, 1)
// Duplicate invite is idempotent
err = svc.InviteByEmail(ctx, net.ID, []string{inviteeEmail})
assert.NoError(t, err)
invitations, err = svc.ListInvitationsForNetwork(ctx, net.ID)
assert.NoError(t, err)
assert.Len(t, invitations, 1)
// Accept invitation
err = svc.AcceptInvitation(ctx, net.ID, inviteeEmail, inviteeHumanId)
assert.NoError(t, err)
// Invitation should be removed
invitations, err = svc.ListInvitationsForEmail(ctx, inviteeEmail)
assert.NoError(t, err)
assert.Len(t, invitations, 0)
// Human should now be a member
isMember, err := svc.IsMember(ctx, net.ID, inviteeHumanId)
assert.NoError(t, err)
assert.True(t, isMember)
// Test revoke invitation
revokeEmail := "revokee@example.com"
err = svc.InviteByEmail(ctx, net.ID, []string{revokeEmail})
assert.NoError(t, err)
err = svc.RevokeInvitation(ctx, net.ID, revokeEmail)
assert.NoError(t, err)
invitations, err = svc.ListInvitationsForEmail(ctx, revokeEmail)
assert.NoError(t, err)
assert.Len(t, invitations, 0)
}