package com.njcn.middle.stream.template; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSONObject; import com.njcn.middle.stream.domain.StreamBaseMessage; import com.njcn.middle.stream.support.StreamKeyBuilder; import lombok.Data; import lombok.EqualsAndHashCode; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.springframework.data.redis.connection.stream.MapRecord; import org.springframework.data.redis.connection.stream.RecordId; import org.springframework.data.redis.core.StreamOperations; import org.springframework.data.redis.core.StringRedisTemplate; import java.util.Map; import static com.njcn.middle.stream.constant.StreamMessageConstant.ENC_GZIP; import static com.njcn.middle.stream.constant.StreamMessageConstant.ENC_PLAIN; import static com.njcn.middle.stream.constant.StreamMessageConstant.F_BODY; import static com.njcn.middle.stream.constant.StreamMessageConstant.F_ENC; import static com.njcn.middle.stream.constant.StreamMessageConstant.F_KEY; import static com.njcn.middle.stream.constant.StreamMessageConstant.F_TAG; import static com.njcn.middle.stream.constant.StreamMessageConstant.F_TS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; class RedisStreamEnhanceTemplateTest { @Data @EqualsAndHashCode(callSuper = true) static class Foo extends StreamBaseMessage { private String name; } @SuppressWarnings({"unchecked", "rawtypes"}) private MapRecord captureSend(boolean compress, Foo foo, String topic, StreamKeyBuilder kb) { StringRedisTemplate redis = mock(StringRedisTemplate.class); StreamOperations ops = mock(StreamOperations.class); doReturn(ops).when(redis).opsForStream(); // send() 内 ofMap 返回 MapRecord,编译期绑定 add(MapRecord) 重载(非 add(Record)) doReturn(RecordId.of("0-1")).when(ops).add(any(MapRecord.class)); RedisStreamEnhanceTemplate tpl = new RedisStreamEnhanceTemplate(redis, kb); RecordId id = tpl.send(topic, foo, compress); assertEquals("0-1", id.getValue(), "返回 XADD 的 RecordId"); ArgumentCaptor cap = ArgumentCaptor.forClass(MapRecord.class); verify(ops).add(cap.capture()); return (MapRecord) cap.getValue(); } @Test void sendPlainFillsFiveFields() { Foo foo = new Foo(); foo.setTag("t1"); foo.setName("hello"); MapRecord rec = captureSend(false, foo, "MyTopic", new StreamKeyBuilder(false, "")); assertEquals("MyTopic", rec.getStream(), "关隔离 → 原 topic 作 stream 名"); Map v = rec.getValue(); assertEquals(5, v.size(), "固定五字段 key/tag/enc/body/ts"); assertEquals(foo.getKey(), v.get(F_KEY), "key 取自 message"); assertEquals("t1", v.get(F_TAG), "tag 取自 message"); assertEquals(ENC_PLAIN, v.get(F_ENC), "不压缩 enc=plain"); assertTrue(v.get(F_BODY).contains("hello"), "明文 body 含原文"); assertNotNull(v.get(F_TS), "ts 非空"); } @Test void sendCompressUsesGzipAndShorterBody() { Foo foo = new Foo(); foo.setName(StrUtil.repeat("abcdefgh", 200)); String json = JSONObject.toJSONString(foo); MapRecord rec = captureSend(true, foo, "MyTopic", new StreamKeyBuilder(false, "")); Map v = rec.getValue(); assertEquals(ENC_GZIP, v.get(F_ENC), "压缩 enc=gzip"); assertTrue(v.get(F_BODY).length() < json.length(), "压缩 body 短于原 json"); } @Test void sendUsesEnvIsolatedStreamName() { MapRecord rec = captureSend(false, new Foo(), "MyTopic", new StreamKeyBuilder(true, "prod")); assertEquals("MyTopic_prod", rec.getStream(), "开隔离 → stream 名带 env 后缀"); } }