Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
de27273
engine: cover Pause idempotent + Remove unknown-hash arms
Apr 26, 2026
e57fcfe
engine: cover SetFilePriority unknown-infohash arm
Apr 26, 2026
c6825ab
httpapi: cover handleSearch swarm-hits iteration arm
Apr 26, 2026
e65ce71
dhtindex: cover LoadOrCreate null-entries-field + Save tmp-write arms
Apr 26, 2026
6d9e04a
indexer/extractors: cover archive ext-fallback + zipMember empty-name…
Apr 26, 2026
66da7ab
indexer/extractors: cover docx empty/malformed + exif no-EXIF/bad-TIF…
Apr 26, 2026
37a6853
indexer/extractors: cover EPUB whitespace + RTF control-word arms
Apr 26, 2026
9f1f300
indexer/extractors: cover chunker default+join+oversized + subtitle e…
Apr 26, 2026
eb3b7c1
indexer: cover OpenWithLogger openOrCreate-error path
Apr 26, 2026
39aefdd
companion+swarmsearch: cover bad-root-index + BuildRecordsFrame cap arms
Apr 26, 2026
38200a7
swarmsearch: cover handleQuery DecodeQuery-error arm
Apr 26, 2026
02f9796
engine: cover New bloom-load + reputation-load warn arms
Apr 26, 2026
ce36e09
engine: cover New session-load + identity-load warn arms
Apr 26, 2026
c818ec5
engine: cover New HTTPUserAgent override arm
Apr 26, 2026
17172a2
indexer/extractors: cover FLAC vorbis-parser + truncated-block arms
Apr 26, 2026
96d83a0
indexer/extractors: cover findExifTIFF non-EXIF segment skip arm
Apr 26, 2026
7340ab6
engine: cover AddTorrentFile writeTorrentCopy-error tolerance arm
Apr 26, 2026
132edd9
companion: cover subscriber ingest IndexContent-error arm
Apr 26, 2026
6d3da94
engine: cover Close bloom-save + tracker-save warn arms
Apr 26, 2026
38002bc
indexer: cover AllTorrentDocs from+=batch pagination arm
Apr 26, 2026
267bf4f
indexer/extractors: cover ZIM Extract redirect/deleted/empty-text/max…
Apr 26, 2026
9d06375
indexer/extractors: cover ZIM dispatch-by-ext + cluster-type guards
Apr 26, 2026
d8e52d5
swarmsearch: cover onSyncNeed BuildRecordsFrame-overflow arm
Apr 26, 2026
c38f265
engine: cover New seed-list import arms
Apr 26, 2026
afcaf9a
engine: cover New trust-load warn arm
Apr 26, 2026
39568fe
companion: cover collectFileRecords late-path-fill arm
Apr 26, 2026
e1bdc07
engine: cover snapshotOf post-metadata paused-status arm
Apr 26, 2026
ff9a57d
indexer/extractors: cover ZIM getZimBlob corner-case error arms
Apr 26, 2026
fc7c5e5
indexer/extractors: cover ZIM zimDeletedMime dir-entry arm
Apr 26, 2026
db46ad9
engine: cover countActiveDownloads seeding-skip arm
Apr 26, 2026
6dfe5b3
dhtindex: cover AnacrolixPutter Put + PutPPMI encode-error arms
Apr 26, 2026
56bc919
dhtindex: cover Manifest AddHit empty-keyword guard
Apr 26, 2026
579748a
swarmsearch: cover RIBLT Converged DataXOR-non-zero arm
Apr 26, 2026
cc01c77
companion: cover Decode read-error arm
Apr 26, 2026
8d458bd
indexer/extractors: cover ZIM readZimMimeList read-error arm
Apr 26, 2026
4d52bd7
indexer/extractors: cover readZimHeader truncated-input arm
Apr 26, 2026
cebefd8
indexer/extractors: cover ZIM Extract dir-entry read-fail continue arm
Apr 26, 2026
cd5f3ee
indexer/extractors: cover ID3 short/version/oversize/padding arms
Apr 26, 2026
ba0024a
indexer/extractors: gofmt id3_branches_test alignment
Apr 26, 2026
c7a0076
indexer/extractors: cover ID3 unknown-frame + empty-text continue arms
Apr 26, 2026
86a3b39
indexer/extractors: cover MKV truncated/missing-segment/empty/discard…
Apr 26, 2026
f00f9ca
indexer/extractors: cover MP4 oversize-skip + PPTX empty/bad/no-slide…
Apr 26, 2026
8414c08
indexer/extractors: cover ODT empty-text + malformed-content arms
Apr 26, 2026
c569682
indexer/extractors: cover MOBI Extract guard arms
Apr 26, 2026
4415f45
indexer/extractors: cover OGG Extract EOF-before-packet arm
Apr 26, 2026
fc3d7cb
indexer/extractors: cover FB2 malformed-XML + empty-text arms
Apr 26, 2026
6d5af48
indexer/extractors: cover ZIM Extract getZimBlob-error continue arm
Apr 26, 2026
723858c
indexer/extractors: cover archive Extract dir-only-zip arm
Apr 26, 2026
9719dbf
indexer/extractors: cover PPTX <a:br/> + malformed-XML arms
Apr 26, 2026
69071b2
indexer/extractors: cover isXHTMLChapter empty-name + trailing-slash arm
Apr 26, 2026
037cf17
indexer/extractors: cover findExifTIFF non-FF-marker arm
Apr 26, 2026
7ec2000
indexer/extractors: cover decodeID3Bytes UTF-16 + unknown-enc arms
Apr 26, 2026
c1ee4e9
indexer/extractors: cover readZimCluster zstd-decode-error arm
Apr 26, 2026
43ede06
engine: cover persistAdd save-error tolerance arm
Apr 26, 2026
ce25cf0
engine: cover persistState save-error tolerance arm
Apr 26, 2026
b020a97
engine: cover DHTAddr nil-server arm
Apr 26, 2026
d37ee78
companion: cover walkToLeaves DecodeInterior-error arm
Apr 26, 2026
6e78fe5
engine: cover autoIndex IndexTorrent success arm
Apr 26, 2026
00235f5
engine: cover autoIndex IndexTorrent-error warn arm
Apr 26, 2026
c20fa3c
engine: cover ingestFileEvents OpenReader-closure happy path
Apr 26, 2026
f277927
engine: cover ingestFileEvents per-torrent indexing-off arm
Apr 26, 2026
b33f908
engine: cover startPublisher full-path with DHT enabled
Apr 26, 2026
f1d7def
engine: cover startPublisher leech-only DHT mode
Apr 26, 2026
87c3a20
engine: extend startPublisher full-path to fire autoIndex pub.Submit
Apr 26, 2026
6fc5a5e
engine: cover upgradeMagnetSession happy path
Apr 26, 2026
d701108
engine: cover upgradeMagnetSession writeTorrentCopy-error arm
Apr 26, 2026
a48bcbc
engine: cover FetchCompanionTorrent happy path
Apr 26, 2026
b8f353d
engine: extend startPublisher full-path with regtest + tracker + bloo…
Apr 26, 2026
acd7dfb
engine: gofmt start_publisher_test alignment
Apr 26, 2026
0548a7f
engine: cover startPublisher manifest-load-error arm
Apr 27, 2026
141dc73
engine: cover engine.New peer-conn callback chain
Apr 27, 2026
73d4d37
engine: cover file_tracker.run piece-event path via peer download
Apr 27, 2026
376ac05
engine: cover sn_search reply-closure body via cross-engine query
Apr 27, 2026
b3c8182
engine: cover upgradeMagnetSession session-update-error arm
Apr 27, 2026
e77f6df
engine: cover FetchCompanionTorrent ctx-cancel mid-poll arm
Apr 27, 2026
902719c
cli: cover loadPrivKey + defaultIdentityPath error arms
Apr 27, 2026
e760b4f
reputation: cover BloomFilter.Save writeBloom-err arm
Apr 27, 2026
096ac13
extractors/zim: cover readZimCluster guard arms
Apr 27, 2026
0d3b1ee
indexer: cover OpenWithLogger schema-rebuild RemoveAll error arm
Apr 27, 2026
41b0a67
engine: cover promoteQueuedLocked cap-limited break arm
Apr 27, 2026
1d1d57d
cli: cover cmdAggregateFind error arms
Apr 27, 2026
c3bf7a0
cli: cover crawl-probe error and explicit-target arms
Apr 27, 2026
d5fc062
extractors/pdf: cover read-err and empty-text arms
Apr 27, 2026
7bd772e
cli: cover readRecords error arms and empty-line skip
Apr 27, 2026
5f4483a
extractors: cover office read-input err arms (odp/odt/docx/pptx)
Apr 27, 2026
803fe71
extractors/mkv: cover Extract truncation arms
Apr 27, 2026
eb44417
extractors: cover read-input err arms (mp4/epub/exif/mobi/archive)
Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions cmd/swartznet/cmd_aggregate_find_errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"bytes"
"os"
"path/filepath"
"testing"
)

