aboutsummaryrefslogtreecommitdiff
path: root/hotline/file_resume_data_test.go
diff options
context:
space:
mode:
authorJeff Halter <868228+jhalter@users.noreply.github.com>2026-07-10 09:48:18 -0700
committerJeff Halter <868228+jhalter@users.noreply.github.com>2026-07-10 09:48:18 -0700
commit21f24d24fd6f501b32f15a2bef41c89cc461f623 (patch)
treeba4952fb82f3ef9c265228633f27536866e23931 /hotline/file_resume_data_test.go
parentae44fb222ec73cae8441f5a5d9a21f8585fe587f (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.go87
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)
+ })
+ }
+}