package db_test import ( "encoding/json" "testing" "github.com/J3vb/OwnCord/Server/db" ) // ─── Role JSON round-trip ──────────────────────────────────────────────────── func TestRole_JSONRoundTrip(t *testing.T) { color := "#ff0000" original := db.Role{ ID: 1, Name: "admin", Color: &color, Permissions: 0x40000000, Position: 100, IsDefault: false, } data, err := json.Marshal(original) if err != nil { t.Fatalf("Marshal: %v", err) } var decoded db.Role if err := json.Unmarshal(data, &decoded); err != nil { t.Fatalf("Unmarshal: %v", err) } if decoded.ID != original.ID { t.Errorf("ID = %d, want %d", decoded.ID, original.ID) } if decoded.Name != original.Name { t.Errorf("Name = %q, want %q", decoded.Name, original.Name) } if decoded.Color == nil || *decoded.Color != color { t.Errorf("Color = %v, want %q", decoded.Color, color) } if decoded.Permissions != original.Permissions { t.Errorf("Permissions = %d, want %d", decoded.Permissions, original.Permissions) } if decoded.Position != original.Position { t.Errorf("Position = %d, want %d", decoded.Position, original.Position) } if decoded.IsDefault != original.IsDefault { t.Errorf("IsDefault = %v, want %v", decoded.IsDefault, original.IsDefault) } } func TestRole_JSONKeys(t *testing.T) { role := db.Role{ID: 1, Name: "member", Permissions: 3, Position: 1, IsDefault: true} data, _ := json.Marshal(role) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal to map: %v", err) } expectedKeys := []string{"id", "name", "color", "permissions", "position", "is_default"} for _, k := range expectedKeys { if _, ok := raw[k]; !ok { t.Errorf("missing JSON key %q", k) } } } func TestRole_NilColor(t *testing.T) { role := db.Role{ID: 1, Name: "member"} data, _ := json.Marshal(role) var raw map[string]any if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } if raw["color"] != nil { t.Errorf("nil Color should serialize as null, got %v", raw["color"]) } } // ─── Channel JSON round-trip ───────────────────────────────────────────────── func TestChannel_JSONRoundTrip(t *testing.T) { quality := "high" threshold := 10 original := db.Channel{ ID: 42, Name: "general", Type: "text", Category: "Main", Topic: "General chat", Position: 0, SlowMode: 5, Archived: false, CreatedAt: "2026-01-01T00:00:00Z", VoiceMaxUsers: 25, VoiceQuality: &quality, MixingThreshold: &threshold, VoiceMaxVideo: 4, } data, err := json.Marshal(original) if err != nil { t.Fatalf("Marshal: %v", err) } var decoded db.Channel if err := json.Unmarshal(data, &decoded); err != nil { t.Fatalf("Unmarshal: %v", err) } if decoded.ID != original.ID || decoded.Name != original.Name { t.Errorf("basic fields mismatch: got ID=%d Name=%q", decoded.ID, decoded.Name) } if decoded.VoiceQuality == nil || *decoded.VoiceQuality != quality { t.Errorf("VoiceQuality = %v, want %q", decoded.VoiceQuality, quality) } if decoded.MixingThreshold == nil || *decoded.MixingThreshold != threshold { t.Errorf("MixingThreshold = %v, want %d", decoded.MixingThreshold, threshold) } } func TestChannel_OmitEmptyFields(t *testing.T) { ch := db.Channel{ID: 1, Name: "voice-1", Type: "voice"} data, _ := json.Marshal(ch) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } // voice_quality and mixing_threshold have omitempty — should be absent when nil. if _, ok := raw["voice_quality"]; ok { t.Error("nil VoiceQuality should be omitted") } if _, ok := raw["mixing_threshold"]; ok { t.Error("nil MixingThreshold should be omitted") } } // ─── VoiceState JSON ───────────────────────────────────────────────────────── func TestVoiceState_JoinedAtOmittedFromJSON(t *testing.T) { vs := db.VoiceState{ UserID: 1, ChannelID: 2, Username: "alice", JoinedAt: "2026-01-01T00:00:00Z", } data, _ := json.Marshal(vs) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } if _, ok := raw["JoinedAt"]; ok { t.Error("JoinedAt has json:\"-\" tag and should not appear in JSON output") } if _, ok := raw["joined_at"]; ok { t.Error("JoinedAt should not appear under any key in JSON output") } } func TestVoiceState_BoolDefaults(t *testing.T) { vs := db.VoiceState{UserID: 1, ChannelID: 2, Username: "bob"} data, _ := json.Marshal(vs) var decoded db.VoiceState if err := json.Unmarshal(data, &decoded); err != nil { t.Fatalf("Unmarshal: %v", err) } if decoded.Muted || decoded.Deafened || decoded.Speaking || decoded.Camera || decoded.Screenshare { t.Error("zero-value VoiceState bools should all be false") } } // ─── MessageAPIResponse JSON ───────────────────────────────────────────────── func TestMessageAPIResponse_JSONKeys(t *testing.T) { resp := db.MessageAPIResponse{ ID: 1, ChannelID: 2, User: db.UserPublic{ID: 3, Username: "alice"}, Content: "hello", Attachments: []db.AttachmentInfo{}, Reactions: []db.ReactionInfo{}, Timestamp: "2026-01-01T00:00:00Z", } data, _ := json.Marshal(resp) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } required := []string{ "id", "channel_id", "user", "content", "reply_to", "attachments", "reactions", "pinned", "edited_at", "deleted", "timestamp", } for _, k := range required { if _, ok := raw[k]; !ok { t.Errorf("missing required JSON key %q", k) } } } // ─── AttachmentInfo omitempty ───────────────────────────────────────────────── func TestAttachmentInfo_OmitsNilDimensions(t *testing.T) { att := db.AttachmentInfo{ ID: "abc", Filename: "doc.pdf", Size: 1024, Mime: "application/pdf", URL: "/files/abc", } data, _ := json.Marshal(att) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } if _, ok := raw["width"]; ok { t.Error("nil Width should be omitted") } if _, ok := raw["height"]; ok { t.Error("nil Height should be omitted") } } func TestAttachmentInfo_IncludesDimensions(t *testing.T) { w, h := 1920, 1080 att := db.AttachmentInfo{ ID: "abc", Filename: "img.png", Size: 2048, Mime: "image/png", URL: "/files/abc", Width: &w, Height: &h, } data, _ := json.Marshal(att) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } if _, ok := raw["width"]; !ok { t.Error("non-nil Width should be present") } if _, ok := raw["height"]; !ok { t.Error("non-nil Height should be present") } } // ─── UserPublic omitempty ──────────────────────────────────────────────────── func TestUserPublic_OmitsNilAvatar(t *testing.T) { u := db.UserPublic{ID: 1, Username: "alice"} data, _ := json.Marshal(u) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } if _, ok := raw["avatar"]; ok { t.Error("nil Avatar should be omitted") } } func TestUserPublic_IncludesAvatar(t *testing.T) { av := "avatar.png" u := db.UserPublic{ID: 1, Username: "alice", Avatar: &av} data, _ := json.Marshal(u) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } if _, ok := raw["avatar"]; !ok { t.Error("non-nil Avatar should be present") } } // ─── ServerStats JSON ──────────────────────────────────────────────────────── func TestServerStats_JSONRoundTrip(t *testing.T) { original := db.ServerStats{ UserCount: 150, MessageCount: 50000, ChannelCount: 20, InviteCount: 5, DBSizeBytes: 1048576, OnlineCount: 42, } data, err := json.Marshal(original) if err != nil { t.Fatalf("Marshal: %v", err) } var decoded db.ServerStats if err := json.Unmarshal(data, &decoded); err != nil { t.Fatalf("Unmarshal: %v", err) } if decoded != original { t.Errorf("round-trip mismatch:\n got %+v\n want %+v", decoded, original) } } // ─── AuditEntry JSON ───────────────────────────────────────────────────────── func TestAuditEntry_JSONKeys(t *testing.T) { entry := db.AuditEntry{ ID: 1, ActorID: 2, ActorName: "admin", Action: "ban_user", TargetType: "user", TargetID: 3, Detail: "reason", CreatedAt: "2026-01-01", } data, _ := json.Marshal(entry) var raw map[string]json.RawMessage if err := json.Unmarshal(data, &raw); err != nil { t.Fatalf("Unmarshal: %v", err) } required := []string{"id", "actor_id", "actor_name", "action", "target_type", "target_id", "detail", "created_at"} for _, k := range required { if _, ok := raw[k]; !ok { t.Errorf("missing JSON key %q", k) } } }