feat(mq-starter-idempotent-redis): 旧键 mq.redis-stream.idempotent.* Binder 逐键回退兼容 + 聚合弃用告警
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package com.njcn.mq.idempotent.redis;
|
||||
|
||||
import com.njcn.mq.spi.MqIdempotentStore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* 旧键 mq.redis-stream.idempotent.* 向后兼容(Binder 逐键回退)。
|
||||
*
|
||||
* <p>规则:中立键 mq.idempotent.* 显式设置则以中立为准;否则旧键显式设置则用旧键值并打一条
|
||||
* 聚合弃用 WARN;否则用默认。store 字段私有(类内容不改),故通过 mock Redis 的行为断言验证生效值。
|
||||
*/
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
class MqIdempotentLegacyKeyCompatTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final ValueOperations<String, String> ops = mock(ValueOperations.class);
|
||||
private final StringRedisTemplate redis = mock(StringRedisTemplate.class);
|
||||
|
||||
private ApplicationContextRunner runner() {
|
||||
when(redis.opsForValue()).thenReturn(ops);
|
||||
return new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(MqIdempotentRedisAutoConfiguration.class))
|
||||
.withBean(StringRedisTemplate.class, () -> redis);
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyKeysOnly_takeEffect_andLogDeprecationWarn(CapturedOutput output) {
|
||||
runner().withPropertyValues(
|
||||
"mq.redis-stream.idempotent.key-prefix=legacy:idem:",
|
||||
"mq.redis-stream.idempotent.processing-ttl=15s")
|
||||
.run(ctx -> {
|
||||
MqIdempotentStore store = ctx.getBean(MqIdempotentStore.class);
|
||||
store.tryAcquire("k");
|
||||
// 旧键前缀 + 旧键 processing TTL 均生效
|
||||
verify(ops).setIfAbsent("legacy:idem:k", "processing", Duration.ofSeconds(15));
|
||||
// 弃用告警必须点名旧前缀,给使用者迁移线索
|
||||
assertThat(output).contains("mq.redis-stream.idempotent");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void neutralKeyWins_whenBothSet_noWarnForOverriddenLegacy(CapturedOutput output) {
|
||||
runner().withPropertyValues(
|
||||
"mq.idempotent.processing-ttl=20s",
|
||||
"mq.redis-stream.idempotent.processing-ttl=15s")
|
||||
.run(ctx -> {
|
||||
MqIdempotentStore store = ctx.getBean(MqIdempotentStore.class);
|
||||
store.tryAcquire("k");
|
||||
// 中立键优先;key-prefix 未设,用默认 mq:idem:
|
||||
verify(ops).setIfAbsent("mq:idem:k", "processing", Duration.ofSeconds(20));
|
||||
// 没有任何字段实际落到旧键 → 不打弃用告警
|
||||
assertThat(output).doesNotContain("mq.redis-stream.idempotent");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void successAndFailTtl_alsoFallBackToLegacy() {
|
||||
runner().withPropertyValues(
|
||||
"mq.redis-stream.idempotent.success-ttl=111s",
|
||||
"mq.redis-stream.idempotent.fail-ttl=222s")
|
||||
.run(ctx -> {
|
||||
MqIdempotentStore store = ctx.getBean(MqIdempotentStore.class);
|
||||
store.markSuccess("k");
|
||||
verify(ops).set("mq:idem:k", "success", Duration.ofSeconds(111));
|
||||
store.markFail("k");
|
||||
verify(ops).set("mq:idem:k", "fail", Duration.ofSeconds(222));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user