Merge branch 'main' into worktree-jpa-persistence-platform

This commit is contained in:
DongHyeonka
2026-08-14 14:24:57 +09:00
5 changed files with 25 additions and 11 deletions
@@ -293,6 +293,9 @@ public final class NotificationAdminServiceImpl implements NotificationAdminServ
case DEGRADED -> runtime.markDegraded(command.reason());
case THROTTLED -> runtime.markThrottled();
case AUTHENTICATION_FAILED -> runtime.markAuthenticationFailed(command.reason());
// Unreachable while the enum is exhaustive; present so a state added later fails loudly here
// rather than silently leaving the runtime in whatever state it was already in.
default -> throw new IllegalStateException("unhandled provider runtime state");
}
AdminOperationResult result =
@@ -22,13 +22,13 @@ import org.slf4j.LoggerFactory;
public final class LoggingNotificationAudit
implements NotificationAuditPort, NotificationSecurityAuditPort {
private static final Logger audit = LoggerFactory.getLogger("notification.audit");
private static final Logger security = LoggerFactory.getLogger("notification.security");
private static final Logger AUDIT = LoggerFactory.getLogger("notification.audit");
private static final Logger SECURITY = LoggerFactory.getLogger("notification.security");
@Override
public void record(NotificationAuditEvent event) {
Objects.requireNonNull(event, "event");
audit.info(
AUDIT.info(
"action={} actor={} reason={} operationId={} occurredAt={} attributes={}",
event.action(),
event.actorRef(),
@@ -43,7 +43,7 @@ public final class LoggingNotificationAudit
Objects.requireNonNull(profileId, "profileId");
// The payload is deliberately absent: a forged callback must not get its content into the log
// just by being rejected.
security.warn(
SECURITY.warn(
"event=callback_signature_rejected providerProfile={} reason={}",
profileId.value(),
reasonCode);
@@ -52,7 +52,7 @@ public final class LoggingNotificationAudit
@Override
public void callbackRejectedByLimit(ProviderProfileId profileId, String reasonCode) {
Objects.requireNonNull(profileId, "profileId");
security.warn(
SECURITY.warn(
"event=callback_rejected_by_limit providerProfile={} reason={}",
profileId.value(),
reasonCode);
@@ -41,6 +41,9 @@ class WebPushCryptoTest {
private static final Clock CLOCK =
Clock.fixed(Instant.parse("2026-08-14T00:00:00Z"), ZoneOffset.UTC);
private static final URI ENDPOINT = URI.create("https://push.example.com/send/abc123");
// One instance: a fresh SecureRandom per call re-seeds from the OS each time, which is slower
// and, on a constrained CI runner, can block on entropy.
private static final SecureRandom RANDOM = new SecureRandom();
@Test
void payloadIsEncryptedForTheSubscriptionAndDecryptsBackToThePlaintext() throws Exception {
@@ -221,7 +224,7 @@ class WebPushCryptoTest {
private static byte[] authSecret() {
byte[] secret = new byte[16];
new SecureRandom().nextBytes(secret);
RANDOM.nextBytes(secret);
return secret;
}
@@ -35,6 +35,10 @@ class WebPushProviderAdapterTest {
private static final Clock CLOCK =
Clock.fixed(Instant.parse("2026-08-14T00:00:00Z"), ZoneOffset.UTC);
// One instance: a fresh SecureRandom per call re-seeds from the OS each time, which is slower
// and, on a constrained CI runner, can block on entropy.
private static final SecureRandom RANDOM = new SecureRandom();
private final ProviderFaultHarness harness = new ProviderFaultHarness();
private final ContactPointProtector protector =
new AesGcmContactPointProtector(SecurityFixtures.keys());
@@ -115,7 +119,7 @@ class WebPushProviderAdapterTest {
@Test
void payloadEncryptionRoundTripsThroughTheSubscriptionKeys() {
var encryptor = new Rfc8291Aes128GcmEncryptor(new SecureRandom());
var encryptor = new Rfc8291Aes128GcmEncryptor(RANDOM);
var subscription = subscription();
var encrypted =
@@ -135,7 +139,7 @@ class WebPushProviderAdapterTest {
return new WebPushNotificationProviderAdapter(
new JdkNotificationHttpGateway(Duration.ofSeconds(2)),
new WebPushRequestMapper(
new Rfc8291Aes128GcmEncryptor(new SecureRandom()),
new Rfc8291Aes128GcmEncryptor(RANDOM),
// Signing needs a PKCS#8 EC key, which VapidJwtSignerTest covers; this keeps the
// transport test about TTL, encryption and status mapping.
(endpoint, signingKey, publicKey) -> "vapid t=stub-token, k=" + publicKey,
@@ -163,7 +167,7 @@ class WebPushProviderAdapterTest {
generator.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair pair = generator.generateKeyPair();
byte[] authSecret = new byte[16];
new SecureRandom().nextBytes(authSecret);
RANDOM.nextBytes(authSecret);
return new WebPushSubscriptionValue(
URI.create(harness.baseUri() + "/push/subscription-1"),
Rfc8291Aes128GcmEncryptor.encodePoint((ECPublicKey) pair.getPublic()),
@@ -61,6 +61,10 @@ import java.util.Optional;
*/
public final class ContractAdapters {
// One instance: a fresh SecureRandom per call re-seeds from the OS each time, which is slower
// and, on a constrained CI runner, can block on entropy.
private static final SecureRandom RANDOM = new SecureRandom();
private static final Clock CLOCK =
Clock.fixed(Instant.parse("2026-08-14T00:00:00Z"), ZoneOffset.UTC);
@@ -186,7 +190,7 @@ public final class ContractAdapters {
new WebPushNotificationProviderAdapter(
new JdkNotificationHttpGateway(Duration.ofSeconds(2)),
new WebPushRequestMapper(
new Rfc8291Aes128GcmEncryptor(new SecureRandom()),
new Rfc8291Aes128GcmEncryptor(RANDOM),
// Signing needs a PKCS#8 EC key, which WebPushCryptoTest covers; the suites here
// are about transport and evidence semantics.
(endpoint, signingKey, publicKey) -> "vapid t=stub-token, k=" + publicKey,
@@ -240,7 +244,7 @@ public final class ContractAdapters {
generator.initialize(new ECGenParameterSpec("secp256r1"));
KeyPair pair = generator.generateKeyPair();
byte[] authSecret = new byte[16];
new SecureRandom().nextBytes(authSecret);
RANDOM.nextBytes(authSecret);
return new WebPushSubscriptionValue(
URI.create(harness.baseUri() + "/push/subscription-1"),
Rfc8291Aes128GcmEncryptor.encodePoint((ECPublicKey) pair.getPublic()),