Files
status-go/internal/protocol/messenger_mailserver_test.go
Igor Sirotin f3e4363808 refactor: move protocol to internal/protocol
Part of the Go project layout migration, item 27.

Pure move plus import-path rewrite across 502 files. No API or behaviour
change. `internal/` keeps the messaging application logic unimportable
from outside the module, which is what the issue asks for -- status-go is
consumed through the C-bindings in mobile/, not as a Go library.

Things that had to follow the move, beyond the Go imports:

- tools/generate-handlers/template.txt. messenger_handlers.go is
  generated, and the template hard-codes the imports it emits, so the
  generated file kept importing protocol/common and failed typecheck.
- .gitignore. The ignore rule for that generated file was pinned to the
  old path; without moving it, a 1486-line generated file starts being
  tracked.
- Makefile: the logosstorage and torrent test targets (both the archive
  packages and ./protocol itself), the archive README, migration-protocol.
- scripts/run_unit_tests.sh, which names the protocol package explicitly
  to shard its tests.
- scripts/cleanup_generated_files.sh and .golangci.yml.

scripts/migration_check.sh also needed a fix that is not specific to this
move: it validated every file the branch touched under a migration dir
against the timestamp naming rule, and a directory rename makes every
migration in it look newly added. It now excludes renames, so moving a
migration is not mistaken for adding one.

refs #7067
2026-08-21 10:11:05 +02:00

158 lines
6.1 KiB
Go

