package store import ( "bytes" "testing" "time" "github.com/tinode/chat/server/store/types" ) func TestMessageCryptoRoundTrip(t *testing.T) { tenantID := types.TenantID(9001) key := bytes.Repeat([]byte{7}, 32) messageKeyCache.Lock() expires := time.Now().Add(time.Minute) messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{enabled: true, version: 3, key: key, expires: expires} messageKeyCache.items[messageKeyCacheID(tenantID, 3)] = cachedMessageKey{enabled: true, version: 3, key: key, expires: expires} messageKeyCache.Unlock() defer InvalidateMessageEncryptionKeys(tenantID) original := &types.Message{TenantID: tenantID, Topic: "p2p-test", SeqId: 42, Content: map[string]any{"txt": "hello"}} stored, err := encryptMessageContent(original) if err != nil { t.Fatal(err) } if stored.KeyVersion != 3 || len(stored.ContentNonce) != 12 || stored.Content == original.Content { t.Fatalf("message was not encrypted: %#v", stored) } if err = decryptMessageContent(stored); err != nil { t.Fatal(err) } content, ok := stored.Content.(map[string]any) if !ok || content["txt"] != "hello" { t.Fatalf("unexpected plaintext: %#v", stored.Content) } } func TestMessageCryptoPlaintextWhenDisabled(t *testing.T) { tenantID := types.TenantID(9002) messageKeyCache.Lock() messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{expires: time.Now().Add(time.Minute)} messageKeyCache.Unlock() defer InvalidateMessageEncryptionKeys(tenantID) original := &types.Message{TenantID: tenantID, Topic: "grp-test", SeqId: 7, Content: "plain"} stored, err := encryptMessageContent(original) if err != nil { t.Fatal(err) } if stored.KeyVersion != 0 || stored.Content != "plain" || len(stored.ContentNonce) != 0 { t.Fatalf("unexpected encrypted message: %#v", stored) } } func TestMessageCryptoRejectsTampering(t *testing.T) { tenantID := types.TenantID(9003) key := bytes.Repeat([]byte{9}, 32) expires := time.Now().Add(time.Minute) messageKeyCache.Lock() messageKeyCache.items[messageKeyCacheID(tenantID, 0)] = cachedMessageKey{enabled: true, version: 4, key: key, expires: expires} messageKeyCache.items[messageKeyCacheID(tenantID, 4)] = cachedMessageKey{enabled: true, version: 4, key: key, expires: expires} messageKeyCache.Unlock() defer InvalidateMessageEncryptionKeys(tenantID) original := &types.Message{TenantID: tenantID, Topic: "p2p-secure", SeqId: 8, Content: map[string]any{"txt": "secret"}} stored, err := encryptMessageContent(original) if err != nil { t.Fatal(err) } stored.Topic = "p2p-tampered" if err = decryptMessageContent(stored); err == nil { t.Fatal("expected authenticated decryption to reject changed topic") } stored, err = encryptMessageContent(original) if err != nil { t.Fatal(err) } stored.ContentNonce = stored.ContentNonce[:len(stored.ContentNonce)-1] if err = decryptMessageContent(stored); err == nil { t.Fatal("expected invalid nonce length to be rejected") } } func TestMessageCryptoSearchMatchesDecryptedContent(t *testing.T) { content := map[string]any{"txt": "项目 验收通过", "nested": map[string]any{"title": "日报"}} if !messageContentMatchesSearch(content, "验收") { t.Fatal("expected plaintext keyword to match") } if messageContentMatchesSearch(content, "不存在") { t.Fatal("unexpected keyword match") } }