// TestCmdAggregateFindBadFlag covers cmdAggregateFind's
// `if err := fs.Parse(args); err != nil { return exitUsage }` arm.
// An unknown flag makes flag.Parse return ErrHelp.
func TestCmdAggregateFindBadFlag(t *testing.T) {
t.Parallel()
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := cmdAggregate([]string{"find", "--no-such-flag", "x.bin", "p"}, stdout, stderr)
if code != exitUsage {
t.Errorf("bad-flag exit = %d, want exitUsage", code)
}
}

// TestCmdAggregateFindMissingArgs covers the
// `if fs.NArg() != 2` usage arm.
func TestCmdAggregateFindMissingArgs(t *testing.T) {
t.Parallel()
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := cmdAggregate([]string{"find"}, stdout, stderr)
if code != exitUsage {
t.Errorf("missing-args exit = %d, want exitUsage", code)
}
}

// TestCmdAggregateFindReadFileError covers the
// `os.ReadFile err → exitRuntime` arm: pass a path that doesn't
// exist.
func TestCmdAggregateFindReadFileError(t *testing.T) {
t.Parallel()
missing := filepath.Join(t.TempDir(), "nope.bin")
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := cmdAggregate([]string{"find", missing, "prefix"}, stdout, stderr)
if code != exitRuntime {
t.Errorf("missing-file exit = %d, want exitRuntime", code)
}
}

