{ "assetKey": "a14-f017-springmvcrouteinventorycollector-run", "kind": "terminal", "command": "set -e\nset -o pipefail\nD=$(mktemp -d); trap 'rm -rf \"$D\"' EXIT\nC=/shared/codebase/clean-architecture-backend-template\nM=$C/src/adapter/inbound/web\nCACHE=/root/.gradle/caches/modules-2/files-2.1\njava -version 2>&1 | grep version\ncp=\"\"\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",testRuntimeClasspath,\"*) ;; *) 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) || true\n [ -n \"$jar\" ] && cp=\"$cp:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$M/gradle.lockfile\")\nSIB=$(find \"$C/src\" -path '*/build/libs/*+21234e38cdb9*.jar' ! -name '*-testkit.jar' | sort | tr '\\n' ':')\n\ncat > \"$D/CollectorProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.inbound.web.admin.route.SpringMvcRouteInventoryCollector;\nimport dev.caskeleton.adapter.inbound.web.admin.route.WebRouteInventory;\nimport dev.caskeleton.adapter.inbound.web.config.PresentationWebConfig;\nimport dev.caskeleton.adapter.inbound.web.core.ApiMajorVersion;\nimport dev.caskeleton.adapter.inbound.web.operation.InMemoryWebOperationCatalog;\nimport dev.caskeleton.adapter.inbound.web.operation.WebOperationProfile;\nimport dev.caskeleton.adapter.inbound.web.settings.PresentationSettings;\nimport dev.caskeleton.adapter.inbound.web.versioning.ApiDeprecationPolicy;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.context.support.AnnotationConfigWebApplicationContext;\nimport org.springframework.web.servlet.config.annotation.EnableWebMvc;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\n\n/** 죽은 수집기를 실제 핸들러 매핑에 물려 돌려 본다. */\npublic final class CollectorProbe {\n\n /** HealthcheckController 처럼 맨 경로를 선언한다. */\n @RestController\n @RequestMapping(\"/documents\")\n static class DocumentsController {\n @GetMapping(\"/{id}\")\n String get(String id) {\n return id;\n }\n }\n\n /** OperationHttpController:43 처럼 경로에 버전을 담아 선언한다. */\n @RestController\n @RequestMapping(\"/api/v1/operations\")\n static class OperationsController {\n @GetMapping(\"/{id}\")\n String get(String id) {\n return id;\n }\n }\n\n /** FileDownloadController:77 처럼 버전만 담은 경로를 선언한다. */\n @RestController\n @RequestMapping(\"/v1/files\")\n static class FilesController {\n @GetMapping(\"/{fileId}\")\n String get(String fileId) {\n return fileId;\n }\n }\n\n @Configuration\n @EnableWebMvc\n static class Wiring {\n @Bean\n DocumentsController documentsController() {\n return new DocumentsController();\n }\n\n @Bean\n OperationsController operationsController() {\n return new OperationsController();\n }\n\n @Bean\n FilesController filesController() {\n return new FilesController();\n }\n\n /** 경로 접두만 갈아 끼운다. 나머지는 두 구성이 같다. */\n @Bean\n PresentationWebConfig presentationWebConfig() {\n return new PresentationWebConfig(new PresentationSettings(prefixValue));\n }\n }\n\n /** 두 구성이 바꾸는 유일한 값. */\n private static String prefixValue = \"/v1\";\n\n private static WebRouteInventory collect(RequestMappingHandlerMapping mapping, int fallback) {\n return new SpringMvcRouteInventoryCollector(new ApiDeprecationPolicy())\n .collect(mapping, new ApiMajorVersion(fallback));\n }\n\n private static WebRouteInventory run(String prefixValue, String label) {\n // 접두를 먼저 세우고 컨텍스트를 만든다.\n var context = new AnnotationConfigWebApplicationContext();\n context.setServletContext(new org.springframework.mock.web.MockServletContext());\n CollectorProbe.prefixValue = prefixValue;\n context.register(Wiring.class);\n context.refresh();\n RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);\n\n // 프로덕션이 이 자리에서 하지 않는 일. 프로브가 대신 한다.\n // 넘긴 기본 버전은 9 다. 경로에서 읽어 내면 9 가 아닌 값이 나온다.\n WebRouteInventory inventory = collect(mapping, 9);\n System.out.printf(\"%n[%s · 매핑 %d 개]%n\", label, mapping.getHandlerMethods().size());\n inventory.routes().values().stream()\n .sorted(java.util.Comparator.comparing(route -> route.routeId().value()))\n .forEach(\n route ->\n System.out.printf(\n \" %-34s %-22s v%d%n\",\n route.routeId().value(),\n route.operationName().value(),\n route.apiVersion().value()));\n context.close();\n return inventory;\n }\n\n public static void main(String[] args) {\n System.out.println();\n System.out.println(\"컨트롤러 셋. 맨 경로 하나, /api/v1 로 시작하는 것 하나, /v1 로 시작하는 것 하나.\");\n System.out.println(\"수집기에 넘긴 기본 버전은 9 다. 경로에서 읽어 내면 9 가 아닌 값이 나온다.\");\n\n WebRouteInventory shipped = run(\"/v1\", \"접두 /v1 · application.yml:635 의 출하 기본값\");\n System.out.println(\" 접두가 앞에 붙어 어느 패턴도 /api/v 로 시작하지 않는다. 셋 다 기본값이다.\");\n\n run(\"\", \"접두 \\\"\\\" · app-bootstrap 시험 리소스 application-test.yml:114\");\n System.out.println(\" /api/v1 로 선언한 것만 그대로 남아 versionOf 가 읽는다.\");\n\n run(\"/api\", \"접두 /api · application-local.yml:164 · src/.env:112\");\n System.out.println(\" 이 접두에서는 /v1 로 선언한 것도 /api/v1 이 되어 versionOf 가 읽는다.\");\n\n System.out.println();\n System.out.println(\"[출하 접두로 모은 목록을 연산 카탈로그에 대 본다]\");\n var catalog = new InMemoryWebOperationCatalog();\n catalog.register(WebOperationProfile.readOnly(\"documents.get\"));\n System.out.printf(\" 등록해 둔 이름 : %s%n\", catalog.registeredNames());\n try {\n shipped.requireRegisteredOperations(catalog);\n System.out.println(\" 결과 : 통과\");\n } catch (RuntimeException refused) {\n System.out.println(\" 결과 : \" + refused.getClass().getSimpleName());\n System.out.println(\" \" + refused.getMessage());\n }\n catalog.register(WebOperationProfile.readOnly(\"operations.get\"));\n catalog.register(WebOperationProfile.readOnly(\"files.get\"));\n try {\n shipped.requireRegisteredOperations(catalog);\n System.out.println(\" 둘 다 등록한 뒤 : 통과\");\n } catch (RuntimeException refused) {\n System.out.println(\" 둘 다 등록한 뒤 : \" + refused.getMessage());\n }\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$SIB$cp\" -d \"$D/out\" \"$D/CollectorProbe.java\" 2>&1 | head -8\nprintf '%s\\n' '' > \"$D/out/logback.xml\"\njava -Dstdout.encoding=UTF-8 -cp \"$D/out:$SIB$cp\" CollectorProbe 2>&1 | grep -vE '^[0-9]{4}-|^\\s+at |WARN|INFO'\n", "cwd": "/shared/codebase/clean-architecture-backend-template/src", "exitCode": 0, "executedAt": "2026-09-03T07:54:51+00:00", "sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916", "raw": "evidence/raw/a14-f017-springmvcrouteinventorycollector-run.txt", "svg": "evidence/rendered/a14-f017-springmvcrouteinventorycollector-run.svg", "rawSha256": "75a8420a3ea110faa0154bcbe0aff510643eb815e762e179ce439e005ea19d6c", "lines": 28, "redaction": "접두 셋과 경로 모양 셋으로 확장" }