Something went wrong. Try again.
A community based topic aggregation platform built on atproto
Something went wrong. Try again.
9.9 kB · 233 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234package moderation_test
import ( "context" "fmt" "strings" "testing" "time"
"Coves/internal/core/moderation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/require")
type modlogValidationCommunityResolver struct{}
func (modlogValidationCommunityResolver) ResolveCommunityIdentifier(context.Context, string) (string, error) { return "did:plc:resolvedcommunity", nil}
func newModlogValidationService() (moderation.Service, *inMemoryModerationStore) { store := newInMemoryModerationStore(time.Now()) service := moderation.NewService(&fakeSubjectReader{}, store, moderation.Config{ InstanceDID: "did:web:test.coves.social", CursorSecret: "test-secret", CommunityResolver: modlogValidationCommunityResolver{}, }) return service, store}
func modlogListForValidation(t *testing.T, service moderation.Service, admin bool, params moderation.ListActionsParams, actionID string) error { t.Helper() if admin { _, err := service.ListAdminActions(t.Context(), moderation.ListAdminActionsParams{ ListActionsParams: params, ActionID: actionID, }) return err } _, err := service.ListActions(t.Context(), params) return err}
func TestModlogListRejectsInvalidParametersBeforeStore(t *testing.T) { for _, test := range []struct { name string params moderation.ListActionsParams }{ {"limit zero", moderation.ListActionsParams{Limit: modlogLimit(0)}}, {"limit above maximum", moderation.ListActionsParams{Limit: modlogLimit(101)}}, {"negative limit", moderation.ListActionsParams{Limit: modlogLimit(-1)}}, {"cursor above 2048 bytes", moderation.ListActionsParams{Cursor: strings.Repeat("a", 2049)}}, {"community above 320 bytes", moderation.ListActionsParams{Community: strings.Repeat("a", 321)}}, {"invalid subject", moderation.ListActionsParams{Subject: "not-a-uri"}}, {"invalid collection", moderation.ListActionsParams{Collection: "not an nsid"}}, {"action above 64 bytes", moderation.ListActionsParams{Action: strings.Repeat("a", 65)}}, {"origin above 64 bytes", moderation.ListActionsParams{Origin: strings.Repeat("a", 65)}}, {"invalid authority", moderation.ListActionsParams{Authority: "not a handle!"}}, {"invalid actor", moderation.ListActionsParams{Actor: "not a handle!"}}, {"invalid since", moderation.ListActionsParams{Since: "yesterday"}}, {"invalid until", moderation.ListActionsParams{Until: "yesterday"}}, {"invalid UTF-8 cursor", moderation.ListActionsParams{Cursor: "\xff"}}, {"invalid UTF-8 action", moderation.ListActionsParams{Action: "\xff"}}, {"invalid UTF-8 origin", moderation.ListActionsParams{Origin: "\xff"}}, {"invalid UTF-8 community", moderation.ListActionsParams{Community: "\xff"}}, } { for _, endpoint := range []struct { name string admin bool }{{"public", false}, {"admin", true}} { t.Run(endpoint.name+"/"+test.name, func(t *testing.T) { service, store := newModlogValidationService() err := modlogListForValidation(t, service, endpoint.admin, test.params, "") require.ErrorIs(t, err, moderation.ErrInvalidRequest) require.Empty(t, store.listQueries, "invalid parameters must never reach the store") }) } } for _, test := range []struct { name string actionID string }{ {"actionId above 128 bytes", strings.Repeat("a", 129)}, {"invalid UTF-8 actionId", "\xff"}, } { t.Run("admin/"+test.name, func(t *testing.T) { service, store := newModlogValidationService() err := modlogListForValidation(t, service, true, moderation.ListActionsParams{}, test.actionID) require.ErrorIs(t, err, moderation.ErrInvalidRequest) require.Empty(t, store.listQueries, "invalid actionId must never reach the store") }) }}
func modlogLimit(value int) *int { return &value }
func TestModlogListAcceptsBoundariesAndRequestsOneExtraRow(t *testing.T) { for _, test := range []struct { name string params moderation.ListActionsParams actionID string storeLimit int }{ {"default limit and absent cursor", moderation.ListActionsParams{}, "", 51}, {"limit one", moderation.ListActionsParams{Limit: modlogLimit(1)}, "", 2}, {"limit ten", moderation.ListActionsParams{Limit: modlogLimit(10)}, "", 11}, {"limit one hundred", moderation.ListActionsParams{Limit: modlogLimit(100)}, "", 101}, {"community exactly 320 bytes", moderation.ListActionsParams{Community: strings.Repeat("a", 320)}, "", 51}, {"action exactly 64 bytes", moderation.ListActionsParams{Action: strings.Repeat("a", 64)}, "", 51}, {"origin exactly 64 bytes", moderation.ListActionsParams{Origin: strings.Repeat("a", 64)}, "", 51}, {"actionId exactly 128 bytes", moderation.ListActionsParams{}, strings.Repeat("a", 128), 51}, } { for _, endpoint := range []struct { name string admin bool }{{"public", false}, {"admin", true}} { if test.actionID != "" && !endpoint.admin { continue } t.Run(endpoint.name+"/"+test.name, func(t *testing.T) { service, store := newModlogValidationService() err := modlogListForValidation(t, service, endpoint.admin, test.params, test.actionID) require.NoError(t, err) require.Len(t, store.listQueries, 1, "accepted filters must reach the store exactly once") query := store.listQueries[0] require.Equal(t, test.storeLimit, query.Limit, "the store fetches one extra row for pagination") if test.params.Community != "" { require.Equal(t, "did:plc:resolvedcommunity", query.CommunityDID) } if test.params.Action != "" { require.Equal(t, test.params.Action, query.Action) } if test.params.Origin != "" { require.Equal(t, test.params.Origin, query.Origin) } if test.actionID != "" { require.Equal(t, test.actionID, query.ActionID) } }) } }}
// Control characters never name a real filter target, and a NUL reaching// Postgres is an encoding error the caller could use to force a 503.func TestModlogListRejectsControlCharactersBeforeCursorResolutionAndStore(t *testing.T) { for _, control := range []string{"\x00", "\x1f", "\x7f", "\u0085"} { for _, test := range []struct { name string params moderation.ListActionsParams actionID string }{ {"action", moderation.ListActionsParams{Action: "remove" + control}, ""}, {"origin", moderation.ListActionsParams{Origin: "lo" + control + "cal"}, ""}, {"community", moderation.ListActionsParams{Community: "name" + control + "@coves.social"}, ""}, {"actor", moderation.ListActionsParams{Actor: "actor.example" + control}, ""}, {"authority", moderation.ListActionsParams{Authority: control + "authority.example"}, ""}, {"actionId", moderation.ListActionsParams{}, "3mwmy" + control}, } { for _, admin := range []bool{false, true} { if test.actionID != "" && !admin { continue } t.Run(fmt.Sprintf("%s/%q/admin=%t", test.name, control, admin), func(t *testing.T) { store := newInMemoryModerationStore(time.Now()) community := &modlogFilterCommunityResolver{did: "did:plc:resolvedcommunity"} handles := &modlogFilterHandleResolver{dids: map[string]string{}} params := test.params params.Cursor = "not?base64url" // Parameter validation precedes the cursor check. _, err := modlogFilterList(t, modlogFilterService(store, community, handles), admin, params, test.actionID) require.ErrorIs(t, err, moderation.ErrInvalidRequest) assert.Empty(t, community.calls, "a control character must never reach the community resolver") assert.Empty(t, handles.calls, "a control character must never reach the handle resolver") assert.Empty(t, store.listQueries, "a control character must never reach the store") }) } } }}
// since is inclusive and until exclusive, so since >= until (after both// round up to whole microseconds) selects nothing and is a caller mistake.func TestModlogListRejectsEmptyTimeWindow(t *testing.T) { for _, test := range []struct { name string since string until string reject bool }{ {"equal", "2026-09-28T12:00:00.123456Z", "2026-09-28T12:00:00.123456Z", true}, {"since after until", "2026-09-29T12:00:00Z", "2026-09-28T12:00:00Z", true}, {"equal across time zones", "2026-09-28T14:00:00+02:00", "2026-09-28T12:00:00Z", true}, {"since rounds up onto until", "2026-09-28T12:00:00.1234561Z", "2026-09-28T12:00:00.123457Z", true}, {"until rounds up past since", "2026-09-28T12:00:00.123456Z", "2026-09-28T12:00:00.1234561Z", false}, {"one microsecond window", "2026-09-28T12:00:00.123456Z", "2026-09-28T12:00:00.123457Z", false}, } { for _, admin := range []bool{false, true} { t.Run(fmt.Sprintf("%s/admin=%t", test.name, admin), func(t *testing.T) { service, store := newModlogValidationService() err := modlogListForValidation(t, service, admin, moderation.ListActionsParams{Since: test.since, Until: test.until}, "") if test.reject { require.ErrorIs(t, err, moderation.ErrInvalidRequest) assert.Empty(t, store.listQueries) return } require.NoError(t, err) assert.Len(t, store.listQueries, 1) }) } }}
// Stored subjects always carry a DID authority, so a handle authority can// never match; GetSubjectState rejects the same input.func TestModlogListRejectsHandleAuthoritySubject(t *testing.T) { for _, admin := range []bool{false, true} { t.Run(fmt.Sprintf("admin=%t", admin), func(t *testing.T) { service, store := newModlogValidationService() err := modlogListForValidation(t, service, admin, moderation.ListActionsParams{ Subject: "at://alice.test/social.coves.community.comment/3mwmycomment", }, "") require.ErrorIs(t, err, moderation.ErrInvalidRequest) assert.Empty(t, store.listQueries)
err = modlogListForValidation(t, service, admin, moderation.ListActionsParams{ Subject: "at://did:plc:alice/social.coves.community.comment/3mwmycomment", }, "") require.NoError(t, err) assert.Len(t, store.listQueries, 1) }) }}