package protocol
import (
"context"
"testing"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/internal/protocol/communities"
"github.com/status-im/status-go/internal/protocol/protobuf"
"github.com/status-im/status-go/internal/protocol/requests"
"github.com/status-im/status-go/pkg/messaging"
"github.com/status-im/status-go/pkg/messaging/types"
wakutypes "github.com/status-im/status-go/pkg/messaging/waku/types"
)
func TestMessengerFetchLatestCommunityDescriptionsSuite(t *testing.T) {
suite.Run(t, new(MessengerFetchLatestCommunityDescriptionsSuite))
}
type MessengerFetchLatestCommunityDescriptionsSuite struct {
suite.Suite
messagingEnv *messaging.TestMessagingEnvironment
m *Messenger
}
func (s *MessengerFetchLatestCommunityDescriptionsSuite) createLocalCommunity() *communities.Community {
response, err := s.m.CreateCommunity(&requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}, true)
s.Require().NoError(err)
s.Require().Len(response.Communities(), 1)
return response.Communities()[0]
}
func (s *MessengerFetchLatestCommunityDescriptionsSuite) SetupTest() {
var err error
s.messagingEnv, err = messaging.NewTestMessagingEnvironment()
s.Require().NoError(err)
s.Require().NoError(s.messagingEnv.Setup(s.T()))
// Use a non-running messenger so no background loop issues store queries and
// interferes with the intercepted batches.
s.m, err = newTestMessenger(s.T(), s.messagingEnv, testMessengerConfig{})
s.Require().NoError(err)
}
type recordedMailserverBatch struct {
batch wakutypes.MailserverBatch
pageLimit uint64
shouldProcessNext func(int) (bool, uint64)
processEnvelopes bool
}
func (s *MessengerFetchLatestCommunityDescriptionsSuite) recordMailserverBatches() *[]recordedMailserverBatch {
recorded := &[]recordedMailserverBatch{}
s.messagingEnv.SetProcessMailserverBatchHook(func(ctx context.Context, batch wakutypes.MailserverBatch, pageLimit uint64, shouldProcessNextPage func(int) (bool, uint64), processEnvelopes bool) error {
*recorded = append(*recorded, recordedMailserverBatch{
batch: batch,
pageLimit: pageLimit,
shouldProcessNext: shouldProcessNextPage,
processEnvelopes: processEnvelopes,
})
return nil
})
s.T().Cleanup(func() {
s.messagingEnv.SetProcessMailserverBatchHook(nil)
})
return recorded
}
func descriptionFilter(chatID, pubsubTopic, contentTopic string) *types.ChatFilter {
return types.NewChatFilter(&types.ChatFilterConfig{
ChatID: chatID,
PubsubTopic: pubsubTopic,
ContentTopic: types.StringToContentTopic(contentTopic),
Listen: true,
})
}
// TestFetchesOnlyNewestPagePerFilter verifies that fetchLatestCommunityDescriptions
// issues exactly one store request per filter that fetches only the newest page
// (pageLimit == 1) and stops immediately, instead of sweeping the whole window.
func (s *MessengerFetchLatestCommunityDescriptionsSuite) TestFetchesOnlyNewestPagePerFilter() {
recorded := s.recordMailserverBatches()
filters := []*types.ChatFilter{
descriptionFilter("0xcommunity1", "/waku/2/pubsub-a", "0xcontenttopic1"),
descriptionFilter("0xcommunity2", "/waku/2/pubsub-b", "0xcontenttopic2"),
}
s.m.fetchLatestCommunityDescriptions(filters)
s.Require().Len(*recorded, len(filters), "expected exactly one store request per filter")
for i, rec := range *recorded {
// Only the newest page must be fetched.
s.Require().Equal(uint64(1), rec.pageLimit, "page limit must be 1")
s.Require().False(rec.processEnvelopes)
// The paging callback must stop immediately, regardless of how many
// envelopes the first page returned (i.e. never fetch a second page).
s.Require().NotNil(rec.shouldProcessNext)
cont, next := rec.shouldProcessNext(1000)
s.Require().False(cont, "must not fetch a second page")
s.Require().Equal(uint64(0), next)
// Each batch must target exactly the filter's description topic.
s.Require().Len(rec.batch.Topics, 1)
s.Require().Equal(filters[i].PubsubTopic(), rec.batch.PubsubTopic)
s.Require().Equal([]string{filters[i].ChatID()}, rec.batch.ChatIDs)
}
}
// TestNoFiltersIsNoop verifies that no store request is made when there are no
// community description filters to fetch.
func (s *MessengerFetchLatestCommunityDescriptionsSuite) TestNoFiltersIsNoop() {
recorded := s.recordMailserverBatches()
s.m.fetchLatestCommunityDescriptions(nil)
s.Require().Empty(*recorded, "no store request should be made when there are no filters")
}
func (s *MessengerFetchLatestCommunityDescriptionsSuite) TestLatestDescriptionUsesRollingMonth() {
recorded := s.recordMailserverBatches()
filter := descriptionFilter("0xcommunity1", "/waku/2/pubsub-a", "0xcontenttopic1")
from, to := s.m.calculateMailserverTimeBounds(oneMonthDuration)
s.m.fetchLatestCommunityDescriptions([]*types.ChatFilter{filter})
s.Require().Len(*recorded, 1)
s.Require().Equal(from, (*recorded)[0].batch.From)
s.Require().Equal(to, (*recorded)[0].batch.To)
}
// TestReusedFilterForgottenForNonMember verifies that a description filter
// reused by a store-node request is forgotten for a community that is neither
// joined nor spectated (so it does not leave a live subscription behind), and
// kept for a joined/spectated community and for contact requests.
func (s *MessengerFetchLatestCommunityDescriptionsSuite) TestReusedFilterForgottenForNonMember() {
// A validly-shaped community id (compressed pubkey) that is not in the DB.
unknownID := "0x02b5bdaf5a25fcfe2ee14c501fab1836b8de57f61621080c3d52073d16de0d98d6"
s.Require().True(s.m.storeNodeRequestsManager.reusedFilterShouldForget(storeNodeCommunityRequest, unknownID),
"a community that is neither joined nor spectated must not keep a reused filter")
joined := s.createLocalCommunity()
s.Require().False(s.m.storeNodeRequestsManager.reusedFilterShouldForget(storeNodeCommunityRequest, joined.IDString()),
"a joined/spectated community keeps its live description subscription")
s.Require().False(s.m.storeNodeRequestsManager.reusedFilterShouldForget(storeNodeContactRequest, unknownID),
"contact requests are unaffected")
}