Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 84c0008

Browse files
committed
fix(syncer): reject inaccessible guild targets
1 parent 7d3dfac commit 84c0008

5 files changed

Lines changed: 82 additions & 0 deletions

File tree

internal/cli/admin_commands.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,15 @@ func (r *runtime) runInit(args []string) error {
8080
return authErr(err)
8181
}
8282
cfg.GuildIDs = make([]string, 0, len(guilds))
83+
discovered := make(map[string]struct{}, len(guilds))
8384
for _, guild := range guilds {
8485
cfg.GuildIDs = append(cfg.GuildIDs, guild.ID)
86+
discovered[guild.ID] = struct{}{}
8587
}
8688
if *guildID != "" {
89+
if _, ok := discovered[*guildID]; !ok {
90+
return usageErr(fmt.Errorf("guild %s is not accessible", *guildID))
91+
}
8792
cfg.DefaultGuildID = *guildID
8893
}
8994
if cfg.DefaultGuildID == "" && len(cfg.GuildIDs) == 1 {

internal/cli/cli_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,6 +2606,31 @@ func TestRunInitWritesDiscoveredGuildConfig(t *testing.T) {
26062606
require.Contains(t, rt.stdout.(*bytes.Buffer).String(), "g2")
26072607
}
26082608

2609+
func TestRunInitRejectsUnknownDefaultGuild(t *testing.T) {
2610+
ctx := context.Background()
2611+
dir := t.TempDir()
2612+
cfgPath := filepath.Join(dir, "config.toml")
2613+
dbPath := filepath.Join(dir, "discrawl.db")
2614+
t.Setenv(config.DefaultTokenEnv, "env-token")
2615+
2616+
fakeSync := &fakeSyncService{discovered: []*discordgo.UserGuild{{ID: "g1"}}}
2617+
rt := &runtime{
2618+
ctx: ctx,
2619+
configPath: cfgPath,
2620+
stdout: &bytes.Buffer{},
2621+
stderr: &bytes.Buffer{},
2622+
logger: discardLogger(),
2623+
newDiscord: func(config.Config) (discordClient, error) { return &fakeDiscordClient{}, nil },
2624+
newSyncer: func(syncer.Client, *store.Store, *slog.Logger) syncService {
2625+
return fakeSync
2626+
},
2627+
}
2628+
2629+
err := rt.runInit([]string{"--db", dbPath, "--guild", "missing"})
2630+
require.Equal(t, 2, ExitCode(err))
2631+
require.ErrorContains(t, err, "guild missing is not accessible")
2632+
}
2633+
26092634
func TestRunMembersShowUsesDefaultGuildForAmbiguousQuery(t *testing.T) {
26102635
t.Parallel()
26112636

internal/syncer/records.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,31 @@ func selectGuilds(all []*discordgo.UserGuild, requested []string) []*discordgo.U
243243
}
244244
return out
245245
}
246+
247+
func missingGuildIDs(all []*discordgo.UserGuild, requested []string) []string {
248+
if len(requested) == 0 {
249+
return nil
250+
}
251+
available := make(map[string]struct{}, len(all))
252+
for _, guild := range all {
253+
if guild != nil && guild.ID != "" {
254+
available[guild.ID] = struct{}{}
255+
}
256+
}
257+
seen := map[string]struct{}{}
258+
var missing []string
259+
for _, id := range requested {
260+
id = strings.TrimSpace(id)
261+
if id == "" {
262+
continue
263+
}
264+
if _, ok := seen[id]; ok {
265+
continue
266+
}
267+
seen[id] = struct{}{}
268+
if _, ok := available[id]; !ok {
269+
missing = append(missing, id)
270+
}
271+
}
272+
return missing
273+
}

internal/syncer/syncer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ func (s *Syncer) Sync(ctx context.Context, opts SyncOptions) (SyncStats, error)
9898
if err != nil {
9999
return SyncStats{}, fmt.Errorf("list guilds: %w", err)
100100
}
101+
if missing := missingGuildIDs(guilds, opts.GuildIDs); len(missing) > 0 {
102+
return SyncStats{}, fmt.Errorf("requested guilds not accessible: %s", strings.Join(missing, ", "))
103+
}
101104
targets := selectGuilds(guilds, opts.GuildIDs)
102105
stats := SyncStats{}
103106
for _, guild := range targets {

internal/syncer/syncer_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,27 @@ func TestSyncMemberRefreshTimeoutStillMarksSuccess(t *testing.T) {
382382
require.NotEmpty(t, lastSync)
383383
}
384384

385+
func TestSyncRejectsUnknownRequestedGuild(t *testing.T) {
386+
t.Parallel()
387+
388+
ctx := context.Background()
389+
s, err := store.Open(ctx, filepath.Join(t.TempDir(), "discrawl.db"))
390+
require.NoError(t, err)
391+
defer func() { _ = s.Close() }()
392+
393+
client := &fakeClient{
394+
guilds: []*discordgo.UserGuild{{ID: "g1", Name: "Guild"}},
395+
}
396+
svc := New(client, s, nil)
397+
398+
stats, err := svc.Sync(ctx, SyncOptions{GuildIDs: []string{"missing"}})
399+
require.ErrorContains(t, err, "requested guilds not accessible: missing")
400+
require.Zero(t, stats)
401+
lastSync, err := s.GetSyncState(ctx, "sync:last_success")
402+
require.NoError(t, err)
403+
require.Empty(t, lastSync)
404+
}
405+
385406
func TestSyncSkipsMemberRefreshWhenExistingSnapshotPresent(t *testing.T) {
386407
t.Parallel()
387408

0 commit comments

Comments
 (0)