// TestCmdAggregateFindOpenBTreeError covers the
// `companion.OpenBTree err → exitRuntime` arm: hand it a real
// file with garbage bytes so the b-tree header check fails.
func TestCmdAggregateFindOpenBTreeError(t *testing.T) {
t.Parallel()
bogus := filepath.Join(t.TempDir(), "bogus.bin")
if err := os.WriteFile(bogus, []byte("this is not a btree page"), 0o644); err != nil {
t.Fatal(err)
}
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
code := cmdAggregate([]string{"find", bogus, "prefix"}, stdout, stderr)
if code != exitRuntime {
t.Errorf("garbage-file exit = %d, want exitRuntime", code)
}
}
77 changes: 77 additions & 0 deletions cmd/swartznet/cmd_crawl_probe_more_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"bytes"
"strings"
"testing"

"github.com/anacrolix/dht/v2/krpc"
)

// TestCmdCrawlProbeBadFlag covers the
// `if err := fs.Parse(args); err != nil { return exitUsage }`
// arm. Unknown flag forces flag.Parse to return ErrHelp.
func TestCmdCrawlProbeBadFlag(t *testing.T) {
t.Parallel()
var stdout, stderr bytes.Buffer
code := cmdCrawlProbe([]string{"--no-such-flag"}, &stdout, &stderr)
if code != exitUsage {
t.Errorf("bad-flag exit = %d, want exitUsage", code)
}
}

// TestCmdCrawlProbeBadAddr covers the `net.ResolveUDPAddr err →
// exitUsage` arm. An obviously malformed addr fails resolution
// without leaving the process.
func TestCmdCrawlProbeBadAddr(t *testing.T) {
t.Parallel()
var stdout, stderr bytes.Buffer
code := cmdCrawlProbe([]string{"--addr", "totally::not::a::host"}, &stdout, &stderr)
if code != exitUsage {
t.Errorf("bad-addr exit = %d, want exitUsage", code)
}
if !strings.Contains(stderr.String(), "resolve") {
t.Errorf("expected 'resolve' in stderr, got %q", stderr.String())
}
}

// TestCmdCrawlProbeExplicitTargetText covers the
// `else { raw, err := hex.DecodeString(targetHex); ... copy(target[:], raw) }`
// happy-path branch — only the random-target branch is otherwise
// covered. Pair with the responder so the request actually
// completes and the text-output renderer runs.
func TestCmdCrawlProbeExplicitTargetText(t *testing.T) {
t.Parallel()
addr := startBep51Responder(t, []krpc.ID{{0x77, 0x88}})

const target = "0123456789abcdef0123456789abcdef01234567" // 40 hex chars
var stdout, stderr bytes.Buffer
code := cmdCrawlProbe([]string{
"--addr", addr,
"--target", target,
"--timeout-ms", "3000",
}, &stdout, &stderr)
if code != exitOK {
t.Fatalf("exit = %d (stderr: %s)", code, stderr.String())
}
if !strings.Contains(stdout.String(), target) {
t.Errorf("expected target %q echoed in output: %s", target, stdout.String())
}
}

