diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-07-10 09:48:18 -0700 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-07-10 09:48:18 -0700 |
| commit | 21f24d24fd6f501b32f15a2bef41c89cc461f623 (patch) | |
| tree | ba4952fb82f3ef9c265228633f27536866e23931 /hotline/file_resume_data_test.go | |
| parent | ae44fb222ec73cae8441f5a5d9a21f8585fe587f (diff) | |
Overhaul regression testing: e2e suite, fuzzing, CI, and bug fixes
Add a protocol-level end-to-end suite (in-process fully wired server on
an ephemeral port pair, driven by hotline.Client over TCP) covering
handshake, login, public and private chat, message board, threaded
news, file list/download/upload, account admin, disconnect
notification, and shutdown broadcast. The harness retries on a fresh
port pair when another process steals a probed port before
ListenAndServe binds it, and Server gains WithConnectionRateLimit so
tests can disable the per-IP connection throttle.
Add native fuzz tests for Transaction, Field, and flattened file
object decoding, and fix the bugs the new tests surfaced:
- Transaction.Write panicked on out-of-range attacker-controlled size
fields, and transactionScanner's uint32 length addition could wrap
and yield a truncated token. The information fork size declared in
an untrusted fork header is now bounded too.
- Client keepalive read c.done unsynchronized while Disconnect
replaces it under the mutex.
- The shared Agreement's Seek+ReadAll login path raced concurrent
logins; the server now prefers an AgreementBytes() snapshot.
Fill unit-test gaps (main's config-copy helpers, file resume data,
ReloaderFunc, R2 error injection and env validation) and add a CI test
workflow (build/vet + race-enabled shuffled suite), fixed lint
workflow triggers with golangci-lint v2.6, and Makefile test/cover/
lint/fuzz targets.
Diffstat (limited to 'hotline/file_resume_data_test.go')
| -rw-r--r-- | hotline/file_resume_data_test.go | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/hotline/file_resume_data_test.go b/hotline/file_resume_data_test.go new file mode 100644 index 0000000..657be76 --- /dev/null +++ b/hotline/file_resume_data_test.go @@ -0,0 +1,87 @@ +package hotline + +import ( + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// UnmarshalBinary is covered in decode_test.go. This file covers the constructors, the marshal +// path, encode↔decode symmetry, and ForkType.String(). + +func TestForkType_String(t *testing.T) { + assert.Equal(t, "DATA", ForkTypeDATA.String()) + assert.Equal(t, "INFO", ForkTypeINFO.String()) + assert.Equal(t, "MACR", ForkTypeMACR.String()) +} + +func TestNewForkInfoList(t *testing.T) { + fil := NewForkInfoList([]byte{0x00, 0x00, 0x10, 0x00}) + assert.Equal(t, ForkTypeDATA, fil.Fork) + assert.Equal(t, [4]byte{0x00, 0x00, 0x10, 0x00}, fil.DataSize) + assert.Equal(t, uint32(0x1000), binary.BigEndian.Uint32(fil.DataSize[:])) +} + +func TestNewFileResumeData(t *testing.T) { + frd := NewFileResumeData([]ForkInfoList{*NewForkInfoList([]byte{0, 0, 0, 5})}) + + assert.Equal(t, FormatRFLT, frd.Format) + assert.Equal(t, [2]byte{0, 1}, frd.Version) + assert.Equal(t, [2]byte{0, 1}, frd.ForkCount, "ForkCount low byte tracks the list length") + require.Len(t, frd.ForkInfoList, 1) +} + +func TestFileResumeData_BinaryMarshal(t *testing.T) { + frd := NewFileResumeData([]ForkInfoList{ + *NewForkInfoList([]byte{0, 0, 0, 5}), + *NewForkInfoList([]byte{0, 0, 0, 9}), + }) + + b, err := frd.BinaryMarshal() + require.NoError(t, err) + assert.Len(t, b, resumeDataHeaderLen+2*forkInfoLen) + assert.Equal(t, FormatRFLT[:], b[0:4]) + assert.Equal(t, byte(2), b[41], "ForkCount low byte") +} + +func TestFileResumeData_RoundTrip(t *testing.T) { + tests := []struct { + name string + forks []ForkInfoList + }{ + { + name: "two forks", + forks: []ForkInfoList{*NewForkInfoList([]byte{0, 0, 0, 5}), *NewForkInfoList([]byte{0, 0, 1, 0})}, + }, + { + name: "three forks", + forks: []ForkInfoList{ + *NewForkInfoList([]byte{0, 0, 0, 5}), + *NewForkInfoList([]byte{0, 0, 1, 0}), + *NewForkInfoList([]byte{0, 0, 0, 1}), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + original := NewFileResumeData(tt.forks) + + b, err := original.BinaryMarshal() + require.NoError(t, err) + + var decoded FileResumeData + require.NoError(t, decoded.UnmarshalBinary(b)) + + // Format/Version/ForkCount and every fork survive the round trip. BinaryMarshal writes + // with LittleEndian and UnmarshalBinary reads with BigEndian, but all fields are + // [n]byte arrays, so the encoding is endian-neutral — this test pins that. + assert.Equal(t, original.Format, decoded.Format) + assert.Equal(t, original.Version, decoded.Version) + assert.Equal(t, original.ForkCount, decoded.ForkCount) + assert.Equal(t, original.ForkInfoList, decoded.ForkInfoList) + }) + } +} |