test: raise coverage floor to 85 percent
This commit is contained in:
parent
1e66b7d698
commit
d83d1c2f6f
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -83,8 +83,8 @@ jobs:
|
||||
print "missing coverage total"
|
||||
exit 1
|
||||
}
|
||||
if (total + 0 < 80.0) {
|
||||
printf("coverage %.1f%% is below 80%%\n", total + 0)
|
||||
if (total + 0 < 85.0) {
|
||||
printf("coverage %.1f%% is below 85%%\n", total + 0)
|
||||
exit 1
|
||||
}
|
||||
printf("coverage %.1f%%\n", total + 0)
|
||||
|
||||
@ -17,7 +17,7 @@ All notable changes to `discrawl` will be documented in this file.
|
||||
### Tests
|
||||
|
||||
- Added regression coverage for DM channel-name inference from cached profile data when Discord Desktop cache lacks explicit channel recipient metadata.
|
||||
- Added coverage for local DM conversation listing/filtering, DM cleanup paths, and Discord Desktop import helper edge cases.
|
||||
- Added coverage for local DM conversation listing/filtering, DM cleanup paths, share import/export helpers, CLI DM windows, and Discord Desktop import helper edge cases.
|
||||
|
||||
## 0.5.1 - 2026-04-24
|
||||
|
||||
|
||||
@ -647,7 +647,7 @@ go tool cover -func=/tmp/discrawl.cover | tail -n 1
|
||||
go build ./cmd/discrawl
|
||||
```
|
||||
|
||||
Target coverage is `>= 80%`.
|
||||
Target coverage is `>= 85%`.
|
||||
|
||||
CI runs:
|
||||
|
||||
|
||||
@ -182,6 +182,32 @@ func TestWiretapImportsDesktopDirectMessages(t *testing.T) {
|
||||
require.Contains(t, out.String(), "secret DM launch plan")
|
||||
}
|
||||
|
||||
func TestParseMessageWindow(t *testing.T) {
|
||||
rt := &runtime{now: func() time.Time {
|
||||
return time.Date(2026, 4, 24, 12, 0, 0, 0, time.UTC)
|
||||
}}
|
||||
|
||||
since, before, err := rt.parseMessageWindow(6, 0, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, time.Date(2026, 4, 24, 6, 0, 0, 0, time.UTC), since)
|
||||
require.True(t, before.IsZero())
|
||||
|
||||
since, before, err = rt.parseMessageWindow(0, 2, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, time.Date(2026, 4, 22, 12, 0, 0, 0, time.UTC), since)
|
||||
require.True(t, before.IsZero())
|
||||
|
||||
since, before, err = rt.parseMessageWindow(0, 0, "2026-04-20T10:00:00Z", "2026-04-21T10:00:00Z")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, time.Date(2026, 4, 20, 10, 0, 0, 0, time.UTC), since)
|
||||
require.Equal(t, time.Date(2026, 4, 21, 10, 0, 0, 0, time.UTC), before)
|
||||
|
||||
_, _, err = rt.parseMessageWindow(0, 0, "bad", "")
|
||||
require.Equal(t, 2, ExitCode(err))
|
||||
_, _, err = rt.parseMessageWindow(0, 0, "", "bad")
|
||||
require.Equal(t, 2, ExitCode(err))
|
||||
}
|
||||
|
||||
func TestWiretapAndSearchWorkWithoutConfig(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dir := t.TempDir()
|
||||
@ -1189,6 +1215,9 @@ func TestRuntimeInitSyncTailAndDoctor(t *testing.T) {
|
||||
require.Equal(t, "g2", cfg.DefaultGuildID)
|
||||
require.Equal(t, "atlas", cfg.Discord.Account)
|
||||
require.True(t, cfg.Search.Embeddings.Enabled)
|
||||
cfg.Desktop.Path = filepath.Join(dir, "empty-discord")
|
||||
require.NoError(t, os.MkdirAll(cfg.Desktop.Path, 0o755))
|
||||
require.NoError(t, config.Write(cfgPath, cfg))
|
||||
|
||||
rt = newRuntime()
|
||||
require.NoError(t, rt.withServices(true, func() error { return rt.runSync([]string{"--guilds", "g2"}) }))
|
||||
@ -1413,6 +1442,9 @@ func TestCommandUsageBranches(t *testing.T) {
|
||||
{[]string{"--config", cfgPath, "messages", "--dm", "--guild", "g1"}, "use either --dm or --guild/--guilds"},
|
||||
{[]string{"--config", cfgPath, "messages", "--dm", "--sync"}, "messages --sync is not supported with --dm"},
|
||||
{[]string{"--config", cfgPath, "dms", "extra"}, "dms takes flags only"},
|
||||
{[]string{"--config", cfgPath, "wiretap", "extra"}, "wiretap takes flags only"},
|
||||
{[]string{"--config", cfgPath, "wiretap", "--max-file-bytes", "0"}, "--max-file-bytes must be positive"},
|
||||
{[]string{"--config", cfgPath, "wiretap", "--watch-every", "1ms"}, "--watch-every must be at least 1s"},
|
||||
{[]string{"--config", cfgPath, "members"}, "members requires a subcommand"},
|
||||
{[]string{"--config", cfgPath, "members", "search"}, "members search requires a query"},
|
||||
{[]string{"--config", cfgPath, "members", "bogus"}, `unknown members subcommand "bogus"`},
|
||||
@ -1444,6 +1476,13 @@ func TestHelpers(t *testing.T) {
|
||||
require.Equal(t, 5, ExitCode(dbErr(assertErr("x"))))
|
||||
require.Equal(t, 3, ExitCode(configErr(assertErr("x"))))
|
||||
require.Equal(t, 1, ExitCode(assertErr("x")))
|
||||
require.True(t, hybridSemanticUnavailable(store.ErrNoCompatibleEmbeddings))
|
||||
require.True(t, hybridSemanticUnavailable(assertErr("semantic query embedding missing")))
|
||||
require.False(t, hybridSemanticUnavailable(assertErr("other")))
|
||||
opts, err := shareOptionsFromFlags("~/share", "", "")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultShareRemote, opts.Remote)
|
||||
require.Equal(t, "main", opts.Branch)
|
||||
var out bytes.Buffer
|
||||
require.NoError(t, printHuman(&out, syncer.SyncStats{Guilds: 1}))
|
||||
require.Contains(t, out.String(), "guilds=1")
|
||||
@ -1506,6 +1545,36 @@ func TestRuntimeHelpersAndSubcommands(t *testing.T) {
|
||||
}))
|
||||
}
|
||||
|
||||
func TestRunInitWritesDiscoveredGuildConfig(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
dir := t.TempDir()
|
||||
cfgPath := filepath.Join(dir, "config.toml")
|
||||
dbPath := filepath.Join(dir, "discrawl.db")
|
||||
t.Setenv(config.DefaultTokenEnv, "env-token")
|
||||
|
||||
fakeSync := &fakeSyncService{discovered: []*discordgo.UserGuild{{ID: "g1"}, {ID: "g2"}}}
|
||||
rt := &runtime{
|
||||
ctx: ctx,
|
||||
configPath: cfgPath,
|
||||
stdout: &bytes.Buffer{},
|
||||
stderr: &bytes.Buffer{},
|
||||
logger: discardLogger(),
|
||||
newDiscord: func(config.Config) (discordClient, error) { return &fakeDiscordClient{}, nil },
|
||||
newSyncer: func(syncer.Client, *store.Store, *slog.Logger) syncService {
|
||||
return fakeSync
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, rt.runInit([]string{"--db", dbPath, "--guild", "g2", "--with-embeddings"}))
|
||||
cfg, err := config.Load(cfgPath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, dbPath, cfg.DBPath)
|
||||
require.Equal(t, []string{"g1", "g2"}, cfg.GuildIDs)
|
||||
require.Equal(t, "g2", cfg.DefaultGuildID)
|
||||
require.True(t, cfg.Search.Embeddings.Enabled)
|
||||
require.Contains(t, rt.stdout.(*bytes.Buffer).String(), "g2")
|
||||
}
|
||||
|
||||
func TestRunMembersShowUsesDefaultGuildForAmbiguousQuery(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@ -49,6 +50,23 @@ func TestDesktopPathAndImportHelpers(t *testing.T) {
|
||||
require.True(t, parseDiscordTime("bad").IsZero())
|
||||
require.Empty(t, formatOptionalTime(time.Time{}))
|
||||
require.NotEmpty(t, formatOptionalTime(time.Date(2026, 4, 24, 12, 0, 0, 0, time.UTC)))
|
||||
|
||||
i, ok := intField(map[string]any{"value": float64(3)}, "value")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 3, i)
|
||||
i, ok = intField(map[string]any{"value": json.Number("4")}, "value")
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 4, i)
|
||||
_, ok = intField(map[string]any{"value": json.Number("nope")}, "value")
|
||||
require.False(t, ok)
|
||||
_, ok = intField(map[string]any{}, "value")
|
||||
require.False(t, ok)
|
||||
|
||||
require.Equal(t, int64(3), int64Field(map[string]any{"value": float64(3)}, "value"))
|
||||
require.Equal(t, int64(4), int64Field(map[string]any{"value": int64(4)}, "value"))
|
||||
require.Equal(t, int64(5), int64Field(map[string]any{"value": 5}, "value"))
|
||||
require.Equal(t, int64(6), int64Field(map[string]any{"value": json.Number("6")}, "value"))
|
||||
require.Zero(t, int64Field(map[string]any{"value": "6"}, "value"))
|
||||
}
|
||||
|
||||
func TestImportExtractsDirectMessageFromDesktopCache(t *testing.T) {
|
||||
|
||||
@ -35,8 +35,9 @@ func TestExportImportRoundTrip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = dst.Close() }()
|
||||
|
||||
imported, err := Import(ctx, dst, Options{RepoPath: repo, Branch: "main"})
|
||||
imported, changed, err := ImportIfChanged(ctx, dst, Options{RepoPath: repo, Branch: "main"})
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
require.Equal(t, manifest.GeneratedAt, imported.GeneratedAt)
|
||||
|
||||
results, err := dst.SearchMessages(ctx, store.SearchOptions{Query: "launch", Limit: 10})
|
||||
@ -55,6 +56,11 @@ func TestExportImportRoundTrip(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, manifest.GeneratedAt.Format(time.RFC3339Nano), lastManifest)
|
||||
require.False(t, NeedsImport(ctx, dst, 15*time.Minute))
|
||||
|
||||
imported, changed, err = ImportIfChanged(ctx, dst, Options{RepoPath: repo, Branch: "main"})
|
||||
require.NoError(t, err)
|
||||
require.False(t, changed)
|
||||
require.Equal(t, manifest.GeneratedAt, imported.GeneratedAt)
|
||||
}
|
||||
|
||||
func TestSnapshotExcludesLocalEmbeddingState(t *testing.T) {
|
||||
@ -560,10 +566,39 @@ func TestShareSmallHelpersAndValidation(t *testing.T) {
|
||||
require.Equal(t, `insert into "messages"("id","weird""column") values(?,?)`, insertSQL("messages", []string{"id", `weird"column`}))
|
||||
require.Equal(t, "blob", exportValue([]byte("blob")))
|
||||
require.Equal(t, "plain", exportValue("plain"))
|
||||
require.Equal(t, "plain", stringValue("plain"))
|
||||
require.Equal(t, "42", stringValue(json.Number("42")))
|
||||
require.Empty(t, stringValue(42))
|
||||
require.True(t, isNonFastForwardPush("failed to push some refs; fetch first"))
|
||||
require.True(t, isNonFastForwardPush("non-fast-forward"))
|
||||
require.False(t, isNonFastForwardPush("everything up-to-date"))
|
||||
|
||||
query, args := snapshotExportQuery("messages")
|
||||
require.Equal(t, "select * from messages where guild_id <> ?", query)
|
||||
require.Equal(t, []any{directMessageGuildID}, args)
|
||||
query, args = snapshotExportQuery("sync_state")
|
||||
require.Equal(t, "select * from sync_state where scope not like 'wiretap:%'", query)
|
||||
require.Nil(t, args)
|
||||
query, args = snapshotExportQuery("custom")
|
||||
require.Equal(t, "select * from custom", query)
|
||||
require.Nil(t, args)
|
||||
|
||||
query, args = snapshotDeleteQuery("channels")
|
||||
require.Equal(t, "delete from channels where guild_id <> ?", query)
|
||||
require.Equal(t, []any{directMessageGuildID}, args)
|
||||
query, args = snapshotDeleteQuery("message_events")
|
||||
require.Equal(t, "delete from message_events", query)
|
||||
require.Nil(t, args)
|
||||
query, args = snapshotDeleteQuery("custom")
|
||||
require.Equal(t, "delete from custom", query)
|
||||
require.Nil(t, args)
|
||||
|
||||
require.True(t, isDirectMessageSnapshotRow("guilds", map[string]any{"id": directMessageGuildID}))
|
||||
require.True(t, isDirectMessageSnapshotRow("channels", map[string]any{"guild_id": directMessageGuildID}))
|
||||
require.True(t, isDirectMessageSnapshotRow("sync_state", map[string]any{"scope": "wiretap:last_import"}))
|
||||
require.False(t, isDirectMessageSnapshotRow("sync_state", map[string]any{"scope": "share:last_import"}))
|
||||
require.False(t, isDirectMessageSnapshotRow("custom", map[string]any{"guild_id": directMessageGuildID}))
|
||||
|
||||
var buf bytes.Buffer
|
||||
cw := &countingWriter{w: &buf}
|
||||
n, err := cw.Write([]byte("abc"))
|
||||
@ -604,6 +639,28 @@ func TestShareSmallHelpersAndValidation(t *testing.T) {
|
||||
}}}), "has no files")
|
||||
}
|
||||
|
||||
func TestTableShardWriterRotates(t *testing.T) {
|
||||
oldMax := maxShardBytes
|
||||
maxShardBytes = 1
|
||||
t.Cleanup(func() { maxShardBytes = oldMax })
|
||||
|
||||
writer := tableShardWriter{rootDir: t.TempDir(), relDir: "tables/messages", label: "messages"}
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(writer.rootDir, filepath.FromSlash(writer.relDir)), 0o755))
|
||||
require.NoError(t, writer.open())
|
||||
_, err := writer.Write([]byte(`{"id":"m1"}` + "\n"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, writer.finishRow())
|
||||
require.NoError(t, writer.rotateIfNeeded())
|
||||
_, err = writer.Write([]byte(`{"id":"m2"}` + "\n"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, writer.finishRow())
|
||||
require.NoError(t, writer.close())
|
||||
require.Len(t, writer.files, 2)
|
||||
for _, rel := range writer.files {
|
||||
require.FileExists(t, filepath.Join(writer.rootDir, filepath.FromSlash(rel)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegacyManifestFileImportAndEmbeddingDecodeErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@ -212,10 +212,14 @@ func TestRunTail(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = s.Close() }()
|
||||
|
||||
client := &fakeClient{}
|
||||
handled := make(chan struct{}, 1)
|
||||
client := &fakeClient{tailHandled: handled}
|
||||
svc := New(client, s, nil)
|
||||
go func() {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
select {
|
||||
case <-handled:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
err = svc.RunTail(ctx, nil, 0)
|
||||
|
||||
@ -34,6 +34,7 @@ type fakeClient struct {
|
||||
beforeErrors map[string]map[string]error
|
||||
memberDelay time.Duration
|
||||
tailCalls int
|
||||
tailHandled chan struct{}
|
||||
messageDelay time.Duration
|
||||
guildChanCalls int
|
||||
threadCalls int
|
||||
@ -197,6 +198,12 @@ func (f *fakeClient) Tail(ctx context.Context, handler discordclient.EventHandle
|
||||
if err := handler.OnMessageCreate(ctx, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
if f.tailHandled != nil {
|
||||
select {
|
||||
case f.tailHandled <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user