// TestCmdCrawlProbeQueryFails covers the
// `dhtindex.SampleInfohashes err → exitRuntime` arm. Point at a
// loopback port nothing's listening on; a short timeout makes the
// query fail rather than wait forever.
func TestCmdCrawlProbeQueryFails(t *testing.T) {
t.Parallel()
var stdout, stderr bytes.Buffer
// 127.0.0.1:1 is reserved + nothing listens. timeout 200ms.
code := cmdCrawlProbe([]string{
"--addr", "127.0.0.1:1",
"--timeout-ms", "200",
}, &stdout, &stderr)
if code != exitRuntime {
t.Errorf("query-fail exit = %d, want exitRuntime", code)
}
}
77 changes: 77 additions & 0 deletions cmd/swartznet/load_priv_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"os"
"path/filepath"
"testing"
)

// TestLoadPrivKeyExplicitPath covers loadPrivKey's explicit-path
// branch: with keyPath != "", LoadOrCreate is called directly
// against that path. On a fresh path it generates and persists a
// new keypair, so subsequent calls return the same one.
func TestLoadPrivKeyExplicitPath(t *testing.T) {
t.Parallel()
keyPath := filepath.Join(t.TempDir(), "id.key")

priv, pub, err := loadPrivKey(keyPath)
if err != nil {
t.Fatalf("loadPrivKey: %v", err)
}
if len(priv) == 0 || len(pub) == 0 {
t.Errorf("loadPrivKey returned empty keypair")
}

// Re-load — must round-trip to the same bytes.
priv2, pub2, err := loadPrivKey(keyPath)
if err != nil {
t.Fatalf("loadPrivKey (round trip): %v", err)
}
if string(priv) != string(priv2) || string(pub) != string(pub2) {
t.Error("loadPrivKey round-trip produced different keypair")
}
}

// TestLoadPrivKeyExplicitPathError covers loadPrivKey's
// `if err != nil → return` arm in the explicit-path branch.
// Plant a directory at the keyPath so LoadOrCreate's read fails.
func TestLoadPrivKeyExplicitPathError(t *testing.T) {
t.Parallel()
keyDir := filepath.Join(t.TempDir(), "id.key")
if err := os.Mkdir(keyDir, 0o755); err != nil {
t.Fatal(err)
}

if _, _, err := loadPrivKey(keyDir); err == nil {
t.Error("loadPrivKey should fail when keyPath is a directory")
}
}

// TestLoadPrivKeyDefaultPath exercises loadPrivKey's empty-path
// branch (keyPath == "" → defaultIdentityPath → LoadOrCreate).
// Override HOME so the test never touches the user's real
// identity file.
func TestLoadPrivKeyDefaultPath(t *testing.T) {
t.Setenv("HOME", t.TempDir())

priv, pub, err := loadPrivKey("")
if err != nil {
t.Fatalf("loadPrivKey(\"\"): %v", err)
}
if len(priv) == 0 || len(pub) == 0 {
t.Errorf("loadPrivKey returned empty keypair")
}
}

// TestLoadPrivKeyDefaultPathHomeUnset covers the
// `defaultIdentityPath err → return nil, nil, err` arm of
// loadPrivKey, plus defaultIdentityPath's own UserHomeDir-err
// arm. Empty HOME makes os.UserHomeDir fail with
// "$HOME is not defined".
func TestLoadPrivKeyDefaultPathHomeUnset(t *testing.T) {
t.Setenv("HOME", "")

if _, _, err := loadPrivKey(""); err == nil {
t.Error("loadPrivKey(\"\") should fail when HOME is unset")
}
}
86 changes: 86 additions & 0 deletions cmd/swartznet/read_records_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"os"
"path/filepath"
"strings"
"testing"
)

// TestReadRecordsHappyPath covers the successful JSONL read with
// blank lines (exercises the `if len(b) == 0 { continue }` arm)
// and a valid record per line.
func TestReadRecordsHappyPath(t *testing.T) {
t.Parallel()
const ih = "0123456789abcdef0123456789abcdef01234567" // 40 hex chars
body := `
{"ih":"` + ih + `","kw":"alpha","t":1}

{"ih":"` + ih + `","kw":"beta","t":2}
`
path := filepath.Join(t.TempDir(), "input.jsonl")
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}

recs, err := readRecords(path)
if err != nil {
t.Fatalf("readRecords: %v", err)
}
if len(recs) != 2 {
t.Fatalf("got %d records, want 2", len(recs))
}
if recs[0].Kw != "alpha" || recs[1].Kw != "beta" {
t.Errorf("unexpected records: %+v", recs)
}
}

// TestReadRecordsOpenError covers the
// `f, err := os.Open(path); if err != nil { return nil, err }` arm.
func TestReadRecordsOpenError(t *testing.T) {
t.Parallel()
missing := filepath.Join(t.TempDir(), "nope.jsonl")
if _, err := readRecords(missing); err == nil {
t.Error("readRecords should fail on a missing file")
}
}

