summaryrefslogtreecommitdiffstats
path: root/pkg/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/protocol')
-rw-r--r--pkg/protocol/messages_test.go428
1 files changed, 428 insertions, 0 deletions
diff --git a/pkg/protocol/messages_test.go b/pkg/protocol/messages_test.go
new file mode 100644
index 0000000..c6211ea
--- /dev/null
+++ b/pkg/protocol/messages_test.go
@@ -0,0 +1,428 @@
+package protocol
+
+import (
+ "encoding/json"
+ "testing"
+ "time"
+)
+
+func TestNewMessage(t *testing.T) {
+ msg := NewMessage("alice", "bob", "Hello!", TypePrivate)
+
+ if msg.From != "alice" {
+ t.Errorf("From = %q, want 'alice'", msg.From)
+ }
+ if msg.To != "bob" {
+ t.Errorf("To = %q, want 'bob'", msg.To)
+ }
+ if msg.Content != "Hello!" {
+ t.Errorf("Content = %q, want 'Hello!'", msg.Content)
+ }
+ if msg.Type != TypePrivate {
+ t.Errorf("Type = %q, want %q", msg.Type, TypePrivate)
+ }
+ if msg.Time == "" {
+ t.Error("Time should not be empty")
+ }
+}
+
+func TestNewSystemMessage(t *testing.T) {
+ msg := NewSystemMessage("Server restarting")
+
+ if msg.From != "System" {
+ t.Errorf("From = %q, want 'System'", msg.From)
+ }
+ if msg.Content != "Server restarting" {
+ t.Errorf("Content = %q, want 'Server restarting'", msg.Content)
+ }
+ if msg.Type != TypeSystem {
+ t.Errorf("Type = %q, want %q", msg.Type, TypeSystem)
+ }
+}
+
+func TestNewErrorMessage(t *testing.T) {
+ msg := NewErrorMessage("Connection failed")
+
+ if msg.From != "System" {
+ t.Errorf("From = %q, want 'System'", msg.From)
+ }
+ if msg.Type != TypeError {
+ t.Errorf("Type = %q, want %q", msg.Type, TypeError)
+ }
+}
+
+func TestNewUserListMessage(t *testing.T) {
+ users := []string{"alice", "bob", "charlie"}
+ msg := NewUserListMessage(users)
+
+ if msg.Type != TypeUserList {
+ t.Errorf("Type = %q, want %q", msg.Type, TypeUserList)
+ }
+ if len(msg.Users) != 3 {
+ t.Errorf("Users count = %d, want 3", len(msg.Users))
+ }
+ if msg.Time == "" {
+ t.Error("Time should not be empty")
+ }
+}
+
+func TestMessage_ToJSON(t *testing.T) {
+ msg := NewMessage("alice", "bob", "Test", TypeMessage)
+
+ data, err := msg.ToJSON()
+ if err != nil {
+ t.Fatalf("ToJSON() error = %v", err)
+ }
+
+ // Should be valid JSON
+ var parsed map[string]interface{}
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("Invalid JSON: %v", err)
+ }
+
+ if parsed["from"] != "alice" {
+ t.Errorf("JSON from = %v, want 'alice'", parsed["from"])
+ }
+ if parsed["type"] != TypeMessage {
+ t.Errorf("JSON type = %v, want %q", parsed["type"], TypeMessage)
+ }
+}
+
+func TestUserListMessage_ToJSON(t *testing.T) {
+ msg := NewUserListMessage([]string{"alice", "bob"})
+
+ data, err := msg.ToJSON()
+ if err != nil {
+ t.Fatalf("ToJSON() error = %v", err)
+ }
+
+ var parsed map[string]interface{}
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("Invalid JSON: %v", err)
+ }
+
+ if parsed["type"] != TypeUserList {
+ t.Errorf("JSON type = %v, want %q", parsed["type"], TypeUserList)
+ }
+
+ users, ok := parsed["users"].([]interface{})
+ if !ok || len(users) != 2 {
+ t.Errorf("JSON users incorrect: %v", parsed["users"])
+ }
+}
+
+func TestFileOfferMessage_ToJSON(t *testing.T) {
+ msg := FileOfferMessage{
+ Type: TypeFileOffer,
+ From: "alice",
+ To: "bob",
+ FileID: "abc123",
+ Filename: "test.txt",
+ Size: 1024,
+ TotalChunks: 4,
+ SHA256: "deadbeef",
+ Compressed: true,
+ Time: time.Now().Format("15:04:05"),
+ }
+
+ data, err := msg.ToJSON()
+ if err != nil {
+ t.Fatalf("ToJSON() error = %v", err)
+ }
+
+ var parsed FileOfferMessage
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("Unmarshal error = %v", err)
+ }
+
+ if parsed.FileID != "abc123" {
+ t.Errorf("FileID = %q, want 'abc123'", parsed.FileID)
+ }
+ if parsed.Size != 1024 {
+ t.Errorf("Size = %d, want 1024", parsed.Size)
+ }
+ if !parsed.Compressed {
+ t.Error("Compressed should be true")
+ }
+}
+
+func TestFileChunkMessage_ToJSON(t *testing.T) {
+ msg := FileChunkMessage{
+ Type: TypeFileChunk,
+ FileID: "abc123",
+ ChunkIndex: 2,
+ TotalChunks: 10,
+ Data: "base64encodeddata",
+ Time: time.Now().Format("15:04:05"),
+ }
+
+ data, err := msg.ToJSON()
+ if err != nil {
+ t.Fatalf("ToJSON() error = %v", err)
+ }
+
+ var parsed FileChunkMessage
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("Unmarshal error = %v", err)
+ }
+
+ if parsed.ChunkIndex != 2 {
+ t.Errorf("ChunkIndex = %d, want 2", parsed.ChunkIndex)
+ }
+ if parsed.TotalChunks != 10 {
+ t.Errorf("TotalChunks = %d, want 10", parsed.TotalChunks)
+ }
+}
+
+func TestFileResponseMessage_ToJSON(t *testing.T) {
+ msg := FileResponseMessage{
+ Type: TypeFileAccept,
+ FileID: "abc123",
+ From: "bob",
+ Status: "accepted",
+ Message: "Transfer starting",
+ Time: time.Now().Format("15:04:05"),
+ }
+
+ data, err := msg.ToJSON()
+ if err != nil {
+ t.Fatalf("ToJSON() error = %v", err)
+ }
+
+ var parsed FileResponseMessage
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("Unmarshal error = %v", err)
+ }
+
+ if parsed.Status != "accepted" {
+ t.Errorf("Status = %q, want 'accepted'", parsed.Status)
+ }
+}
+
+func TestKeyRotationMessage_ToJSON(t *testing.T) {
+ msg := KeyRotationMessage{
+ Type: TypeKeyRotation,
+ PublicKey: []byte{1, 2, 3, 4},
+ Nonce: []byte{5, 6, 7, 8},
+ Signature: []byte{9, 10, 11, 12},
+ Time: time.Now().Format("15:04:05"),
+ }
+
+ data, err := msg.ToJSON()
+ if err != nil {
+ t.Fatalf("ToJSON() error = %v", err)
+ }
+
+ var parsed KeyRotationMessage
+ if err := json.Unmarshal(data, &parsed); err != nil {
+ t.Fatalf("Unmarshal error = %v", err)
+ }
+
+ if len(parsed.PublicKey) != 4 {
+ t.Errorf("PublicKey length = %d, want 4", len(parsed.PublicKey))
+ }
+}
+
+func TestParseMessage(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ wantType string
+ wantErr bool
+ }{
+ {
+ name: "message type",
+ input: `{"type":"message","from":"alice","content":"hi"}`,
+ wantType: TypeMessage,
+ },
+ {
+ name: "private type",
+ input: `{"type":"private","from":"alice","to":"bob"}`,
+ wantType: TypePrivate,
+ },
+ {
+ name: "system type",
+ input: `{"type":"system","content":"welcome"}`,
+ wantType: TypeSystem,
+ },
+ {
+ name: "no type field",
+ input: `{"from":"alice","content":"hi"}`,
+ wantType: TypeMessage, // Default
+ },
+ {
+ name: "invalid json",
+ input: `not json`,
+ wantErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ msgType, data, err := ParseMessage([]byte(tt.input))
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ParseMessage() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if err != nil {
+ return
+ }
+ if msgType != tt.wantType {
+ t.Errorf("ParseMessage() type = %q, want %q", msgType, tt.wantType)
+ }
+ if data == nil {
+ t.Error("ParseMessage() data should not be nil")
+ }
+ })
+ }
+}
+
+func TestParseAsMessage(t *testing.T) {
+ input := `{"type":"message","from":"alice","to":"bob","content":"hello","time":"12:34:56"}`
+
+ msg, err := ParseAsMessage([]byte(input))
+ if err != nil {
+ t.Fatalf("ParseAsMessage() error = %v", err)
+ }
+
+ if msg.From != "alice" {
+ t.Errorf("From = %q, want 'alice'", msg.From)
+ }
+ if msg.To != "bob" {
+ t.Errorf("To = %q, want 'bob'", msg.To)
+ }
+ if msg.Content != "hello" {
+ t.Errorf("Content = %q, want 'hello'", msg.Content)
+ }
+}
+
+func TestParseAsUserList(t *testing.T) {
+ input := `{"type":"user_list","users":["alice","bob","charlie"],"time":"12:34:56"}`
+
+ msg, err := ParseAsUserList([]byte(input))
+ if err != nil {
+ t.Fatalf("ParseAsUserList() error = %v", err)
+ }
+
+ if msg.Type != TypeUserList {
+ t.Errorf("Type = %q, want %q", msg.Type, TypeUserList)
+ }
+ if len(msg.Users) != 3 {
+ t.Errorf("Users count = %d, want 3", len(msg.Users))
+ }
+}
+
+func TestParseAsFileOffer(t *testing.T) {
+ input := `{"type":"file_offer","from":"alice","to":"bob","file_id":"abc","filename":"test.txt","size":100,"total_chunks":1}`
+
+ msg, err := ParseAsFileOffer([]byte(input))
+ if err != nil {
+ t.Fatalf("ParseAsFileOffer() error = %v", err)
+ }
+
+ if msg.FileID != "abc" {
+ t.Errorf("FileID = %q, want 'abc'", msg.FileID)
+ }
+ if msg.Filename != "test.txt" {
+ t.Errorf("Filename = %q, want 'test.txt'", msg.Filename)
+ }
+}
+
+func TestParseAsFileChunk(t *testing.T) {
+ input := `{"type":"file_chunk","file_id":"abc","chunk_index":5,"total_chunks":10,"data":"base64"}`
+
+ msg, err := ParseAsFileChunk([]byte(input))
+ if err != nil {
+ t.Fatalf("ParseAsFileChunk() error = %v", err)
+ }
+
+ if msg.ChunkIndex != 5 {
+ t.Errorf("ChunkIndex = %d, want 5", msg.ChunkIndex)
+ }
+}
+
+func TestParseAsFileResponse(t *testing.T) {
+ input := `{"type":"file_accept","file_id":"abc","from":"bob","status":"accepted","message":"ok"}`
+
+ msg, err := ParseAsFileResponse([]byte(input))
+ if err != nil {
+ t.Fatalf("ParseAsFileResponse() error = %v", err)
+ }
+
+ if msg.Status != "accepted" {
+ t.Errorf("Status = %q, want 'accepted'", msg.Status)
+ }
+}
+
+func TestParseAs_InvalidJSON(t *testing.T) {
+ invalid := []byte("not json")
+
+ _, err := ParseAsMessage(invalid)
+ if err == nil {
+ t.Error("ParseAsMessage should fail on invalid JSON")
+ }
+
+ _, err = ParseAsUserList(invalid)
+ if err == nil {
+ t.Error("ParseAsUserList should fail on invalid JSON")
+ }
+
+ _, err = ParseAsFileOffer(invalid)
+ if err == nil {
+ t.Error("ParseAsFileOffer should fail on invalid JSON")
+ }
+
+ _, err = ParseAsFileChunk(invalid)
+ if err == nil {
+ t.Error("ParseAsFileChunk should fail on invalid JSON")
+ }
+
+ _, err = ParseAsFileResponse(invalid)
+ if err == nil {
+ t.Error("ParseAsFileResponse should fail on invalid JSON")
+ }
+}
+
+// Test message type constants
+func TestMessageTypeConstants(t *testing.T) {
+ types := map[string]string{
+ "TypeMessage": TypeMessage,
+ "TypePrivate": TypePrivate,
+ "TypeSystem": TypeSystem,
+ "TypeError": TypeError,
+ "TypeUserList": TypeUserList,
+ "TypeUserJoin": TypeUserJoin,
+ "TypeUserLeave": TypeUserLeave,
+ "TypeHandshake": TypeHandshake,
+ "TypePublic": TypePublic,
+ "TypeKeyRotation": TypeKeyRotation,
+ "TypeFileOffer": TypeFileOffer,
+ "TypeFileChunk": TypeFileChunk,
+ "TypeFileAccept": TypeFileAccept,
+ "TypeFileReject": TypeFileReject,
+ "TypeFileComplete": TypeFileComplete,
+ }
+
+ for name, value := range types {
+ if value == "" {
+ t.Errorf("%s should not be empty", name)
+ }
+ }
+}
+
+// Benchmark
+func BenchmarkMessage_ToJSON(b *testing.B) {
+ msg := NewMessage("alice", "bob", "Test message content", TypeMessage)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ msg.ToJSON()
+ }
+}
+
+func BenchmarkParseMessage(b *testing.B) {
+ data := []byte(`{"type":"message","from":"alice","to":"bob","content":"hello","time":"12:34:56"}`)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ParseMessage(data)
+ }
+}