{ "assetKey": "a13-f005-retry-after-illegalargumentexception-negative", "kind": "terminal", "command": "set -e\nset -o pipefail\nD=$(mktemp -d); trap 'rm -rf \"$D\"' EXIT\nC=/shared/codebase/clean-architecture-backend-template\nN=$C/src/adapter/outbound/notification\nCACHE=/root/.gradle/caches/modules-2/files-2.1\njava -version 2>&1 | head -1\ncp=\"\"\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",runtimeClasspath,\"*) ;; *) continue ;; esac\n g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}\n jar=$(find \"$CACHE/$g/$n/$v\" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)\n [ -n \"$jar\" ] && cp=\"$cp:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$N/gradle.lockfile\")\nCP=$(find \"$N/build/libs\" \"$C/src/application-core/build/libs\" -name '*+21234e38cdb9.jar' | tr '\\n' ':')${cp#:}\ncat > \"$D/RetryAfterProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.ProviderResults;\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.apns.ApnsFailureClassifier;\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.fcm.FcmFailureClassifier;\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.http.NotificationHttpResponse;\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.ses.SesFailureClassifier;\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.twilio.TwilioFailureClassifier;\nimport dev.caskeleton.adapter.outbound.notification.platform.provider.webpush.WebPushFailureClassifier;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.nio.charset.StandardCharsets;\nimport java.time.Duration;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.function.Function;\n\n/** 같은 Retry-After 헤더를 파서 넷과 분류기 다섯에 넣고 무엇이 나오는지 잰다. */\npublic final class RetryAfterProbe {\n\n private static final String[] HEADERS = {\"30\", \"-30\", \"0\", \"Wed, 21 Oct 2026 07:28:00 GMT\"};\n\n private static NotificationHttpResponse throttled(String header) {\n return new NotificationHttpResponse(\n 429, Map.of(\"retry-after\", List.of(header)), \"{}\".getBytes(StandardCharsets.UTF_8));\n }\n\n private static String outcome(java.util.concurrent.Callable call) {\n try {\n Object value = call.call();\n if (value instanceof Optional parsed) {\n return parsed.map(Object::toString).orElse(\"빈 값\");\n }\n return String.valueOf(value);\n } catch (Exception thrown) {\n Throwable failure = thrown instanceof InvocationTargetException wrapped ? wrapped.getCause() : thrown;\n return failure.getClass().getSimpleName() + \": \" + failure.getMessage();\n }\n }\n\n private static Method webhookParser() throws Exception {\n Class adapter = Class.forName(\"dev.caskeleton.adapter.outbound.notification.platform\"\n + \".provider.webhook.WebhookNotificationProviderAdapter\");\n Method parser = adapter.getDeclaredMethod(\"retryAfter\", NotificationHttpResponse.class);\n parser.setAccessible(true);\n return parser;\n }\n\n public static void main(String[] args) throws Exception {\n Method webhook = webhookParser();\n FcmFailureClassifier fcm = new FcmFailureClassifier();\n\n System.out.println();\n System.out.println(\"[알림 리프의 파서 셋에 같은 헤더를 넣는다]\");\n for (String header : HEADERS) {\n System.out.println(\" retry-after: \" + header);\n System.out.println(\" ProviderResults.retryAfter \" + outcome(() ->\n ProviderResults.retryAfter(Optional.of(header))));\n System.out.println(\" FcmFailureClassifier.retryAfter \" + outcome(() ->\n fcm.retryAfter(Optional.of(header))));\n System.out.println(\" Webhook 어댑터의 것 \" + outcome(() ->\n webhook.invoke(null, throttled(header))));\n }\n System.out.println();\n\n System.out.println(\"[헤더가 -30 인 429 응답을 분류기 넷에 넣는다]\");\n NotificationHttpResponse negative = throttled(\"-30\");\n record Case(String name, Function run) {}\n List cases = List.of(\n new Case(\"SesFailureClassifier\", r -> new SesFailureClassifier().classify(r)),\n new Case(\"TwilioFailureClassifier\", r -> new TwilioFailureClassifier().classify(r)),\n new Case(\"WebPushFailureClassifier\", r -> new WebPushFailureClassifier().classify(r)),\n new Case(\"ApnsFailureClassifier\", r -> new ApnsFailureClassifier().classify(r)));\n for (Case one : cases) {\n String result = outcome(() -> one.run().apply(negative));\n System.out.println(\" \" + one.name());\n System.out.println(\" \" + (result.length() > 118 ? result.substring(0, 118) + \" …\" : result));\n }\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$CP\" -d \"$D\" \"$D/RetryAfterProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$CP:$D\" RetryAfterProbe\n", "cwd": "/shared/codebase/clean-architecture-backend-template/src", "exitCode": 0, "executedAt": "2026-09-02T16:28:48+00:00", "sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916", "raw": "evidence/raw/a13-f005-retry-after-illegalargumentexception-negative.txt", "svg": "evidence/rendered/a13-f005-retry-after-illegalargumentexception-negative.svg", "rawSha256": "9e0d03cfad03369251269723d06c8d1b9bc79399d9b80746df779ecfb98ad62a", "lines": 29, "redaction": "none — 헤더 값과 응답 본문은 이 프로브가 만든 합성 값이다" }