diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2025-07-05 15:19:15 -0700 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2025-07-05 15:19:15 -0700 |
| commit | b6bca2b7fe943ca4fd8f62a44bcfcc0c9f5a6de1 (patch) | |
| tree | a689744f07dbd61e6e33e1b081dc335a43c1fb57 /hotline | |
| parent | 1f3e5ec139022c20be8ca61b467b96ae7e78a36a (diff) | |
Add comprehensive test coverage for news.go functions
- Add table-driven tests for GetNewsArtListData, DataSize, NewsArtListData.Read, NewsArtList.Read, and newsPathScanner
- Improve test coverage from 0% to 87.5%-100% for these functions
- Add test for Field.DecodeNewsPath function
- Fix NewsArtList.Read to return nil instead of io.EOF for proper io.Reader behavior
- Add constants for NewsFlavorCount and improve code documentation
Diffstat (limited to 'hotline')
| -rw-r--r-- | hotline/field_test.go | 67 | ||||
| -rw-r--r-- | hotline/news.go | 12 | ||||
| -rw-r--r-- | hotline/news_test.go | 271 |
3 files changed, 344 insertions, 6 deletions
diff --git a/hotline/field_test.go b/hotline/field_test.go index 676098c..3440b0d 100644 --- a/hotline/field_test.go +++ b/hotline/field_test.go @@ -229,3 +229,70 @@ func TestField_DecodeInt(t *testing.T) { }) } } + +func TestField_DecodeNewsPath(t *testing.T) { + type fields struct { + Data []byte + } + tests := []struct { + name string + fields fields + want []string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "empty field data", + fields: fields{Data: []byte{}}, + want: []string{}, + wantErr: assert.NoError, + }, + { + name: "single path", + fields: fields{Data: []byte{ + 0x00, 0x01, // path count = 1 + 0x00, 0x00, 0x05, // 2 bytes unused + 1 byte length (5) + 0x48, 0x65, 0x6c, 0x6c, 0x6f, // "Hello" + }}, + want: []string{"Hello"}, + wantErr: assert.NoError, + }, + { + name: "multiple paths", + fields: fields{Data: []byte{ + 0x00, 0x02, // path count = 2 + 0x00, 0x00, 0x05, // 2 bytes unused + 1 byte length (5) + 0x48, 0x65, 0x6c, 0x6c, 0x6f, // "Hello" + 0x00, 0x00, 0x05, // 2 bytes unused + 1 byte length (5) + 0x57, 0x6f, 0x72, 0x6c, 0x64, // "World" + }}, + want: []string{"Hello", "World"}, + wantErr: assert.NoError, + }, + { + name: "example from comments - nested categories", + fields: fields{Data: []byte{ + 0x00, 0x03, // path count = 3 + 0x00, 0x00, 0x10, // 2 bytes unused + 1 byte length (16) + 0x54, 0x6f, 0x70, 0x20, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, // "Top Level Bundle" + 0x00, 0x00, 0x13, // 2 bytes unused + 1 byte length (19) + 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, // "Second Level Bundle" + 0x00, 0x00, 0x0f, // 2 bytes unused + 1 byte length (15) + 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, // "Nested Category" + }}, + want: []string{"Top Level Bundle", "Second Level Bundle", "Nested Category"}, + wantErr: assert.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &Field{ + Data: tt.fields.Data, + } + got, err := f.DecodeNewsPath() + if !tt.wantErr(t, err, "DecodeNewsPath()") { + return + } + assert.Equalf(t, tt.want, got, "DecodeNewsPath()") + }) + } +} diff --git a/hotline/news.go b/hotline/news.go index c61a76f..1ddeebd 100644 --- a/hotline/news.go +++ b/hotline/news.go @@ -47,7 +47,7 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() (NewsArtListData, er for i, art := range newscat.Articles { id := make([]byte, 4) - binary.BigEndian.PutUint32(id, i) + binary.BigEndian.PutUint32(id, i) // The article's map key in the Articles map is its ID. newsArts = append(newsArts, NewsArtList{ ID: [4]byte(id), @@ -156,8 +156,8 @@ type NewsArtList struct { } var ( - NewsFlavorLen = []byte{0x0a} - NewsFlavor = []byte("text/plain") + NewsFlavor = []byte("text/plain") // NewsFlavor is always "text/plain" + NewsFlavorCount = []byte{0, 1} // NewsFlavorCount is always 1 ) func (nal *NewsArtList) Read(p []byte) (int, error) { @@ -166,12 +166,12 @@ func (nal *NewsArtList) Read(p []byte) (int, error) { nal.TimeStamp[:], nal.ParentID[:], nal.Flags[:], - []byte{0, 1}, // Flavor Count TODO: make this not hardcoded + NewsFlavorCount, []byte{uint8(len(nal.Title))}, nal.Title, []byte{uint8(len(nal.Poster))}, nal.Poster, - NewsFlavorLen, + []byte{uint8(len(NewsFlavor))}, NewsFlavor, nal.ArticleSize[:], ) @@ -183,7 +183,7 @@ func (nal *NewsArtList) Read(p []byte) (int, error) { n := copy(p, out[nal.readOffset:]) nal.readOffset += n - return n, io.EOF + return n, nil } type NewsFlavorList struct { diff --git a/hotline/news_test.go b/hotline/news_test.go index d1b043e..46df2e3 100644 --- a/hotline/news_test.go +++ b/hotline/news_test.go @@ -97,3 +97,274 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { }) } } + +func TestNewsCategoryListData15_GetNewsArtListData(t *testing.T) { + tests := []struct { + name string + newscat NewsCategoryListData15 + wantData NewsArtListData + wantErr bool + }{ + { + name: "empty articles", + newscat: NewsCategoryListData15{ + Articles: map[uint32]*NewsArtData{}, + }, + wantData: NewsArtListData{ + Count: 0, + Name: []byte{}, + Description: []byte{}, + NewsArtList: []byte{}, + }, + wantErr: false, + }, + { + name: "single article", + newscat: NewsCategoryListData15{ + Articles: map[uint32]*NewsArtData{ + 1: { + Title: "Test Title", + Poster: "Test Poster", + Date: [8]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, + Data: "Test content", + }, + }, + }, + wantData: NewsArtListData{ + Count: 1, + Name: []byte{}, + Description: []byte{}, + }, + wantErr: false, + }, + { + name: "multiple articles", + newscat: NewsCategoryListData15{ + Articles: map[uint32]*NewsArtData{ + 2: { + Title: "Second Article", + Poster: "Author2", + Date: [8]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08}, + Data: "Second content", + }, + 1: { + Title: "First Article", + Poster: "Author1", + Date: [8]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, + Data: "First content", + }, + }, + }, + wantData: NewsArtListData{ + Count: 2, + Name: []byte{}, + Description: []byte{}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotData, err := tt.newscat.GetNewsArtListData() + if (err != nil) != tt.wantErr { + t.Errorf("GetNewsArtListData() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.wantData.Count, gotData.Count) + assert.Equal(t, tt.wantData.Name, gotData.Name) + assert.Equal(t, tt.wantData.Description, gotData.Description) + if tt.wantData.Count > 0 { + assert.NotEmpty(t, gotData.NewsArtList) + } + }) + } +} + +func TestNewsArtData_DataSize(t *testing.T) { + tests := []struct { + name string + art NewsArtData + want [2]byte + }{ + { + name: "empty data", + art: NewsArtData{Data: ""}, + want: [2]byte{0x00, 0x00}, + }, + { + name: "short data", + art: NewsArtData{Data: "hello"}, + want: [2]byte{0x00, 0x05}, + }, + { + name: "longer data", + art: NewsArtData{Data: "This is a longer test message with more content"}, + want: [2]byte{0x00, 0x2F}, // 47 bytes + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.art.DataSize() + assert.Equal(t, tt.want, got) + }) + } +} + +func TestNewsArtListData_Read(t *testing.T) { + tests := []struct { + name string + nald NewsArtListData + bufferSize int + wantN int + wantErr bool + }{ + { + name: "empty data", + nald: NewsArtListData{ + ID: [4]byte{0x00, 0x01, 0x02, 0x03}, + Name: []byte("test"), + Description: []byte("desc"), + NewsArtList: []byte{}, + Count: 0, + }, + bufferSize: 100, + wantN: 18, // 4 (ID) + 4 (count) + 1 (name len) + 4 (name) + 1 (desc len) + 4 (desc) + 0 (news art list) + wantErr: false, + }, + { + name: "with article list", + nald: NewsArtListData{ + ID: [4]byte{0x00, 0x01, 0x02, 0x03}, + Name: []byte("test"), + Description: []byte("desc"), + NewsArtList: []byte{0x01, 0x02, 0x03}, + Count: 1, + }, + bufferSize: 100, + wantN: 21, // 4 (ID) + 4 (count) + 1 (name len) + 4 (name) + 1 (desc len) + 4 (desc) + 3 (news art list) + wantErr: false, + }, + { + name: "small buffer", + nald: NewsArtListData{ + ID: [4]byte{0x00, 0x01, 0x02, 0x03}, + Name: []byte("test"), + Description: []byte("desc"), + Count: 0, + }, + bufferSize: 5, + wantN: 5, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := make([]byte, tt.bufferSize) + gotN, err := tt.nald.Read(p) + if (err != nil) != tt.wantErr { + t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.wantN, gotN) + }) + } +} + +func TestNewsArtList_Read(t *testing.T) { + tests := []struct { + name string + nal NewsArtList + bufferSize int + wantN int + wantErr bool + }{ + { + name: "basic article", + nal: NewsArtList{ + ID: [4]byte{0x00, 0x01, 0x02, 0x03}, + TimeStamp: [8]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, + ParentID: [4]byte{0x00, 0x00, 0x00, 0x00}, + Flags: [4]byte{0x00, 0x00, 0x00, 0x00}, + Title: []byte("Test Title"), + Poster: []byte("Test Poster"), + ArticleSize: [2]byte{0x00, 0x0A}, + }, + bufferSize: 100, + wantN: 58, // 4 (ID) + 8 (timestamp) + 4 (parent) + 4 (flags) + 2 (flavor count) + 1 (title len) + 10 (title) + 1 (poster len) + 11 (poster) + 1 (flavor len) + 10 (flavor) + 2 (article size) + wantErr: false, + }, + { + name: "small buffer", + nal: NewsArtList{ + ID: [4]byte{0x00, 0x01, 0x02, 0x03}, + TimeStamp: [8]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, + Title: []byte("Test"), + Poster: []byte("Author"), + }, + bufferSize: 10, + wantN: 10, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := make([]byte, tt.bufferSize) + gotN, err := tt.nal.Read(p) + if (err != nil) != tt.wantErr { + t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.wantN, gotN) + }) + } +} + +func TestNewsPathScanner(t *testing.T) { + tests := []struct { + name string + data []byte + wantAdvance int + wantToken []byte + wantErr bool + }{ + { + name: "insufficient data", + data: []byte{0x00, 0x01}, + wantAdvance: 0, + wantToken: nil, + wantErr: false, + }, + { + name: "valid token", + data: []byte{0x00, 0x01, 0x04, 0x74, 0x65, 0x73, 0x74}, // length 4, "test" + wantAdvance: 7, + wantToken: []byte("test"), + wantErr: false, + }, + { + name: "zero length token", + data: []byte{0x00, 0x01, 0x00}, + wantAdvance: 3, + wantToken: []byte{}, + wantErr: false, + }, + { + name: "single character token", + data: []byte{0x00, 0x01, 0x01, 0x61}, // length 1, "a" + wantAdvance: 4, + wantToken: []byte("a"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotAdvance, gotToken, err := newsPathScanner(tt.data, false) + if (err != nil) != tt.wantErr { + t.Errorf("newsPathScanner() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.Equal(t, tt.wantAdvance, gotAdvance) + assert.Equal(t, tt.wantToken, gotToken) + }) + } +} |