// TestReadRecordsEmptyKwError covers the
// `if jr.Kw == "" { return nil, ... empty kw ... }` arm.
func TestReadRecordsEmptyKwError(t *testing.T) {
t.Parallel()
const ih = "0123456789abcdef0123456789abcdef01234567"
body := `{"ih":"` + ih + `","kw":"","t":1}` + "\n"
path := filepath.Join(t.TempDir(), "input.jsonl")
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}

_, err := readRecords(path)
if err == nil {
t.Fatal("readRecords should fail on empty kw")
}
if !strings.Contains(err.Error(), "empty kw") {
t.Errorf("unexpected err: %v", err)
}
}

// TestReadRecordsScannerError covers the
// `if err := scanner.Err(); err != nil` arm. A line longer than
// the 1 MiB scanner buffer triggers bufio.ErrTooLong, which
// surfaces from scanner.Err() after Scan returns false.
func TestReadRecordsScannerError(t *testing.T) {
t.Parallel()
const ih = "0123456789abcdef0123456789abcdef01234567"
// 2 MiB record — exceeds the 1 MiB scanner buffer.
huge := strings.Repeat("a", 2*1024*1024)
body := `{"ih":"` + ih + `","kw":"` + huge + `","t":1}` + "\n"
path := filepath.Join(t.TempDir(), "input.jsonl")
if err := os.WriteFile(path, []byte(body), 0o644); err != nil {
t.Fatal(err)
}

if _, err := readRecords(path); err == nil {
t.Error("readRecords should fail when a line exceeds the scanner buffer")
}
}
28 changes: 28 additions & 0 deletions internal/companion/collect_file_records_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package companion

import (
"testing"

"github.com/swartznet/swartznet/internal/indexer"
)

// TestCollectFileRecordsFillsLatePath covers collectFileRecords'
// `if b.path == "" && c.FilePath != "" { b.path = c.FilePath }`
// arm. Sequencing matters: the FIRST ContentDoc seen for a file
// must have FilePath="", a LATER one for the same file must
// have a non-empty FilePath. Internal test so we can control
// ordering by calling collectFileRecords directly.
func TestCollectFileRecordsFillsLatePath(t *testing.T) {
t.Parallel()
docs := []indexer.ContentDoc{
{InfoHash: "x", FileIndex: 0, FilePath: "", Text: "first"},
{InfoHash: "x", FileIndex: 0, FilePath: "late.txt", Text: "second"},
}
out := collectFileRecords([]string{"late.txt"}, docs, DefaultBuildOptions())
if len(out) != 1 {
t.Fatalf("len(out) = %d, want 1", len(out))
}
if out[0].Path != "late.txt" {
t.Errorf("FileRecord.Path = %q, want late.txt", out[0].Path)
}
}
55 changes: 55 additions & 0 deletions internal/companion/decode_read_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package companion_test

import (
"bytes"
"compress/gzip"
"errors"
"io"
"testing"

"github.com/swartznet/swartznet/internal/companion"
)

// gzipPrefixThenError starts a valid gzip stream so
// gzip.NewReader's header validation passes, then errors on
// every subsequent Read so io.ReadAll inside Decode surfaces
// the wrapped error.
type gzipPrefixThenError struct {
prefix []byte
pos int
}

func (g *gzipPrefixThenError) Read(p []byte) (int, error) {
if g.pos < len(g.prefix) {
n := copy(p, g.prefix[g.pos:])
g.pos += n
return n, nil
}
return 0, errors.New("companion test: forced read failure")
}

// TestDecodeReadAllError covers Decode's
// `body, err := io.ReadAll(io.LimitReader(gz, ...))` error arm.
// gzip.NewReader successfully reads the magic + header, then
// the underlying reader errors on subsequent reads. Decode must
// surface the wrapped 'read gzip' error rather than panic.
func TestDecodeReadAllError(t *testing.T) {
t.Parallel()
// Build a real gzip header (10 bytes), enough for
// gzip.NewReader to succeed but not enough to decode any
// real content.
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
if _, err := gz.Write([]byte("partial")); err != nil {
t.Fatal(err)
}
// Don't close — keep the stream incomplete.

r := &gzipPrefixThenError{prefix: buf.Bytes()}
if _, err := companion.Decode(r); err == nil {
t.Error("Decode should surface read error")
}
}

// satisfy io import.
var _ io.Reader = (*gzipPrefixThenError)(nil)
Loading
Loading