mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-21 23:22:34 +01:00
tests: extra tests for Dicord bit and nullcomp.
This commit is contained in:
407
server/channelserver/compression/nullcomp/nullcomp_test.go
Normal file
407
server/channelserver/compression/nullcomp/nullcomp_test.go
Normal file
@@ -0,0 +1,407 @@
|
||||
package nullcomp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDecompress_WithValidHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
name: "empty data after header",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00"),
|
||||
expected: []byte{},
|
||||
},
|
||||
{
|
||||
name: "single regular byte",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00\x42"),
|
||||
expected: []byte{0x42},
|
||||
},
|
||||
{
|
||||
name: "multiple regular bytes",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00\x48\x65\x6c\x6c\x6f"),
|
||||
expected: []byte("Hello"),
|
||||
},
|
||||
{
|
||||
name: "single null byte compression",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00\x00\x05"),
|
||||
expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
name: "multiple null bytes with max count",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00\x00\xFF"),
|
||||
expected: make([]byte, 255),
|
||||
},
|
||||
{
|
||||
name: "mixed regular and null bytes",
|
||||
input: append(
|
||||
[]byte("cmp\x2020110113\x20\x20\x20\x00\x48\x65\x6c\x6c\x6f"),
|
||||
[]byte{0x00, 0x03, 0x57, 0x6f, 0x72, 0x6c, 0x64}...,
|
||||
),
|
||||
expected: []byte("Hello\x00\x00\x00World"),
|
||||
},
|
||||
{
|
||||
name: "multiple null compressions",
|
||||
input: append(
|
||||
[]byte("cmp\x2020110113\x20\x20\x20\x00"),
|
||||
[]byte{0x41, 0x00, 0x02, 0x42, 0x00, 0x03, 0x43}...,
|
||||
),
|
||||
expected: []byte{0x41, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x43},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := Decompress(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Decompress() error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(result, tt.expected) {
|
||||
t.Errorf("Decompress() = %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecompress_WithoutHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectError bool
|
||||
expectOriginal bool // Expect original data returned
|
||||
}{
|
||||
{
|
||||
name: "plain data without header (16+ bytes)",
|
||||
// Data must be at least 16 bytes to read header
|
||||
input: []byte("Hello, World!!!!"), // Exactly 16 bytes
|
||||
expectError: false,
|
||||
expectOriginal: true,
|
||||
},
|
||||
{
|
||||
name: "binary data without header (16+ bytes)",
|
||||
input: []byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
|
||||
},
|
||||
expectError: false,
|
||||
expectOriginal: true,
|
||||
},
|
||||
{
|
||||
name: "data shorter than 16 bytes",
|
||||
// When data is shorter than 16 bytes, Read returns what it can with err=nil
|
||||
// Then n != len(header) returns nil, nil (not an error)
|
||||
input: []byte("Short"),
|
||||
expectError: false,
|
||||
expectOriginal: false, // Returns empty slice
|
||||
},
|
||||
{
|
||||
name: "empty data",
|
||||
input: []byte{},
|
||||
expectError: true, // EOF on first read
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := Decompress(tt.input)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Decompress() expected error but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Decompress() error = %v", err)
|
||||
}
|
||||
if tt.expectOriginal && !bytes.Equal(result, tt.input) {
|
||||
t.Errorf("Decompress() = %v, want %v (original data)", result, tt.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecompress_InvalidData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "incomplete header",
|
||||
// Less than 16 bytes: Read returns what it can (no error),
|
||||
// but n != len(header) returns nil, nil
|
||||
input: []byte("cmp\x20201"),
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "header with missing null count",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00\x00"),
|
||||
expectErr: false, // Valid header, EOF during decompression is handled
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := Decompress(tt.input)
|
||||
if tt.expectErr {
|
||||
if err == nil {
|
||||
t.Errorf("Decompress() expected error but got none, result = %v", result)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Decompress() unexpected error = %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompress_BasicData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
}{
|
||||
{
|
||||
name: "empty data",
|
||||
input: []byte{},
|
||||
},
|
||||
{
|
||||
name: "regular bytes without nulls",
|
||||
input: []byte("Hello, World!"),
|
||||
},
|
||||
{
|
||||
name: "single null byte",
|
||||
input: []byte{0x00},
|
||||
},
|
||||
{
|
||||
name: "multiple consecutive nulls",
|
||||
input: []byte{0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
name: "mixed data with nulls",
|
||||
input: []byte("Hello\x00\x00\x00World"),
|
||||
},
|
||||
{
|
||||
name: "data starting with nulls",
|
||||
input: []byte{0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f},
|
||||
},
|
||||
{
|
||||
name: "data ending with nulls",
|
||||
input: []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00, 0x00, 0x00},
|
||||
},
|
||||
{
|
||||
name: "alternating nulls and bytes",
|
||||
input: []byte{0x41, 0x00, 0x42, 0x00, 0x43},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
compressed, err := Compress(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Compress() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify it has the correct header
|
||||
expectedHeader := []byte("cmp\x2020110113\x20\x20\x20\x00")
|
||||
if !bytes.HasPrefix(compressed, expectedHeader) {
|
||||
t.Errorf("Compress() result doesn't have correct header")
|
||||
}
|
||||
|
||||
// Verify round-trip
|
||||
decompressed, err := Decompress(compressed)
|
||||
if err != nil {
|
||||
t.Fatalf("Decompress() error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(decompressed, tt.input) {
|
||||
t.Errorf("Round-trip failed: got %v, want %v", decompressed, tt.input)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompress_LargeNullSequences(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
nullCount int
|
||||
}{
|
||||
{
|
||||
name: "exactly 255 nulls",
|
||||
nullCount: 255,
|
||||
},
|
||||
{
|
||||
name: "256 nulls (overflow case)",
|
||||
nullCount: 256,
|
||||
},
|
||||
{
|
||||
name: "500 nulls",
|
||||
nullCount: 500,
|
||||
},
|
||||
{
|
||||
name: "1000 nulls",
|
||||
nullCount: 1000,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
input := make([]byte, tt.nullCount)
|
||||
compressed, err := Compress(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Compress() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify round-trip
|
||||
decompressed, err := Decompress(compressed)
|
||||
if err != nil {
|
||||
t.Fatalf("Decompress() error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(decompressed, input) {
|
||||
t.Errorf("Round-trip failed: got len=%d, want len=%d", len(decompressed), len(input))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompressDecompress_RoundTrip(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{
|
||||
name: "binary data with mixed nulls",
|
||||
data: []byte{0x01, 0x02, 0x00, 0x00, 0x03, 0x04, 0x00, 0x05},
|
||||
},
|
||||
{
|
||||
name: "large binary data",
|
||||
data: append(append([]byte{0xFF, 0xFE, 0xFD}, make([]byte, 300)...), []byte{0x01, 0x02, 0x03}...),
|
||||
},
|
||||
{
|
||||
name: "text with embedded nulls",
|
||||
data: []byte("Test\x00\x00Data\x00\x00\x00End"),
|
||||
},
|
||||
{
|
||||
name: "all non-null bytes",
|
||||
data: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A},
|
||||
},
|
||||
{
|
||||
name: "only null bytes",
|
||||
data: make([]byte, 100),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Compress
|
||||
compressed, err := Compress(tt.data)
|
||||
if err != nil {
|
||||
t.Fatalf("Compress() error = %v", err)
|
||||
}
|
||||
|
||||
// Decompress
|
||||
decompressed, err := Decompress(compressed)
|
||||
if err != nil {
|
||||
t.Fatalf("Decompress() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify
|
||||
if !bytes.Equal(decompressed, tt.data) {
|
||||
t.Errorf("Round-trip failed:\ngot = %v\nwant = %v", decompressed, tt.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompress_CompressionEfficiency(t *testing.T) {
|
||||
// Test that data with many nulls is actually compressed
|
||||
input := make([]byte, 1000)
|
||||
compressed, err := Compress(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Compress() error = %v", err)
|
||||
}
|
||||
|
||||
// The compressed size should be much smaller than the original
|
||||
// With 1000 nulls, we expect roughly 16 (header) + 4*3 (for 255*3 + 235) bytes
|
||||
if len(compressed) >= len(input) {
|
||||
t.Errorf("Compression failed: compressed size (%d) >= input size (%d)", len(compressed), len(input))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecompress_EdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
}{
|
||||
{
|
||||
name: "only header",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00"),
|
||||
},
|
||||
{
|
||||
name: "null with count 1",
|
||||
input: []byte("cmp\x2020110113\x20\x20\x20\x00\x00\x01"),
|
||||
},
|
||||
{
|
||||
name: "multiple sections of compressed nulls",
|
||||
input: append([]byte("cmp\x2020110113\x20\x20\x20\x00"), []byte{0x00, 0x10, 0x41, 0x00, 0x20, 0x42}...),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := Decompress(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("Decompress() unexpected error = %v", err)
|
||||
}
|
||||
// Just ensure it doesn't crash and returns something
|
||||
_ = result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCompress(b *testing.B) {
|
||||
data := make([]byte, 10000)
|
||||
// Fill with some pattern (half nulls, half data)
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i%2 == 0 {
|
||||
data[i] = 0x00
|
||||
} else {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Compress(data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecompress(b *testing.B) {
|
||||
data := make([]byte, 10000)
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i%2 == 0 {
|
||||
data[i] = 0x00
|
||||
} else {
|
||||
data[i] = byte(i % 256)
|
||||
}
|
||||
}
|
||||
|
||||
compressed, err := Compress(data)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Decompress(compressed)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
419
server/discordbot/discord_bot_test.go
Normal file
419
server/discordbot/discord_bot_test.go
Normal file
@@ -0,0 +1,419 @@
|
||||
package discordbot
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReplaceTextAll(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
regex *regexp.Regexp
|
||||
handler func(string) string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "replace single match",
|
||||
text: "Hello @123456789012345678",
|
||||
regex: regexp.MustCompile(`@(\d+)`),
|
||||
handler: func(id string) string {
|
||||
return "@user_" + id
|
||||
},
|
||||
expected: "Hello @user_123456789012345678",
|
||||
},
|
||||
{
|
||||
name: "replace multiple matches",
|
||||
text: "Users @111111111111111111 and @222222222222222222",
|
||||
regex: regexp.MustCompile(`@(\d+)`),
|
||||
handler: func(id string) string {
|
||||
return "@user_" + id
|
||||
},
|
||||
expected: "Users @user_111111111111111111 and @user_222222222222222222",
|
||||
},
|
||||
{
|
||||
name: "no matches",
|
||||
text: "Hello World",
|
||||
regex: regexp.MustCompile(`@(\d+)`),
|
||||
handler: func(id string) string {
|
||||
return "@user_" + id
|
||||
},
|
||||
expected: "Hello World",
|
||||
},
|
||||
{
|
||||
name: "replace with empty string",
|
||||
text: "Remove @123456789012345678 this",
|
||||
regex: regexp.MustCompile(`@(\d+)`),
|
||||
handler: func(id string) string {
|
||||
return ""
|
||||
},
|
||||
expected: "Remove this",
|
||||
},
|
||||
{
|
||||
name: "replace emoji syntax",
|
||||
text: "Hello :smile: and :wave:",
|
||||
regex: regexp.MustCompile(`:(\w+):`),
|
||||
handler: func(emoji string) string {
|
||||
return "[" + emoji + "]"
|
||||
},
|
||||
expected: "Hello [smile] and [wave]",
|
||||
},
|
||||
{
|
||||
name: "complex replacement",
|
||||
text: "Text with <@!123456789012345678> mention",
|
||||
regex: regexp.MustCompile(`<@!?(\d+)>`),
|
||||
handler: func(id string) string {
|
||||
return "@user_" + id
|
||||
},
|
||||
expected: "Text with @user_123456789012345678 mention",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ReplaceTextAll(tt.text, tt.regex, tt.handler)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ReplaceTextAll() = %q, want %q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceTextAll_UserMentionPattern(t *testing.T) {
|
||||
// Test the actual user mention regex used in NormalizeDiscordMessage
|
||||
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
expected []string // Expected captured IDs
|
||||
}{
|
||||
{
|
||||
name: "standard mention",
|
||||
text: "<@123456789012345678>",
|
||||
expected: []string{"123456789012345678"},
|
||||
},
|
||||
{
|
||||
name: "nickname mention",
|
||||
text: "<@!123456789012345678>",
|
||||
expected: []string{"123456789012345678"},
|
||||
},
|
||||
{
|
||||
name: "multiple mentions",
|
||||
text: "<@123456789012345678> and <@!987654321098765432>",
|
||||
expected: []string{"123456789012345678", "987654321098765432"},
|
||||
},
|
||||
{
|
||||
name: "17 digit ID",
|
||||
text: "<@12345678901234567>",
|
||||
expected: []string{"12345678901234567"},
|
||||
},
|
||||
{
|
||||
name: "19 digit ID",
|
||||
text: "<@1234567890123456789>",
|
||||
expected: []string{"1234567890123456789"},
|
||||
},
|
||||
{
|
||||
name: "invalid - too short",
|
||||
text: "<@1234567890123456>",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "invalid - too long",
|
||||
text: "<@12345678901234567890>",
|
||||
expected: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matches := userRegex.FindAllStringSubmatch(tt.text, -1)
|
||||
if len(matches) != len(tt.expected) {
|
||||
t.Fatalf("Expected %d matches, got %d", len(tt.expected), len(matches))
|
||||
}
|
||||
for i, match := range matches {
|
||||
if len(match) < 2 {
|
||||
t.Fatalf("Match %d: expected capture group", i)
|
||||
}
|
||||
if match[1] != tt.expected[i] {
|
||||
t.Errorf("Match %d: got ID %q, want %q", i, match[1], tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceTextAll_EmojiPattern(t *testing.T) {
|
||||
// Test the actual emoji regex used in NormalizeDiscordMessage
|
||||
emojiRegex := regexp.MustCompile(`(?:<a?)?:(\w+):(?:\d{18}>)?`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
text string
|
||||
expectedName []string // Expected emoji names
|
||||
}{
|
||||
{
|
||||
name: "simple emoji",
|
||||
text: ":smile:",
|
||||
expectedName: []string{"smile"},
|
||||
},
|
||||
{
|
||||
name: "custom emoji",
|
||||
text: "<:customemoji:123456789012345678>",
|
||||
expectedName: []string{"customemoji"},
|
||||
},
|
||||
{
|
||||
name: "animated emoji",
|
||||
text: "<a:animated:123456789012345678>",
|
||||
expectedName: []string{"animated"},
|
||||
},
|
||||
{
|
||||
name: "multiple emojis",
|
||||
text: ":wave: <:custom:123456789012345678> :smile:",
|
||||
expectedName: []string{"wave", "custom", "smile"},
|
||||
},
|
||||
{
|
||||
name: "emoji with underscores",
|
||||
text: ":thumbs_up:",
|
||||
expectedName: []string{"thumbs_up"},
|
||||
},
|
||||
{
|
||||
name: "emoji with numbers",
|
||||
text: ":emoji123:",
|
||||
expectedName: []string{"emoji123"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matches := emojiRegex.FindAllStringSubmatch(tt.text, -1)
|
||||
if len(matches) != len(tt.expectedName) {
|
||||
t.Fatalf("Expected %d matches, got %d", len(tt.expectedName), len(matches))
|
||||
}
|
||||
for i, match := range matches {
|
||||
if len(match) < 2 {
|
||||
t.Fatalf("Match %d: expected capture group", i)
|
||||
}
|
||||
if match[1] != tt.expectedName[i] {
|
||||
t.Errorf("Match %d: got name %q, want %q", i, match[1], tt.expectedName[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDiscordMessage_Integration(t *testing.T) {
|
||||
// Create a mock bot for testing the normalization logic
|
||||
// Note: We can't fully test this without a real Discord session,
|
||||
// but we can test the regex patterns and structure
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
contains []string // Strings that should be in the output
|
||||
}{
|
||||
{
|
||||
name: "plain text unchanged",
|
||||
input: "Hello World",
|
||||
contains: []string{"Hello World"},
|
||||
},
|
||||
{
|
||||
name: "user mention format",
|
||||
input: "Hello <@123456789012345678>",
|
||||
// We can't test the actual replacement without a real Discord session
|
||||
// but we can verify the pattern is matched
|
||||
contains: []string{"Hello"},
|
||||
},
|
||||
{
|
||||
name: "emoji format preserved",
|
||||
input: "Hello :smile:",
|
||||
contains: []string{"Hello", ":smile:"},
|
||||
},
|
||||
{
|
||||
name: "mixed content",
|
||||
input: "<@123456789012345678> sent :wave:",
|
||||
contains: []string{"sent"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Test that the message contains expected parts
|
||||
for _, expected := range tt.contains {
|
||||
if len(expected) > 0 && !contains(tt.input, expected) {
|
||||
t.Errorf("Input %q should contain %q", tt.input, expected)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_Structure(t *testing.T) {
|
||||
// Test that the Commands slice is properly structured
|
||||
if len(Commands) == 0 {
|
||||
t.Error("Commands slice should not be empty")
|
||||
}
|
||||
|
||||
expectedCommands := map[string]bool{
|
||||
"link": false,
|
||||
"password": false,
|
||||
}
|
||||
|
||||
for _, cmd := range Commands {
|
||||
if cmd.Name == "" {
|
||||
t.Error("Command should have a name")
|
||||
}
|
||||
if cmd.Description == "" {
|
||||
t.Errorf("Command %q should have a description", cmd.Name)
|
||||
}
|
||||
|
||||
if _, exists := expectedCommands[cmd.Name]; exists {
|
||||
expectedCommands[cmd.Name] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Verify expected commands exist
|
||||
for name, found := range expectedCommands {
|
||||
if !found {
|
||||
t.Errorf("Expected command %q not found in Commands", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_LinkCommand(t *testing.T) {
|
||||
var linkCmd *struct {
|
||||
Name string
|
||||
Description string
|
||||
Options []struct {
|
||||
Type int
|
||||
Name string
|
||||
Description string
|
||||
Required bool
|
||||
}
|
||||
}
|
||||
|
||||
// Find the link command
|
||||
for _, cmd := range Commands {
|
||||
if cmd.Name == "link" {
|
||||
// Verify structure
|
||||
if cmd.Description == "" {
|
||||
t.Error("Link command should have a description")
|
||||
}
|
||||
if len(cmd.Options) == 0 {
|
||||
t.Error("Link command should have options")
|
||||
}
|
||||
|
||||
// Verify token option
|
||||
for _, opt := range cmd.Options {
|
||||
if opt.Name == "token" {
|
||||
if !opt.Required {
|
||||
t.Error("Token option should be required")
|
||||
}
|
||||
if opt.Description == "" {
|
||||
t.Error("Token option should have a description")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Error("Link command should have a 'token' option")
|
||||
}
|
||||
}
|
||||
|
||||
if linkCmd == nil {
|
||||
t.Error("Link command not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_PasswordCommand(t *testing.T) {
|
||||
// Find the password command
|
||||
for _, cmd := range Commands {
|
||||
if cmd.Name == "password" {
|
||||
// Verify structure
|
||||
if cmd.Description == "" {
|
||||
t.Error("Password command should have a description")
|
||||
}
|
||||
if len(cmd.Options) == 0 {
|
||||
t.Error("Password command should have options")
|
||||
}
|
||||
|
||||
// Verify password option
|
||||
for _, opt := range cmd.Options {
|
||||
if opt.Name == "password" {
|
||||
if !opt.Required {
|
||||
t.Error("Password option should be required")
|
||||
}
|
||||
if opt.Description == "" {
|
||||
t.Error("Password option should have a description")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Error("Password command should have a 'password' option")
|
||||
}
|
||||
}
|
||||
|
||||
t.Error("Password command not found")
|
||||
}
|
||||
|
||||
func TestDiscordBotStruct(t *testing.T) {
|
||||
// Test that the DiscordBot struct can be initialized
|
||||
bot := &DiscordBot{
|
||||
Session: nil, // Can't create real session in tests
|
||||
MainGuild: nil,
|
||||
RelayChannel: nil,
|
||||
}
|
||||
|
||||
if bot == nil {
|
||||
t.Error("Failed to create DiscordBot struct")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionsStruct(t *testing.T) {
|
||||
// Test that the Options struct can be initialized
|
||||
opts := Options{
|
||||
Config: nil,
|
||||
Logger: nil,
|
||||
}
|
||||
|
||||
// Just verify we can create the struct
|
||||
_ = opts
|
||||
}
|
||||
|
||||
// Helper function
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && containsHelper(s, substr))
|
||||
}
|
||||
|
||||
func containsHelper(s, substr string) bool {
|
||||
for i := 0; i <= len(s)-len(substr); i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func BenchmarkReplaceTextAll(b *testing.B) {
|
||||
text := "Message with <@123456789012345678> and <@!987654321098765432> mentions and :smile: :wave: emojis"
|
||||
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
|
||||
handler := func(id string) string {
|
||||
return "@user_" + id
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ReplaceTextAll(text, userRegex, handler)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplaceTextAll_NoMatches(b *testing.B) {
|
||||
text := "Message with no mentions or special syntax at all, just plain text"
|
||||
userRegex := regexp.MustCompile(`<@!?(\d{17,19})>`)
|
||||
handler := func(id string) string {
|
||||
return "@user_" + id
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ReplaceTextAll(text, userRegex, handler)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user