mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
test: add unit tests for core packages
Add comprehensive test coverage for: - common/token: token generation and RNG tests - common/stringsupport: string encoding, CSV operations - common/byteframe: binary read/write operations - common/mhfcourse: course/subscription logic - network/crypt_packet: packet header parsing - network/binpacket: binary packet round-trips - network/mhfpacket: packet interface and opcode mapping - config: configuration struct and loading - server/entranceserver: response building - server/signserver: response ID constants - server/signv2server: HTTP endpoint validation - server/channelserver: session, semaphore, and handler tests All tests pass with race detector enabled.
This commit is contained in:
467
common/byteframe/byteframe_test.go
Normal file
467
common/byteframe/byteframe_test.go
Normal file
@@ -0,0 +1,467 @@
|
||||
package byteframe
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewByteFrame(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
|
||||
if bf == nil {
|
||||
t.Fatal("NewByteFrame() returned nil")
|
||||
}
|
||||
if len(bf.Data()) != 0 {
|
||||
t.Errorf("NewByteFrame().Data() len = %d, want 0", len(bf.Data()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewByteFrameFromBytes(t *testing.T) {
|
||||
data := []byte{1, 2, 3, 4, 5}
|
||||
bf := NewByteFrameFromBytes(data)
|
||||
|
||||
if bf == nil {
|
||||
t.Fatal("NewByteFrameFromBytes() returned nil")
|
||||
}
|
||||
if len(bf.Data()) != len(data) {
|
||||
t.Errorf("NewByteFrameFromBytes().Data() len = %d, want %d", len(bf.Data()), len(data))
|
||||
}
|
||||
|
||||
// Verify data is copied, not referenced
|
||||
data[0] = 99
|
||||
if bf.Data()[0] == 99 {
|
||||
t.Error("NewByteFrameFromBytes() did not copy data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadUint8(t *testing.T) {
|
||||
tests := []uint8{0, 1, 127, 128, 255}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint8(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadUint8()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadUint8(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadUint16(t *testing.T) {
|
||||
tests := []uint16{0, 1, 255, 256, 32767, 65535}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint16(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadUint16()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadUint16(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadUint32(t *testing.T) {
|
||||
tests := []uint32{0, 1, 255, 65535, 2147483647, 4294967295}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint32(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadUint32()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadUint32(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadUint64(t *testing.T) {
|
||||
tests := []uint64{0, 1, 255, 65535, 4294967295, 18446744073709551615}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint64(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadUint64()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadUint64(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadInt8(t *testing.T) {
|
||||
tests := []int8{-128, -1, 0, 1, 127}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteInt8(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadInt8()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadInt8(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadInt16(t *testing.T) {
|
||||
tests := []int16{-32768, -1, 0, 1, 32767}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteInt16(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadInt16()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadInt16(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadInt32(t *testing.T) {
|
||||
tests := []int32{-2147483648, -1, 0, 1, 2147483647}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteInt32(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadInt32()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadInt32(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadInt64(t *testing.T) {
|
||||
tests := []int64{-9223372036854775808, -1, 0, 1, 9223372036854775807}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteInt64(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadInt64()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadInt64(%d) = %d", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadFloat32(t *testing.T) {
|
||||
tests := []float32{0, 1.5, -1.5, 3.14159, math.MaxFloat32, math.SmallestNonzeroFloat32}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteFloat32(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadFloat32()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadFloat32(%f) = %f", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadFloat64(t *testing.T) {
|
||||
tests := []float64{0, 1.5, -1.5, 3.14159265358979, math.MaxFloat64, math.SmallestNonzeroFloat64}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteFloat64(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadFloat64()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadFloat64(%f) = %f", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadBool(t *testing.T) {
|
||||
tests := []bool{true, false}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteBool(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadBool()
|
||||
if got != val {
|
||||
t.Errorf("Write/ReadBool(%v) = %v", val, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadBytes(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
{},
|
||||
{1},
|
||||
{1, 2, 3, 4, 5},
|
||||
{0, 255, 128, 64, 32},
|
||||
}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteBytes(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadBytes(uint(len(val)))
|
||||
if len(got) != len(val) {
|
||||
t.Errorf("Write/ReadBytes len = %d, want %d", len(got), len(val))
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != val[i] {
|
||||
t.Errorf("Write/ReadBytes[%d] = %d, want %d", i, got[i], val[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteReadNullTerminatedBytes(t *testing.T) {
|
||||
tests := [][]byte{
|
||||
{},
|
||||
{65},
|
||||
{65, 66, 67},
|
||||
}
|
||||
|
||||
for _, val := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteNullTerminatedBytes(val)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
got := bf.ReadNullTerminatedBytes()
|
||||
if len(got) != len(val) {
|
||||
t.Errorf("Write/ReadNullTerminatedBytes len = %d, want %d", len(got), len(val))
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != val[i] {
|
||||
t.Errorf("Write/ReadNullTerminatedBytes[%d] = %d, want %d", i, got[i], val[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeek(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint32(0x12345678)
|
||||
bf.WriteUint32(0xDEADBEEF)
|
||||
|
||||
// SeekStart
|
||||
pos, err := bf.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Errorf("Seek(0, SeekStart) error = %v", err)
|
||||
}
|
||||
if pos != 0 {
|
||||
t.Errorf("Seek(0, SeekStart) pos = %d, want 0", pos)
|
||||
}
|
||||
|
||||
val := bf.ReadUint32()
|
||||
if val != 0x12345678 {
|
||||
t.Errorf("After Seek(0, SeekStart) ReadUint32() = %x, want 0x12345678", val)
|
||||
}
|
||||
|
||||
// SeekCurrent
|
||||
pos, err = bf.Seek(-4, io.SeekCurrent)
|
||||
if err != nil {
|
||||
t.Errorf("Seek(-4, SeekCurrent) error = %v", err)
|
||||
}
|
||||
if pos != 0 {
|
||||
t.Errorf("Seek(-4, SeekCurrent) pos = %d, want 0", pos)
|
||||
}
|
||||
|
||||
// SeekEnd
|
||||
pos, err = bf.Seek(-4, io.SeekEnd)
|
||||
if err != nil {
|
||||
t.Errorf("Seek(-4, SeekEnd) error = %v", err)
|
||||
}
|
||||
if pos != 4 {
|
||||
t.Errorf("Seek(-4, SeekEnd) pos = %d, want 4", pos)
|
||||
}
|
||||
|
||||
val = bf.ReadUint32()
|
||||
if val != 0xDEADBEEF {
|
||||
t.Errorf("After Seek(-4, SeekEnd) ReadUint32() = %x, want 0xDEADBEEF", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeekErrors(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint32(0x12345678)
|
||||
|
||||
// Seek beyond end
|
||||
_, err := bf.Seek(100, io.SeekStart)
|
||||
if err == nil {
|
||||
t.Error("Seek(100, SeekStart) should return error")
|
||||
}
|
||||
|
||||
// Seek before start
|
||||
_, err = bf.Seek(-100, io.SeekCurrent)
|
||||
if err == nil {
|
||||
t.Error("Seek(-100, SeekCurrent) should return error")
|
||||
}
|
||||
|
||||
// Seek before start from end
|
||||
_, err = bf.Seek(-100, io.SeekEnd)
|
||||
if err == nil {
|
||||
t.Error("Seek(-100, SeekEnd) should return error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndianness(t *testing.T) {
|
||||
// Test big endian (default)
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint16(0x1234)
|
||||
data := bf.Data()
|
||||
if data[0] != 0x12 || data[1] != 0x34 {
|
||||
t.Errorf("Big endian WriteUint16(0x1234) = %v, want [0x12, 0x34]", data)
|
||||
}
|
||||
|
||||
// Test little endian
|
||||
bf = NewByteFrame()
|
||||
bf.SetLE()
|
||||
bf.WriteUint16(0x1234)
|
||||
data = bf.Data()
|
||||
if data[0] != 0x34 || data[1] != 0x12 {
|
||||
t.Errorf("Little endian WriteUint16(0x1234) = %v, want [0x34, 0x12]", data)
|
||||
}
|
||||
|
||||
// Test switching back to big endian
|
||||
bf = NewByteFrame()
|
||||
bf.SetLE()
|
||||
bf.SetBE()
|
||||
bf.WriteUint16(0x1234)
|
||||
data = bf.Data()
|
||||
if data[0] != 0x12 || data[1] != 0x34 {
|
||||
t.Errorf("Switched back to big endian WriteUint16(0x1234) = %v, want [0x12, 0x34]", data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDataFromCurrent(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
bf.WriteUint8(1)
|
||||
bf.WriteUint8(2)
|
||||
bf.WriteUint8(3)
|
||||
bf.WriteUint8(4)
|
||||
|
||||
bf.Seek(2, io.SeekStart)
|
||||
remaining := bf.DataFromCurrent()
|
||||
|
||||
if len(remaining) != 2 {
|
||||
t.Errorf("DataFromCurrent() len = %d, want 2", len(remaining))
|
||||
}
|
||||
if remaining[0] != 3 || remaining[1] != 4 {
|
||||
t.Errorf("DataFromCurrent() = %v, want [3, 4]", remaining)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufferGrowth(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
|
||||
// Write more data than initial buffer size (4 bytes)
|
||||
for i := 0; i < 100; i++ {
|
||||
bf.WriteUint32(uint32(i))
|
||||
}
|
||||
|
||||
if len(bf.Data()) != 400 {
|
||||
t.Errorf("After writing 100 uint32s, Data() len = %d, want 400", len(bf.Data()))
|
||||
}
|
||||
|
||||
// Verify data integrity
|
||||
bf.Seek(0, io.SeekStart)
|
||||
for i := 0; i < 100; i++ {
|
||||
val := bf.ReadUint32()
|
||||
if val != uint32(i) {
|
||||
t.Errorf("After growth, ReadUint32()[%d] = %d, want %d", i, val, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleWrites(t *testing.T) {
|
||||
bf := NewByteFrame()
|
||||
|
||||
bf.WriteUint8(0x01)
|
||||
bf.WriteUint16(0x0203)
|
||||
bf.WriteUint32(0x04050607)
|
||||
bf.WriteUint64(0x08090A0B0C0D0E0F)
|
||||
|
||||
expected := []byte{
|
||||
0x01,
|
||||
0x02, 0x03,
|
||||
0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
}
|
||||
|
||||
data := bf.Data()
|
||||
if len(data) != len(expected) {
|
||||
t.Errorf("Multiple writes Data() len = %d, want %d", len(data), len(expected))
|
||||
return
|
||||
}
|
||||
|
||||
for i := range expected {
|
||||
if data[i] != expected[i] {
|
||||
t.Errorf("Multiple writes Data()[%d] = %x, want %x", i, data[i], expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPanicsOnOverflow(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("ReadUint32 on empty buffer should panic")
|
||||
}
|
||||
}()
|
||||
|
||||
bf := NewByteFrame()
|
||||
bf.ReadUint32()
|
||||
}
|
||||
|
||||
func TestReadBoolNonZero(t *testing.T) {
|
||||
// Test that any non-zero value is considered true
|
||||
bf := NewByteFrameFromBytes([]byte{0, 1, 2, 255})
|
||||
|
||||
if bf.ReadBool() != false {
|
||||
t.Error("ReadBool(0) should be false")
|
||||
}
|
||||
if bf.ReadBool() != true {
|
||||
t.Error("ReadBool(1) should be true")
|
||||
}
|
||||
if bf.ReadBool() != true {
|
||||
t.Error("ReadBool(2) should be true")
|
||||
}
|
||||
if bf.ReadBool() != true {
|
||||
t.Error("ReadBool(255) should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadNullTerminatedBytesNoTerminator(t *testing.T) {
|
||||
// Test behavior when there's no null terminator
|
||||
bf := NewByteFrameFromBytes([]byte{65, 66, 67})
|
||||
result := bf.ReadNullTerminatedBytes()
|
||||
|
||||
if len(result) != 0 {
|
||||
t.Errorf("ReadNullTerminatedBytes with no terminator should return empty, got %v", result)
|
||||
}
|
||||
}
|
||||
288
common/mhfcourse/mhfcourse_test.go
Normal file
288
common/mhfcourse/mhfcourse_test.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package mhfcourse
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCourses(t *testing.T) {
|
||||
courses := Courses()
|
||||
|
||||
if len(courses) != 32 {
|
||||
t.Errorf("Courses() len = %d, want 32", len(courses))
|
||||
}
|
||||
|
||||
for i, course := range courses {
|
||||
if course.ID != uint16(i) {
|
||||
t.Errorf("Courses()[%d].ID = %d, want %d", i, course.ID, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
id uint16
|
||||
want uint32
|
||||
}{
|
||||
{0, 1},
|
||||
{1, 2},
|
||||
{2, 4},
|
||||
{3, 8},
|
||||
{4, 16},
|
||||
{5, 32},
|
||||
{10, 1024},
|
||||
{20, 1048576},
|
||||
{31, 2147483648},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
c := Course{ID: tt.id}
|
||||
got := c.Value()
|
||||
if got != tt.want {
|
||||
t.Errorf("Course{ID: %d}.Value() = %d, want %d", tt.id, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseValueIsPowerOf2(t *testing.T) {
|
||||
for i := uint16(0); i < 32; i++ {
|
||||
c := Course{ID: i}
|
||||
val := c.Value()
|
||||
expected := uint32(math.Pow(2, float64(i)))
|
||||
if val != expected {
|
||||
t.Errorf("Course{ID: %d}.Value() = %d, want %d (2^%d)", i, val, expected, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseAliases(t *testing.T) {
|
||||
tests := []struct {
|
||||
id uint16
|
||||
wantLen int
|
||||
contains string
|
||||
}{
|
||||
{1, 2, "Trial"},
|
||||
{2, 2, "HunterLife"},
|
||||
{3, 3, "Extra"},
|
||||
{6, 1, "Premium"},
|
||||
{8, 4, "Assist"},
|
||||
{26, 4, "NetCafe"},
|
||||
{29, 1, "Free"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
c := Course{ID: tt.id}
|
||||
aliases := c.Aliases()
|
||||
|
||||
if len(aliases) != tt.wantLen {
|
||||
t.Errorf("Course{ID: %d}.Aliases() len = %d, want %d", tt.id, len(aliases), tt.wantLen)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, alias := range aliases {
|
||||
if alias == tt.contains {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Course{ID: %d}.Aliases() should contain %q", tt.id, tt.contains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseAliasesUnknown(t *testing.T) {
|
||||
// Test IDs without aliases
|
||||
unknownIDs := []uint16{13, 14, 15, 16, 17, 18, 19}
|
||||
|
||||
for _, id := range unknownIDs {
|
||||
c := Course{ID: id}
|
||||
aliases := c.Aliases()
|
||||
if aliases != nil {
|
||||
t.Errorf("Course{ID: %d}.Aliases() = %v, want nil", id, aliases)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseExists(t *testing.T) {
|
||||
courses := []Course{
|
||||
{ID: 1},
|
||||
{ID: 5},
|
||||
{ID: 10},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
id uint16
|
||||
want bool
|
||||
}{
|
||||
{1, true},
|
||||
{5, true},
|
||||
{10, true},
|
||||
{0, false},
|
||||
{2, false},
|
||||
{99, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got := CourseExists(tt.id, courses)
|
||||
if got != tt.want {
|
||||
t.Errorf("CourseExists(%d, courses) = %v, want %v", tt.id, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseExistsEmptySlice(t *testing.T) {
|
||||
var courses []Course
|
||||
|
||||
if CourseExists(1, courses) {
|
||||
t.Error("CourseExists(1, nil) should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCourseStruct(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rights uint32
|
||||
wantMinLen int
|
||||
shouldHave []uint16
|
||||
shouldNotHave []uint16
|
||||
}{
|
||||
{
|
||||
name: "zero rights",
|
||||
rights: 0,
|
||||
wantMinLen: 1, // Always includes ID: 1 (Trial)
|
||||
shouldHave: []uint16{1},
|
||||
},
|
||||
{
|
||||
name: "HunterLife course",
|
||||
rights: 4, // 2^2 = 4 for ID 2
|
||||
wantMinLen: 2,
|
||||
shouldHave: []uint16{1, 2},
|
||||
},
|
||||
{
|
||||
name: "multiple courses",
|
||||
rights: 6, // 2^1 + 2^2 = 2 + 4 = 6 for IDs 1 and 2
|
||||
wantMinLen: 2,
|
||||
shouldHave: []uint16{1, 2},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
courses, _ := GetCourseStruct(tt.rights)
|
||||
|
||||
if len(courses) < tt.wantMinLen {
|
||||
t.Errorf("GetCourseStruct(%d) len = %d, want >= %d", tt.rights, len(courses), tt.wantMinLen)
|
||||
}
|
||||
|
||||
for _, id := range tt.shouldHave {
|
||||
if !CourseExists(id, courses) {
|
||||
t.Errorf("GetCourseStruct(%d) should have course ID %d", tt.rights, id)
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range tt.shouldNotHave {
|
||||
if CourseExists(id, courses) {
|
||||
t.Errorf("GetCourseStruct(%d) should not have course ID %d", tt.rights, id)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCourseStructReturnsRights(t *testing.T) {
|
||||
// GetCourseStruct returns the recalculated rights value
|
||||
_, rights := GetCourseStruct(0)
|
||||
|
||||
// Should at least include the Trial course (ID: 1, Value: 2)
|
||||
if rights < 2 {
|
||||
t.Errorf("GetCourseStruct(0) rights = %d, want >= 2", rights)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCourseStructNetCafeCourses(t *testing.T) {
|
||||
// Test that course 26 (NetCafe) adds course 25 (CAFE_SP)
|
||||
courses, _ := GetCourseStruct(Course{ID: 26}.Value())
|
||||
|
||||
if !CourseExists(25, courses) {
|
||||
t.Error("GetCourseStruct with course 26 should add course 25")
|
||||
}
|
||||
if !CourseExists(30, courses) {
|
||||
t.Error("GetCourseStruct with course 26 should add course 30")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCourseStructNCourse(t *testing.T) {
|
||||
// Test that course 9 (N) adds course 30
|
||||
courses, _ := GetCourseStruct(Course{ID: 9}.Value())
|
||||
|
||||
if !CourseExists(30, courses) {
|
||||
t.Error("GetCourseStruct with course 9 should add course 30")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCourseExpiry(t *testing.T) {
|
||||
// Test that courses returned by GetCourseStruct have expiry set
|
||||
courses, _ := GetCourseStruct(4) // HunterLife
|
||||
|
||||
for _, c := range courses {
|
||||
// Course ID 1 is always added without expiry in some cases
|
||||
if c.ID != 1 && c.ID != 25 && c.ID != 30 {
|
||||
if c.Expiry.IsZero() {
|
||||
// Note: expiry is only set for courses extracted from rights
|
||||
// This behavior is expected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllCoursesHaveValidValues(t *testing.T) {
|
||||
courses := Courses()
|
||||
|
||||
for _, c := range courses {
|
||||
val := c.Value()
|
||||
// Verify value is a power of 2
|
||||
if val == 0 || (val&(val-1)) != 0 {
|
||||
t.Errorf("Course{ID: %d}.Value() = %d is not a power of 2", c.ID, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKnownAliasesExist(t *testing.T) {
|
||||
knownCourses := map[string]uint16{
|
||||
"Trial": 1,
|
||||
"HunterLife": 2,
|
||||
"Extra": 3,
|
||||
"Mobile": 5,
|
||||
"Premium": 6,
|
||||
"Assist": 8,
|
||||
"Hiden": 10,
|
||||
"NetCafe": 26,
|
||||
"Free": 29,
|
||||
}
|
||||
|
||||
for name, expectedID := range knownCourses {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
c := Course{ID: expectedID}
|
||||
aliases := c.Aliases()
|
||||
|
||||
found := false
|
||||
for _, alias := range aliases {
|
||||
if alias == name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Course ID %d should have alias %q, got %v", expectedID, name, aliases)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
341
common/stringsupport/string_convert_test.go
Normal file
341
common/stringsupport/string_convert_test.go
Normal file
@@ -0,0 +1,341 @@
|
||||
package stringsupport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/text/encoding/japanese"
|
||||
)
|
||||
|
||||
func TestStringConverterDecode(t *testing.T) {
|
||||
sc := &StringConverter{Encoding: japanese.ShiftJIS}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"empty", []byte{}, "", false},
|
||||
{"ascii", []byte("Hello"), "Hello", false},
|
||||
{"japanese hello", []byte{0x82, 0xb1, 0x82, 0xf1, 0x82, 0xc9, 0x82, 0xbf, 0x82, 0xcd}, "こんにちは", false},
|
||||
{"mixed", []byte{0x41, 0x42, 0x43}, "ABC", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := sc.Decode(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Decode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("Decode() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConverterEncode(t *testing.T) {
|
||||
sc := &StringConverter{Encoding: japanese.ShiftJIS}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{"empty", "", []byte{}, false},
|
||||
{"ascii", "Hello", []byte("Hello"), false},
|
||||
{"japanese hello", "こんにちは", []byte{0x82, 0xb1, 0x82, 0xf1, 0x82, 0xc9, 0x82, 0xbf, 0x82, 0xcd}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := sc.Encode(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Encode() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(got, tt.want) {
|
||||
t.Errorf("Encode() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConverterMustDecode(t *testing.T) {
|
||||
sc := &StringConverter{Encoding: japanese.ShiftJIS}
|
||||
|
||||
// Valid input should not panic
|
||||
result := sc.MustDecode([]byte("Hello"))
|
||||
if result != "Hello" {
|
||||
t.Errorf("MustDecode() = %q, want %q", result, "Hello")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringConverterMustEncode(t *testing.T) {
|
||||
sc := &StringConverter{Encoding: japanese.ShiftJIS}
|
||||
|
||||
// Valid input should not panic
|
||||
result := sc.MustEncode("Hello")
|
||||
if !bytes.Equal(result, []byte("Hello")) {
|
||||
t.Errorf("MustEncode() = %v, want %v", result, []byte("Hello"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUTF8ToSJIS(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want []byte
|
||||
}{
|
||||
{"empty", "", []byte{}},
|
||||
{"ascii", "ABC", []byte("ABC")},
|
||||
{"japanese", "こんにちは", []byte{0x82, 0xb1, 0x82, 0xf1, 0x82, 0xc9, 0x82, 0xbf, 0x82, 0xcd}},
|
||||
{"mixed", "Hello世界", []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x90, 0xa2, 0x8a, 0x45}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := UTF8ToSJIS(tt.input)
|
||||
if !bytes.Equal(got, tt.want) {
|
||||
t.Errorf("UTF8ToSJIS(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSJISToUTF8(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
want string
|
||||
}{
|
||||
{"empty", []byte{}, ""},
|
||||
{"ascii", []byte("ABC"), "ABC"},
|
||||
{"japanese", []byte{0x82, 0xb1, 0x82, 0xf1, 0x82, 0xc9, 0x82, 0xbf, 0x82, 0xcd}, "こんにちは"},
|
||||
{"mixed", []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x90, 0xa2, 0x8a, 0x45}, "Hello世界"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := SJISToUTF8(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("SJISToUTF8(%v) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUTF8ToSJISRoundTrip(t *testing.T) {
|
||||
tests := []string{
|
||||
"Hello",
|
||||
"ABC123",
|
||||
"こんにちは",
|
||||
"テスト",
|
||||
"モンスターハンター",
|
||||
}
|
||||
|
||||
for _, input := range tests {
|
||||
t.Run(input, func(t *testing.T) {
|
||||
encoded := UTF8ToSJIS(input)
|
||||
decoded := SJISToUTF8(encoded)
|
||||
if decoded != input {
|
||||
t.Errorf("Round trip failed: %q -> %v -> %q", input, encoded, decoded)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaddedString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
size uint
|
||||
transform bool
|
||||
wantLen int
|
||||
wantEnd byte
|
||||
}{
|
||||
{"empty ascii", "", 10, false, 10, 0},
|
||||
{"short ascii", "Hi", 10, false, 10, 0},
|
||||
{"exact ascii", "1234567890", 10, false, 10, 0},
|
||||
{"empty sjis", "", 10, true, 10, 0},
|
||||
{"short sjis", "Hi", 10, true, 10, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := PaddedString(tt.input, tt.size, tt.transform)
|
||||
if len(got) != tt.wantLen {
|
||||
t.Errorf("PaddedString() len = %d, want %d", len(got), tt.wantLen)
|
||||
}
|
||||
if got[len(got)-1] != tt.wantEnd {
|
||||
t.Errorf("PaddedString() last byte = %d, want %d", got[len(got)-1], tt.wantEnd)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPaddedStringContent(t *testing.T) {
|
||||
// Verify the content is correctly placed at the beginning
|
||||
result := PaddedString("ABC", 10, false)
|
||||
|
||||
if result[0] != 'A' || result[1] != 'B' || result[2] != 'C' {
|
||||
t.Errorf("PaddedString() content mismatch: got %v", result[:3])
|
||||
}
|
||||
|
||||
// Rest should be zeros (except last which is forced to 0)
|
||||
for i := 3; i < 10; i++ {
|
||||
if result[i] != 0 {
|
||||
t.Errorf("PaddedString() byte at %d = %d, want 0", i, result[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSVAdd(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
csv string
|
||||
v int
|
||||
want string
|
||||
}{
|
||||
{"empty add", "", 5, "5"},
|
||||
{"add to existing", "1,2,3", 4, "1,2,3,4"},
|
||||
{"add duplicate", "1,2,3", 2, "1,2,3"},
|
||||
{"add to single", "1", 2, "1,2"},
|
||||
{"add zero", "", 0, "0"},
|
||||
{"add negative", "1,2", -5, "1,2,-5"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CSVAdd(tt.csv, tt.v)
|
||||
if got != tt.want {
|
||||
t.Errorf("CSVAdd(%q, %d) = %q, want %q", tt.csv, tt.v, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSVRemove(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
csv string
|
||||
v int
|
||||
want string
|
||||
}{
|
||||
{"remove from middle", "1,2,3", 2, "1,3"},
|
||||
{"remove first", "1,2,3", 1, "3,2"},
|
||||
{"remove last", "1,2,3", 3, "1,2"},
|
||||
{"remove only", "5", 5, ""},
|
||||
{"remove nonexistent", "1,2,3", 99, "1,2,3"},
|
||||
{"remove from empty", "", 5, ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CSVRemove(tt.csv, tt.v)
|
||||
if got != tt.want {
|
||||
t.Errorf("CSVRemove(%q, %d) = %q, want %q", tt.csv, tt.v, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSVContains(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
csv string
|
||||
v int
|
||||
want bool
|
||||
}{
|
||||
{"contains first", "1,2,3", 1, true},
|
||||
{"contains middle", "1,2,3", 2, true},
|
||||
{"contains last", "1,2,3", 3, true},
|
||||
{"not contains", "1,2,3", 99, false},
|
||||
{"empty csv", "", 5, false},
|
||||
{"single contains", "5", 5, true},
|
||||
{"single not contains", "5", 3, false},
|
||||
{"contains zero", "0,1,2", 0, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CSVContains(tt.csv, tt.v)
|
||||
if got != tt.want {
|
||||
t.Errorf("CSVContains(%q, %d) = %v, want %v", tt.csv, tt.v, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSVLength(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
csv string
|
||||
want int
|
||||
}{
|
||||
{"empty", "", 0},
|
||||
{"single", "5", 1},
|
||||
{"two", "1,2", 2},
|
||||
{"three", "1,2,3", 3},
|
||||
{"many", "1,2,3,4,5,6,7,8,9,10", 10},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CSVLength(tt.csv)
|
||||
if got != tt.want {
|
||||
t.Errorf("CSVLength(%q) = %d, want %d", tt.csv, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSVElems(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
csv string
|
||||
want []int
|
||||
}{
|
||||
{"empty", "", nil},
|
||||
{"single", "5", []int{5}},
|
||||
{"multiple", "1,2,3", []int{1, 2, 3}},
|
||||
{"with zero", "0,1,2", []int{0, 1, 2}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CSVElems(tt.csv)
|
||||
if len(got) != len(tt.want) {
|
||||
t.Errorf("CSVElems(%q) len = %d, want %d", tt.csv, len(got), len(tt.want))
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != tt.want[i] {
|
||||
t.Errorf("CSVElems(%q)[%d] = %d, want %d", tt.csv, i, got[i], tt.want[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCSVAddRemoveRoundTrip(t *testing.T) {
|
||||
csv := ""
|
||||
csv = CSVAdd(csv, 1)
|
||||
csv = CSVAdd(csv, 2)
|
||||
csv = CSVAdd(csv, 3)
|
||||
|
||||
if !CSVContains(csv, 1) || !CSVContains(csv, 2) || !CSVContains(csv, 3) {
|
||||
t.Error("CSVAdd did not add all elements")
|
||||
}
|
||||
|
||||
csv = CSVRemove(csv, 2)
|
||||
if CSVContains(csv, 2) {
|
||||
t.Error("CSVRemove did not remove element")
|
||||
}
|
||||
if CSVLength(csv) != 2 {
|
||||
t.Errorf("CSVLength after remove = %d, want 2", CSVLength(csv))
|
||||
}
|
||||
}
|
||||
120
common/token/token_test.go
Normal file
120
common/token/token_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
length int
|
||||
}{
|
||||
{"zero length", 0},
|
||||
{"short token", 8},
|
||||
{"medium token", 32},
|
||||
{"long token", 256},
|
||||
{"single char", 1},
|
||||
}
|
||||
|
||||
alphanumeric := regexp.MustCompile(`^[a-zA-Z0-9]*$`)
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Generate(tt.length)
|
||||
if len(result) != tt.length {
|
||||
t.Errorf("Generate(%d) length = %d, want %d", tt.length, len(result), tt.length)
|
||||
}
|
||||
if !alphanumeric.MatchString(result) {
|
||||
t.Errorf("Generate(%d) = %q, contains non-alphanumeric characters", tt.length, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateUniqueness(t *testing.T) {
|
||||
// Generate multiple tokens and check they're different
|
||||
tokens := make(map[string]bool)
|
||||
iterations := 100
|
||||
length := 32
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
token := Generate(length)
|
||||
if tokens[token] {
|
||||
t.Errorf("Generate(%d) produced duplicate token: %s", length, token)
|
||||
}
|
||||
tokens[token] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateCharacterDistribution(t *testing.T) {
|
||||
// Generate a long token and verify it uses various characters
|
||||
token := Generate(1000)
|
||||
|
||||
hasLower := regexp.MustCompile(`[a-z]`).MatchString(token)
|
||||
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(token)
|
||||
hasDigit := regexp.MustCompile(`[0-9]`).MatchString(token)
|
||||
|
||||
if !hasLower {
|
||||
t.Error("Generate(1000) did not produce any lowercase letters")
|
||||
}
|
||||
if !hasUpper {
|
||||
t.Error("Generate(1000) did not produce any uppercase letters")
|
||||
}
|
||||
if !hasDigit {
|
||||
t.Error("Generate(1000) did not produce any digits")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRNG(t *testing.T) {
|
||||
rng1 := RNG()
|
||||
rng2 := RNG()
|
||||
|
||||
if rng1 == nil {
|
||||
t.Error("RNG() returned nil")
|
||||
}
|
||||
if rng2 == nil {
|
||||
t.Error("RNG() returned nil")
|
||||
}
|
||||
|
||||
// Both should generate valid random numbers
|
||||
val1 := rng1.Intn(100)
|
||||
val2 := rng2.Intn(100)
|
||||
|
||||
if val1 < 0 || val1 >= 100 {
|
||||
t.Errorf("RNG().Intn(100) = %d, want value in [0, 100)", val1)
|
||||
}
|
||||
if val2 < 0 || val2 >= 100 {
|
||||
t.Errorf("RNG().Intn(100) = %d, want value in [0, 100)", val2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRNGIndependence(t *testing.T) {
|
||||
// Create multiple RNGs and verify they produce different sequences
|
||||
rng1 := RNG()
|
||||
rng2 := RNG()
|
||||
|
||||
// Generate sequences
|
||||
seq1 := make([]int, 10)
|
||||
seq2 := make([]int, 10)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
seq1[i] = rng1.Intn(1000000)
|
||||
seq2[i] = rng2.Intn(1000000)
|
||||
}
|
||||
|
||||
// Check that sequences are likely different (not identical)
|
||||
identical := true
|
||||
for i := 0; i < 10; i++ {
|
||||
if seq1[i] != seq2[i] {
|
||||
identical = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Note: There's an extremely small chance both RNGs could produce
|
||||
// the same sequence, but it's astronomically unlikely
|
||||
if identical {
|
||||
t.Log("Warning: Two independent RNGs produced identical sequences (this is extremely unlikely)")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user